Reland: [COLRv1] Support retrieving ClipBox.

Reland after MSAN failure, initializing SkRect to empty in
computeColrV1GlyphBoundingBox().

After the discussion in [1] which was filed also in response to feedback
from Ben, the COLRv1 spec moved to not using a bounding box derived from
the `glyf` glyph for a give glyph id, but instead either use a ClipBox
found for a particular glyph id range from a ClipList array in the
COLRv1 table. If such a ClipBox is not found, perform a traversal of the
COLRv1 graph to compute the union of rectangles to compute a bounding
box.

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

Includes FreeType roll:
https://chromium.googlesource.com/chromium/src/third_party/freetype2.git/+log/47b1a541cb1943d85da3976b93f9a5ed490288e2..2c853b38a717c615d3113a64033fc896e5888fa8

Fixed: skia:12297
Cq-Include-Trybots: luci.skia.skia.primary:Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NativeFonts, luci.skia.skia.primary:FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Release-All-MSAN
Change-Id: I165fb95c89045c4c7671af2cbe097af38ca65e84
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/437996
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
Auto-Submit: Dominik Röttsches <drott@chromium.org>
diff --git a/DEPS b/DEPS
index cea47cd..52a9b19 100644
--- a/DEPS
+++ b/DEPS
@@ -23,7 +23,7 @@
   "third_party/externals/dng_sdk"         : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
   "third_party/externals/egl-registry"    : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@a0bca08de07c7d7651047bedc0b653cfaaa4f2ae",
   "third_party/externals/expat"           : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@a28238bdeebc087071777001245df1876a11f5ee",
-  "third_party/externals/freetype"        : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@47b1a541cb1943d85da3976b93f9a5ed490288e2",
+  "third_party/externals/freetype"        : "https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@2c853b38a717c615d3113a64033fc896e5888fa8",
   "third_party/externals/harfbuzz"        : "https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@368e9578873798e2d17ed78a0474dec7d4e9d6c0",
   "third_party/externals/icu"             : "https://chromium.googlesource.com/chromium/deps/icu.git@a0718d4f121727e30b8d52c7a189ebf5ab52421f",
   "third_party/externals/imgui"           : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@9418dcb69355558f70de260483424412c5ca2fce",
diff --git a/gm/colrv1.cpp b/gm/colrv1.cpp
index 9b0f6fd..3bc9d74 100644
--- a/gm/colrv1.cpp
+++ b/gm/colrv1.cpp
@@ -35,7 +35,8 @@
     kColorFontsRepoExtendMode,
     kColorFontsRepoRotate,
     kColorFontsRepoSkew,
-    kColorFontsRepoTransform
+    kColorFontsRepoTransform,
+    kColorFontsRepoClipBox
   };
 
   ColrV1GM(ColrV1TestType testType, SkScalar skewX, SkScalar rotateDeg)
@@ -58,6 +59,8 @@
                 return SkString("skew");
             case kColorFontsRepoTransform:
                 return SkString("transform");
+            case kColorFontsRepoClipBox:
+                return SkString("clipbox");
         }
         SkASSERT(false); /* not reached */
         return SkString();
@@ -100,6 +103,9 @@
             case kColorFontsRepoTransform:
                 fEmojiFont.fGlyphs = {31, 32, 33, 34};
                 break;
+            case kColorFontsRepoClipBox:
+                fEmojiFont.fGlyphs = {35, 36, 37, 38, 39};
+                break;
         }
     }
 
@@ -166,5 +172,7 @@
 DEF_GM(return new ColrV1GM(ColrV1GM::kColorFontsRepoRotate, 0.f, 0.f);)
 DEF_GM(return new ColrV1GM(ColrV1GM::kColorFontsRepoSkew, 0.f, 0.f);)
 DEF_GM(return new ColrV1GM(ColrV1GM::kColorFontsRepoTransform, 0.f, 0.f);)
+DEF_GM(return new ColrV1GM(ColrV1GM::kColorFontsRepoClipBox, 0.f, 0.f);)
+DEF_GM(return new ColrV1GM(ColrV1GM::kColorFontsRepoClipBox, -0.5f, 20.f);)
 
 }  // namespace skiagm
