[colr/cpal] Improvements and add a sample renderer (#927)

diff --git a/src/Makefile.am b/src/Makefile.am
index c5a01e7..46fc141 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -362,15 +362,11 @@
 endif
 
 check_PROGRAMS += \
-	dump-emoji \
 	dump-indic-data \
 	dump-khmer-data \
 	dump-myanmar-data \
 	dump-use-data \
 	$(NULL)
-dump_emoji_SOURCES = dump-emoji.cc
-dump_emoji_CPPFLAGS = $(HBCFLAGS)
-dump_emoji_LDADD = libharfbuzz.la $(HBLIBS)
 dump_indic_data_SOURCES = dump-indic-data.cc hb-ot-shape-complex-indic-table.cc
 dump_indic_data_CPPFLAGS = $(HBCFLAGS)
 dump_indic_data_LDADD = libharfbuzz.la $(HBLIBS)
@@ -384,6 +380,15 @@
 dump_use_data_CPPFLAGS = $(HBCFLAGS)
 dump_use_data_LDADD = libharfbuzz.la $(HBLIBS)
 
+if HAVE_FREETYPE
+if HAVE_CAIRO_FT
+check_PROGRAMS += dump-emoji
+dump_emoji_SOURCES = dump-emoji.cc
+dump_emoji_CPPFLAGS = $(HBCFLAGS) $(FREETYPE_CFLAGS) $(CAIRO_FT_CFLAGS)
+dump_emoji_LDADD = libharfbuzz.la $(HBLIBS) $(FREETYPE_LIBS) $(CAIRO_LIBS) $(CAIRO_FT_LIBS)
+endif # HAVE_CAIRO_FT
+endif # HAVE_FREETYPE
+
 check_PROGRAMS += test-ot-tag test-unicode-ranges
 TESTS += test-ot-tag test-unicode-ranges
 
diff --git a/src/dump-emoji.cc b/src/dump-emoji.cc
index a9595e4..db586bd 100644
--- a/src/dump-emoji.cc
+++ b/src/dump-emoji.cc
@@ -25,9 +25,21 @@
 #include "hb.h"
 #include "hb-private.hh"
 #include "hb-ot-color-cbdt-table.hh"
+#include "hb-ot-color-colr-table.hh"
+#include "hb-ot-color-cpal-table.hh"
 #include "hb-ot-color-sbix-table.hh"
 #include "hb-ot-color-svg-table.hh"
 
+#include "hb-ft.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+#include <cairo.h>
+#include <cairo-ft.h>
+#include <cairo-svg.h>
+
 #ifdef HAVE_GLIB
 #include <glib.h>
 #endif
@@ -41,9 +53,9 @@
 void cbdt_callback (const uint8_t* data, unsigned int length,
                     unsigned int group, unsigned int gid)
 {
-  char outName[255];
-  sprintf (outName, "out/cbdt-%d-%d.png", group, gid);
-  FILE *f = fopen (outName, "wb");
+  char output_path[255];
+  sprintf (output_path, "out/cbdt-%d-%d.png", group, gid);
+  FILE *f = fopen (output_path, "wb");
   fwrite (data, 1, length, f);
   fclose (f);
 }
@@ -51,9 +63,9 @@
 void sbix_callback (const uint8_t* data, unsigned int length,
                     unsigned int group, unsigned int gid)
 {
-  char outName[255];
-  sprintf (outName, "out/sbix-%d-%d.png", group, gid);
-  FILE *f = fopen (outName, "wb");
+  char output_path[255];
+  sprintf (output_path, "out/sbix-%d-%d.png", group, gid);
+  FILE *f = fopen (output_path, "wb");
   fwrite (data, 1, length, f);
   fclose (f);
 }
@@ -61,22 +73,145 @@
 void svg_callback (const uint8_t* data, unsigned int length,
                    unsigned int start_glyph, unsigned int end_glyph)
 {
-  char outName[255];
+  char output_path[255];
   if (start_glyph == end_glyph)
-    sprintf (outName, "out/svg-%d.svg", start_glyph);
+    sprintf (output_path, "out/svg-%d.svg", start_glyph);
   else
-    sprintf (outName, "out/svg-%d-%d.svg", start_glyph, end_glyph);
+    sprintf (output_path, "out/svg-%d-%d.svg", start_glyph, end_glyph);
 
   // append "z" if the content is gzipped
   if ((data[0] == 0x1F) && (data[1] == 0x8B))
-    strcat (outName, "z");
+    strcat (output_path, "z");
 
-  FILE *f = fopen (outName, "wb");
+  FILE *f = fopen (output_path, "wb");
   fwrite (data, 1, length, f);
   fclose (f);
 }
 
-int main(int argc, char **argv)
+void colr_cpal_rendering (cairo_font_face_t *cairo_face, unsigned int upem, unsigned int num_glyphs,
+			  const OT::COLR *colr, const OT::CPAL *cpal)
+{
+  for (int i = 0; i < num_glyphs; ++i)
+  {
+    unsigned int first_layer_index, num_layers;
+    if (colr->get_base_glyph_record (i, &first_layer_index, &num_layers))
+    {
+      // Measure
+      cairo_text_extents_t extents;
+      {
+	cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+	cairo_t *cr = cairo_create (surface);
+	cairo_set_font_face (cr, cairo_face);
+	cairo_set_font_size (cr, upem);
+
+	cairo_glyph_t *glyphs = (cairo_glyph_t *) calloc (num_layers, sizeof (cairo_glyph_t));
+	for (unsigned int j = 0; j < num_layers; ++j)
+	{
+	  hb_codepoint_t glyph_id;
+	  unsigned int color_index;
+	  colr->get_layer_record (first_layer_index + j, &glyph_id, &color_index);
+	  glyphs[j].index = glyph_id;
+	}
+	cairo_glyph_extents (cr, glyphs, num_layers, &extents);
+	free (glyphs);
+	cairo_surface_destroy (surface);
+	cairo_destroy (cr);
+      }
+
+      // Add a slight margin
+      extents.width += extents.width / 10;
+      extents.height += extents.height / 10;
+      extents.x_bearing -= extents.width / 20;
+      extents.y_bearing -= extents.height / 20;
+
+      // Render
+      unsigned int pallet_count = cpal->get_palette_count ();
+      for (unsigned int pallet = 0; pallet < pallet_count; ++pallet) {
+	char output_path[255];
+
+	// If we have more than one pallet, use a better namin
+	if (pallet_count == 1)
+	  sprintf (output_path, "out/colr-%d.svg", i);
+	else
+	  sprintf (output_path, "out/colr-%d-%d.svg", i, pallet);
+
+	cairo_surface_t *surface = cairo_svg_surface_create (output_path, extents.width, extents.height);
+	cairo_t *cr = cairo_create (surface);
+	cairo_set_font_face (cr, cairo_face);
+	cairo_set_font_size (cr, upem);
+
+	for (unsigned int j = 0; j < num_layers; ++j)
+	{
+	  hb_codepoint_t glyph_id;
+	  unsigned int color_index;
+	  colr->get_layer_record (first_layer_index + j, &glyph_id, &color_index);
+
+	  uint32_t color = cpal->get_color_record_argb (color_index, pallet);
+	  int alpha = color & 0xFF;
+	  int r = (color >> 8) & 0xFF;
+	  int g = (color >> 16) & 0xFF;
+	  int b = (color >> 24) & 0xFF;
+	  cairo_set_source_rgba (cr, r / 255.f, g / 255.f, b / 255.f, alpha);
+
+	  cairo_glyph_t glyph;
+	  glyph.index = glyph_id;
+	  glyph.x = -extents.x_bearing;
+	  glyph.y = -extents.y_bearing;
+	  cairo_show_glyphs (cr, &glyph, 1);
+	}
+
+	cairo_surface_destroy (surface);
+	cairo_destroy (cr);
+      }
+    }
+  }
+}
+
+void dump_glyphs (cairo_font_face_t *cairo_face, unsigned int upem, unsigned int num_glyphs)
+{
+  // Dump every glyph available on the font
+  for (int i = 0; i < num_glyphs; ++i)
+  {
+    cairo_text_extents_t extents;
+    cairo_glyph_t glyph = {0};
+    glyph.index = i;
+
+    // Measure
+    {
+      cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
+      cairo_t *cr = cairo_create (surface);
+      cairo_set_font_face (cr, cairo_face);
+      cairo_set_font_size (cr, upem);
+
+      cairo_glyph_extents (cr, &glyph, 1, &extents);
+      cairo_surface_destroy (surface);
+      cairo_destroy (cr);
+    }
+
+    // Add a slight margin
+    extents.width += extents.width / 10;
+    extents.height += extents.height / 10;
+    extents.x_bearing -= extents.width / 20;
+    extents.y_bearing -= extents.height / 20;
+
+    // Render
+    {
+      char output_path[255];
+      sprintf (output_path, "out/%d.svg", i);
+      cairo_surface_t *surface = cairo_svg_surface_create (output_path, extents.width, extents.height);
+      cairo_t *cr = cairo_create (surface);
+      cairo_set_font_face (cr, cairo_face);
+      cairo_set_font_size (cr, upem);
+      glyph.x = -extents.x_bearing;
+      glyph.y = -extents.y_bearing;
+      cairo_show_glyphs (cr, &glyph, 1);
+      cairo_surface_destroy (surface);
+      cairo_destroy (cr);
+    }
+  }
+}
+
+int main (int argc, char **argv)
 {
   if (argc != 2) {
     fprintf (stderr, "usage: %s font-file.ttf\n", argv[0]);
@@ -133,6 +268,28 @@
   svg.dump (svg_callback);
   svg.fini ();
 
+  OT::Sanitizer<OT::COLR> sanitizerCOLR;
+  hb_blob_t* colr_blob = sanitizerCOLR.sanitize (face->reference_table (HB_OT_TAG_COLR));
+  const OT::COLR *colr = OT::Sanitizer<OT::COLR>::lock_instance (colr_blob);
+
+  OT::Sanitizer<OT::CPAL> sanitizerCPAL;
+  hb_blob_t* cpal_blob = sanitizerCPAL.sanitize (face->reference_table (HB_OT_TAG_CPAL));
+  const OT::CPAL *cpal = OT::Sanitizer<OT::CPAL>::lock_instance (cpal_blob);
+
+  cairo_font_face_t *cairo_face;
+  {
+    FT_Library library;
+    FT_Init_FreeType (&library);
+    FT_Face ftface;
+    FT_New_Face (library, argv[1], 0, &ftface);
+    cairo_face = cairo_ft_font_face_create_for_ft_face (ftface, 0);
+  }
+  unsigned int num_glyphs = hb_face_get_glyph_count (face);
+  unsigned int upem = hb_face_get_upem (face);
+  colr_cpal_rendering (cairo_face, upem, num_glyphs, colr, cpal);
+  dump_glyphs (cairo_face, upem, num_glyphs);
+
+
   hb_font_destroy (font);
   hb_face_destroy (face);
   hb_blob_destroy (blob);
diff --git a/src/hb-ot-color-colr-table.hh b/src/hb-ot-color-colr-table.hh
index c1cf6de..5308b5a 100644
--- a/src/hb-ot-color-colr-table.hh
+++ b/src/hb-ot-color-colr-table.hh
@@ -64,6 +64,10 @@
     return_trace (c->check_struct (this));
   }
 
+  inline int cmp (hb_codepoint_t g) const {
+    return g < glyphid ? -1 : g > glyphid ? 1 : 0;
+  }
+
   protected:
   GlyphID	glyphid;	/* Glyph ID of reference glyph */
   HBUINT16	firstLayerIdx;	/* Index to the layer record */
@@ -72,6 +76,13 @@
   DEFINE_SIZE_STATIC (6);
 };
 
+static int compare_bgr (const void *pa, const void *pb)
+{
+  const hb_codepoint_t *a = (const hb_codepoint_t *) pa;
+  const BaseGlyphRecord *b = (const BaseGlyphRecord *) pb;
+  return b->cmp (*a);
+}
+
 struct COLR
 {
   static const hb_tag_t tableTag = HB_OT_TAG_COLR;
@@ -80,17 +91,41 @@
   {
     TRACE_SANITIZE (this);
     return_trace (c->check_struct (this) &&
-		  (this+baseGlyphs).sanitize (c, numBaseGlyphs) &&
-		  (this+layers).sanitize (c, numLayers));
+		  (this+baseGlyphsZ).sanitize (c, numBaseGlyphs) &&
+		  (this+layersZ).sanitize (c, numLayers));
+  }
+
+  inline bool get_base_glyph_record (hb_codepoint_t glyph_id,
+				     unsigned int *first_layer /* OUT */,
+				     unsigned int *num_layers /* OUT */) const
+  {
+    const BaseGlyphRecord* record;
+    record = (BaseGlyphRecord *) bsearch (&glyph_id, &(this+baseGlyphsZ), numBaseGlyphs,
+					  sizeof (BaseGlyphRecord), compare_bgr);
+    if (!record)
+      return false;
+
+    *first_layer = record->firstLayerIdx;
+    *num_layers = record->numLayers;
+    return true;
+  }
+
+  inline void get_layer_record (unsigned int record,
+				hb_codepoint_t *glyph_id /* OUT */,
+				unsigned int *palette_index /* OUT */) const
+  {
+    const LayerRecord &layer = (this+layersZ)[record];
+    *glyph_id = layer.glyphid;
+    *palette_index = layer.colorIdx;
   }
 
   protected:
   HBUINT16	version;	/* Table version number */
   HBUINT16	numBaseGlyphs;	/* Number of Base Glyph Records */
   LOffsetTo<UnsizedArrayOf<BaseGlyphRecord> >
-		baseGlyphs;	/* Offset to Base Glyph records. */
+		baseGlyphsZ;	/* Offset to Base Glyph records. */
   LOffsetTo<UnsizedArrayOf<LayerRecord> >
-		layers;		/* Offset to Layer Records */
+		layersZ;	/* Offset to Layer Records */
   HBUINT16	numLayers;	/* Number of Layer Records */
   public:
   DEFINE_SIZE_STATIC (14);
diff --git a/src/hb-ot-color-cpal-table.hh b/src/hb-ot-color-cpal-table.hh
index e364c8a..7feb3e1 100644
--- a/src/hb-ot-color-cpal-table.hh
+++ b/src/hb-ot-color-cpal-table.hh
@@ -92,35 +92,44 @@
 {
   friend struct CPAL;
 
-  inline bool sanitize (hb_sanitize_context_t *c, unsigned int palettes) const
+  inline bool
+  sanitize (hb_sanitize_context_t *c, const void *base, unsigned int palettes) const
   {
     TRACE_SANITIZE (this);
-    return_trace (
-      c->check_struct (this) &&
-      c->check_array ((const void*) &paletteFlags, sizeof (HBUINT32), palettes) &&
-      c->check_array ((const void*) &paletteLabel, sizeof (HBUINT16), palettes) &&
-      c->check_array ((const void*) &paletteEntryLabel, sizeof (HBUINT16), palettes));
+    return_trace (c->check_struct (this) &&
+		  (base+paletteFlagsZ).sanitize (c, palettes) &&
+		  (base+paletteLabelZ).sanitize (c, palettes) &&
+		  (base+paletteEntryLabelZ).sanitize (c, palettes));
   }
 
   private:
   inline hb_ot_color_palette_flags_t
   get_palette_flags (const void *base, unsigned int palette) const
   {
-    const HBUINT32* flags = &paletteFlags (base);
-    return (hb_ot_color_palette_flags_t) (uint32_t) flags[palette];
+    // range checked at the CPAL caller
+    return (hb_ot_color_palette_flags_t) (uint32_t) (base+paletteFlagsZ)[palette];
   }
 
   inline unsigned int
   get_palette_name_id (const void *base, unsigned int palette) const
   {
-    const HBUINT16* name_ids = &paletteLabel (base);
-    return name_ids[palette];
+    // range checked at the CPAL caller
+    return (base+paletteLabelZ)[palette];
   }
 
   protected:
-  LOffsetTo<HBUINT32> paletteFlags;
-  LOffsetTo<HBUINT16> paletteLabel;
-  LOffsetTo<HBUINT16> paletteEntryLabel;
+  LOffsetTo<UnsizedArrayOf<HBUINT32> >
+		paletteFlagsZ;		/* Offset from the beginning of CPAL table to
+					 * the Palette Type Array. Set to 0 if no array
+					 * is provided. */
+  LOffsetTo<UnsizedArrayOf<HBUINT16> >
+		paletteLabelZ;		/* Offset from the beginning of CPAL table to
+					 * the Palette Labels Array. Set to 0 if no
+					 * array is provided. */
+  LOffsetTo<UnsizedArrayOf<HBUINT16> >
+		paletteEntryLabelZ;	/* Offset from the beginning of CPAL table to
+					 * the Palette Entry Label Array. Set to 0
+					 * if no array is provided. */
   public:
   DEFINE_SIZE_STATIC (12);
 };
@@ -134,13 +143,13 @@
   inline bool sanitize (hb_sanitize_context_t *c) const
   {
     TRACE_SANITIZE (this);
-    if (!(c->check_struct (this) && // This checks colorRecordIndicesX sanity also, see #get_size
-        c->check_array ((const void*) &colorRecordsZ, sizeof (BGRAColor), numColorRecords)))
+    if (!(c->check_struct (this) && // it checks colorRecordIndices also, see #get_size
+          (this+colorRecordsZ).sanitize (c, numColorRecords)))
       return_trace (false);
 
     // Check for indices sanity so no need for doing it runtime
     for (unsigned int i = 0; i < numPalettes; ++i)
-      if (colorRecordIndicesX[i] + numPaletteEntries > numColorRecords)
+      if (colorRecordIndicesZ[i] + numPaletteEntries > numColorRecords)
         return_trace (false);
 
     // If version is zero, we are done here; otherwise we need to check tail also
@@ -148,7 +157,7 @@
       return_trace (true);
 
     const CPALV1Tail &v1 = StructAfter<CPALV1Tail> (*this);
-    return_trace (v1.sanitize (c, numPalettes));
+    return_trace (v1.sanitize (c, this, numPalettes));
   }
 
   inline unsigned int get_size (void) const
@@ -179,27 +188,32 @@
     return numPalettes;
   }
 
