Imported Upstream version 2.13.2
[platform/upstream/freetype2.git] / src / sfnt / ttcolr.c
index 9025e35..281e713 100644 (file)
@@ -4,8 +4,8 @@
  *
  *   TrueType and OpenType colored glyph layer support (body).
  *
- * Copyright (C) 2018-2020 by
- * David Turner, Robert Wilhelm, and Werner Lemberg.
+ * Copyright (C) 2018-2023 by
+ * David Turner, Robert Wilhelm, Dominik Röttsches, and Werner Lemberg.
  *
  * Originally written by Shao Yu Zhang <shaozhang@fb.com>.
  *
    */
 
 
+#include <freetype/internal/ftcalc.h>
 #include <freetype/internal/ftdebug.h>
 #include <freetype/internal/ftstream.h>
 #include <freetype/tttags.h>
 #include <freetype/ftcolor.h>
+#include <freetype/config/integer-types.h>
 
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+#include <freetype/internal/services/svmm.h>
+#endif
 
 #ifdef TT_CONFIG_OPTION_COLOR_LAYERS
 
 
 
   /* NOTE: These are the table sizes calculated through the specs. */
-#define BASE_GLYPH_SIZE            6U
-#define LAYER_SIZE                 4U
-#define COLR_HEADER_SIZE          14U
-
-
-  typedef struct BaseGlyphRecord_
+#define BASE_GLYPH_SIZE                   6U
+#define BASE_GLYPH_PAINT_RECORD_SIZE      6U
+#define LAYER_V1_LIST_PAINT_OFFSET_SIZE   4U
+#define LAYER_V1_LIST_NUM_LAYERS_SIZE     4U
+#define COLOR_STOP_SIZE                   6U
+#define VAR_IDX_BASE_SIZE                 4U
+#define LAYER_SIZE                        4U
+/* https://docs.microsoft.com/en-us/typography/opentype/spec/colr#colr-header */
+/* 3 * uint16 + 2 * Offset32 */
+#define COLRV0_HEADER_SIZE               14U
+/* COLRV0_HEADER_SIZE + 5 * Offset32 */
+#define COLRV1_HEADER_SIZE               34U
+
+
+#define ENSURE_READ_BYTES( byte_size )                             \
+  if ( p < colr->paints_start_v1                                || \
+       p > (FT_Byte*)colr->table + colr->table_size - byte_size )  \
+    return 0
+
+
+  typedef enum  FT_PaintFormat_Internal_
+  {
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SOLID                = 3,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_LINEAR_GRADIENT      = 5,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_RADIAL_GRADIENT      = 7,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SWEEP_GRADIENT       = 9,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_TRANSFORM            = 13,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_TRANSLATE            = 15,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE                = 17,
+    FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER             = 18,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_CENTER         = 19,
+    FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM            = 20,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM        = 21,
+    FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM_CENTER     = 22,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM_CENTER = 23,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE               = 25,
+    FT_COLR_PAINTFORMAT_INTERNAL_ROTATE_CENTER            = 26,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE_CENTER        = 27,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW                 = 29,
+    FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER              = 30,
+    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW_CENTER          = 31,
+
+  } FT_PaintFormat_Internal;
+
+
+  typedef struct  BaseGlyphRecord_
   {
     FT_UShort  gid;
     FT_UShort  first_layer_index;
   } BaseGlyphRecord;
 
 