diff --git a/resources/fonts/more_samples-glyf_colr_1.ttf b/resources/fonts/more_samples-glyf_colr_1.ttf
index 67b3c5b..724eba1 100644
--- a/resources/fonts/more_samples-glyf_colr_1.ttf
+++ b/resources/fonts/more_samples-glyf_colr_1.ttf
Binary files differ
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index fa40e14..62127ba 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -37,6 +37,7 @@
 
 #include <ft2build.h>
 #include <freetype/ftadvanc.h>
+#include <freetype/ftimage.h>
 #include <freetype/ftbitmap.h>
 #ifdef FT_COLOR_H  // 2.10.0
 #   include <freetype/ftcolor.h>
@@ -1104,60 +1105,62 @@
         opaqueLayerPaint.p = nullptr;
         if (FT_Get_Color_Glyph_Paint(fFace, glyph->getGlyphID(),
                                      FT_COLOR_INCLUDE_ROOT_TRANSFORM, &opaqueLayerPaint)) {
-          haveLayers = true;
+            haveLayers = true;
 
-          // COLRv1 glyphs define a placeholder glyph in the glyf table that
-          // defines the extrema of the glyph. If this placeholder glyph
-          // contains only two points and FreeType applies the transform first -
-          // as configured by setupSize() - then we get a transformed bounding
-          // box that is too small as it only needs to fit the two points. To
-          // fix that, create a temporary contour consisting of all four corners
-          // of an imaginary rectangle enclosing those two points, then
-          // transform that and perform the bounding box computations on this
-          // one.
+            FT_ClipBox colrGlyphBbox;
 
-          FT_Set_Transform(fFace, nullptr, nullptr);
+            // COLRv1 optionally provides a ClipBox that we can use for allocation.
+            if (FT_Get_Color_Glyph_ClipBox(fFace, glyph->getGlyphID(), &colrGlyphBbox)) {
+                // Find enclosing bounding box of clip box corner points, needed
+                // when clipbox is transformed.
+                bounds.xMin = colrGlyphBbox.bottom_left.x;
+                bounds.xMax = colrGlyphBbox.bottom_left.x;
+                bounds.yMin = colrGlyphBbox.bottom_left.y;
+                bounds.yMax = colrGlyphBbox.bottom_left.y;
 
-          err = FT_Load_Glyph(fFace, glyph->getGlyphID(),
-                              fLoadGlyphFlags | FT_LOAD_BITMAP_METRICS_ONLY);
-          if (err != 0 || fFace->glyph->outline.n_contours == 0) {
-            glyph->zeroMetrics();
-            return;
+                for (auto& corner : {colrGlyphBbox.top_left,
+                                     colrGlyphBbox.top_right,
+                                     colrGlyphBbox.bottom_right}) {
+                    if (corner.x < bounds.xMin) {
+                        bounds.xMin = corner.x;
+                    }
+                    if (corner.y < bounds.yMin) {
+                        bounds.yMin = corner.y;
+                    }
+                    if (corner.x > bounds.xMax) {
+                        bounds.xMax = corner.x;
+                    }
+                    if (corner.y > bounds.yMax) {
+                        bounds.yMax = corner.y;
+                    }
+                }
+          } else {
+              // Otherwise we need to traverse the glyph graph with a focus on measuring the
+              // required bounding box.
+              FT_BBox computed_bounds;
+              if (!computeColrV1GlyphBoundingBox(fFace, glyph->getGlyphID(), &computed_bounds)) {
+                  glyph->zeroMetrics();
+                  return;
+              }
+
+              // Reset face so the main glyph slot contains information about the
+              // base glyph again, for usage for computing and copying horizontal
+              // metrics from FreeType to Skia below.
+              if (this->setupSize()) {
+                  glyph->zeroMetrics();
+                  return;
+              }
+
+              FT_Error err;
+              err = FT_Load_Glyph(
+                      fFace, glyph->getGlyphID(), fLoadGlyphFlags | FT_LOAD_BITMAP_METRICS_ONLY);
+              if (err != 0) {
+                  glyph->zeroMetrics();
+                  return;
+              }
+
+              bounds = computed_bounds;
           }
-          emboldenIfNeeded(fFace, fFace->glyph, glyph->getGlyphID());
-
-          FT_BBox bbox_untransformed;
-          FT_Outline_Get_CBox(&fFace->glyph->outline, &bbox_untransformed);
-
-          FT_Outline bboxOutline;
-          err = FT_Outline_New(gFTLibrary->library(), 4, 0, &bboxOutline);
-          if (err != 0) {
-            glyph->zeroMetrics();
-            return;
-          }
-
-          // Compose a rectangle contour by setting the four corners.
-          bboxOutline.points[0].x = bbox_untransformed.xMin;
-          bboxOutline.points[0].y = bbox_untransformed.yMin;
-
-          bboxOutline.points[1].x = bbox_untransformed.xMin;
-          bboxOutline.points[1].y = bbox_untransformed.yMax;
-
-          bboxOutline.points[2].x = bbox_untransformed.xMax;
-          bboxOutline.points[2].y = bbox_untransformed.yMax;
-
-          bboxOutline.points[3].x = bbox_untransformed.xMax;
-          bboxOutline.points[3].y = bbox_untransformed.yMin;
-
-          FT_Outline_Transform(&bboxOutline, &fMatrix22);
-
-          FT_Outline faceOutline = fFace->glyph->outline;
-          fFace->glyph->outline = bboxOutline;
-          // Retrieve from temporarily replaced transformed rectangle outline.
-          getBBoxForCurrentGlyph(glyph, &bounds, true);
-          fFace->glyph->outline = faceOutline;
-          FT_Outline_Done(gFTLibrary->library(), &bboxOutline);
-
         }
 #endif // #TT_SUPPORT_COLRV1
 
diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
index b4b5112..0e73462 100644
--- a/src/ports/SkFontHost_FreeType_common.cpp
+++ b/src/ports/SkFontHost_FreeType_common.cpp
@@ -756,9 +756,18 @@
     }
 }
 
