diff options
Diffstat (limited to 'ottie/ottietextlayer.c')
-rw-r--r-- | ottie/ottietextlayer.c | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/ottie/ottietextlayer.c b/ottie/ottietextlayer.c new file mode 100644 index 0000000000..894c5b7b19 --- /dev/null +++ b/ottie/ottietextlayer.c @@ -0,0 +1,295 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#include "config.h" + +#include "ottietextlayerprivate.h" + +#include "ottietextvalueprivate.h" +#include "ottieparserprivate.h" +#include "ottiefontprivate.h" +#include "ottiecharprivate.h" +#include "ottiepathshapeprivate.h" + +#include <glib/gi18n-lib.h> +#include <gsk/gsk.h> + +struct _OttieTextLayer +{ + OttieLayer parent; + + OttieTextValue text; + + GHashTable *fonts; + GHashTable *chars; +}; + +struct _OttieTextLayerClass +{ + OttieLayerClass parent_class; +}; + +G_DEFINE_TYPE (OttieTextLayer, ottie_text_layer, OTTIE_TYPE_LAYER) + +static void +ottie_text_layer_update (OttieLayer *layer, + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars) +{ + OttieTextLayer *self = OTTIE_TEXT_LAYER (layer); + + g_clear_pointer (&self->fonts, g_hash_table_unref); + g_clear_pointer (&self->chars, g_hash_table_unref); + + self->fonts = g_hash_table_ref (fonts); + self->chars = g_hash_table_ref (chars); +} + +static GskPath * +get_char_path (OttieChar *ch, + OttieRender *render, + double timestamp) +{ + OttieRender child_render; + GskPath *path; + + ottie_render_init_child (&child_render, render); + ottie_shape_render (ch->shapes, &child_render, timestamp); + path = gsk_path_ref (ottie_render_get_path (&child_render)); + + ottie_render_clear (&child_render); + + return path; +} + +static OttieChar * +get_char (OttieTextLayer *self, + OttieFont *font, + gunichar ch) +{ + OttieCharKey key; + char s[6] = { 0, }; + + g_unichar_to_utf8 (ch, s); + + key.ch = s; + key.family = font->family; + key.style = font->style; + + return g_hash_table_lookup (self->chars, &key); +} + +static void +render_text_item (OttieTextLayer *self, + OttieTextItem *item, + OttieRender *render, + double timestamp) +{ + OttieFont *font; + GskTransform *transform, *transform2; + float font_scale, tx; + char **lines; + int n_lines; + + font = g_hash_table_lookup (self->fonts, item->font); + if (font == NULL) + { + g_print ("Ottie is missing a font (%s). Sad!\n", item->font); + return; + } + + font_scale = item->size / 100.0; + transform = gsk_transform_scale (NULL, font_scale, font_scale); + + lines = g_strsplit (item->text, "\r", -1); + n_lines = g_strv_length (lines); + + transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, - (n_lines - 1) * item->line_height / 2 - item->line_shift)); + + for (int i = 0; i < n_lines; i++) + { + float line_width = 0; + char *p; + + for (p = lines[i]; *p; p = g_utf8_next_char (p)) + { + OttieChar *ch = get_char (self, font, g_utf8_get_char (p)); + if (ch == NULL) + continue; + line_width += ch->width * font_scale; + } + + transform2 = gsk_transform_ref (transform); + + switch (item->justify) + { + case OTTIE_TEXT_JUSTIFY_LEFT: + break; + case OTTIE_TEXT_JUSTIFY_RIGHT: + transform2 = gsk_transform_translate (transform2, &GRAPHENE_POINT_INIT (- line_width, 0)); + break; + case OTTIE_TEXT_JUSTIFY_CENTER: + transform2 = gsk_transform_translate (transform2, &GRAPHENE_POINT_INIT (- line_width/2, 0)); + break; + default: + g_assert_not_reached (); + } + + for (p = lines[i]; *p; p = g_utf8_next_char (p)) + { + OttieChar *ch = get_char (self, font, g_utf8_get_char (p)); + GskPath *path; + + if (ch == NULL) + { + g_print ("Ottie is missing a char. Sad!\n"); + continue; + } + + path = get_char_path (ch, render, timestamp); + + ottie_render_add_transformed_path (render, path, gsk_transform_ref (transform2)); + + tx = ch->width * font_scale + item->tracking / 10.0; + transform2 = gsk_transform_translate (transform2, &GRAPHENE_POINT_INIT (tx, 0)); + } + + gsk_transform_unref (transform2); + + transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, item->line_height)); + } + + gsk_transform_unref (transform); + + g_strfreev (lines); +} + +static void +ottie_text_layer_render (OttieLayer *layer, + OttieRender *render, + double timestamp) +{ + OttieTextLayer *self = OTTIE_TEXT_LAYER (layer); + OttieTextItem item; + OttieRender child_render; + GskPath *path; + graphene_rect_t bounds; + GskRenderNode *color_node; + + ottie_text_value_get (&self->text, timestamp, &item); + + ottie_render_init_child (&child_render, render); + + render_text_item (self, &item, &child_render, timestamp); + + path = ottie_render_get_path (&child_render); + + gsk_path_get_bounds (path, &bounds); + + color_node = gsk_color_node_new (&item.color, &bounds); + + ottie_render_add_node (&child_render, gsk_fill_node_new (color_node, path, GSK_FILL_RULE_WINDING)); + + gsk_render_node_unref (color_node); + + ottie_render_merge (render, &child_render); + + ottie_render_clear (&child_render); +} + +static void +ottie_text_layer_print (OttieObject *obj, + OttiePrinter *printer) +{ + OttieTextLayer *self = OTTIE_TEXT_LAYER (obj); + + OTTIE_OBJECT_CLASS (ottie_text_layer_parent_class)->print (obj, printer); + + ottie_printer_add_int (printer, "ty", 5); + ottie_printer_start_object (printer, "t"); + ottie_text_value_print (&self->text, "d", printer); + ottie_printer_end_object (printer); +} + +static void +ottie_text_layer_dispose (GObject *object) +{ + OttieTextLayer *self = OTTIE_TEXT_LAYER (object); + + ottie_text_value_clear (&self->text); + + g_clear_pointer (&self->fonts, g_hash_table_unref); + g_clear_pointer (&self->chars, g_hash_table_unref); + + G_OBJECT_CLASS (ottie_text_layer_parent_class)->dispose (object); +} + +static void +ottie_text_layer_class_init (OttieTextLayerClass *klass) +{ + OttieObjectClass *oobject_class = OTTIE_OBJECT_CLASS (klass); + OttieLayerClass *layer_class = OTTIE_LAYER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + oobject_class->print = ottie_text_layer_print; + + layer_class->update = ottie_text_layer_update; + layer_class->render = ottie_text_layer_render; + + gobject_class->dispose = ottie_text_layer_dispose; +} + +static void +ottie_text_layer_init (OttieTextLayer *self) +{ +} + +static gboolean +ottie_text_layer_parse_text (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieTextLayer *self = data; + OttieParserOption options[] = { + { "d", ottie_text_value_parse, G_STRUCT_OFFSET (OttieTextLayer, text) }, + }; + + return ottie_parser_parse_object (reader, "text data", options, G_N_ELEMENTS (options), self); +} + +OttieLayer * +ottie_text_layer_parse (JsonReader *reader) +{ + OttieParserOption options[] = { + OTTIE_PARSE_OPTIONS_LAYER, + { "t", ottie_text_layer_parse_text, 0 }, + }; + OttieTextLayer *self; + + self = g_object_new (OTTIE_TYPE_TEXT_LAYER, NULL); + + if (!ottie_parser_parse_object (reader, "text layer", options, G_N_ELEMENTS (options), self)) + { + g_object_unref (self); + return NULL; + } + + return OTTIE_LAYER (self); +} |