From 47cf8ebf4a78ed42da455a98d77a92ce6a180d78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominik=20R=C3=B6ttsches?= <drott@chromium.org>
Date: Wed, 28 Jul 2021 17:36:57 +0300
Subject: [PATCH] [sfnt] Add API for retrieving a 'COLR' v1 'ClipBox' table.

The optional 'COLR' v1 glyph-specific clip box helps upstream graphics
libraries allocate a sufficiently large bitmap for a glyph without having to
traverse the glyph graph for that.  See

  https://github.com/googlefonts/colr-gradients-spec/issues/251

for background on the introduction of this specification change.

* include/freetype/ftcolor.h (FT_ClipBox): New structure.
(FT_Get_Color_Glyph_ClipBox): New function declaration.

* include/freetype/internal/sfnt.h (TT_Get_Color_Glyph_ClipBox_Func):
New function type.
(SFNT_Interface, FT_DEFINE_SFNT_INTERFACE): Use it.

* src/base/ftobjs.c (FT_Get_Color_Glyph_ClipBox): New function to link API
with SFNT implementation.

* src/sfnt/sfdriver.c (sfnt_interface): Updated.
* src/sfnt/ttcolr.c (Colr): New field `clip_list`.
(tt_face_load_colr): Parse global clip list offset.
(tt_face_get_color_glyph_clipbox): New function to find the clip box for a
glyph id from the clip list array.
* src/sfnt/ttcolr.h: Updated.
---
 include/freetype/ftcolor.h       |  86 +++++++++++++++++++++
 include/freetype/internal/sfnt.h |  63 +++++++++++++---
 src/base/ftobjs.c                |  29 +++++++
 src/sfnt/sfdriver.c              |  10 ++-
 src/sfnt/ttcolr.c                | 125 ++++++++++++++++++++++++++++++-
 src/sfnt/ttcolr.h                |   5 ++
 6 files changed, 302 insertions(+), 16 deletions(-)

diff --git a/include/freetype/ftcolor.h b/include/freetype/ftcolor.h
index a4bd62a26..b98289917 100644
--- a/include/freetype/ftcolor.h
+++ b/include/freetype/ftcolor.h
@@ -1383,6 +1383,49 @@ FT_BEGIN_HEADER
   } FT_Color_Root_Transform;
 
 