-void colrv1_transform(SkCanvas* canvas, FT_Face face, FT_COLR_Paint colrv1_paint) {
+
+/* In drawing mode, concatenates the transforms directly on SkCanvas. In
+ * bounding box calculation mode, no SkCanvas is specified, but we only want to
+ * retrieve the transform from the FreeType paint object. */
+void colrv1_transform(FT_Face face,
+                      FT_COLR_Paint colrv1_paint,
+                      SkCanvas* canvas,
+                      SkMatrix* out_transform = 0) {
     SkMatrix transform;
 
+    SkASSERT(canvas || out_transform);
+
     switch (colrv1_paint.format) {
         case FT_COLR_PAINTFORMAT_TRANSFORM: {
             transform = ToSkMatrix(colrv1_paint.u.transform.affine);
@@ -809,10 +818,14 @@
             SkASSERT(false);
         }
     }
-    canvas->concat(transform);
+    if (canvas) {
+        canvas->concat(transform);
+    }
+    if (out_transform) {
+        *out_transform = transform;
+    }
 }
 
-
 bool colrv1_start_glyph(SkCanvas* canvas,
                         const FT_Color* palette,
                         FT_Face ft_face,
@@ -877,28 +890,28 @@
                                                  FT_COLOR_NO_ROOT_TRANSFORM);
             break;
         case FT_COLR_PAINTFORMAT_TRANSFORM:
-            colrv1_transform(canvas, face, paint);
+            colrv1_transform(face, paint, canvas);
             traverse_result = colrv1_traverse_paint(canvas, palette, face,
                                                     paint.u.transform.paint, visited_set);
             break;
         case FT_COLR_PAINTFORMAT_TRANSLATE:
-            colrv1_transform(canvas, face, paint);
+            colrv1_transform(face, paint, canvas);
             traverse_result = colrv1_traverse_paint(canvas, palette, face,
                                                     paint.u.translate.paint, visited_set);
             break;
         case FT_COLR_PAINTFORMAT_SCALE:
-            colrv1_transform(canvas, face, paint);
+            colrv1_transform(face, paint, canvas);
             traverse_result = colrv1_traverse_paint(canvas, palette, face,
                                                     paint.u.scale.paint, visited_set);
             break;
         case FT_COLR_PAINTFORMAT_ROTATE:
-            colrv1_transform(canvas, face, paint);
+            colrv1_transform(face, paint, canvas);
             traverse_result =
                     colrv1_traverse_paint(canvas, palette, face,
                                           paint.u.rotate.paint, visited_set);
             break;
         case FT_COLR_PAINTFORMAT_SKEW:
-            colrv1_transform(canvas, face, paint);
+            colrv1_transform(face, paint, canvas);
             traverse_result =
                     colrv1_traverse_paint(canvas, palette, face,
                                           paint.u.skew.paint, visited_set);
@@ -930,6 +943,72 @@
     return traverse_result;
 }
 
