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;
};