diff options
author | Matthias Clasen <mclasen@redhat.com> | 2020-12-29 02:37:06 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-12-29 10:14:32 -0500 |
commit | 7515715fe4ce9407c6022b167c4c95565e5de85b (patch) | |
tree | 3cc43de546bcf64eb4419a4fab73e2e3370ab167 | |
parent | 43d9fe9320c25c07a859cded02d245be4260e630 (diff) | |
download | gtk+-wip/ottie/print.tar.gz |
ottie: Support text layerswip/ottie/print
Add basic support for text layers. We are parsing
the toplevel "fonts" and "chars", and support static text.
-rw-r--r-- | ottie/meson.build | 4 | ||||
-rw-r--r-- | ottie/ottiechar.c | 86 | ||||
-rw-r--r-- | ottie/ottiecharprivate.h | 55 | ||||
-rw-r--r-- | ottie/ottiecolorvalue.c | 29 | ||||
-rw-r--r-- | ottie/ottiecomposition.c | 11 | ||||
-rw-r--r-- | ottie/ottiecompositionlayer.c | 4 | ||||
-rw-r--r-- | ottie/ottiecreation.c | 186 | ||||
-rw-r--r-- | ottie/ottiefont.c | 43 | ||||
-rw-r--r-- | ottie/ottiefontprivate.h | 43 | ||||
-rw-r--r-- | ottie/ottielayer.c | 10 | ||||
-rw-r--r-- | ottie/ottielayerprivate.h | 8 | ||||
-rw-r--r-- | ottie/ottieparser.c | 64 | ||||
-rw-r--r-- | ottie/ottieparserprivate.h | 13 | ||||
-rw-r--r-- | ottie/ottiepathshapeprivate.h | 2 | ||||
-rw-r--r-- | ottie/ottierender.c | 13 | ||||
-rw-r--r-- | ottie/ottierenderprivate.h | 3 | ||||
-rw-r--r-- | ottie/ottietextlayer.c | 295 | ||||
-rw-r--r-- | ottie/ottietextvalue.c | 194 | ||||
-rw-r--r-- | ottie/ottietextvalueprivate.h | 80 |
19 files changed, 1102 insertions, 41 deletions
diff --git a/ottie/meson.build b/ottie/meson.build index 9c91630b4c..8dc624e793 100644 --- a/ottie/meson.build +++ b/ottie/meson.build @@ -26,9 +26,13 @@ ottie_private_sources = files([ 'ottieshape.c', 'ottieshapelayer.c', 'ottiestrokeshape.c', + 'ottietextlayer.c', + 'ottietextvalue.c', 'ottietransform.c', 'ottietrimshape.c', 'ottieprinter.c', + 'ottiefont.c', + 'ottiechar.c', ]) ottie_public_headers = files([ diff --git a/ottie/ottiechar.c b/ottie/ottiechar.c new file mode 100644 index 0000000000..f2e802c0ba --- /dev/null +++ b/ottie/ottiechar.c @@ -0,0 +1,86 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#include "ottiecharprivate.h" + +static void +ottie_char_key_clear (OttieCharKey *key) +{ + g_free (key->ch); + g_free (key->family); + g_free (key->style); +} + +void +ottie_char_key_free (OttieCharKey *key) +{ + ottie_char_key_clear (key); + g_free (key); +} + +guint +ottie_char_key_hash (const OttieCharKey *key) +{ + guint res = 0; + + res = 31 * res + g_str_hash (key->ch); + res = 31 * res + g_str_hash (key->family); + res = 31 * res + g_str_hash (key->style); + + return res; +} + +gboolean +ottie_char_key_equal (const OttieCharKey *key1, + const OttieCharKey *key2) +{ + return strcmp (key1->ch, key2->ch) == 0 && + strcmp (key1->family, key2->family) == 0 && + strcmp (key1->style, key2->style) == 0; +} + +OttieChar * +ottie_char_copy (OttieChar *ch) +{ + OttieChar *c; + + c = g_new0 (OttieChar, 1); + c->key.ch = g_strdup (ch->key.ch); + c->key.family = g_strdup (ch->key.family); + c->key.style = g_strdup (ch->key.style); + c->size = ch->size; + c->width = ch->width; + c->shapes = g_object_ref (ch->shapes); + + return c; +} + +void +ottie_char_clear (OttieChar *ch) +{ + ottie_char_key_clear (&ch->key); + g_object_unref (ch->shapes); +} + +void +ottie_char_free (OttieChar *ch) +{ + ottie_char_clear (ch); + g_free (ch); +} diff --git a/ottie/ottiecharprivate.h b/ottie/ottiecharprivate.h new file mode 100644 index 0000000000..0551c56065 --- /dev/null +++ b/ottie/ottiecharprivate.h @@ -0,0 +1,55 @@ + +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#ifndef __OTTIE_CHAR_PRIVATE_H__ +#define __OTTIE_CHAR_PRIVATE_H__ + +#include <glib.h> + +#include "ottieshapeprivate.h" + +G_BEGIN_DECLS + +typedef struct +{ + char *ch; + char *family; + char *style; +} OttieCharKey; + +typedef struct +{ + OttieCharKey key; + double size; + double width; + OttieShape *shapes; +} OttieChar; + +void ottie_char_key_free (OttieCharKey *key); +guint ottie_char_key_hash (const OttieCharKey *key); +gboolean ottie_char_key_equal (const OttieCharKey *key1, + const OttieCharKey *key2); +OttieChar * ottie_char_copy (OttieChar *ch); +void ottie_char_clear (OttieChar *ch); +void ottie_char_free (OttieChar *ch); + +G_END_DECLS + +#endif /* __OTTIE_CHAR_PRIVATE_H__ */ diff --git a/ottie/ottiecolorvalue.c b/ottie/ottiecolorvalue.c index 72cfa4c808..01f51b7fbf 100644 --- a/ottie/ottiecolorvalue.c +++ b/ottie/ottiecolorvalue.c @@ -26,31 +26,6 @@ #include <glib/gi18n-lib.h> -static gboolean -ottie_color_value_parse_one (JsonReader *reader, - gsize offset, - gpointer data) -{ - GdkRGBA *rgba = (GdkRGBA *) ((guint8 *) data + offset); - double d[3]; - - if (!ottie_parser_parse_array (reader, "color value", - 3, 3, NULL, - 0, sizeof (double), - ottie_parser_option_double, - d)) - { - d[0] = d[1] = d[2] = 0; - } - - rgba->red = d[0]; - rgba->green = d[1]; - rgba->blue = d[2]; - rgba->alpha = 1; - - return TRUE; -} - static void ottie_color_value_interpolate (const GdkRGBA *start, const GdkRGBA *end, @@ -68,7 +43,7 @@ ottie_color_value_interpolate (const GdkRGBA *start, #define OTTIE_KEYFRAMES_ELEMENT_TYPE GdkRGBA #define OTTIE_KEYFRAMES_BY_VALUE 1 #define OTTIE_KEYFRAMES_DIMENSIONS 4 -#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_color_value_parse_one +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_parser_option_color #define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_color_value_interpolate #define OTTIE_KEYFRAMES_PRINT_FUNC ottie_printer_add_color #include "ottiekeyframesimpl.c" @@ -127,7 +102,7 @@ ottie_color_value_parse (JsonReader *reader, if (is_static) { self->is_static = TRUE; - ottie_color_value_parse_one (reader, 0, &self->static_value); + ottie_parser_option_color (reader, 0, &self->static_value); } else { diff --git a/ottie/ottiecomposition.c b/ottie/ottiecomposition.c index 7f30bad018..d3b4111020 100644 --- a/ottie/ottiecomposition.c +++ b/ottie/ottiecomposition.c @@ -25,6 +25,7 @@ #include "ottiecompositionlayerprivate.h" #include "ottienulllayerprivate.h" #include "ottieshapelayerprivate.h" +#include "ottietextlayerprivate.h" #include <glib/gi18n-lib.h> #include <gsk/gsk.h> @@ -98,13 +99,15 @@ G_DEFINE_TYPE_WITH_CODE (OttieComposition, ottie_composition, OTTIE_TYPE_LAYER, static void ottie_composition_update (OttieLayer *layer, - GHashTable *compositions) + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars) { OttieComposition *self = OTTIE_COMPOSITION (layer); for (gsize i = ottie_layer_list_get_size (&self->layers); i-- > 0; ) { - ottie_layer_update (ottie_layer_list_get (&self->layers, i), compositions); + ottie_layer_update (ottie_layer_list_get (&self->layers, i), compositions, fonts, chars); } } @@ -236,6 +239,10 @@ ottie_composition_parse_layer (JsonReader *reader, layer = ottie_shape_layer_parse (reader); break; + case 5: + layer = ottie_text_layer_parse (reader); + break; + default: ottie_parser_error_value (reader, "Layer %zu has unknown type %d", ottie_layer_list_get_size (&self->layers), diff --git a/ottie/ottiecompositionlayer.c b/ottie/ottiecompositionlayer.c index 034374e531..adc9b96618 100644 --- a/ottie/ottiecompositionlayer.c +++ b/ottie/ottiecompositionlayer.c @@ -46,7 +46,9 @@ G_DEFINE_TYPE (OttieCompositionLayer, ottie_composition_layer, OTTIE_TYPE_LAYER) static void ottie_composition_layer_update (OttieLayer *layer, - GHashTable *compositions) + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars) { OttieCompositionLayer *self = OTTIE_COMPOSITION_LAYER (layer); diff --git a/ottie/ottiecreation.c b/ottie/ottiecreation.c index d48182da19..6ceff8ee1c 100644 --- a/ottie/ottiecreation.c +++ b/ottie/ottiecreation.c @@ -25,6 +25,9 @@ #include "ottieparserprivate.h" #include "ottiecompositionprivate.h" #include "ottieprinterprivate.h" +#include "ottiegroupshapeprivate.h" +#include "ottiefontprivate.h" +#include "ottiecharprivate.h" #include <glib/gi18n-lib.h> #include <json-glib/json-glib.h> @@ -56,6 +59,8 @@ struct _OttieCreation OttieComposition *layers; GHashTable *composition_assets; + GHashTable *fonts; + GHashTable *chars; GCancellable *cancellable; }; @@ -164,6 +169,8 @@ ottie_creation_reset (OttieCreation *self) { g_clear_object (&self->layers); g_hash_table_remove_all (self->composition_assets); + g_hash_table_remove_all (self->fonts); + g_hash_table_remove_all (self->chars); g_clear_pointer (&self->name, g_free); self->frame_rate = 0; @@ -190,6 +197,8 @@ ottie_creation_finalize (GObject *object) OttieCreation *self = OTTIE_CREATION (object); g_hash_table_unref (self->composition_assets); + g_hash_table_unref (self->fonts); + g_hash_table_unref (self->chars); G_OBJECT_CLASS (ottie_creation_parent_class)->finalize (object); } @@ -307,6 +316,11 @@ static void ottie_creation_init (OttieCreation *self) { self->composition_assets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + self->fonts = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)ottie_font_free); + self->chars = g_hash_table_new_full ((GHashFunc)ottie_char_key_hash, + (GEqualFunc)ottie_char_key_equal, + NULL, (GDestroyNotify)ottie_char_free); } /** @@ -436,6 +450,130 @@ ottie_creation_parse_markers (JsonReader *reader, } static gboolean +ottie_creation_parse_font (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "fName", ottie_parser_option_string, G_STRUCT_OFFSET (OttieFont, name) }, + { "fFamily", ottie_parser_option_string, G_STRUCT_OFFSET (OttieFont, family) }, + { "fStyle", ottie_parser_option_string, G_STRUCT_OFFSET (OttieFont, style) }, + { "ascent", ottie_parser_option_double, G_STRUCT_OFFSET (OttieFont, ascent) }, + }; + OttieCreation *self = data; + OttieFont font = { }; + gboolean result; + + result = ottie_parser_parse_object (reader, "font", options, G_N_ELEMENTS (options), &font); + + if (result) + { + if (font.name == NULL) + ottie_parser_error_syntax (reader, "No name given to font"); + else if (g_hash_table_contains (self->fonts, font.name)) + ottie_parser_error_syntax (reader, "Duplicate font name: %s", font.name); + else + g_hash_table_insert (self->fonts, g_strdup (font.name), ottie_font_copy (&font)); + } + + g_clear_pointer (&font.name, g_free); + g_clear_pointer (&font.family, g_free); + g_clear_pointer (&font.style, g_free); + + return result; +} + +static gboolean +ottie_creation_parse_font_list (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_parser_parse_array (reader, "assets", + 0, G_MAXUINT, NULL, + offset, 0, + ottie_creation_parse_font, + data); +} + +static gboolean +ottie_creation_parse_fonts (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "list", ottie_creation_parse_font_list, 0 } + }; + + return ottie_parser_parse_object (reader, "fonts", + options, G_N_ELEMENTS (options), + data); +} + +static gboolean +ottie_creation_parse_char_data (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "shapes", ottie_group_shape_parse_shapes, 0 } + }; + OttieChar *ch = data; + + ch->shapes = ottie_group_shape_new (); + + return ottie_parser_parse_object (reader, "char data", + options, G_N_ELEMENTS (options), + ch->shapes); +} + +static gboolean +ottie_creation_parse_char (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieParserOption options[] = { + { "ch", ottie_parser_option_string, G_STRUCT_OFFSET (OttieCharKey, ch) }, + { "fFamily", ottie_parser_option_string, G_STRUCT_OFFSET (OttieCharKey, family) }, + { "style", ottie_parser_option_string, G_STRUCT_OFFSET (OttieCharKey, style) }, + { "size", ottie_parser_option_double, G_STRUCT_OFFSET (OttieChar, size) }, + { "w", ottie_parser_option_double, G_STRUCT_OFFSET (OttieChar, width) }, + { "data", ottie_creation_parse_char_data, G_STRUCT_OFFSET (OttieChar, shapes) }, + }; + OttieCreation *self = data; + OttieChar ch = { }; + gboolean result; + + result = ottie_parser_parse_object (reader, "char", options, G_N_ELEMENTS (options), &ch); + + if (result) + { + if (ch.key.ch == NULL) + ottie_parser_error_syntax (reader, "Char without \"ch\""); + else if (ch.shapes == NULL) + ottie_parser_error_syntax (reader, "Char without \"data\""); + else if (g_hash_table_contains (self->chars, &ch)) + ottie_parser_error_syntax (reader, "Duplicate char: %s/%s/%s", ch.key.ch, ch.key.family, ch.key.style); + else + g_hash_table_add (self->chars, ottie_char_copy (&ch)); + } + + ottie_char_clear (&ch); + + return result; +} +static gboolean +ottie_creation_parse_chars (JsonReader *reader, + gsize offset, + gpointer data) +{ + return ottie_parser_parse_array (reader, "chars", + 0, G_MAXUINT, NULL, + offset, 0, + ottie_creation_parse_char, + data); +} + +static gboolean ottie_creation_load_from_reader (OttieCreation *self, JsonReader *reader) { @@ -451,6 +589,8 @@ ottie_creation_load_from_reader (OttieCreation *self, { "layers", ottie_composition_parse_layers, G_STRUCT_OFFSET (OttieCreation, layers) }, { "assets", ottie_creation_parse_assets, 0 }, { "markers", ottie_creation_parse_markers, 0 }, + { "fonts", ottie_creation_parse_fonts, 0 }, + { "chars", ottie_creation_parse_chars, 0 }, }; return ottie_parser_parse_object (reader, "toplevel", options, G_N_ELEMENTS (options), self); @@ -465,9 +605,9 @@ ottie_creation_update_layers (OttieCreation *self) g_hash_table_iter_init (&iter, self->composition_assets); while (g_hash_table_iter_next (&iter, NULL, &layer)) - ottie_layer_update (layer, self->composition_assets); + ottie_layer_update (layer, self->composition_assets, self->fonts, self->chars); - ottie_layer_update (OTTIE_LAYER (self->layers), self->composition_assets); + ottie_layer_update (OTTIE_LAYER (self->layers), self->composition_assets, self->fonts, self->chars); } static void @@ -766,6 +906,9 @@ ottie_creation_print (OttiePrinter *printer, const char *id; OttieComposition *composition; GHashTableIter iter; + const char *name; + OttieFont *font; + OttieChar *ch; ottie_printer_start_object (printer, NULL); @@ -788,13 +931,50 @@ ottie_creation_print (OttiePrinter *printer, ottie_printer_end_array (printer); ottie_printer_end_object (printer); } - ottie_printer_end_array (printer); ottie_printer_start_array (printer, "layers"); ottie_composition_print (printer, self->layers); ottie_printer_end_array (printer); + if (g_hash_table_size (self->fonts) > 0) + { + ottie_printer_start_object (printer, "fonts"); + ottie_printer_start_array (printer, "list"); + g_hash_table_iter_init (&iter, self->fonts); + while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&font)) + { + ottie_printer_start_object (printer, NULL); + ottie_printer_add_string (printer, "fName", font->name); + ottie_printer_add_string (printer, "fFamily", font->family); + ottie_printer_add_string (printer, "fStyle", font->style); + ottie_printer_add_double (printer, "ascent", font->ascent); + ottie_printer_end_object (printer); + } + ottie_printer_end_array (printer); + ottie_printer_end_object (printer); + } + + if (g_hash_table_size (self->chars) > 0) + { + ottie_printer_start_array (printer, "chars"); + g_hash_table_iter_init (&iter, self->chars); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&ch)) + { + ottie_printer_start_object (printer, NULL); + ottie_printer_add_string (printer, "ch", ch->key.ch); + ottie_printer_add_string (printer, "fFamily", ch->key.family); + ottie_printer_add_string (printer, "style", ch->key.style); + ottie_printer_add_double (printer, "size", ch->size); + ottie_printer_add_double (printer, "w", ch->width); + ottie_printer_start_object (printer, "data"); + ottie_group_shape_print_shapes (ch->shapes, "shapes", printer); + ottie_printer_end_object (printer); + ottie_printer_end_object (printer); + } + ottie_printer_end_array (printer); + } + ottie_printer_end_object (printer); } diff --git a/ottie/ottiefont.c b/ottie/ottiefont.c new file mode 100644 index 0000000000..b51c8efd57 --- /dev/null +++ b/ottie/ottiefont.c @@ -0,0 +1,43 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#include "ottiefontprivate.h" + +OttieFont * +ottie_font_copy (OttieFont *font) +{ + OttieFont *f; + + f = g_new0 (OttieFont, 1); + f->name = g_strdup (font->name); + f->family = g_strdup (font->family); + f->style = g_strdup (font->style); + f->ascent = font->ascent; + + return f; +} + +void +ottie_font_free (OttieFont *font) +{ + g_free (font->name); + g_free (font->family); + g_free (font->style); + g_free (font); +} diff --git a/ottie/ottiefontprivate.h b/ottie/ottiefontprivate.h new file mode 100644 index 0000000000..629186bedd --- /dev/null +++ b/ottie/ottiefontprivate.h @@ -0,0 +1,43 @@ + +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#ifndef __OTTIE_FONT_PRIVATE_H__ +#define __OTTIE_FONT_PRIVATE_H__ + +#include <json-glib/json-glib.h> + +#include "ottieprinterprivate.h" +#include <gdk/gdk.h> + +G_BEGIN_DECLS + +typedef struct +{ + char *name; + char *family; + char *style; + double ascent; +} OttieFont; + +OttieFont * ottie_font_copy (OttieFont *font); +void ottie_font_free (OttieFont *font); + + +#endif /* __OTTIE_FONT_PRIVATE_H__ */ diff --git a/ottie/ottielayer.c b/ottie/ottielayer.c index d0b442f7b7..b418c8f5cd 100644 --- a/ottie/ottielayer.c +++ b/ottie/ottielayer.c @@ -40,7 +40,9 @@ G_DEFINE_TYPE (OttieLayer, ottie_layer, OTTIE_TYPE_OBJECT) static void ottie_layer_default_update (OttieLayer *self, - GHashTable *compositions) + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars) { } @@ -116,9 +118,11 @@ ottie_layer_init (OttieLayer *self) void ottie_layer_update (OttieLayer *self, - GHashTable *compositions) + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars) { - OTTIE_LAYER_GET_CLASS (self)->update (self, compositions); + OTTIE_LAYER_GET_CLASS (self)->update (self, compositions, fonts, chars); } void diff --git a/ottie/ottielayerprivate.h b/ottie/ottielayerprivate.h index 4a60ea6463..788d5af2db 100644 --- a/ottie/ottielayerprivate.h +++ b/ottie/ottielayerprivate.h @@ -58,7 +58,9 @@ struct _OttieLayerClass OttieObjectClass parent_class; void (* update) (OttieLayer *layer, - GHashTable *compositions); + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars); void (* render) (OttieLayer *layer, OttieRender *render, double timestamp); @@ -69,7 +71,9 @@ struct _OttieLayerClass GType ottie_layer_get_type (void) G_GNUC_CONST; void ottie_layer_update (OttieLayer *self, - GHashTable *compositions); + GHashTable *compositions, + GHashTable *fonts, + GHashTable *chars); void ottie_layer_render (OttieLayer *self, OttieRender *render, double timestamp); diff --git a/ottie/ottieparser.c b/ottie/ottieparser.c index 03a8b9c00a..b8f44485f4 100644 --- a/ottie/ottieparser.c +++ b/ottie/ottieparser.c @@ -590,3 +590,67 @@ ottie_parser_option_transform (JsonReader *reader, return TRUE; } +gboolean +ottie_parser_option_color (JsonReader *reader, + gsize offset, + gpointer data) +{ + GdkRGBA *rgba = (GdkRGBA *) ((guint8 *) data + offset); + double d[3]; + + if (!ottie_parser_parse_array (reader, "color value", + 3, 3, NULL, + 0, sizeof (double), + ottie_parser_option_double, + d)) + { + d[0] = d[1] = d[2] = 0; + } + + rgba->red = d[0]; + rgba->green = d[1]; + rgba->blue = d[2]; + rgba->alpha = 1; + + return TRUE; +} + +gboolean +ottie_parser_option_text_justify (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieTextJustify justify; + gint64 i; + + i = json_reader_get_int_value (reader); + if (json_reader_get_error (reader)) + { + ottie_parser_emit_error (reader, json_reader_get_error (reader)); + return FALSE; + } + + switch (i) + { + case 0: + justify = OTTIE_TEXT_JUSTIFY_LEFT; + break; + + case 1: + justify = OTTIE_TEXT_JUSTIFY_RIGHT; + break; + + case 2: + justify = OTTIE_TEXT_JUSTIFY_CENTER; + break; + + default: + ottie_parser_error_value (reader, "%"G_GINT64_FORMAT" is not a known text justification", i); + return FALSE; + } + + *(OttieTextJustify *) ((guint8 *) data + offset) = justify; + + return TRUE; +} + diff --git a/ottie/ottieparserprivate.h b/ottie/ottieparserprivate.h index 27fc41acdc..0a8dde2ad9 100644 --- a/ottie/ottieparserprivate.h +++ b/ottie/ottieparserprivate.h @@ -33,6 +33,13 @@ typedef enum OTTIE_DIRECTION_BACKWARD } OttieDirection; +typedef enum +{ + OTTIE_TEXT_JUSTIFY_LEFT, + OTTIE_TEXT_JUSTIFY_RIGHT, + OTTIE_TEXT_JUSTIFY_CENTER +} OttieTextJustify; + typedef struct _OttieParserOption OttieParserOption; typedef gboolean (* OttieParseFunc) (JsonReader *reader, gsize offset, gpointer data); @@ -109,6 +116,12 @@ gboolean ottie_parser_option_fill_rule (JsonReader gboolean ottie_parser_option_transform (JsonReader *reader, gsize offset, gpointer data); +gboolean ottie_parser_option_color (JsonReader *reader, + gsize offset, + gpointer data); +gboolean ottie_parser_option_text_justify (JsonReader *reader, + gsize offset, + gpointer data); G_END_DECLS diff --git a/ottie/ottiepathshapeprivate.h b/ottie/ottiepathshapeprivate.h index 827e3cf3b1..830923c42b 100644 --- a/ottie/ottiepathshapeprivate.h +++ b/ottie/ottiepathshapeprivate.h @@ -22,6 +22,8 @@ #include "ottieshapeprivate.h" +#include <gsk/gsk.h> + #include <json-glib/json-glib.h> G_BEGIN_DECLS diff --git a/ottie/ottierender.c b/ottie/ottierender.c index daa764a1be..f1b2a9fd9d 100644 --- a/ottie/ottierender.c +++ b/ottie/ottierender.c @@ -109,6 +109,14 @@ void ottie_render_add_path (OttieRender *self, GskPath *path) { + ottie_render_add_transformed_path (self, path, NULL); +} + +void +ottie_render_add_transformed_path (OttieRender *self, + GskPath *path, + GskTransform *transform) +{ g_clear_pointer (&self->cached_path, gsk_path_unref); if (gsk_path_is_empty (path)) @@ -116,11 +124,10 @@ ottie_render_add_path (OttieRender *self, gsk_path_unref (path); return; } - - ottie_render_paths_append (&self->paths, &(OttieRenderPath) { path, NULL }); + ottie_render_paths_append (&self->paths, &(OttieRenderPath) { path, transform }); } -typedef struct +typedef struct { GskPathBuilder *builder; GskTransform *transform; diff --git a/ottie/ottierenderprivate.h b/ottie/ottierenderprivate.h index 57c5fa46e4..4ea1330c78 100644 --- a/ottie/ottierenderprivate.h +++ b/ottie/ottierenderprivate.h @@ -75,6 +75,9 @@ void ottie_render_merge (OttieRender void ottie_render_add_path (OttieRender *self, GskPath *path); +void ottie_render_add_transformed_path (OttieRender *self, + GskPath *path, + GskTransform *transform); GskPath * ottie_render_get_path (OttieRender *self); void ottie_render_clear_path (OttieRender *self); gsize ottie_render_get_n_subpaths (OttieRender *self); 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); +} diff --git a/ottie/ottietextvalue.c b/ottie/ottietextvalue.c new file mode 100644 index 0000000000..74495befd3 --- /dev/null +++ b/ottie/ottietextvalue.c @@ -0,0 +1,194 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#include "config.h" + +#include "ottietextvalueprivate.h" + +#include "ottieparserprivate.h" +#include "ottieprinterprivate.h" + +#include <glib/gi18n-lib.h> + +static inline void +text_item_copy (const OttieTextItem *source, + OttieTextItem *dest) +{ + *dest = *source; + dest->font = g_strdup (dest->font); + dest->text = g_strdup (dest->text); +} + +static gboolean +ottie_text_value_parse_one (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieTextItem *item = (OttieTextItem *) ((guint8 *) data + offset); + OttieParserOption options[] = { + { "f", ottie_parser_option_string, G_STRUCT_OFFSET (OttieTextItem, font) }, + { "t", ottie_parser_option_string, G_STRUCT_OFFSET (OttieTextItem, text) }, + { "s", ottie_parser_option_double, G_STRUCT_OFFSET (OttieTextItem, size) }, + { "fc", ottie_parser_option_color, G_STRUCT_OFFSET (OttieTextItem, color) }, + { "j", ottie_parser_option_text_justify, G_STRUCT_OFFSET (OttieTextItem, justify) }, + { "lh", ottie_parser_option_double, G_STRUCT_OFFSET (OttieTextItem, line_height) }, + { "ls", ottie_parser_option_double, G_STRUCT_OFFSET (OttieTextItem, line_shift) }, + { "tr", ottie_parser_option_double, G_STRUCT_OFFSET (OttieTextItem, tracking) }, + }; + + if (!ottie_parser_parse_object (reader, "text value", + options, G_N_ELEMENTS (options), + item)) + { + g_print ("sorry no text\n"); + } + + return TRUE; +} + +static void +ottie_text_value_print_one (OttiePrinter *printer, + const char *name, + const OttieTextItem *text) +{ + ottie_printer_start_object (printer, name); + ottie_printer_add_string (printer, "f", text->font); + ottie_printer_add_string (printer, "t", text->text); + ottie_printer_add_double (printer, "s", text->size); + g_string_append (printer->str, ",\n"); + ottie_printer_indent (printer); + g_string_append_printf (printer->str, "\"fc\" : [ %g, %g, %g ]", + text->color.red, text->color.green, text->color.blue); + ottie_printer_add_int (printer, "j", text->justify); + ottie_printer_add_double (printer, "lh", text->line_height); + ottie_printer_add_double (printer, "ls", text->line_shift); + ottie_printer_add_double (printer, "tr", text->tracking); + ottie_printer_end_object (printer); +} + +static void +ottie_text_value_interpolate (const OttieTextItem *start, + const OttieTextItem *end, + double progress, + OttieTextItem *result) +{ + text_item_copy (start, result); +} + +#define OTTIE_KEYFRAMES_NAME ottie_text_keyframes +#define OTTIE_KEYFRAMES_TYPE_NAME OttieTextKeyframes +#define OTTIE_KEYFRAMES_ELEMENT_TYPE OttieTextItem +#define OTTIE_KEYFRAMES_BY_VALUE 1 +#define OTTIE_KEYFRAMES_PARSE_FUNC ottie_text_value_parse_one +#define OTTIE_KEYFRAMES_INTERPOLATE_FUNC ottie_text_value_interpolate +#define OTTIE_KEYFRAMES_PRINT_FUNC ottie_text_value_print_one +#include "ottiekeyframesimpl.c" + +void +ottie_text_value_init (OttieTextValue *self, + const OttieTextItem *value) +{ + self->is_static = TRUE; + text_item_copy (value, &self->static_value); +} + +void +ottie_text_value_clear (OttieTextValue *self) +{ + if (!self->is_static) + g_clear_pointer (&self->keyframes, ottie_text_keyframes_free); +} + +void +ottie_text_value_get (OttieTextValue *self, + double timestamp, + OttieTextItem *text) +{ + if (self->is_static) + { + text_item_copy (&self->static_value, text); + return; + } + + ottie_text_keyframes_get (self->keyframes, timestamp, text); +} + +gboolean +ottie_text_value_parse (JsonReader *reader, + gsize offset, + gpointer data) +{ + OttieTextValue *self = (OttieTextValue *) ((guint8 *) data + offset); + + if (json_reader_read_member (reader, "k")) + { + gboolean is_static; + + if (!json_reader_is_array (reader)) + is_static = TRUE; + else + { + if (json_reader_read_element (reader, 0)) + is_static = !json_reader_is_object (reader); + else + is_static = TRUE; + json_reader_end_element (reader); + } + + if (is_static) + { + self->is_static = TRUE; + ottie_text_value_parse_one (reader, 0, &self->static_value); + } + else + { + self->is_static = FALSE; + self->keyframes = ottie_text_keyframes_parse (reader); + if (self->keyframes == NULL) + { + json_reader_end_member (reader); + return FALSE; + } + } + } + else + { + ottie_parser_error_syntax (reader, "Property is not a text value"); + } + json_reader_end_member (reader); + + return TRUE; +} + +void +ottie_text_value_print (OttieTextValue *self, + const char *name, + OttiePrinter *printer) +{ + ottie_printer_start_object (printer, name); + + ottie_printer_add_boolean (printer, "a", !self->is_static); + if (self->is_static) + ottie_text_value_print_one (printer, "k", &self->static_value); + else + ottie_text_keyframes_print (self->keyframes, printer); + + ottie_printer_end_object (printer); +} + diff --git a/ottie/ottietextvalueprivate.h b/ottie/ottietextvalueprivate.h new file mode 100644 index 0000000000..6bb5766b55 --- /dev/null +++ b/ottie/ottietextvalueprivate.h @@ -0,0 +1,80 @@ +/* + * Copyright © 2020 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#ifndef __OTTIE_TEXT_VALUE_PRIVATE_H__ +#define __OTTIE_TEXT_VALUE_PRIVATE_H__ + +#include <json-glib/json-glib.h> +#include "ottie/ottieparserprivate.h" +#include "ottie/ottieprinterprivate.h" + +G_BEGIN_DECLS + +typedef struct _OttieTextItem OttieTextItem; + +struct _OttieTextItem +{ + const char *font; + const char *text; + GdkRGBA color; + double size; + OttieTextJustify justify; + double line_height; + double line_shift; + double tracking; +}; + +typedef struct _OttieTextValue OttieTextValue; + +struct _OttieTextValue +{ + gboolean is_static; + union { + OttieTextItem static_value; + gpointer keyframes; + }; +}; + +void ottie_text_value_init (OttieTextValue *self, + const OttieTextItem *item); + +void ottie_text_value_clear (OttieTextValue *self); + +static inline gboolean ottie_text_value_is_static (OttieTextValue *self); +void ottie_text_value_get (OttieTextValue *self, + double timestamp, + OttieTextItem *item); + +gboolean ottie_text_value_parse (JsonReader *reader, + gsize offset, + gpointer data); + +void ottie_text_value_print (OttieTextValue *self, + const char *name, + OttiePrinter *printer); + +static inline gboolean +ottie_text_value_is_static (OttieTextValue *self) +{ + return self->is_static; +} + +G_END_DECLS + +#endif /* __OTTIE_TEXT_VALUE_PRIVATE_H__ */ |