+SkPath GetClipBoxPath(FT_Face ft_face, uint16_t glyph_id, bool untransformed) {
+    SkPath resultPath;
+
+    using DoneFTSize = SkFunctionWrapper<decltype(FT_Done_Size), FT_Done_Size>;
+    std::unique_ptr<std::remove_pointer_t<FT_Size>, DoneFTSize> unscaledFtSize = nullptr;
+
+    FT_Size oldSize = ft_face->size;
+    FT_Matrix oldTransform;
+    FT_Vector oldDelta;
+    FT_Error err = 0;
+
+    if (untransformed) {
+        unscaledFtSize.reset(
+                [ft_face]() -> FT_Size {
+                    FT_Size size;
+                    FT_Error err = FT_New_Size(ft_face, &size);
+                    if (err != 0) {
+                        SK_TRACEFTR(err,
+                                    "FT_New_Size(%s) failed in generateFacePathStaticCOLRv1.",
+                                    ft_face->family_name);
+                        return nullptr;
+                    }
+                    return size;
+                }());
+        if (!unscaledFtSize) {
+            return resultPath;
+        }
+
+        err = FT_Activate_Size(unscaledFtSize.get());
+        if (err != 0) {
+          return resultPath;
+        }
+
+        err = FT_Set_Char_Size(ft_face, SkIntToFDot6(ft_face->units_per_EM), 0, 0, 0);
+        if (err != 0) {
+          return resultPath;
+        }
+
+        FT_Get_Transform(ft_face, &oldTransform, &oldDelta);
+        FT_Set_Transform(ft_face, nullptr, nullptr);
+    }
+
+    FT_ClipBox colrGlyphClipBox;
+    if (FT_Get_Color_Glyph_ClipBox(ft_face, glyph_id, &colrGlyphClipBox)) {
+        resultPath = SkPath::Polygon({{SkFDot6ToScalar(colrGlyphClipBox.bottom_left.x),
+                                       -SkFDot6ToScalar(colrGlyphClipBox.bottom_left.y)},
+                                      {SkFDot6ToScalar(colrGlyphClipBox.top_left.x),
+                                       -SkFDot6ToScalar(colrGlyphClipBox.top_left.y)},
+                                      {SkFDot6ToScalar(colrGlyphClipBox.top_right.x),
+                                       -SkFDot6ToScalar(colrGlyphClipBox.top_right.y)},
+                                      {SkFDot6ToScalar(colrGlyphClipBox.bottom_right.x),
+                                       -SkFDot6ToScalar(colrGlyphClipBox.bottom_right.y)}},
+                                     true);
+    }
+
+    if (untransformed) {
+        err = FT_Activate_Size(oldSize);
+        if (err != 0) {
+          return resultPath;
+        }
+        FT_Set_Transform(ft_face, &oldTransform, &oldDelta);
+    }
+
+    return resultPath;
+}
+
 bool colrv1_start_glyph(SkCanvas* canvas,
                         const FT_Color* palette,
                         FT_Face ft_face,
@@ -940,11 +1019,149 @@
     bool has_colrv1_layers = false;
     if (FT_Get_Color_Glyph_Paint(ft_face, glyph_id, root_transform, &opaque_paint)) {
         has_colrv1_layers = true;
+
+        SkPath clipBoxPath =
+                GetClipBoxPath(ft_face, glyph_id, root_transform == FT_COLOR_NO_ROOT_TRANSFORM);
+        if (!clipBoxPath.isEmpty()) {
+            canvas->clipPath(clipBoxPath, true);
+        }
+
         VisitedSet visited_set;
         colrv1_traverse_paint(canvas, palette, ft_face, opaque_paint, &visited_set);
     }
     return has_colrv1_layers;
 }
