
/*
 * Copyright 2012 Mozilla Foundation
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/ports/SkFontHost_FreeType_common.h"

#include "src/core/SkAdvancedTypefaceMetrics.h"
#include "src/core/SkFDot6.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkStream.h"
#include "src/core/SkScalerContext.h"
#include "src/core/SkTypefaceCache.h"

#include <cfloat>
#include <cmath>

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H

// for FT_GlyphSlot_Embolden
#ifdef FT_SYNTHESIS_H
#include FT_SYNTHESIS_H
#endif

// for FT_Library_SetLcdFilter
#ifdef FT_LCD_FILTER_H
#include FT_LCD_FILTER_H
#else
typedef enum FT_LcdFilter_
{
    FT_LCD_FILTER_NONE    = 0,
    FT_LCD_FILTER_DEFAULT = 1,
    FT_LCD_FILTER_LIGHT   = 2,
    FT_LCD_FILTER_LEGACY  = 16,
} FT_LcdFilter;
#endif

// If compiling with FreeType before 2.5.0
#ifndef FT_LOAD_COLOR
#    define FT_LOAD_COLOR ( 1L << 20 )
#    define FT_PIXEL_MODE_BGRA 7
#endif

// If compiling with FreeType before 2.12.0
#ifndef FT_FACE_FLAG_SVG
// We need the format tag so that we can switch on it and handle a possibly-
// newer version of the library at runtime.
static constexpr FT_UInt32 FT_IMAGE_TAG(FT_GLYPH_FORMAT_SVG, 'S', 'V', 'G', ' ');
#endif

#ifndef SK_CAN_USE_DLOPEN
#define SK_CAN_USE_DLOPEN 1
#endif
#if SK_CAN_USE_DLOPEN
#include <dlfcn.h>
#endif

#ifndef SK_FONTHOST_CAIRO_STANDALONE
#define SK_FONTHOST_CAIRO_STANDALONE 1
#endif

static bool gFontHintingEnabled = true;
static FT_Error (*gSetLcdFilter)(FT_Library, FT_LcdFilter) = nullptr;

extern "C"
{
    void mozilla_LockFTLibrary(FT_Library aLibrary);
    void mozilla_UnlockFTLibrary(FT_Library aLibrary);
    void mozilla_AddRefSharedFTFace(void* aContext);
    void mozilla_ReleaseSharedFTFace(void* aContext, void* aOwner);
    void mozilla_ForgetSharedFTFaceLockOwner(void* aContext, void* aOwner);
    int mozilla_LockSharedFTFace(void* aContext, void* aOwner);
    void mozilla_UnlockSharedFTFace(void* aContext);
    FT_Error mozilla_LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex, int32_t aFlags);
    void mozilla_glyphslot_embolden_less(FT_GlyphSlot slot);
}

void SkInitCairoFT(bool fontHintingEnabled)
{
    gFontHintingEnabled = fontHintingEnabled;
#if SK_CAN_USE_DLOPEN
    gSetLcdFilter = (FT_Error (*)(FT_Library, FT_LcdFilter))dlsym(RTLD_DEFAULT, "FT_Library_SetLcdFilter");
#else
    gSetLcdFilter = &FT_Library_SetLcdFilter;
#endif
    // FT_Library_SetLcdFilter may be provided but have no effect if FreeType
    // is built without FT_CONFIG_OPTION_SUBPIXEL_RENDERING.
    if (gSetLcdFilter &&
        gSetLcdFilter(nullptr, FT_LCD_FILTER_NONE) == FT_Err_Unimplemented_Feature) {
        gSetLcdFilter = nullptr;
    }
}

class SkScalerContext_CairoFT : public SkScalerContext {
public:
    SkScalerContext_CairoFT(SkTypeface& typeface,
                            const SkScalerContextEffects& effects,
                            const SkDescriptor* desc, FT_Face face,
                            void* faceContext, FT_LcdFilter lcdFilter);

    virtual ~SkScalerContext_CairoFT() {
        mozilla_ForgetSharedFTFaceLockOwner(fFTFaceContext, this);
    }

    bool isValid() const { return fFTFaceContext != nullptr; }

    void Lock() {
        if (!mozilla_LockSharedFTFace(fFTFaceContext, this)) {
            FT_Set_Transform(fFTFace, fHaveShape ? &fShapeMatrixFT : nullptr, nullptr);
            FT_Set_Char_Size(fFTFace, FT_F26Dot6(fScaleX * 64.0f + 0.5f),
                             FT_F26Dot6(fScaleY * 64.0f + 0.5f), 0, 0);
        }
    }

    void Unlock() { mozilla_UnlockSharedFTFace(fFTFaceContext); }

protected:
    GlyphMetrics generateMetrics(const SkGlyph& glyph, SkArenaAlloc* arena) override;
    void generateImage(const SkGlyph& glyph, void* imageBuffer) override;
    std::optional<GeneratedPath> generatePath(const SkGlyph& glyph) override;
    void generateFontMetrics(SkFontMetrics* metrics) override;

private:
    bool computeShapeMatrix(const SkMatrix& m);
    bool prepareGlyph(FT_GlyphSlot glyph);

    SkScalerContextFTUtils fUtils;
    FT_Face fFTFace;
    void* fFTFaceContext;
    FT_Int32 fLoadGlyphFlags;
    FT_LcdFilter fLcdFilter;
    SkScalar fScaleX;
    SkScalar fScaleY;
    SkMatrix fShapeMatrix;
    FT_Matrix fShapeMatrixFT;
    bool fHaveShape;
};

class AutoLockFTFace {
public:
    AutoLockFTFace(SkScalerContext_CairoFT* scalerContext)
        : fScalerContext(scalerContext) {
        fScalerContext->Lock();
    }

    ~AutoLockFTFace() { fScalerContext->Unlock(); }

private:
    SkScalerContext_CairoFT* fScalerContext;
};

static bool isLCD(const SkScalerContextRec& rec) {
    return SkMask::kLCD16_Format == rec.fMaskFormat;
}

static bool bothZero(SkScalar a, SkScalar b) {
    return 0 == a && 0 == b;
}

// returns false if there is any non-90-rotation or skew
static bool isAxisAligned(const SkScalerContextRec& rec) {
    return 0 == rec.fPreSkewX &&
           (bothZero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) ||
            bothZero(rec.fPost2x2[0][0], rec.fPost2x2[1][1]));
}

class SkCairoFTTypeface : public SkTypeface {
public:
    std::unique_ptr<SkStreamAsset> onOpenStream(int*) const override { return nullptr; }

    std::unique_ptr<SkAdvancedTypefaceMetrics> onGetAdvancedMetrics() const override
    {
        SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetAdvancedMetrics unimplemented\n"));
        return nullptr;
    }

    std::unique_ptr<SkScalerContext> onCreateScalerContext(const SkScalerContextEffects& effects, const SkDescriptor* desc) const override
    {
        SkScalerContext_CairoFT* ctx = new SkScalerContext_CairoFT(
            *const_cast<SkCairoFTTypeface*>(this), effects, desc,
            fFTFace, fFTFaceContext, fLcdFilter);
        std::unique_ptr<SkScalerContext> result(ctx);
        if (!ctx->isValid()) {
            return nullptr;
        }
        return result;
    }

    void onFilterRec(SkScalerContextRec* rec) const override
    {
        // rotated text looks bad with hinting, so we disable it as needed
        if (!gFontHintingEnabled || !isAxisAligned(*rec)) {
            rec->setHinting(SkFontHinting::kNone);
        }

#ifndef SK_GAMMA_APPLY_TO_A8
        // Don't apply any gamma so that we match cairo-ft's results.
        // Only do this if not requesting gamma in build config.
        rec->ignorePreBlend();
#endif
    }

    void onGetFontDescriptor(SkFontDescriptor*, bool*) const override
    {
        SkDEBUGCODE(SkDebugf("SkCairoFTTypeface::onGetFontDescriptor unimplemented\n"));
    }

    void onCharsToGlyphs(SkSpan<const SkUnichar> chars, SkSpan<SkGlyphID> glyphs) const override
    {
        mozilla_LockSharedFTFace(fFTFaceContext, nullptr);
        for (int i = 0; i < chars.size(); ++i) {
            glyphs[i] = SkToU16(FT_Get_Char_Index(fFTFace, chars[i]));
        }
        mozilla_UnlockSharedFTFace(fFTFaceContext);
    }

    int onCountGlyphs() const override
    {
        return fFTFace->num_glyphs;
    }

    int onGetUPEM() const override
    {
        return fFTFace->units_per_EM;
    }

    SkTypeface::LocalizedStrings* onCreateFamilyNameIterator() const override
    {
        return nullptr;
    }

    void onGetFamilyName(SkString* familyName) const override
    {
        familyName->reset();
    }

    bool onGetPostScriptName(SkString*) const override {
        return false;
    }

    bool onGlyphMaskNeedsCurrentColor() const override {
        return false;
    }

    int onGetTableTags(SkSpan<SkFontTableTag>) const override
    {
        return 0;
    }

    size_t onGetTableData(SkFontTableTag, size_t, size_t, void*) const override
    {
        return 0;
    }

    void getPostScriptGlyphNames(SkString*) const override {}

    void getGlyphToUnicodeMap(SkSpan<SkUnichar>) const override {}

    int onGetVariationDesignPosition(SkSpan<SkFontArguments::VariationPosition::Coordinate>) const override
    {
        return 0;
    }

    int onGetVariationDesignParameters(SkSpan<SkFontParameters::Variation::Axis> parameters) const override
    {
        return 0;
    }

    sk_sp<SkTypeface> onMakeClone(const SkFontArguments& args) const override {
        return sk_ref_sp(this);
    }

    SkCairoFTTypeface(FT_Face face, void* faceContext, FT_LcdFilter lcdFilter)
        : SkTypeface(SkFontStyle::Normal())
        , fFTFace(face)
        , fFTFaceContext(faceContext)
        , fLcdFilter(lcdFilter)
    {
        mozilla_AddRefSharedFTFace(fFTFaceContext);
    }

    void* GetFTFaceContext() const { return fFTFaceContext; }

    bool hasColorGlyphs() const override
    {
        // Check if the font has scalable outlines. If not, then avoid trying
        // to render it as a path.
        if (fFTFace) {
            return !FT_IS_SCALABLE(fFTFace);
        }
        return false;
    }

private:
    ~SkCairoFTTypeface()
    {
        mozilla_ReleaseSharedFTFace(fFTFaceContext, nullptr);
    }

    FT_Face            fFTFace;
    void*              fFTFaceContext;
    FT_LcdFilter       fLcdFilter;
};

static bool FindByFTFaceContext(SkTypeface* typeface, void* context) {
    return static_cast<SkCairoFTTypeface*>(typeface)->GetFTFaceContext() == context;
}

SkTypeface* SkCreateTypefaceFromCairoFTFont(FT_Face face, void* faceContext,
                                            uint8_t lcdFilter)
{
    sk_sp<SkTypeface> typeface =
        SkTypefaceCache::FindByProcAndRef(FindByFTFaceContext, faceContext);
    if (!typeface) {
        typeface = sk_make_sp<SkCairoFTTypeface>(face, faceContext,
                                                 (FT_LcdFilter)lcdFilter);
        SkTypefaceCache::Add(typeface);
    }

    return typeface.release();
}

SkScalerContext_CairoFT::SkScalerContext_CairoFT(
    SkTypeface& typeface, const SkScalerContextEffects& effects,
    const SkDescriptor* desc, FT_Face face, void* faceContext,
    FT_LcdFilter lcdFilter)
    : SkScalerContext(typeface, effects, desc)
    , fFTFace(face)
    , fFTFaceContext(faceContext)
    , fLcdFilter(lcdFilter)
{
    SkMatrix matrix = fRec.getSingleMatrix();

    computeShapeMatrix(matrix);

    FT_Int32 loadFlags = FT_LOAD_DEFAULT;

    if (SkMask::kBW_Format == fRec.fMaskFormat) {
        if (fRec.getHinting() == SkFontHinting::kNone) {
            loadFlags |= FT_LOAD_NO_HINTING;
        } else {
            loadFlags = FT_LOAD_TARGET_MONO;
        }
        loadFlags |= FT_LOAD_MONOCHROME;
    } else {
        switch (fRec.getHinting()) {
        case SkFontHinting::kNone:
            loadFlags |= FT_LOAD_NO_HINTING;
            break;
        case SkFontHinting::kSlight:
            loadFlags = FT_LOAD_TARGET_LIGHT;  // This implies FORCE_AUTOHINT
            break;
        case SkFontHinting::kNormal:
            if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) {
                loadFlags |= FT_LOAD_FORCE_AUTOHINT;
            }
            break;
        case SkFontHinting::kFull:
            if (isLCD(fRec)) {
                if (fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag) {
                    loadFlags = FT_LOAD_TARGET_LCD_V;
                } else {
                    loadFlags = FT_LOAD_TARGET_LCD;
                }
            }
            if (fRec.fFlags & SkScalerContext::kForceAutohinting_Flag) {
                loadFlags |= FT_LOAD_FORCE_AUTOHINT;
            }
            break;
        default:
            SkDebugf("---------- UNKNOWN hinting %d\n", (int)fRec.getHinting());
            break;
        }
    }

    // Disable autohinting when asked to disable hinting, except for "tricky" fonts.
    if (!gFontHintingEnabled) {
        if (fFTFace && !(fFTFace->face_flags & FT_FACE_FLAG_TRICKY)) {
            loadFlags |= FT_LOAD_NO_AUTOHINT;
        }
    }

    if ((fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag) == 0) {
        loadFlags |= FT_LOAD_NO_BITMAP;
    }

    // Always using FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH to get correct
    // advances, as fontconfig and cairo do.
    // See http://code.google.com/p/skia/issues/detail?id=222.
    loadFlags |= FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;

    loadFlags |= FT_LOAD_COLOR;

    fLoadGlyphFlags = loadFlags;

    fUtils.init(fRec.fForegroundColor, (SkScalerContext::Flags)fRec.fFlags);
}

bool SkScalerContext_CairoFT::computeShapeMatrix(const SkMatrix& m)
{
    // Compute a shape matrix compatible with Cairo's _compute_transform.
    // Finds major/minor scales and uses them to normalize the transform.
    double scaleX = m.getScaleX();
    double skewX = m.getSkewX();
    double skewY = m.getSkewY();
    double scaleY = m.getScaleY();
    double det = scaleX * scaleY - skewY * skewX;
    if (!std::isfinite(det)) {
        fScaleX = fRec.fTextSize * fRec.fPreScaleX;
        fScaleY = fRec.fTextSize;
        fHaveShape = false;
        return false;
    }
    double major = det != 0.0 ? hypot(scaleX, skewY) : 0.0;
    double minor = major != 0.0 ? fabs(det) / major : 0.0;
    // Limit scales to be above 1pt.
    major = std::max(major, 1.0);
    minor = std::max(minor, 1.0);

    // If the font is not scalable, then choose the best available size.
    if (fFTFace && !FT_IS_SCALABLE(fFTFace)) {
        double bestDist = DBL_MAX;
        FT_Int bestSize = -1;
        for (FT_Int i = 0; i < fFTFace->num_fixed_sizes; i++) {
            // Distance is positive if strike is larger than desired size,
            // or negative if smaller. If previously a found smaller strike,
            // then prefer a larger strike. Otherwise, minimize distance.
            double dist = fFTFace->available_sizes[i].y_ppem / 64.0 - minor;
            if (bestDist < 0 ? dist >= bestDist : fabs(dist) <= bestDist) {
                bestDist = dist;
                bestSize = i;
            }
        }
        if (bestSize < 0) {
            fScaleX = fRec.fTextSize * fRec.fPreScaleX;
            fScaleY = fRec.fTextSize;
            fHaveShape = false;
            return false;
        }
        major = fFTFace->available_sizes[bestSize].x_ppem / 64.0;
        minor = fFTFace->available_sizes[bestSize].y_ppem / 64.0;
        fHaveShape = true;
    } else {
        fHaveShape = !m.isScaleTranslate() || scaleX < 0.0 || scaleY < 0.0;
    }

    fScaleX = SkDoubleToScalar(major);
    fScaleY = SkDoubleToScalar(minor);

    if (fHaveShape) {
        // Normalize the transform and convert to fixed-point.
        fShapeMatrix = m;
        fShapeMatrix.preScale(SkDoubleToScalar(1.0 / major), SkDoubleToScalar(1.0 / minor));

        fShapeMatrixFT.xx = SkScalarToFixed(fShapeMatrix.getScaleX());
        fShapeMatrixFT.yx = SkScalarToFixed(-fShapeMatrix.getSkewY());
        fShapeMatrixFT.xy = SkScalarToFixed(-fShapeMatrix.getSkewX());
        fShapeMatrixFT.yy = SkScalarToFixed(fShapeMatrix.getScaleY());
    }
    return true;
}

bool SkScalerContext_CairoFT::prepareGlyph(FT_GlyphSlot glyph)
{
    bool modified = false;
    if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) {
        // Not FT_GlyphSlot_Embolden because we want a less extreme effect.
        mozilla_glyphslot_embolden_less(glyph);
        modified = true;
    }
    return modified;
}

SkScalerContext::GlyphMetrics SkScalerContext_CairoFT::generateMetrics(const SkGlyph& glyph, SkArenaAlloc* arena)
{
    GlyphMetrics mx(glyph.maskFormat());

    AutoLockFTFace faceLock(this);

    FT_Error err = mozilla_LoadFTGlyph(fFTFace, glyph.getGlyphID(), fLoadGlyphFlags);
    if (err != 0) {
        return mx;
    }

    prepareGlyph(fFTFace->glyph);

    mx.advance.fX = SkFDot6ToFloat(fFTFace->glyph->advance.x);
    mx.advance.fY = -SkFDot6ToFloat(fFTFace->glyph->advance.y);

    SkIRect bounds;
    switch (fFTFace->glyph->format) {
    case FT_GLYPH_FORMAT_OUTLINE:
        if (!fFTFace->glyph->outline.n_contours) {
            return mx;
        }

        FT_BBox bbox;
        FT_Outline_Get_CBox(&fFTFace->glyph->outline, &bbox);
        if (this->isSubpixel()) {
            int dx = SkFixedToFDot6(glyph.getSubXFixed());
            int dy = SkFixedToFDot6(glyph.getSubYFixed());
            bbox.xMin += dx;
            bbox.yMin -= dy;
            bbox.xMax += dx;
            bbox.yMax -= dy;
        }
        bbox.xMin &= ~63;
        bbox.yMin &= ~63;
        bbox.xMax = (bbox.xMax + 63) & ~63;
        bbox.yMax = (bbox.yMax + 63) & ~63;
        bounds = SkIRect::MakeLTRB(SkFDot6Floor(bbox.xMin),
                                   -SkFDot6Floor(bbox.yMax),
                                   SkFDot6Floor(bbox.xMax),
                                   -SkFDot6Floor(bbox.yMin));

        if (isLCD(fRec)) {
            // In FreeType < 2.8.1, LCD filtering, if explicitly used, may
            // add padding to the glyph. When not used, there is no padding.
            // As of 2.8.1, LCD filtering is now always supported and may
            // add padding even if an LCD filter is not explicitly set.
            // Regardless, if no LCD filtering is used, or if LCD filtering
            // doesn't add padding, it is safe to modify the glyph's bounds
            // here. generateGlyphImage will detect if the mask is smaller
            // than the bounds and clip things appropriately.
            if (fRec.fFlags & kLCD_Vertical_Flag) {
                bounds.outset(0, 1);
            } else {
                bounds.outset(1, 0);
            }
        }
        break;
    case FT_GLYPH_FORMAT_BITMAP:
        mx.neverRequestPath = true;

        if (fFTFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
            mx.maskFormat = SkMask::kARGB32_Format;
        } else if (isLCD(fRec)) {
            mx.maskFormat = SkMask::kA8_Format;
        }

        if (fHaveShape) {
            // Ensure filtering is preserved when the bitmap is transformed.
            // Otherwise, the result will look horrifically aliased.
            if (mx.maskFormat == SkMask::kBW_Format) {
                mx.maskFormat = SkMask::kA8_Format;
            }

            // Apply the shape matrix to the glyph's bounding box.
            SkRect srcRect = SkRect::MakeXYWH(
                SkIntToScalar(fFTFace->glyph->bitmap_left),
                -SkIntToScalar(fFTFace->glyph->bitmap_top),
                SkIntToScalar(fFTFace->glyph->bitmap.width),
                SkIntToScalar(fFTFace->glyph->bitmap.rows));
            SkRect destRect;
            fShapeMatrix.mapRect(&destRect, srcRect);
            SkIRect glyphRect = destRect.roundOut();
            bounds = SkIRect::MakeXYWH(SkScalarRoundToInt(destRect.fLeft),
                                       SkScalarRoundToInt(destRect.fTop),
                                       glyphRect.width(),
                                       glyphRect.height());
        } else {
            bounds = SkIRect::MakeXYWH(fFTFace->glyph->bitmap_left,
                                       -fFTFace->glyph->bitmap_top,
                                       fFTFace->glyph->bitmap.width,
                                       fFTFace->glyph->bitmap.rows);
        }
        break;
    case FT_GLYPH_FORMAT_SVG:
        // We don't support getting glyph bounds for SVG, but at least the advance
        // should be correctly returned, and we don't want to fire an assertion.
        break;
    default:
        SkDEBUGFAIL("unknown glyph format");
        return mx;
    }

    if (SkIRect::MakeXYWH(SHRT_MIN, SHRT_MIN, USHRT_MAX, USHRT_MAX).contains(bounds)) {
      mx.bounds = SkRect::Make(bounds);
    }

    return mx;
}

void SkScalerContext_CairoFT::generateImage(const SkGlyph& glyph, void* imageBuffer)
{
    AutoLockFTFace faceLock(this);

    FT_Error err = mozilla_LoadFTGlyph(fFTFace, glyph.getGlyphID(), fLoadGlyphFlags);

    if (err != 0) {
        sk_bzero(imageBuffer, glyph.imageSize());
        return;
    }

    prepareGlyph(fFTFace->glyph);

    bool useLcdFilter =
        fFTFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE &&
        glyph.maskFormat() == SkMask::kLCD16_Format &&
        gSetLcdFilter;
    if (useLcdFilter) {
        mozilla_LockFTLibrary(fFTFace->glyph->library);
        gSetLcdFilter(fFTFace->glyph->library, fLcdFilter);
    }

    SkMatrix matrix;
    if (fFTFace->glyph->format == FT_GLYPH_FORMAT_BITMAP &&
        fHaveShape) {
        matrix = fShapeMatrix;
    } else {
        matrix.setIdentity();
    }
    fUtils.generateGlyphImage(fFTFace, glyph, imageBuffer, matrix, fPreBlend);

    if (useLcdFilter) {
        gSetLcdFilter(fFTFace->glyph->library, FT_LCD_FILTER_NONE);
        mozilla_UnlockFTLibrary(fFTFace->glyph->library);
    }
}

std::optional<SkScalerContext::GeneratedPath> SkScalerContext_CairoFT::generatePath(const SkGlyph& glyph)
{
    AutoLockFTFace faceLock(this);

    SkGlyphID glyphID = glyph.getGlyphID();

    uint32_t flags = fLoadGlyphFlags;
    flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
    flags &= ~FT_LOAD_RENDER;   // don't scan convert (we just want the outline)

    FT_Error err = mozilla_LoadFTGlyph(fFTFace, glyphID, flags);

    if (err != 0) {
        return {};
    }

    bool modified = prepareGlyph(fFTFace->glyph);

    SkPathBuilder builder;
    if (!fUtils.generateGlyphPath(fFTFace, &builder)) {
      return {};
    }
    return {{builder.detach(), modified}};
}

void SkScalerContext_CairoFT::generateFontMetrics(SkFontMetrics* metrics)
{
    if (metrics) {
        sk_bzero(metrics, sizeof(SkFontMetrics));
    }
}