-  typedef struct Colr_
+  typedef struct  BaseGlyphV1Record_
+  {
+    FT_UShort  gid;
+    /* Offset from start of BaseGlyphV1List, i.e., from base_glyphs_v1. */
+    FT_ULong   paint_offset;
+
+  } BaseGlyphV1Record;
+
+
+  typedef struct  Colr_
   {
     FT_UShort  version;
     FT_UShort  num_base_glyphs;
     FT_Byte*  base_glyphs;
     FT_Byte*  layers;
 
-    /* The memory which backs up the `COLR' table. */
+    FT_ULong  num_base_glyphs_v1;
+    /* Points at beginning of BaseGlyphV1List. */
+    FT_Byte*  base_glyphs_v1;
+
+    FT_ULong  num_layers_v1;
+    FT_Byte*  layers_v1;
+
+    FT_Byte*  clip_list;
+
+    /*
+     * Paint tables start at the minimum of the end of the LayerList and the
+     * end of the BaseGlyphList.  Record this location in a field here for
+     * safety checks when accessing paint tables.
+     */
+    FT_Byte*  paints_start_v1;
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+    /* Item Variation Store for variable 'COLR' v1. */
+    GX_ItemVarStoreRec    var_store;
+    GX_DeltaSetIdxMapRec  delta_set_idx_map;
+#endif
+
+    /* The memory that backs up the `COLR' table. */
     void*     table;
     FT_ULong  table_size;
 
 
     FT_Byte*  table = NULL;
     FT_Byte*  p     = NULL;
+    /* Needed for reading array lengths in referenced tables. */
+    FT_Byte*  p1    = NULL;
 
     Colr*  colr = NULL;
 
     FT_ULong  base_glyph_offset, layer_offset;
+    FT_ULong  base_glyphs_offset_v1, num_base_glyphs_v1;
+    FT_ULong  layer_offset_v1, num_layers_v1, clip_list_offset;
     FT_ULong  table_size;
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+    FT_ULong  colr_offset_in_stream;
+#endif
 
 
     /* `COLR' always needs `CPAL' */
     if ( error )
       goto NoColr;
 
-    if ( table_size < COLR_HEADER_SIZE )
-      goto InvalidTable;
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+    colr_offset_in_stream = FT_STREAM_POS();
+#endif
+
+    if ( table_size < COLRV0_HEADER_SIZE )
+      goto NoColr;
 
     if ( FT_FRAME_EXTRACT( table_size, table ) )
       goto NoColr;
       goto NoColr;
 
     colr->version = FT_NEXT_USHORT( p );
-    if ( colr->version != 0 )
+    if ( colr->version != 0 && colr->version != 1 )
       goto InvalidTable;
 
     colr->num_base_glyphs = FT_NEXT_USHORT( p );
     if ( colr->num_layers * LAYER_SIZE > table_size - layer_offset )
       goto InvalidTable;
 
+    if ( colr->version == 1 )
+    {
+      if ( table_size < COLRV1_HEADER_SIZE )
+        goto InvalidTable;
+
+      base_glyphs_offset_v1 = FT_NEXT_ULONG( p );
+
+      if ( base_glyphs_offset_v1 >= table_size - 4 )
+        goto InvalidTable;
+
+      p1                 = (FT_Byte*)( table + base_glyphs_offset_v1 );
+      num_base_glyphs_v1 = FT_PEEK_ULONG( p1 );
+
+      if ( num_base_glyphs_v1 * BASE_GLYPH_PAINT_RECORD_SIZE >
+             table_size - base_glyphs_offset_v1 )
+        goto InvalidTable;
+
+      colr->num_base_glyphs_v1 = num_base_glyphs_v1;
+      colr->base_glyphs_v1     = p1;
+
+      layer_offset_v1 = FT_NEXT_ULONG( p );
+
+      if ( layer_offset_v1 >= table_size )
+        goto InvalidTable;
+
+      if ( layer_offset_v1 )
+      {
+        if ( layer_offset_v1 >= table_size - 4 )
+          goto InvalidTable;
+
+        p1            = (FT_Byte*)( table + layer_offset_v1 );
+        num_layers_v1 = FT_PEEK_ULONG( p1 );
+
+        if ( num_layers_v1 * LAYER_V1_LIST_PAINT_OFFSET_SIZE >
+               table_size - layer_offset_v1 )
+          goto InvalidTable;
+
+        colr->num_layers_v1 = num_layers_v1;
+        colr->layers_v1     = p1;
+
+        colr->paints_start_v1 =
+            FT_MIN( colr->base_glyphs_v1 +
+                    colr->num_base_glyphs_v1 * BASE_GLYPH_PAINT_RECORD_SIZE,
+                    colr->layers_v1 +
+                    colr->num_layers_v1 * LAYER_V1_LIST_PAINT_OFFSET_SIZE );
+      }
+      else
+      {
+        colr->num_layers_v1   = 0;
+        colr->layers_v1       = 0;
+        colr->paints_start_v1 =
+          colr->base_glyphs_v1 +
+          colr->num_base_glyphs_v1 * BASE_GLYPH_PAINT_RECORD_SIZE;
+      }
+
+      clip_list_offset = FT_NEXT_ULONG( p );
+
+      if ( clip_list_offset >= table_size )
+        goto InvalidTable;
+
+      if ( clip_list_offset )
+        colr->clip_list = (FT_Byte*)( table + clip_list_offset );
+      else
+        colr->clip_list = 0;
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      colr->var_store.dataCount     = 0;
+      colr->var_store.varData       = NULL;
+      colr->var_store.axisCount     = 0;
+      colr->var_store.regionCount   = 0;
+      colr->var_store.varRegionList = 0;
+
+      colr->delta_set_idx_map.mapCount   = 0;
+      colr->delta_set_idx_map.outerIndex = NULL;
+      colr->delta_set_idx_map.innerIndex = NULL;
+
+      if ( face->variation_support & TT_FACE_FLAG_VAR_FVAR )
+      {
+        FT_ULong  var_idx_map_offset, var_store_offset;
+
+        FT_Service_MultiMasters  mm = (FT_Service_MultiMasters)face->mm;
+
+
+        var_idx_map_offset = FT_NEXT_ULONG( p );
+
+        if ( var_idx_map_offset >= table_size )
+          goto InvalidTable;
+
+        var_store_offset = FT_NEXT_ULONG( p );
+        if ( var_store_offset >= table_size )
+          goto InvalidTable;
+
+        if ( var_store_offset )
+        {
+          /* If variation info has not been initialized yet, try doing so, */
+          /* otherwise loading the variation store will fail as it         */
+          /* requires access to `blend` for checking the number of axes.   */
+          if ( !face->blend )
+            if ( mm->get_mm_var( FT_FACE( face ), NULL ) )
+              goto InvalidTable;
+
+          /* Try loading `VarIdxMap` and `VarStore`. */
+          error = mm->load_item_var_store(
+                    FT_FACE( face ),
+                    colr_offset_in_stream + var_store_offset,
+                    &colr->var_store );
+          if ( error != FT_Err_Ok )
+            goto InvalidTable;
+        }
+
+        if ( colr->var_store.axisCount && var_idx_map_offset )
+        {
+          error = mm->load_delta_set_idx_map(
+                    FT_FACE( face ),
+                    colr_offset_in_stream + var_idx_map_offset,
+                    &colr->delta_set_idx_map,
+                    &colr->var_store,
+                    table_size );
+          if ( error != FT_Err_Ok )
+            goto InvalidTable;
+        }
+      }
+#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */
+    }
+
     colr->base_glyphs = (FT_Byte*)( table + base_glyph_offset );
     colr->layers      = (FT_Byte*)( table + layer_offset      );
     colr->table       = table;
     return FT_Err_Ok;
 
   InvalidTable:
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+    {
+      FT_Service_MultiMasters  mm = (FT_Service_MultiMasters)face->mm;
+
+
+      mm->done_delta_set_idx_map( FT_FACE( face ),
+                                  &colr->delta_set_idx_map );
+      mm->done_item_var_store( FT_FACE( face ),
+                               &colr->var_store );
+    }
+#endif
+
     error = FT_THROW( Invalid_Table );
 
   NoColr:
 
     if ( colr )
     {
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      {
+        FT_Service_MultiMasters  mm = (FT_Service_MultiMasters)face->mm;
+
+
+        mm->done_delta_set_idx_map( FT_FACE( face ),
+                                    &colr->delta_set_idx_map );
+        mm->done_item_var_store( FT_FACE( face ),
+                                 &colr->var_store );
+      }
+#endif
       FT_FRAME_RELEASE( colr->table );
       FT_FREE( colr );
     }
 
   static FT_Bool
   find_base_glyph_record( FT_Byte*          base_glyph_begin,
-                          FT_Int            num_base_glyph,
+                          FT_UInt           num_base_glyph,
                           FT_UInt           glyph_id,
                           BaseGlyphRecord*  record )
   {
-    FT_Int  min = 0;
-    FT_Int  max = num_base_glyph - 1;
+    FT_UInt  min = 0;
+    FT_UInt  max = num_base_glyph;
 
 
-    while ( min <= max )
+    while ( min < max )
     {
-      FT_Int    mid = min + ( max - min ) / 2;
+      FT_UInt   mid = min + ( max - min ) / 2;
       FT_Byte*  p   = base_glyph_begin + mid * BASE_GLYPH_SIZE;
 
       FT_UShort  gid = FT_NEXT_USHORT( p );
       if ( gid < glyph_id )
         min = mid + 1;
       else if (gid > glyph_id )
-        max = mid - 1;
+        max = mid;
       else
       {
         record->gid               = gid;
       iterator->p = colr->layers + offset;
     }
 
-    if ( iterator->layer >= iterator->num_layers )
+    if ( iterator->layer >= iterator->num_layers                     ||
+         iterator->p < colr->layers                                  ||
+         iterator->p >= ( (FT_Byte*)colr->table + colr->table_size ) )
       return 0;
 
     *aglyph_index = FT_NEXT_USHORT( iterator->p );
   }
 
 
+  static FT_Bool
+  read_color_line( Colr*          colr,
+                   FT_Byte*       color_line_p,
+                   FT_ColorLine*  colorline,
+                   FT_Bool        read_variable )
+  {
+    FT_Byte*        p = color_line_p;
+    FT_PaintExtend  paint_extend;
+
+
+    ENSURE_READ_BYTES( 3 );
+
+    paint_extend = (FT_PaintExtend)FT_NEXT_BYTE( p );
+    if ( paint_extend > FT_COLR_PAINT_EXTEND_REFLECT )
+      return 0;
+
+    colorline->extend = paint_extend;
+
+    colorline->color_stop_iterator.num_color_stops    = FT_NEXT_USHORT( p );
+    colorline->color_stop_iterator.p                  = p;
+    colorline->color_stop_iterator.current_color_stop = 0;
+    colorline->color_stop_iterator.read_variable      = read_variable;
+
+    return 1;
+  }
+
+
+  /*
+   * Read a paint offset for `FT_Paint*` objects that have them and check
+   * whether it is within reasonable limits within the font and the COLR
+   * table.
+   *
+   * Return 1 on success, 0 on failure.
+   */
+  static FT_Bool
+  get_child_table_pointer ( Colr*      colr,
+                            FT_Byte*   paint_base,
+                            FT_Byte**  p,
+                            FT_Byte**  child_table_pointer )
+  {
+    FT_UInt32  paint_offset;
+    FT_Byte*   child_table_p;
+
+
+    if ( !child_table_pointer )
+      return 0;
+
+    if ( *p < colr->paints_start_v1                            ||
+         *p > (FT_Byte*)colr->table + colr->table_size - 1 - 3 )
+      return 0;
+
+    paint_offset = FT_NEXT_UOFF3( *p );
+    if ( !paint_offset )
+      return 0;
+
+    child_table_p = (FT_Byte*)( paint_base + paint_offset );
+
+    if ( child_table_p < colr->paints_start_v1                         ||
+         child_table_p >= ( (FT_Byte*)colr->table + colr->table_size ) )
+      return 0;
+
+    *child_table_pointer = child_table_p;
+    return 1;
+  }
+
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+
+  static FT_Bool
+  get_deltas_for_var_index_base ( TT_Face           face,
+                                  Colr*             colr,
+                                  FT_ULong          var_index_base,
+                                  FT_UInt           num_deltas,
+                                  FT_ItemVarDelta*  deltas )
+  {
+    FT_UInt   outer_index    = 0;
+    FT_UInt   inner_index    = 0;
+    FT_ULong  loop_var_index = var_index_base;
+
+    FT_Service_MultiMasters  mm = (FT_Service_MultiMasters)face->mm;
+
+    FT_UInt  i = 0;
+
+
+    if ( var_index_base == 0xFFFFFFFF )
+    {
+      for ( i = 0; i < num_deltas; ++i )
+        deltas[i] = 0;
+      return 1;
+    }
+
+    for ( i = 0; i < num_deltas; ++i )
+    {
+      loop_var_index = var_index_base + i;
+
+      if ( colr->delta_set_idx_map.innerIndex )
+      {
+        if ( loop_var_index >= colr->delta_set_idx_map.mapCount )
+          loop_var_index = colr->delta_set_idx_map.mapCount - 1;
+
+        outer_index = colr->delta_set_idx_map.outerIndex[loop_var_index];
+        inner_index = colr->delta_set_idx_map.innerIndex[loop_var_index];
+      }
+      else
+      {
+        outer_index = 0;
+        inner_index = loop_var_index;
+      }
+
+      deltas[i] = mm->get_item_delta( FT_FACE( face ), &colr->var_store,
+                                      outer_index, inner_index );
+    }
+
+    return 1;
+  }
+
+#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */
+
+
+  static FT_Bool
+  read_paint( TT_Face         face,
+              Colr*           colr,
+              FT_Byte*        p,
+              FT_COLR_Paint*  apaint )
+  {
+    FT_Byte*  paint_base    = p;
+    FT_Byte*  child_table_p = NULL;
+    FT_Bool   do_read_var   = FALSE;
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+    FT_ULong         var_index_base = 0;
+    /* Longest varIndexBase offset is 5 in the spec. */
+    FT_ItemVarDelta  item_deltas[6] = { 0, 0, 0, 0, 0, 0 };
+#else
+    FT_UNUSED( face );
+#endif
+
+
+    if ( !p || !colr || !colr->table )
+      return 0;
+
+    /* The last byte of the 'COLR' table is at 'size-1'; subtract 1 of    */
+    /* that to account for the expected format byte we are going to read. */
+    if ( p < colr->paints_start_v1                        ||
+         p > (FT_Byte*)colr->table + colr->table_size - 2 )
+      return 0;
+
+    apaint->format = (FT_PaintFormat)FT_NEXT_BYTE( p );
+
+    if ( apaint->format >= FT_COLR_PAINT_FORMAT_MAX )
+      return 0;
+
+    if ( apaint->format == FT_COLR_PAINTFORMAT_COLR_LAYERS )
+    {
+      /* Initialize layer iterator/ */
+      FT_Byte    num_layers;
+      FT_UInt32  first_layer_index;
+
+
+      num_layers = FT_NEXT_BYTE( p );
+      if ( num_layers > colr->num_layers_v1 )
+        return 0;
+
+      first_layer_index = FT_NEXT_ULONG( p );
+      if ( first_layer_index + num_layers > colr->num_layers_v1 )
+        return 0;
+
+      apaint->u.colr_layers.layer_iterator.num_layers = num_layers;
+      apaint->u.colr_layers.layer_iterator.layer      = 0;
+      /* TODO: Check whether pointer is outside colr? */
+      apaint->u.colr_layers.layer_iterator.p =
+        colr->layers_v1 +
+        LAYER_V1_LIST_NUM_LAYERS_SIZE +
+        LAYER_V1_LIST_PAINT_OFFSET_SIZE * first_layer_index;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_SOLID ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                 FT_COLR_PAINTFORMAT_INTERNAL_VAR_SOLID   )
+    {
+      ENSURE_READ_BYTES( 4 );
+      apaint->u.solid.color.palette_index = FT_NEXT_USHORT( p );
+      apaint->u.solid.color.alpha         = FT_NEXT_SHORT( p );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+              FT_COLR_PAINTFORMAT_INTERNAL_VAR_SOLID )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG( p );
+
+        if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 1,
+                                             item_deltas ) )
+          return 0;
+
+        apaint->u.solid.color.alpha += (FT_F2Dot14)item_deltas[0];
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_SOLID;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_COLR_GLYPH )
+    {
+      ENSURE_READ_BYTES(2);
+      apaint->u.colr_glyph.glyphID = FT_NEXT_USHORT( p );
+
+      return 1;
+    }
+
+    /*
+     * Grouped below here are all paint formats that have an offset to a
+     * child paint table as the first entry (for example, a color line or a
+     * child paint table).  Retrieve that and determine whether that paint
+     * offset is valid first.
+     */
+
+    if ( !get_child_table_pointer( colr, paint_base, &p, &child_table_p ) )
+      return 0;
+
+    if ( apaint->format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT      ||
+         ( do_read_var =
+             ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_LINEAR_GRADIENT ) ) )
+    {
+      if ( !read_color_line( colr,
+                             child_table_p,
+                             &apaint->u.linear_gradient.colorline,
+                             do_read_var ) )
+        return 0;
+
+      /*
+       * In order to support variations expose these as FT_Fixed 16.16
+       * values so that we can support fractional values after
+       * interpolation.
+       */
+      ENSURE_READ_BYTES( 12 );
+      apaint->u.linear_gradient.p0.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.linear_gradient.p0.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.linear_gradient.p1.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.linear_gradient.p1.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.linear_gradient.p2.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.linear_gradient.p2.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( do_read_var )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG ( p );
+
+        if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 6,
+                                             item_deltas ) )
+          return 0;
+
+        apaint->u.linear_gradient.p0.x += INT_TO_FIXED( item_deltas[0] );
+        apaint->u.linear_gradient.p0.y += INT_TO_FIXED( item_deltas[1] );
+        apaint->u.linear_gradient.p1.x += INT_TO_FIXED( item_deltas[2] );
+        apaint->u.linear_gradient.p1.y += INT_TO_FIXED( item_deltas[3] );
+        apaint->u.linear_gradient.p2.x += INT_TO_FIXED( item_deltas[4] );
+        apaint->u.linear_gradient.p2.y += INT_TO_FIXED( item_deltas[5] );
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_LINEAR_GRADIENT;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT      ||
+              ( do_read_var =
+                  ( (FT_PaintFormat_Internal)apaint->format ==
+                    FT_COLR_PAINTFORMAT_INTERNAL_VAR_RADIAL_GRADIENT ) ) )
+    {
+      FT_Pos  tmp;
+
+
+      if ( !read_color_line( colr,
+                             child_table_p,
+                             &apaint->u.radial_gradient.colorline,
+                             do_read_var ) )
+        return 0;
+
+
+      /* In the OpenType specification, `r0` and `r1` are defined as   */
+      /* `UFWORD`.  Since FreeType doesn't have a corresponding 16.16  */
+      /* format we convert to `FWORD` and replace negative values with */
+      /* (32bit) `FT_INT_MAX`.                                         */
+
+      ENSURE_READ_BYTES( 12 );
+
+      apaint->u.radial_gradient.c0.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.radial_gradient.c0.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+      tmp                          = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.radial_gradient.r0 = tmp < 0 ? FT_INT_MAX : tmp;
+
+      apaint->u.radial_gradient.c1.x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.radial_gradient.c1.y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+      tmp                          = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.radial_gradient.r1 = tmp < 0 ? FT_INT_MAX : tmp;
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( do_read_var )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG ( p );
+
+        if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 6,
+                                             item_deltas ) )
+          return 0;
+
+        apaint->u.radial_gradient.c0.x += INT_TO_FIXED( item_deltas[0] );
+        apaint->u.radial_gradient.c0.y += INT_TO_FIXED( item_deltas[1] );
+
+        // TODO: Anything to be done about UFWORD deltas here?
+        apaint->u.radial_gradient.r0 += INT_TO_FIXED( item_deltas[2] );
+
+        apaint->u.radial_gradient.c1.x += INT_TO_FIXED( item_deltas[3] );
+        apaint->u.radial_gradient.c1.y += INT_TO_FIXED( item_deltas[4] );
+
+        apaint->u.radial_gradient.r1 += INT_TO_FIXED( item_deltas[5] );
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_RADIAL_GRADIENT;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT      ||
+              ( do_read_var =
+                  ( (FT_PaintFormat_Internal)apaint->format ==
+                    FT_COLR_PAINTFORMAT_INTERNAL_VAR_SWEEP_GRADIENT ) ) )
+    {
+      if ( !read_color_line( colr,
+                             child_table_p,
+                             &apaint->u.sweep_gradient.colorline,
+                             do_read_var) )
+        return 0;
+
+      ENSURE_READ_BYTES( 8 );
+
+      apaint->u.sweep_gradient.center.x =
+          INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.sweep_gradient.center.y =
+          INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+      apaint->u.sweep_gradient.start_angle =
+          F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.sweep_gradient.end_angle =
+          F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( do_read_var )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG ( p );
+
+        if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 4,
+                                             item_deltas ) )
+          return 0;
+
+        // TODO: Handle overflow?
+        apaint->u.sweep_gradient.center.x += INT_TO_FIXED( item_deltas[0] );
+        apaint->u.sweep_gradient.center.y += INT_TO_FIXED( item_deltas[1] );
+
+        apaint->u.sweep_gradient.start_angle +=
+          F2DOT14_TO_FIXED( item_deltas[2] );
+        apaint->u.sweep_gradient.end_angle +=
+          F2DOT14_TO_FIXED( item_deltas[3] );
+      }
+#endif
+      apaint->format = FT_COLR_PAINTFORMAT_SWEEP_GRADIENT;
+
+      return 1;
+    }
+
+    if ( apaint->format == FT_COLR_PAINTFORMAT_GLYPH )
+    {
+      ENSURE_READ_BYTES( 2 );
+      apaint->u.glyph.paint.p                     = child_table_p;
+      apaint->u.glyph.paint.insert_root_transform = 0;
+      apaint->u.glyph.glyphID                     = FT_NEXT_USHORT( p );
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_TRANSFORM ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_TRANSFORM    )
+    {
+      apaint->u.transform.paint.p                     = child_table_p;
+      apaint->u.transform.paint.insert_root_transform = 0;
+
+      if ( !get_child_table_pointer( colr, paint_base, &p, &child_table_p ) )
+         return 0;
+
+      p = child_table_p;
+
+      /*
+       * The following matrix coefficients are encoded as
+       * OpenType 16.16 fixed-point values.
+       */
+      ENSURE_READ_BYTES( 24 );
+      apaint->u.transform.affine.xx = FT_NEXT_LONG( p );
+      apaint->u.transform.affine.yx = FT_NEXT_LONG( p );
+      apaint->u.transform.affine.xy = FT_NEXT_LONG( p );
+      apaint->u.transform.affine.yy = FT_NEXT_LONG( p );
+      apaint->u.transform.affine.dx = FT_NEXT_LONG( p );
+      apaint->u.transform.affine.dy = FT_NEXT_LONG( p );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_TRANSFORM )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG( p );
+
+        if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 6,
+                                             item_deltas ) )
+          return 0;
+
+        apaint->u.transform.affine.xx += (FT_Fixed)item_deltas[0];
+        apaint->u.transform.affine.yx += (FT_Fixed)item_deltas[1];
+        apaint->u.transform.affine.xy += (FT_Fixed)item_deltas[2];
+        apaint->u.transform.affine.yy += (FT_Fixed)item_deltas[3];
+        apaint->u.transform.affine.dx += (FT_Fixed)item_deltas[4];
+        apaint->u.transform.affine.dy += (FT_Fixed)item_deltas[5];
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_TRANSFORM;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_TRANSLATE ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_TRANSLATE    )
+    {
+      apaint->u.translate.paint.p                     = child_table_p;
+      apaint->u.translate.paint.insert_root_transform = 0;
+
+      ENSURE_READ_BYTES( 4 );
+      apaint->u.translate.dx = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.translate.dy = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_TRANSLATE )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG( p );
+
+        if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 2,
+                                             item_deltas ) )
+          return 0;
+
+        apaint->u.translate.dx += INT_TO_FIXED( item_deltas[0] );
+        apaint->u.translate.dy += INT_TO_FIXED( item_deltas[1] );
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_TRANSLATE;
+
+      return 1;
+    }
+
+    else if ( apaint->format >= FT_COLR_PAINTFORMAT_SCALE             &&
+              (FT_PaintFormat_Internal)apaint->format <=
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM_CENTER )
+    {
+      apaint->u.scale.paint.p                     = child_table_p;
+      apaint->u.scale.paint.insert_root_transform = 0;
+
+      /* All scale paints get at least one scale value. */
+      ENSURE_READ_BYTES( 2 );
+      apaint->u.scale.scale_x = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+      /* Non-uniform ones read an extra y value. */
+      if ( apaint->format == FT_COLR_PAINTFORMAT_SCALE     ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE        ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER     ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_CENTER )
+      {
+        ENSURE_READ_BYTES( 2 );
+        apaint->u.scale.scale_y = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+      }
+      else
+        apaint->u.scale.scale_y = apaint->u.scale.scale_x;
+
+      /* Scale paints that have a center read center coordinates, */
+      /* otherwise the center is (0,0).                           */
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_SCALE_CENTER             ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_CENTER         ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_SCALE_UNIFORM_CENTER     ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM_CENTER )
+      {
+        ENSURE_READ_BYTES( 4 );
+        apaint->u.scale.center_x = INT_TO_FIXED( FT_NEXT_SHORT ( p ) );
+        apaint->u.scale.center_y = INT_TO_FIXED( FT_NEXT_SHORT ( p ) );
+      }
+      else
+      {
+        apaint->u.scale.center_x = 0;
+        apaint->u.scale.center_y = 0;
+      }
+
+      /* Base values set, now handle variations. */
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE                ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_CENTER         ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM        ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM_CENTER )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG( p );
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 2,
+                                               item_deltas ) )
+            return 0;
+
+          apaint->u.scale.scale_x += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.scale.scale_y += F2DOT14_TO_FIXED( item_deltas[1] );
+        }
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_CENTER )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 4,
+                                               item_deltas ) )
+            return 0;
+
+          apaint->u.scale.scale_x  += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.scale.scale_y  += F2DOT14_TO_FIXED( item_deltas[1] );
+          apaint->u.scale.center_x += INT_TO_FIXED( item_deltas[2] );
+          apaint->u.scale.center_y += INT_TO_FIXED( item_deltas[3] );
+        }
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 1,
+                                               item_deltas ) )
+            return 0;
+
+          apaint->u.scale.scale_x += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.scale.scale_y += F2DOT14_TO_FIXED( item_deltas[0] );
+        }
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_SCALE_UNIFORM_CENTER )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 3,
+                                               item_deltas ) )
+            return 0;
+
+          apaint->u.scale.scale_x  += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.scale.scale_y  += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.scale.center_x += INT_TO_FIXED( item_deltas[1] );
+          apaint->u.scale.center_y += INT_TO_FIXED( item_deltas[2] );
+        }
+      }
+#endif
+
+      /* FT 'COLR' v1 API output format always returns fully defined */
+      /* structs; we thus set the format to the public API value.    */
+      apaint->format = FT_COLR_PAINTFORMAT_SCALE;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_ROTATE     ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_ROTATE_CENTER     ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE        ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE_CENTER )
+    {
+      apaint->u.rotate.paint.p                     = child_table_p;
+      apaint->u.rotate.paint.insert_root_transform = 0;
+
+      ENSURE_READ_BYTES( 2 );
+      apaint->u.rotate.angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_ROTATE_CENTER     ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE_CENTER )
+      {
+        ENSURE_READ_BYTES( 4 );
+        apaint->u.rotate.center_x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+        apaint->u.rotate.center_y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      }
+      else
+      {
+        apaint->u.rotate.center_x = 0;
+        apaint->u.rotate.center_y = 0;
+      }
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE        ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE_CENTER )
+      {
+        FT_UInt  num_deltas = 0;
+
+
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG( p );
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE_CENTER )
+          num_deltas = 3;
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_ROTATE )
+          num_deltas = 1;
+
+        if ( num_deltas > 0 )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base,
+                                               num_deltas, item_deltas ) )
+            return 0;
+
+          apaint->u.rotate.angle += F2DOT14_TO_FIXED( item_deltas[0] );
+
+          if ( num_deltas == 3 )
+          {
+            apaint->u.rotate.center_x += INT_TO_FIXED( item_deltas[1] );
+            apaint->u.rotate.center_y += INT_TO_FIXED( item_deltas[2] );
+          }
+        }
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_ROTATE;
+
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_SKEW     ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW        ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER     ||
+              (FT_PaintFormat_Internal)apaint->format ==
+                FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW_CENTER )
+    {
+      apaint->u.skew.paint.p                     = child_table_p;
+      apaint->u.skew.paint.insert_root_transform = 0;
+
+      ENSURE_READ_BYTES( 4 );
+      apaint->u.skew.x_skew_angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+      apaint->u.skew.y_skew_angle = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER     ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW_CENTER )
+      {
+        ENSURE_READ_BYTES( 4 );
+        apaint->u.skew.center_x = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+        apaint->u.skew.center_y = INT_TO_FIXED( FT_NEXT_SHORT( p ) );
+      }
+      else
+      {
+        apaint->u.skew.center_x = 0;
+        apaint->u.skew.center_y = 0;
+      }
+
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      if ( (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW        ||
+           (FT_PaintFormat_Internal)apaint->format ==
+             FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW_CENTER )
+      {
+        ENSURE_READ_BYTES( 4 );
+        var_index_base = FT_NEXT_ULONG( p );
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 2,
+                                               item_deltas ) )
+            return 0;
+
+          apaint->u.skew.x_skew_angle += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.skew.y_skew_angle += F2DOT14_TO_FIXED( item_deltas[1] );
+        }
+
+        if ( (FT_PaintFormat_Internal)apaint->format ==
+               FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW_CENTER )
+        {
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 4,
+                                               item_deltas ) )
+            return 0;
+
+          apaint->u.skew.x_skew_angle += F2DOT14_TO_FIXED( item_deltas[0] );
+          apaint->u.skew.y_skew_angle += F2DOT14_TO_FIXED( item_deltas[1] );
+          apaint->u.skew.center_x     += INT_TO_FIXED( item_deltas[2] );
+          apaint->u.skew.center_y     += INT_TO_FIXED( item_deltas[3] );
+        }
+      }
+#endif
+
+      apaint->format = FT_COLR_PAINTFORMAT_SKEW;
+
+      return 1;
+    }
+
+    else if ( apaint->format == FT_COLR_PAINTFORMAT_COMPOSITE )
+    {
+      FT_UInt  composite_mode;
+
+
+      apaint->u.composite.source_paint.p                     = child_table_p;
+      apaint->u.composite.source_paint.insert_root_transform = 0;
+
+      ENSURE_READ_BYTES( 1 );
+      composite_mode = FT_NEXT_BYTE( p );
+      if ( composite_mode >= FT_COLR_COMPOSITE_MAX )
+        return 0;
+
+      apaint->u.composite.composite_mode = (FT_Composite_Mode)composite_mode;
+
+      if ( !get_child_table_pointer( colr, paint_base, &p, &child_table_p ) )
+         return 0;
+
+      apaint->u.composite.backdrop_paint.p =
+        child_table_p;
+      apaint->u.composite.backdrop_paint.insert_root_transform =
+        0;
+
+      return 1;
+    }
+
+    return 0;
+  }
+
+
+  static FT_Bool
+  find_base_glyph_v1_record( FT_Byte *           base_glyph_begin,
+                             FT_UInt             num_base_glyph,
+                             FT_UInt             glyph_id,
+                             BaseGlyphV1Record  *record )
+  {
+    FT_UInt  min = 0;
+    FT_UInt  max = num_base_glyph;
+
+
+    while ( min < max )
+    {
+      FT_UInt  mid = min + ( max - min ) / 2;
+
+      /*
+       * `base_glyph_begin` is the beginning of `BaseGlyphV1List`;
+       * skip `numBaseGlyphV1Records` by adding 4 to start binary search
+       * in the array of `BaseGlyphV1Record`.
+       */
+      FT_Byte  *p = base_glyph_begin + 4 + mid * BASE_GLYPH_PAINT_RECORD_SIZE;
+
+      FT_UShort  gid = FT_NEXT_USHORT( p );
+
+
+      if ( gid < glyph_id )
+        min = mid + 1;
+      else if (gid > glyph_id )
+        max = mid;
+      else
+      {
+        record->gid          = gid;
+        record->paint_offset = FT_NEXT_ULONG ( p );
+        return 1;
+      }
+    }
+
+    return 0;
+  }
+
+
+  FT_LOCAL_DEF( FT_Bool )
+  tt_face_get_colr_glyph_paint( TT_Face                  face,
+                                FT_UInt                  base_glyph,
+                                FT_Color_Root_Transform  root_transform,
+                                FT_OpaquePaint*          opaque_paint )
+  {
+    Colr*              colr = (Colr*)face->colr;
+    BaseGlyphV1Record  base_glyph_v1_record;
+    FT_Byte*           p;
+
+    if ( !colr || !colr->table )
+      return 0;
+
+    if ( colr->version < 1 || !colr->num_base_glyphs_v1 ||
+         !colr->base_glyphs_v1 )
+      return 0;
+
+    if ( opaque_paint->p )
+      return 0;
+
+    if ( !find_base_glyph_v1_record( colr->base_glyphs_v1,
+                                     colr->num_base_glyphs_v1,
+                                     base_glyph,
+                                     &base_glyph_v1_record ) )
+      return 0;
+
+    if ( !base_glyph_v1_record.paint_offset                   ||
+         base_glyph_v1_record.paint_offset > colr->table_size )
+      return 0;
+
+    p = (FT_Byte*)( colr->base_glyphs_v1 +
+                    base_glyph_v1_record.paint_offset );
+    if ( p >= ( (FT_Byte*)colr->table + colr->table_size ) )
+      return 0;
+
+    opaque_paint->p = p;
+
+    if ( root_transform == FT_COLOR_INCLUDE_ROOT_TRANSFORM )
+      opaque_paint->insert_root_transform = 1;
+    else
+      opaque_paint->insert_root_transform = 0;
+
+    return 1;
+  }
+
+
+  FT_LOCAL_DEF( FT_Bool )
+  tt_face_get_color_glyph_clipbox( TT_Face      face,
+                                   FT_UInt      base_glyph,
+                                   FT_ClipBox*  clip_box )
+  {
+    Colr*  colr;
+
+    FT_Byte  *p, *p1, *clip_base, *limit;
+
+    FT_Byte    clip_list_format;
+    FT_ULong   num_clip_boxes, i;
+    FT_UShort  gid_start, gid_end;
+    FT_UInt32  clip_box_offset;
+    FT_Byte    format;
+
+    const FT_Byte  num_corners = 4;
+    FT_Vector      corners[4];
+    FT_Byte        j;
+    FT_BBox        font_clip_box;
+
+
+    colr = (Colr*)face->colr;
+    if ( !colr )
+      return 0;
+
+    if ( !colr->clip_list )
+      return 0;
+
+    p = colr->clip_list;
+
+    /* Limit points to the first byte after the end of the color table.    */
+    /* Thus, in subsequent limit checks below we need to check whether the */
+    /* read pointer is strictly greater than a position offset by certain  */
+    /* field sizes to the left of that position.                           */
+    limit = (FT_Byte*)colr->table + colr->table_size;
+
+    /* Check whether we can extract one `uint8` and one `uint32`. */
+    if ( p > limit - ( 1 + 4 ) )
+      return 0;
+
+    clip_base        = p;
+    clip_list_format = FT_NEXT_BYTE ( p );
+
+    /* Format byte used here to be able to upgrade ClipList for >16bit */
+    /* glyph ids; for now we can expect it to be 1.                    */
+    if ( !( clip_list_format == 1 ) )
+      return 0;
+
+    num_clip_boxes = FT_NEXT_ULONG( p );
+
+    /* Check whether we can extract two `uint16` and one `Offset24`, */
+    /* `num_clip_boxes` times.                                       */
+    if ( colr->table_size / ( 2 + 2 + 3 ) < num_clip_boxes ||
+         p > limit - ( 2 + 2 + 3 ) * num_clip_boxes        )
+      return 0;
+
+    for ( i = 0; i < num_clip_boxes; ++i )
+    {
+      gid_start       = FT_NEXT_USHORT( p );
+      gid_end         = FT_NEXT_USHORT( p );
+      clip_box_offset = FT_NEXT_UOFF3( p );
+
+      if ( base_glyph >= gid_start && base_glyph <= gid_end )
+      {
+        p1 = (FT_Byte*)( clip_base + clip_box_offset );
+
+        /* Check whether we can extract one `uint8`. */
+        if ( p1 > limit - 1 )
+          return 0;
+
+        format = FT_NEXT_BYTE( p1 );
+
+        if ( format > 2 )
+          return 0;
+
+        /* Check whether we can extract four `FWORD`. */
+        if ( p1 > limit - ( 2 + 2 + 2 + 2 ) )
+          return 0;
+
+        /* `face->root.size->metrics.x_scale` and `y_scale` are factors   */
+        /* that scale a font unit value in integers to a 26.6 fixed value */
+        /* according to the requested size, see for example               */
+        /* `ft_recompute_scaled_metrics`.                                 */
+        font_clip_box.xMin = FT_MulFix( FT_NEXT_SHORT( p1 ),
+                                        face->root.size->metrics.x_scale );
+        font_clip_box.yMin = FT_MulFix( FT_NEXT_SHORT( p1 ),
+                                        face->root.size->metrics.y_scale );
+        font_clip_box.xMax = FT_MulFix( FT_NEXT_SHORT( p1 ),
+                                        face->root.size->metrics.x_scale );
+        font_clip_box.yMax = FT_MulFix( FT_NEXT_SHORT( p1 ),
+                                        face->root.size->metrics.y_scale );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+        if ( format == 2 )
+        {
+          FT_ULong         var_index_base = 0;
+          /* varIndexBase offset for clipbox is 3 at most. */
+          FT_ItemVarDelta  item_deltas[4] = { 0, 0, 0, 0 };
+
+
+          /* Check whether we can extract a 32-bit varIndexBase now. */
+          if ( p1 > limit - 4 )
+            return 0;
+
+          var_index_base = FT_NEXT_ULONG( p1 );
+
+          if ( !get_deltas_for_var_index_base( face, colr, var_index_base, 4,
+                                               item_deltas ) )
+            return 0;
+
+          font_clip_box.xMin +=
+            FT_MulFix( item_deltas[0], face->root.size->metrics.x_scale );
+          font_clip_box.yMin +=
+            FT_MulFix( item_deltas[1], face->root.size->metrics.y_scale );
+          font_clip_box.xMax +=
+            FT_MulFix( item_deltas[2], face->root.size->metrics.x_scale );
+          font_clip_box.yMax +=
+            FT_MulFix( item_deltas[3], face->root.size->metrics.y_scale );
+        }
+#endif
+
+        /* Make 4 corner points (xMin, yMin), (xMax, yMax) and transform */
+        /* them.  If we we would only transform two corner points and    */
+        /* span a rectangle based on those, the rectangle may become too */
+        /* small to cover the glyph.                                     */
+        corners[0].x = font_clip_box.xMin;
+        corners[1].x = font_clip_box.xMin;
+        corners[2].x = font_clip_box.xMax;
+        corners[3].x = font_clip_box.xMax;
+
+        corners[0].y = font_clip_box.yMin;
+        corners[1].y = font_clip_box.yMax;
+        corners[2].y = font_clip_box.yMax;
+        corners[3].y = font_clip_box.yMin;
+
+        for ( j = 0; j < num_corners; ++j )
+        {
+          if ( face->root.internal->transform_flags & 1 )
+            FT_Vector_Transform( &corners[j],
+                                 &face->root.internal->transform_matrix );
+
+          if ( face->root.internal->transform_flags & 2 )
+          {
+            corners[j].x += face->root.internal->transform_delta.x;
+            corners[j].y += face->root.internal->transform_delta.y;
+          }
+        }
+
+        clip_box->bottom_left  = corners[0];
+        clip_box->top_left     = corners[1];
+        clip_box->top_right    = corners[2];
+        clip_box->bottom_right = corners[3];
+
+        return 1;
+      }
+    }
+
+    return 0;
+  }
+
+
+  FT_LOCAL_DEF( FT_Bool )
+  tt_face_get_paint_layers( TT_Face            face,
+                            FT_LayerIterator*  iterator,
+                            FT_OpaquePaint*    opaque_paint )
+  {
+    FT_Byte*   p             = NULL;
+    FT_Byte*   p_first_layer = NULL;
+    FT_Byte*   p_paint       = NULL;
+    FT_UInt32  paint_offset;
+
+    Colr*  colr;
+
+
+    if ( iterator->layer == iterator->num_layers )
+      return 0;
+
+    colr = (Colr*)face->colr;
+    if ( !colr )
+      return 0;
+
+    /*
+     * We have an iterator pointing at a paint offset as part of the
+     * `paintOffset` array in `LayerV1List`.
+     */
+    p = iterator->p;
+
+    /*
+     * Do a cursor sanity check of the iterator.  Counting backwards from
+     * where it stands, we need to end up at a position after the beginning
+     * of the `LayerV1List` table and not after the end of the
+     * `LayerV1List`.
+     */
+    p_first_layer = p -
+                      iterator->layer * LAYER_V1_LIST_PAINT_OFFSET_SIZE -
+                      LAYER_V1_LIST_NUM_LAYERS_SIZE;
+    if ( p_first_layer < (FT_Byte*)colr->layers_v1 )
+      return 0;
+    if ( p_first_layer >= (FT_Byte*)(
+           colr->layers_v1 + LAYER_V1_LIST_NUM_LAYERS_SIZE +
+           colr->num_layers_v1 * LAYER_V1_LIST_PAINT_OFFSET_SIZE ) )
+      return 0;
+
+    /*
+     * Before reading, ensure that `p` is within 'COLR' v1 and we can read a
+     * 4-byte ULONG.
+     */
+    if ( p < colr->layers_v1                              ||
+         p > (FT_Byte*)colr->table + colr->table_size - 4 )
+      return 0;
+
+    paint_offset =
+      FT_NEXT_ULONG( p );
+    opaque_paint->insert_root_transform =
+      0;
+
+    p_paint = (FT_Byte*)( colr->layers_v1 + paint_offset );
+
+    if ( p_paint < colr->paints_start_v1                         ||
+         p_paint >= ( (FT_Byte*)colr->table + colr->table_size ) )
+      return 0;
+
+    opaque_paint->p = p_paint;
+
+    iterator->p = p;
+
+    iterator->layer++;
+
+    return 1;
+  }
+
+
+  FT_LOCAL_DEF( FT_Bool )
+  tt_face_get_colorline_stops( TT_Face                face,
+                               FT_ColorStop*          color_stop,
+                               FT_ColorStopIterator  *iterator )
+  {
+    Colr*  colr = (Colr*)face->colr;
+
+    FT_Byte*  p;
+    FT_ULong  var_index_base;
+    FT_Byte*  last_entry_p = NULL;
+    FT_UInt   entry_size   = COLOR_STOP_SIZE;
+
+
+    if ( !colr || !colr->table || !iterator )
+      return 0;
+
+    if ( iterator->current_color_stop >= iterator->num_color_stops )
+      return 0;
+
+    if ( iterator->read_variable )
+      entry_size += VAR_IDX_BASE_SIZE;
+
+    /* Calculate the start pointer for the last to-be-read (Var)ColorStop */
+    /* and check whether we can read a full (Var)ColorStop at that        */
+    /* position by comparing it to the position that is the size of one   */
+    /* (Var)ColorStop before the end of the 'COLR' table.                 */
+    last_entry_p =
+      iterator->p + ( iterator->num_color_stops - 1 -
+                      iterator->current_color_stop ) * entry_size;
+    if ( iterator->p < colr->paints_start_v1          ||
+         last_entry_p > (FT_Byte*)colr->table +
+                        colr->table_size - entry_size )
+      return 0;
+
+    /* Iterator points at first `ColorStop` of `ColorLine`. */
+    p = iterator->p;
+
+    color_stop->stop_offset = F2DOT14_TO_FIXED( FT_NEXT_SHORT( p ) );
+
+    color_stop->color.palette_index = FT_NEXT_USHORT( p );
+
+    color_stop->color.alpha = FT_NEXT_SHORT( p );
+
+    if ( iterator->read_variable )
+    {
+      /* Pointer p needs to be advanced independently of whether we intend */
+      /* to take variable deltas into account or not.  Otherwise iteration */
+      /* would fail due to wrong offsets.                                  */
+      var_index_base = FT_NEXT_ULONG( p );
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+      {
+        FT_Int  item_deltas[2];
+
+
+        if ( !get_deltas_for_var_index_base( face, colr,
+                                             var_index_base,
+                                             2,
+                                             item_deltas ) )
+          return 0;
+
+        color_stop->stop_offset += F2DOT14_TO_FIXED( item_deltas[0] );
+        color_stop->color.alpha += (FT_F2Dot14)item_deltas[1];
+      }
+#else
+      FT_UNUSED( var_index_base );
+#endif
+    }
+
+    iterator->p = p;
+    iterator->current_color_stop++;
+
+    return 1;
+  }
+
+
+  FT_LOCAL_DEF( FT_Bool )
+  tt_face_get_paint( TT_Face         face,
+                     FT_OpaquePaint  opaque_paint,
+                     FT_COLR_Paint*  paint )
+  {
+    Colr*           colr = (Colr*)face->colr;
+    FT_OpaquePaint  next_paint;
+    FT_Matrix       ft_root_scale;
+
+    if ( !colr || !colr->base_glyphs_v1 || !colr->table )
+      return 0;
+
+    if ( opaque_paint.insert_root_transform )
+    {
+      /* 'COLR' v1 glyph information is returned in unscaled coordinates,
+       * i.e., `FT_Size` is not applied or multiplied into the values.  When
+       * client applications draw color glyphs, they can request to include
+       * a top-level transform, which includes the active `x_scale` and
+       * `y_scale` information for scaling the glyph, as well the additional
+       * transform and translate configured through `FT_Set_Transform`.
+       * This allows client applications to apply this top-level transform
+       * to the graphics context first and only once, then have gradient and
+       * contour scaling applied correctly when performing the additional
+       * drawing operations for subsequenct paints.  Prepare this initial
+       * transform here.
+       */
+      paint->format = FT_COLR_PAINTFORMAT_TRANSFORM;
+
+      next_paint.p                     = opaque_paint.p;
+      next_paint.insert_root_transform = 0;
+      paint->u.transform.paint         = next_paint;
+
+      /* `x_scale` and `y_scale` are in 26.6 format, representing the scale
+       * factor to get from font units to requested size.  However, expected
+       * return values are in 16.16, so we shift accordingly with rounding.
+       */
+      ft_root_scale.xx = ( face->root.size->metrics.x_scale + 32 ) >> 6;
+      ft_root_scale.xy = 0;
+      ft_root_scale.yx = 0;
+      ft_root_scale.yy = ( face->root.size->metrics.y_scale + 32 ) >> 6;
+
+      if ( face->root.internal->transform_flags & 1 )
+        FT_Matrix_Multiply( &face->root.internal->transform_matrix,
+                            &ft_root_scale );
+
+      paint->u.transform.affine.xx = ft_root_scale.xx;
+      paint->u.transform.affine.xy = ft_root_scale.xy;
+      paint->u.transform.affine.yx = ft_root_scale.yx;
+      paint->u.transform.affine.yy = ft_root_scale.yy;
+
+      /* The translation is specified in 26.6 format and, according to the
+       * documentation of `FT_Set_Translate`, is performed on the character
+       * size given in the last call to `FT_Set_Char_Size`.  The
+       * 'PaintTransform' paint table's `FT_Affine23` format expects
+       * values in 16.16 format, thus we need to shift by 10 bits.
+       */
+      if ( face->root.internal->transform_flags & 2 )
+      {
+        paint->u.transform.affine.dx =
+          face->root.internal->transform_delta.x * ( 1 << 10 );
+        paint->u.transform.affine.dy =
+          face->root.internal->transform_delta.y * ( 1 << 10 );
+      }
+      else
+      {
+        paint->u.transform.affine.dx = 0;
+        paint->u.transform.affine.dy = 0;
+      }
+
+      return 1;
+    }
+
+    return read_paint( face, colr, opaque_paint.p, paint );
+  }
+
+
   FT_LOCAL_DEF( FT_Error )
   tt_face_colr_blend_layer( TT_Face       face,
                             FT_UInt       color_index,
 #else /* !TT_CONFIG_OPTION_COLOR_LAYERS */
 
   /* ANSI C doesn't like empty source files */
-  typedef int  _tt_colr_dummy;
+  typedef int  tt_colr_dummy_;
 
 #endif /* !TT_CONFIG_OPTION_COLOR_LAYERS */