+
+bool colrv1_start_glyph_bounds(SkMatrix *ctm,
+                               SkRect* bounds,
+                               FT_Face ft_face,
+                               uint16_t glyph_id,
+                               FT_Color_Root_Transform root_transform);
+
+bool colrv1_traverse_paint_bounds(SkMatrix* ctm,
+                                  SkRect* bounds,
+                                  FT_Face face,
+                                  FT_OpaquePaint opaque_paint,
+                                  VisitedSet* visited_set) {
+    // Cycle detection, see section "5.7.11.1.9 Color glyphs as a directed acyclic graph".
+    if (visited_set->contains(opaque_paint)) {
+        return false;
+    }
+
+    visited_set->add(opaque_paint);
+    SK_AT_SCOPE_EXIT(visited_set->remove(opaque_paint));
+
+    FT_COLR_Paint paint;
+    if (!FT_Get_Paint(face, opaque_paint, &paint)) {
+      return false;
+    }
+
+    // Keep track of failures to retrieve the FT_COLR_Paint from FreeType in the
+    // recursion, cancel recursion when a paint retrieval fails.
+    bool traverse_result = true;
+    SkMatrix restore_matrix = *ctm;
+    SK_AT_SCOPE_EXIT(*ctm = restore_matrix);
+
+    switch (paint.format) {
+        case FT_COLR_PAINTFORMAT_COLR_LAYERS: {
+            FT_LayerIterator& layer_iterator = paint.u.colr_layers.layer_iterator;
+            FT_OpaquePaint opaque_paint_fetch;
+            opaque_paint_fetch.p = nullptr;
+            while (FT_Get_Paint_Layers(face, &layer_iterator, &opaque_paint_fetch)) {
+                colrv1_traverse_paint_bounds(ctm, bounds, face, opaque_paint_fetch, visited_set);
+            }
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_GLYPH: {
+            FT_UInt glyphID = paint.u.glyph.glyphID;
+            SkPath path;
+            if ((traverse_result = generateFacePathCOLRv1(face, glyphID, &path))) {
+              path.transform(*ctm);
+              bounds->join(path.getBounds());
+            }
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_COLR_GLYPH:
+            traverse_result = colrv1_start_glyph_bounds(
+                    ctm, bounds, face, paint.u.colr_glyph.glyphID, FT_COLOR_NO_ROOT_TRANSFORM);
+            break;
+
+        case FT_COLR_PAINTFORMAT_TRANSFORM: {
+            SkMatrix transform_matrix;
+            colrv1_transform(face, paint, nullptr, &transform_matrix);
+            ctm->preConcat(transform_matrix);
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.transform.paint, visited_set);
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_TRANSLATE: {
+            SkMatrix transform_matrix;
+            colrv1_transform(face, paint, nullptr, &transform_matrix);
+            ctm->preConcat(transform_matrix);
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.translate.paint, visited_set);
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_SCALE: {
+            SkMatrix transform_matrix;
+            colrv1_transform(face, paint, nullptr, &transform_matrix);
+            ctm->preConcat(transform_matrix);
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.scale.paint, visited_set);
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_ROTATE: {
+            SkMatrix transform_matrix;
+            colrv1_transform(face, paint, nullptr, &transform_matrix);
+            ctm->preConcat(transform_matrix);
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.rotate.paint, visited_set);
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_SKEW: {
+            SkMatrix transform_matrix;
+            colrv1_transform(face, paint, nullptr, &transform_matrix);
+            ctm->preConcat(transform_matrix);
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.skew.paint, visited_set);
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_COMPOSITE: {
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.composite.backdrop_paint, visited_set);
+            traverse_result = colrv1_traverse_paint_bounds(
+                    ctm, bounds, face, paint.u.composite.source_paint, visited_set);
+            break;
+        }
+        case FT_COLR_PAINTFORMAT_SOLID:
+        case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT:
+        case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT:
+        case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
+            break;
+        }
+        default:
+            SkASSERT(false);
+            break;
+}
+    return traverse_result;
+}
+
+
+bool colrv1_start_glyph_bounds(SkMatrix *ctm,
+                               SkRect* bounds,
+                               FT_Face ft_face,
+                               uint16_t glyph_id,
+                               FT_Color_Root_Transform root_transform) {
+    FT_OpaquePaint opaque_paint;
+    opaque_paint.p = nullptr;
+    bool has_colrv1_layers = false;
+    if (FT_Get_Color_Glyph_Paint(ft_face, glyph_id, root_transform, &opaque_paint)) {
+        has_colrv1_layers = true;
+        VisitedSet visited_set;
+        colrv1_traverse_paint_bounds(ctm, bounds, ft_face, opaque_paint, &visited_set);
+    }
+    return has_colrv1_layers;
+}
 #endif // TT_SUPPORT_COLRV1
 
 }  // namespace
