[gtk/wip/ottie/print: 90/90] ottie: Support text layers




commit 7515715fe4ce9407c6022b167c4c95565e5de85b
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Dec 29 02:37:06 2020 -0500

    ottie: Support text layers
    
    Add basic support for text layers. We are parsing
    the toplevel "fonts" and "chars", and support static text.

 ottie/meson.build             |   4 +
 ottie/ottiechar.c             |  86 ++++++++++++
 ottie/ottiecharprivate.h      |  55 ++++++++
 ottie/ottiecolorvalue.c       |  29 +----
 ottie/ottiecomposition.c      |  11 +-
 ottie/ottiecompositionlayer.c |   4 +-
 ottie/ottiecreation.c         | 186 +++++++++++++++++++++++++-
 ottie/ottiefont.c             |  43 ++++++
 ottie/ottiefontprivate.h      |  43 ++++++
 ottie/ottielayer.c            |  10 +-
 ottie/ottielayerprivate.h     |   8 +-
 ottie/ottieparser.c           |  64 +++++++++
 ottie/ottieparserprivate.h    |  13 ++
 ottie/ottiepathshapeprivate.h |   2 +
 ottie/ottierender.c           |  13 +-
 ottie/ottierenderprivate.h    |   3 +
 ottie/ottietextlayer.c        | 295 ++++++++++++++++++++++++++++++++++++++++++
 ottie/ottietextvalue.c        | 194 +++++++++++++++++++++++++++
 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);
 }
 
 /**
@@ -435,6 +449,130 @@ ottie_creation_parse_markers (JsonReader *reader,
                                    data);
 }
 
+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
@@ -108,6 +108,14 @@ ottie_render_merge (OttieRender *self,
 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);
 
@@ -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__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]