+  /**************************************************************************
+   *
+   * @struct:
+   *   FT_ClipBox
+   *
+   * @description:
+   *   A structure representing a 'COLR' v1 'ClipBox' table.  'COLR' v1
+   *   glyphs may optionally define a clip box for aiding allocation or
+   *   defining a maximum drawable region.  Use @FT_Get_Color_Glyph_ClipBox
+   *   to retrieve it.
+   *
+   * @fields:
+   *   bottom_left ::
+   *     The bottom left corner of the clip box as an @FT_Vector with
+   *     fixed-point coordinates in 26.6 format.
+   *
+   *   top_left ::
+   *     The top left corner of the clip box as an @FT_Vector with
+   *     fixed-point coordinates in 26.6 format.
+   *
+   *   top_right ::
+   *     The top right corner of the clip box as an @FT_Vector with
+   *     fixed-point coordinates in 26.6 format.
+   *
+   *   bottom_right ::
+   *     The bottom right corner of the clip box as an @FT_Vector with
+   *     fixed-point coordinates in 26.6 format.
+   *
+   * @since:
+   *   2.12 -- **currently experimental only!**  There might be changes
+   *   without retaining backward compatibility of both the API and ABI.
+   *
+   */
+  typedef struct  FT_ClipBox_
+  {
+    FT_Vector  bottom_left;
+    FT_Vector  top_left;
+    FT_Vector  top_right;
+    FT_Vector  bottom_right;
+
+  } FT_ClipBox;
+
+
   /**************************************************************************
    *
    * @function:
@@ -1475,6 +1518,49 @@ FT_BEGIN_HEADER
                             FT_OpaquePaint*          paint );
 
 
+  /**************************************************************************
+   *
+   * @function:
+   *   FT_Get_Color_Glyph_ClipBox
+   *
+   * @description:
+   *   Search for a 'COLR' v1 clip box for the specified `base_glyph` and
+   *   fill the `clip_box` parameter with the 'COLR' v1 'ClipBox' information
+   *   if one is found.
+   *
+   * @input:
+   *   face ::
+   *     A handle to the parent face object.
+   *
+   *   base_glyph ::
+   *     The glyph index for which to retrieve the clip box.
+   *
+   * @output:
+   *   clip_box ::
+   *     The clip box for the requested `base_glyph` if one is found.  The
+   *     clip box is computed taking scale and transformations configured on
+   *     the @FT_Face into account.  @FT_ClipBox contains @FT_Vector values
+   *     in 26.6 format.
+   *
+   * @return:
+   *   Value~1 if a clip box is found.  If no clip box is found or an error
+   *   occured, value~0 is returned.
+   *
+   * @note:
+   *   To retrieve the clip box in font units, reset scale to units-per-em
+   *   and remove transforms configured using @FT_Set_Transform.
+   *
+   * @since:
+   *   2.12 -- **currently experimental only!**  There might be changes
+   *   without retaining backward compatibility of both the API and ABI.
+   *
+   */
+  FT_EXPORT( FT_Bool )
+  FT_Get_Color_Glyph_ClipBox( FT_Face      face,
+                              FT_UInt      base_glyph,
+                              FT_ClipBox*  clip_box );
+
+
   /**************************************************************************
    *
    * @function:
diff --git a/include/freetype/internal/sfnt.h b/include/freetype/internal/sfnt.h
index 438ec897e..e8d77e232 100644
--- a/include/freetype/internal/sfnt.h
+++ b/include/freetype/internal/sfnt.h
@@ -555,6 +555,44 @@ FT_BEGIN_HEADER
                                       FT_OpaquePaint           *paint );
 
 
+  /**************************************************************************
+   *
+   * @functype:
+   *   TT_Get_Color_Glyph_ClipBox_Func
+   *
+   * @description:
+   *   Search for a 'COLR' v1 clip box for the specified `base_glyph` and
+   *   fill the `clip_box` parameter with the 'COLR' v1 'ClipBox' information
+   *   if one is found.
+   *
+   * @input:
+   *   face ::
+   *     A handle to the parent face object.
+   *
+   *   base_glyph ::
+   *     The glyph index for which to retrieve the clip box.
+   *
+   * @output:
+   *   clip_box ::
+   *     The clip box for the requested base_glyph if one is found.  The clip
+   *     box is computed taking scale and transformations configured on the
+   *     @FT_Face into account.  @FT_ClipBox contains @FT_Vector values in
+   *     26.6 format.
+   *
+   * @note:
+   *     To retrieve the clip box in font units, reset scale to units-per-em
+   *     and remove transforms configured using @FT_Set_Transform.
+   *
+   * @return:
+   *   Value~1 if a ClipBox is found.  If no clip box is found or an
+   *   error occured, value~0 is returned.
+   */
+  typedef FT_Bool
+  ( *TT_Get_Color_Glyph_ClipBox_Func )( TT_Face      face,
+                                        FT_UInt      base_glyph,
+                                        FT_ClipBox*  clip_box );
+
+
   /**************************************************************************
    *
    * @functype:
@@ -890,17 +928,18 @@ FT_BEGIN_HEADER
     TT_Set_SBit_Strike_Func      set_sbit_strike;
     TT_Load_Strike_Metrics_Func  load_strike_metrics;
 
-    TT_Load_Table_Func             load_cpal;
-    TT_Load_Table_Func             load_colr;
-    TT_Free_Table_Func             free_cpal;
-    TT_Free_Table_Func             free_colr;
-    TT_Set_Palette_Func            set_palette;
-    TT_Get_Colr_Layer_Func         get_colr_layer;
-    TT_Get_Color_Glyph_Paint_Func  get_colr_glyph_paint;
-    TT_Get_Paint_Layers_Func       get_paint_layers;
-    TT_Get_Colorline_Stops_Func    get_colorline_stops;
-    TT_Get_Paint_Func              get_paint;
-    TT_Blend_Colr_Func             colr_blend;
+    TT_Load_Table_Func               load_cpal;
+    TT_Load_Table_Func               load_colr;
+    TT_Free_Table_Func               free_cpal;
+    TT_Free_Table_Func               free_colr;
+    TT_Set_Palette_Func              set_palette;
+    TT_Get_Colr_Layer_Func           get_colr_layer;
+    TT_Get_Color_Glyph_Paint_Func    get_colr_glyph_paint;
+    TT_Get_Color_Glyph_ClipBox_Func  get_color_glyph_clipbox;
+    TT_Get_Paint_Layers_Func         get_paint_layers;
+    TT_Get_Colorline_Stops_Func      get_colorline_stops;
+    TT_Get_Paint_Func                get_paint;
+    TT_Blend_Colr_Func               colr_blend;
 
     TT_Get_Metrics_Func  get_metrics;
 
@@ -951,6 +990,7 @@ FT_BEGIN_HEADER
           set_palette_,                  \
           get_colr_layer_,               \
           get_colr_glyph_paint_,         \
+          get_color_glyph_clipbox,       \
           get_paint_layers_,             \
           get_colorline_stops_,          \
           get_paint_,                    \
@@ -995,6 +1035,7 @@ FT_BEGIN_HEADER
     set_palette_,                        \
     get_colr_layer_,                     \
     get_colr_glyph_paint_,               \
+    get_color_glyph_clipbox,             \
     get_paint_layers_,                   \
     get_colorline_stops_,                \
     get_paint_,                          \
diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c
index 0ded2440f..5c1a4d034 100644
--- a/src/base/ftobjs.c
+++ b/src/base/ftobjs.c
@@ -5639,6 +5639,35 @@
   }
 
 
+  /* documentation is in ftcolor.h */
+
+  FT_EXPORT_DEF( FT_Bool )
+  FT_Get_Color_Glyph_ClipBox( FT_Face      face,
+                              FT_UInt      base_glyph,
+                              FT_ClipBox*  clip_box )
+  {
+    TT_Face       ttface;
+    SFNT_Service  sfnt;
+
+
+    if ( !face || !clip_box )
+      return 0;
+
+    if ( !FT_IS_SFNT( face ) )
+      return 0;
+
+    ttface = (TT_Face)face;
+    sfnt   = (SFNT_Service)ttface->sfnt;
+
+    if ( sfnt->get_color_glyph_clipbox )
+      return sfnt->get_color_glyph_clipbox( ttface,
+                                            base_glyph,
+                                            clip_box );
+    else
+      return 0;
+  }
+
+
   /* documentation is in freetype.h */
 
   FT_EXPORT_DEF( FT_Bool )