@@ -1450,3 +1667,27 @@
                                                      SkPath* path) {
     return generateFacePathStatic(face, glyphID, path);
 }
+
+bool SkScalerContext_FreeType_Base::computeColrV1GlyphBoundingBox(FT_Face face,
+                                                                  SkGlyphID glyphID,
+                                                                  FT_BBox* boundingBox) {
+#ifdef TT_SUPPORT_COLRV1
+    SkMatrix ctm;
+    SkRect bounds = SkRect::MakeEmpty();
+    if (!colrv1_start_glyph_bounds(&ctm, &bounds, face, glyphID, FT_COLOR_INCLUDE_ROOT_TRANSFORM)) {
+        return false;
+    }
+
+    /* Convert back to FT_BBox as caller needs it in this format. */
+    bounds.sort();
+    boundingBox->xMin = SkScalarToFDot6(bounds.left());
+    boundingBox->xMax = SkScalarToFDot6(bounds.right());
+    boundingBox->yMin = SkScalarToFDot6(-bounds.bottom());
+    boundingBox->yMax = SkScalarToFDot6(-bounds.top());
+
+    return true;
+#else
+    SkASSERT(false);
+    return false;
+#endif
+}
diff --git a/src/ports/SkFontHost_FreeType_common.h b/src/ports/SkFontHost_FreeType_common.h
index 5e97038..4833b7f 100644
--- a/src/ports/SkFontHost_FreeType_common.h
+++ b/src/ports/SkFontHost_FreeType_common.h
@@ -23,6 +23,8 @@
 typedef struct FT_FaceRec_* FT_Face;
 typedef struct FT_StreamRec_* FT_Stream;
 typedef signed long FT_Pos;
+typedef struct FT_BBox_ FT_BBox;
+
 
 #ifdef SK_DEBUG
 const char* SkTraceFtrGetError(int);
@@ -48,6 +50,16 @@
     void generateGlyphImage(FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform);
     bool generateGlyphPath(FT_Face face, SkPath* path);
     bool generateFacePath(FT_Face face, SkGlyphID glyphID, SkPath* path);
+
+    // Computes a bounding box for a COLRv1 glyph id in FT_BBox 26.6 format and FreeType's y-up
+    // coordinate space.
+    // Needed to call into COLRv1 from generateMetrics().
+    //
+    // Note : This method may change the configured size and transforms on FT_Face. Make sure to
+    // configure size, matrix and load glyphs as needed after using this function to restore the
+    // state of FT_Face.
+    bool computeColrV1GlyphBoundingBox(FT_Face face, SkGlyphID glyphID, FT_BBox* boundingBox);
+
 private:
     using INHERITED = SkScalerContext;
 };