-  inline hb_ot_color_t get_color_record_argb (unsigned int color_index, unsigned int palette) const
+  inline hb_ot_color_t
+  get_color_record_argb (unsigned int color_index, unsigned int palette) const
   {
     if (color_index >= numPaletteEntries || palette >= numPalettes)
       return 0;
 
-    const BGRAColor* records = &colorRecordsZ(this);
     // No need for more range check as it is already done on #sanitize
-    return records[colorRecordIndicesX[palette] + color_index];
+    return (this+colorRecordsZ)[colorRecordIndicesZ[palette] + color_index];
   }
 
   protected:
-  HBUINT16	version;
+  HBUINT16	version;		/* Table version number */
   /* Version 0 */
-  HBUINT16	numPaletteEntries;
-  HBUINT16	numPalettes;
-  HBUINT16	numColorRecords;
-  LOffsetTo<HBUINT32>	colorRecordsZ;
-  HBUINT16	colorRecordIndicesX[VAR];  // VAR=numPalettes
-/*CPALV1Tail	v1[VAR];*/
+  HBUINT16	numPaletteEntries;	/* Number of palette entries in each palette. */
+  HBUINT16	numPalettes;		/* Number of palettes in the table. */
+  HBUINT16	numColorRecords;	/* Total number of color records, combined for
+					 * all palettes. */
+  LOffsetTo<UnsizedArrayOf<BGRAColor> >
+		colorRecordsZ;		/* Offset from the beginning of CPAL table to
+					 * the first ColorRecord. */
+  UnsizedArrayOf<HBUINT16>
+		colorRecordIndicesZ;	/* Index of each paletteā€™s first color record in
+					 * the combined color record array. */
+/*CPALV1Tail	v1;*/
   public:
-  DEFINE_SIZE_ARRAY (12, colorRecordIndicesX);
+  DEFINE_SIZE_ARRAY (12, colorRecordIndicesZ);
 };
 
 } /* namespace OT */