diff --git a/src/sfnt/sfdriver.c b/src/sfnt/sfdriver.c
index 824e709fa..d1d01c99e 100644
--- a/src/sfnt/sfdriver.c
+++ b/src/sfnt/sfdriver.c
@@ -1292,13 +1292,15 @@
                             /* TT_Get_Colr_Layer_Func  get_colr_layer  */
 
     PUT_COLOR_LAYERS_V1( tt_face_get_colr_glyph_paint ),
-                 /* TT_Get_Colr_Glyph_Paint_Func  get_colr_glyph_paint */
+              /* TT_Get_Color_Glyph_Paint_Func    get_colr_glyph_paint */
+    PUT_COLOR_LAYERS_V1( tt_face_get_color_glyph_clipbox ),
+              /* TT_Get_Color_Glyph_ClipBox_Func  get_clipbox          */
     PUT_COLOR_LAYERS_V1( tt_face_get_paint_layers ),
-                 /* TT_Get_Paint_Layers_Func      get_paint_layers     */
+              /* TT_Get_Paint_Layers_Func         get_paint_layers     */
     PUT_COLOR_LAYERS_V1( tt_face_get_colorline_stops ),
-                 /* TT_Get_Paint                  get_paint            */
+              /* TT_Get_Paint                     get_paint            */
     PUT_COLOR_LAYERS_V1( tt_face_get_paint ),
-                 /* TT_Get_Colorline_Stops_Func   get_colorline_stops  */
+              /* TT_Get_Colorline_Stops_Func      get_colorline_stops  */
 
     PUT_COLOR_LAYERS( tt_face_colr_blend_layer ),
                             /* TT_Blend_Colr_Func      colr_blend      */
diff --git a/src/sfnt/ttcolr.c b/src/sfnt/ttcolr.c
index b8f4777ed..2f3e8846d 100644
--- a/src/sfnt/ttcolr.c
+++ b/src/sfnt/ttcolr.c
@@ -94,6 +94,8 @@
     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
@@ -134,7 +136,7 @@
 
     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;
+    FT_ULong  layer_offset_v1, num_layers_v1, clip_list_offset;
     FT_ULong  table_size;
 
 
@@ -226,6 +228,16 @@
           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;
     }
 
     colr->base_glyphs = (FT_Byte*)( table + base_glyph_offset );
@@ -796,6 +808,117 @@
   }
 
 
+  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;
+
+    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;
+
+    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 0. */
+    if ( !( clip_list_format == 0 ) )
+      return 0;
+
+    num_clip_boxes = FT_NEXT_ULONG( p );
+
+    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 );
+
+        if ( p1 >= ( (FT_Byte*)colr->table + colr->table_size ) )
+          return 0;
+
+        format = FT_NEXT_BYTE( p1 );
+
+        if ( format < 0 || format > 1 )
+          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.x_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.x_scale );
+
+        /* 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,
diff --git a/src/sfnt/ttcolr.h b/src/sfnt/ttcolr.h
index c91d2b172..b81e4cb95 100644
--- a/src/sfnt/ttcolr.h
+++ b/src/sfnt/ttcolr.h
@@ -48,6 +48,11 @@ FT_BEGIN_HEADER
                                 FT_Color_Root_Transform  root_transform,
                                 FT_OpaquePaint*          paint );
 
+  FT_LOCAL( FT_Bool )
+  tt_face_get_color_glyph_clipbox( TT_Face      face,
+                                   FT_UInt      base_glyph,
+                                   FT_ClipBox*  clip_box );
+
   FT_LOCAL( FT_Bool )
   tt_face_get_paint_layers( TT_Face            face,
                             FT_LayerIterator*  iterator,
