/*
 * Copyright 2018 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkGlyphRunPainter.h"

#if SK_SUPPORT_GPU
#include "GrCaps.h"
#include "GrColorSpaceInfo.h"
#include "GrContextPriv.h"
#include "GrRecordingContext.h"
#include "GrRecordingContextPriv.h"
#include "GrRenderTargetContext.h"
#include "SkGr.h"
#include "text/GrTextBlobCache.h"
#include "text/GrTextContext.h"
#endif

#include "SkColorFilter.h"
#include "SkDevice.h"
#include "SkDistanceFieldGen.h"
#include "SkDraw.h"
#include "SkFontPriv.h"
#include "SkMaskFilter.h"
#include "SkPaintPriv.h"
#include "SkPathEffect.h"
#include "SkRasterClip.h"
#include "SkRemoteGlyphCacheImpl.h"
#include "SkStrikeInterface.h"
#include "SkStrike.h"
#include "SkStrikeCache.h"
#include "SkTDArray.h"
#include "SkTraceEvent.h"

// -- SkGlyphCacheCommon ---------------------------------------------------------------------------

SkVector SkStrikeCommon::PixelRounding(bool isSubpixel, SkAxisAlignment axisAlignment) {
    if (!isSubpixel) {
        return {SK_ScalarHalf, SK_ScalarHalf};
    } else {
        static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound);
        switch (axisAlignment) {
            case kX_SkAxisAlignment:
                return {kSubpixelRounding, SK_ScalarHalf};
            case kY_SkAxisAlignment:
                return {SK_ScalarHalf, kSubpixelRounding};
            case kNone_SkAxisAlignment:
                return {kSubpixelRounding, kSubpixelRounding};
        }
    }

    // Some compilers need this.
    return {0, 0};
}

SkIPoint SkStrikeCommon::SubpixelLookup(SkAxisAlignment axisAlignment, SkPoint position) {
    // TODO: SkScalarFraction uses truncf to calculate the fraction. This should be floorf.
    SkFixed lookupX = SkScalarToFixed(SkScalarFraction(position.x())),
            lookupY = SkScalarToFixed(SkScalarFraction(position.y()));

    // Snap to a given axis if alignment is requested.
    if (axisAlignment == kX_SkAxisAlignment) {
        lookupY = 0;
    } else if (axisAlignment == kY_SkAxisAlignment) {
        lookupX = 0;
    }

    return {lookupX, lookupY};
}

bool SkStrikeCommon::GlyphTooBigForAtlas(const SkGlyph& glyph) {
    return glyph.fWidth > kSkSideTooBigForAtlas || glyph.fHeight > kSkSideTooBigForAtlas;
}

// -- SkGlyphRunListPainter ------------------------------------------------------------------------
SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
                                             SkColorType colorType,
                                             SkScalerContextFlags flags,
                                             SkStrikeCacheInterface* strikeCache)
        : fDeviceProps{props}
        ,  fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
        ,  fColorType{colorType}, fScalerContextFlags{flags}
        ,  fStrikeCache{strikeCache} {}

// TODO: unify with code in GrTextContext.cpp
static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
    // If we're doing linear blending, then we can disable the gamma hacks.
    // Otherwise, leave them on. In either case, we still want the contrast boost:
    // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
    if (cs && cs->gammaIsLinear()) {
        return SkScalerContextFlags::kBoostContrast;
    } else {
        return SkScalerContextFlags::kFakeGammaAndBoostContrast;
    }
}

SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
                                             SkColorType colorType,
                                             SkColorSpace* cs,
                                             SkStrikeCacheInterface* strikeCache)
        : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {}

#if SK_SUPPORT_GPU
SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
                                             const GrColorSpaceInfo& csi)
        : SkGlyphRunListPainter(props,
                                kUnknown_SkColorType,
                                compute_scaler_context_flags(csi.colorSpace()),
                                SkStrikeCache::GlobalStrikeCache()) {}

SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc)
        : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorSpaceInfo()} {}

#endif

bool SkGlyphRunListPainter::ShouldDrawAsPath(
        const SkPaint& paint, const SkFont& font, const SkMatrix& matrix) {
    // hairline glyphs are fast enough so we don't need to cache them
    if (SkPaint::kStroke_Style == paint.getStyle() && 0 == paint.getStrokeWidth()) {
        return true;
    }

    // we don't cache perspective
    if (matrix.hasPerspective()) {
        return true;
    }

    return SkFontPriv::TooBigToUseCache(matrix, SkFontPriv::MakeTextMatrix(font), 1024);
}

static bool check_glyph_position(SkPoint position) {
    // Prevent glyphs from being drawn outside of or straddling the edge of device space.
    // Comparisons written a little weirdly so that NaN coordinates are treated safely.
    auto gt = [](float a, int b) { return !(a <= (float)b); };
    auto lt = [](float a, int b) { return !(a >= (float)b); };
    return !(gt(position.fX, INT_MAX - (INT16_MAX + SkTo<int>(UINT16_MAX))) ||
             lt(position.fX, INT_MIN - (INT16_MIN + 0 /*UINT16_MIN*/)) ||
             gt(position.fY, INT_MAX - (INT16_MAX + SkTo<int>(UINT16_MAX))) ||
             lt(position.fY, INT_MIN - (INT16_MIN + 0 /*UINT16_MIN*/)));
}

static SkMask create_mask(const SkGlyph& glyph, SkPoint position, const void* image) {
    SkMask mask;
    int left = SkScalarFloorToInt(position.fX);
    int top  = SkScalarFloorToInt(position.fY);

    left += glyph.fLeft;
    top  += glyph.fTop;

    int right   = left + glyph.fWidth;
    int bottom  = top  + glyph.fHeight;

    mask.fBounds.set(left, top, right, bottom);
    SkASSERT(!mask.fBounds.isEmpty());

    mask.fImage    = (uint8_t*)image;
    mask.fRowBytes = glyph.rowBytes();
    mask.fFormat   = static_cast<SkMask::Format>(glyph.fMaskFormat);

    return mask;
}

void SkGlyphRunListPainter::drawForBitmapDevice(
        const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix,
        const BitmapDevicePainter* bitmapDevice) {
    ScopedBuffers _ = this->ensureBuffers(glyphRunList);

    const SkPaint& runPaint = glyphRunList.paint();
    // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
    // convert the lcd text into A8 text. The props communicates this to the scaler.
    auto& props = (kN32_SkColorType == fColorType && runPaint.isSrcOver())
                  ? fDeviceProps
                  : fBitmapFallbackProps;

    SkPoint origin = glyphRunList.origin();
    for (auto& glyphRun : glyphRunList) {
        const SkFont& runFont = glyphRun.font();
        auto runSize = glyphRun.runSize();

        if (ShouldDrawAsPath(runPaint, runFont, deviceMatrix)) {
            SkMatrix::MakeTrans(origin.x(), origin.y()).mapPoints(
                    fPositions, glyphRun.positions().data(), runSize);
            // setup our std pathPaint, in hopes of getting hits in the cache
            SkPaint pathPaint(runPaint);
            SkFont  pathFont{runFont};
            SkScalar textScale = pathFont.setupForAsPaths(&pathPaint);

            auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
                                pathFont, pathPaint, props,
                                fScalerContextFlags, SkMatrix::I());

            SkTDArray<SkPathPos> pathsAndPositions;
            pathsAndPositions.setReserve(runSize);
            SkPoint* positionCursor = fPositions;
            for (auto glyphID : glyphRun.glyphsIDs()) {
                SkPoint position = *positionCursor++;
                if (check_glyph_position(position)) {
                    const SkGlyph& glyph = pathCache->getGlyphMetrics(glyphID, {0, 0});
                    if (!glyph.isEmpty()) {
                        const SkPath* path = pathCache->findPath(glyph);
                        if (path != nullptr) {
                            pathsAndPositions.push_back(SkPathPos{path, position});
                        }
                    }
                }
            }

            // The paint we draw paths with must have the same anti-aliasing state as the runFont
            // allowing the paths to have the same edging as the glyph masks.
            pathPaint = runPaint;
            pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());

            bitmapDevice->paintPaths(
                    SkSpan<const SkPathPos>{pathsAndPositions.begin(), pathsAndPositions.size()},
                    textScale, pathPaint);
        } else {
            auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
                                        runFont, runPaint, props,
                                        fScalerContextFlags, deviceMatrix);

            // Add rounding and origin.
            SkMatrix matrix = deviceMatrix;
            matrix.preTranslate(origin.x(), origin.y());
            SkPoint rounding = cache->rounding();
            matrix.postTranslate(rounding.x(), rounding.y());
            matrix.mapPoints(fPositions, glyphRun.positions().data(), runSize);

            SkTDArray<SkMask> masks;
            masks.setReserve(runSize);
            const SkPoint* positionCursor = fPositions;
            for (auto glyphID : glyphRun.glyphsIDs()) {
                auto position = *positionCursor++;
                if (check_glyph_position(position)) {
                    const SkGlyph& glyph = cache->getGlyphMetrics(glyphID, position);
                    const void* image;
                    if (!glyph.isEmpty() && (image = cache->findImage(glyph))) {
                        masks.push_back(create_mask(glyph, position, image));
                    }
                }
            }
            bitmapDevice->paintMasks(SkSpan<const SkMask>{masks.begin(), masks.size()}, runPaint);
        }
    }
}

// Getting glyphs to the screen in a fallback situation can be complex. Here is the set of
// transformations that have to happen. Normally, they would all be accommodated by the font
// scaler, but the atlas has an upper limit to the glyphs it can handle. So the GPU is used to
// make up the difference from the smaller atlas size to the larger size needed by the final
// transform. Here are the transformations that are applied.
//
// final transform = [view matrix] * [text scale] * [text size]
//
// There are three cases:
// * Go Fast - view matrix is scale and translate, and all the glyphs are small enough
//   Just scale the positions, and have the glyph cache handle the view matrix transformation.
//   The text scale is 1.
// * It's complicated - view matrix is not scale and translate, and the glyphs are small enough
//   The glyph cache does not handle the view matrix, but stores the glyphs at the text size
//   specified by the run paint. The GPU handles the rotation, etc. specified by the view matrix.
//   The text scale is 1.
// * Too big - The glyphs are too big to fit in the atlas
//   Reduce the text size so the glyphs will fit in the atlas, but don't apply any
//   transformations from the view matrix. Calculate a text scale based on that reduction. This
//   scale factor is used to increase the size of the destination rectangles. The destination
//   rectangles are then scaled, rotated, etc. by the GPU using the view matrix.
void SkGlyphRunListPainter::processARGBFallback(SkScalar maxSourceGlyphDimension,
                                                const SkPaint& runPaint,
                                                const SkFont& runFont,
                                                const SkMatrix& viewMatrix,
                                                SkGlyphRunPainterInterface* process) {
    SkASSERT(!fARGBGlyphsIDs.empty());

    SkScalar maxScale = viewMatrix.getMaxScale();

    // This is a linear estimate of the longest dimension among all the glyph widths and heights.
    SkScalar conservativeMaxGlyphDimension = maxSourceGlyphDimension * maxScale;

    // If the situation that the matrix is simple, and all the glyphs are small enough. Go fast!
    // N.B. If the matrix has scale, that will be reflected in the strike through the viewMatrix
    // in the useFastPath case.
    bool useDeviceCache =
            viewMatrix.isScaleTranslate()
            && conservativeMaxGlyphDimension <= SkStrikeCommon::kSkSideTooBigForAtlas;

    // A scaled and translated transform is the common case, and is handled directly in fallback.
    // Even if the transform is scale and translate, fallback must be careful to use glyphs that
    // fit in the atlas. If a glyph will not fit in the atlas, then the general transform case is
    // used to render the glyphs.
    if (useDeviceCache) {
        // Translate the positions to device space.
        viewMatrix.mapPoints(fARGBPositions.data(), fARGBPositions.size());
        for (SkPoint& point : fARGBPositions) {
            point.fX =  SkScalarFloorToScalar(point.fX);
            point.fY =  SkScalarFloorToScalar(point.fY);
        }

        SkAutoDescriptor ad;
        SkScalerContextEffects effects;
        SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
                runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix, &ad, &effects);

        SkScopedStrike strike =
                fStrikeCache->findOrCreateScopedStrike(
                        *ad.getDesc(), effects, *runFont.getTypefaceOrDefault());

        int drawableGlyphCount = strike->glyphMetrics(fARGBGlyphsIDs.data(),
                                                      fARGBPositions.data(),
                                                      fARGBGlyphsIDs.size(),
                                                      fGlyphPos);

        process->processDeviceFallback(
                SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(drawableGlyphCount)},
                strike.get());

    } else {
        // If the matrix is complicated or if scaling is used to fit the glyphs in the cache,
        // then this case is used.

        // Subtract 2 to account for the bilerp pad around the glyph
        SkScalar maxAtlasDimension = SkStrikeCommon::kSkSideTooBigForAtlas - 2;

        SkScalar runFontTextSize = runFont.getSize();

        // Scale the text size down so the long side of all the glyphs will fit in the atlas.
        SkScalar fallbackTextSize = SkScalarFloorToScalar(
                (maxAtlasDimension / maxSourceGlyphDimension) * runFontTextSize);

        SkFont fallbackFont{runFont};
        fallbackFont.setSize(fallbackTextSize);

        // The scale factor to go from strike size to the source size for glyphs.
        SkScalar fallbackTextScale = runFontTextSize / fallbackTextSize;

        SkAutoDescriptor ad;
        SkScalerContextEffects effects;
        SkScalerContext::CreateDescriptorAndEffectsUsingPaint(fallbackFont,
                                                              runPaint,
                                                              fDeviceProps,
                                                              fScalerContextFlags,
                                                              SkMatrix::I(),
                                                              &ad,
                                                              &effects);

        SkScopedStrike strike =
                fStrikeCache->findOrCreateScopedStrike(
                        *ad.getDesc(), effects, *fallbackFont.getTypefaceOrDefault());

        SkPoint* posCursor = fARGBPositions.data();
        int glyphCount = 0;
        for (SkGlyphID glyphID : fARGBGlyphsIDs) {
            SkPoint pos = *posCursor++;
            const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
            fGlyphPos[glyphCount++] = {&glyph, pos};
        }

        process->processSourceFallback(
                SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
                strike.get(),
                fallbackTextScale,
                viewMatrix.hasPerspective());
    }
}

#if SK_SUPPORT_GPU
void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunList,
                                                const SkMatrix& viewMatrix,
                                                const SkSurfaceProps& props,
                                                bool contextSupportsDistanceFieldText,
                                                const GrTextContext::Options& options,
                                                SkGlyphRunPainterInterface* process) {

    SkPoint origin = glyphRunList.origin();
    const SkPaint& runPaint = glyphRunList.paint();

    for (const auto& glyphRun : glyphRunList) {
        const SkFont& runFont = glyphRun.font();

        bool useSDFT = GrTextContext::CanDrawAsDistanceFields(
                runPaint, runFont, viewMatrix, props, contextSupportsDistanceFieldText, options);
        if (process) {
            process->startRun(glyphRun, useSDFT);
        }

        if (useSDFT) {
            ScopedBuffers _ = this->ensureBuffers(glyphRun);
            SkScalar maxFallbackDimension{-SK_ScalarInfinity};

            // Setup distance field runPaint and text ratio
            SkPaint dfPaint = GrTextContext::InitDistanceFieldPaint(runPaint);
            SkScalar cacheToSourceScale;
            SkFont dfFont = GrTextContext::InitDistanceFieldFont(
                    runFont, viewMatrix, options, &cacheToSourceScale);
            // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
            // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
            SkScalerContextFlags flags = SkScalerContextFlags::kNone;

            SkScalar minScale, maxScale;
            std::tie(minScale, maxScale) = GrTextContext::InitDistanceFieldMinMaxScale(
                    runFont.getSize(), viewMatrix, options);

            SkAutoDescriptor ad;
            SkScalerContextEffects effects;
            SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
                    dfFont, dfPaint, fDeviceProps, flags, SkMatrix::I(), &ad, &effects);
            SkScopedStrike strike =
                    fStrikeCache->findOrCreateScopedStrike(
                            *ad.getDesc(), effects, *dfFont.getTypefaceOrDefault());

            std::vector<SkGlyphPos> paths;

            int glyphCount = 0;
            const SkPoint* positionCursor = glyphRun.positions().data();
            for (auto glyphID : glyphRun.glyphsIDs()) {
                const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
                SkPoint glyphPos = origin + *positionCursor++;
                if (!glyph.isEmpty()) {
                    if (glyph.fMaskFormat == SkMask::kSDF_Format) {
                        if (!SkStrikeCommon::GlyphTooBigForAtlas(glyph)) {
                            // If the glyph is not empty, then it will have a pointer to SDF data.
                            fGlyphPos[glyphCount++] = {&glyph, glyphPos};
                        } else {
                            if (strike->decideCouldDrawFromPath(glyph)) {
                                paths.push_back({&glyph, glyphPos});
                            }
                        }
                    } else {
                        SkASSERT(glyph.fMaskFormat == SkMask::kARGB32_Format);
                        SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
                        maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
                        fARGBGlyphsIDs.push_back(glyphID);
                        fARGBPositions.push_back(glyphPos);
                    }
                }
            }

            if (process) {
                if (glyphCount > 0) {
                    bool hasWCoord = viewMatrix.hasPerspective()
                                     || options.fDistanceFieldVerticesAlwaysHaveW;
                    process->processSourceSDFT(
                            SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
                            strike.get(),
                            runFont,
                            cacheToSourceScale,
                            minScale,
                            maxScale,
                            hasWCoord);
                }

                if (!paths.empty()) {
                    process->processSourcePaths(
                            SkSpan<const SkGlyphPos>{paths}, strike.get(), cacheToSourceScale);
                }

                {
                    // fGlyphPos will be reused here.
                    if (!fARGBGlyphsIDs.empty()) {
                        this->processARGBFallback(maxFallbackDimension * cacheToSourceScale,
                                                  runPaint, runFont, viewMatrix, process);
                    }
                }
            }
        } else if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, runFont, viewMatrix)) {
            ScopedBuffers _ = this->ensureBuffers(glyphRun);
            SkScalar maxFallbackDimension{-SK_ScalarInfinity};

            // setup our std runPaint, in hopes of getting hits in the cache
            SkPaint pathPaint{runPaint};
            SkFont pathFont{runFont};

            // The factor to get from the size stored in the strike to the size needed for
            // the source.
            SkScalar strikeToSourceRatio = pathFont.setupForAsPaths(&pathPaint);

            SkAutoDescriptor ad;
            SkScalerContextEffects effects;
            SkScalerContext::CreateDescriptorAndEffectsUsingPaint(pathFont,
                                                                  pathPaint,
                                                                  fDeviceProps,
                                                                  fScalerContextFlags,
                                                                  SkMatrix::I(),
                                                                  &ad,
                                                                  &effects);
            SkScopedStrike strike =
                    fStrikeCache->findOrCreateScopedStrike(
                            *ad.getDesc(), effects,*pathFont.getTypefaceOrDefault());

            int glyphCount = 0;
            const SkPoint* positionCursor = glyphRun.positions().data();
            for (auto glyphID : glyphRun.glyphsIDs()) {
                SkPoint glyphPos = origin + *positionCursor++;

                // Use outline from {0, 0} because all transforms including subpixel translation
                // happen during drawing.
                const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
                if (!glyph.isEmpty()) {
                    if (glyph.fMaskFormat != SkMask::kARGB32_Format) {
                        if (strike->decideCouldDrawFromPath(glyph)) {
                            fGlyphPos[glyphCount++] = {&glyph, glyphPos};
                        }
                    } else {
                        SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
                        maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
                        fARGBGlyphsIDs.push_back(glyphID);
                        fARGBPositions.push_back(glyphPos);
                    }
                }
            }

            if (process) {
                if (glyphCount > 0) {
                    process->processSourcePaths(
                            SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
                            strike.get(),
                            strikeToSourceRatio);
                }

                // fGlyphPos will be reused here.
                if (!fARGBGlyphsIDs.empty()) {
                    this->processARGBFallback(maxFallbackDimension * strikeToSourceRatio,
                                              runPaint, runFont, viewMatrix, process);
                }
            }
        } else {

            SkAutoDescriptor ad;
            SkScalerContextEffects effects;

            SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
                    runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix, &ad,
                    &effects);

            SkTypeface* typeface = runFont.getTypefaceOrDefault();
            SkScopedStrike strike =
                    fStrikeCache->findOrCreateScopedStrike(*ad.getDesc(), effects, *typeface);

            ScopedBuffers _ = this->ensureBuffers(glyphRun);
            SkScalar maxFallbackDimension{-SK_ScalarInfinity};

            SkMatrix mapping = viewMatrix;
            mapping.preTranslate(origin.x(), origin.y());
            SkVector rounding = strike->rounding();
            mapping.postTranslate(rounding.x(), rounding.y());
            mapping.mapPoints(fPositions, glyphRun.positions().data(), glyphRun.runSize());

            int glyphsWithMaskCount = 0;
            const SkPoint* positionCursor = glyphRun.positions().data();
            const SkPoint* devicePositionCursor = fPositions;
            for (auto glyphID : glyphRun.glyphsIDs()) {
                SkPoint glyphPos = *positionCursor++;
                SkPoint deviceGlyphPos = *devicePositionCursor++;
                if (!SkScalarsAreFinite(deviceGlyphPos.x(), deviceGlyphPos.y())) {
                    continue;
                }

                const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, deviceGlyphPos);
                if (glyph.isEmpty()) {
                    continue;
                }

                if (SkStrikeCommon::GlyphTooBigForAtlas(glyph)) {
                    if (strike->decideCouldDrawFromPath(glyph)) {
                        fPaths.push_back({&glyph, deviceGlyphPos});
                    } else {
                        SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
                        maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
                        fARGBGlyphsIDs.push_back(glyph.getGlyphID());
                        fARGBPositions.push_back(origin + glyphPos);
                    }
                } else {
                    fGlyphPos[glyphsWithMaskCount++] = {&glyph, deviceGlyphPos};
                }
            }

            if (process) {
                if (glyphsWithMaskCount > 0) {
                    process->processDeviceMasks(
                            SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphsWithMaskCount)},
                            strike.get());
                }
                if (!fPaths.empty()) {
                    process->processDevicePaths(SkSpan<const SkGlyphPos>{fPaths});
                }

                // fGlyphPos will be reused here.
                if (!fARGBGlyphsIDs.empty()) {
                    this->processARGBFallback(maxFallbackDimension / viewMatrix.getMaxScale(),
                                              runPaint, runFont, viewMatrix, process);
                }
            }

        }

    }
}
#endif  // SK_SUPPORT_GPU

auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers {
    size_t size = 0;
    for (const SkGlyphRun& run : glyphRunList) {
        size = std::max(run.runSize(), size);
    }
    return ScopedBuffers(this, size);
}

SkGlyphRunListPainter::ScopedBuffers
SkGlyphRunListPainter::ensureBuffers(const SkGlyphRun& glyphRun) {
    return ScopedBuffers(this, glyphRun.runSize());
}

#if SK_SUPPORT_GPU
// -- GrTextContext --------------------------------------------------------------------------------
SkPMColor4f generate_filtered_color(const SkPaint& paint, const GrColorSpaceInfo& colorSpaceInfo) {
    SkColor4f filteredColor = paint.getColor4f();
    if (auto* xform = colorSpaceInfo.colorSpaceXformFromSRGB()) {
        filteredColor = xform->apply(filteredColor);
    }
    if (paint.getColorFilter() != nullptr) {
        filteredColor = paint.getColorFilter()->filterColor4f(filteredColor,
                                                              colorSpaceInfo.colorSpace());
    }
    return filteredColor.premul();
}

void GrTextContext::drawGlyphRunList(
        GrRecordingContext* context, GrTextTarget* target, const GrClip& clip,
        const SkMatrix& viewMatrix, const SkSurfaceProps& props,
        const SkGlyphRunList& glyphRunList) {
    SkPoint origin = glyphRunList.origin();

    // Get the first paint to use as the key paint.
    const SkPaint& listPaint = glyphRunList.paint();

    SkPMColor4f filteredColor = generate_filtered_color(listPaint, target->colorSpaceInfo());
    GrColor color = generate_filtered_color(listPaint, target->colorSpaceInfo()).toBytes_RGBA();

    // If we have been abandoned, then don't draw
    if (context->priv().abandoned()) {
        return;
    }

    SkMaskFilterBase::BlurRec blurRec;
    // It might be worth caching these things, but its not clear at this time
    // TODO for animated mask filters, this will fill up our cache.  We need a safeguard here
    const SkMaskFilter* mf = listPaint.getMaskFilter();
    bool canCache = glyphRunList.canCache() && !(listPaint.getPathEffect() ||
                                                 (mf && !as_MFB(mf)->asABlur(&blurRec)));
    SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorSpaceInfo());

    auto grStrikeCache = context->priv().getGrStrikeCache();
    GrTextBlobCache* textBlobCache = context->priv().getTextBlobCache();

    sk_sp<GrTextBlob> cacheBlob;
    GrTextBlob::Key key;
    if (canCache) {
        bool hasLCD = glyphRunList.anyRunsLCD();

        // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
        SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
                                        kUnknown_SkPixelGeometry;

        // TODO we want to figure out a way to be able to use the canonical color on LCD text,
        // see the note on ComputeCanonicalColor above.  We pick a dummy value for LCD text to
        // ensure we always match the same key
        GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
                                 ComputeCanonicalColor(listPaint, hasLCD);

        key.fPixelGeometry = pixelGeometry;
        key.fUniqueID = glyphRunList.uniqueID();
        key.fStyle = listPaint.getStyle();
        key.fHasBlur = SkToBool(mf);
        key.fCanonicalColor = canonicalColor;
        key.fScalerContextFlags = scalerContextFlags;
        cacheBlob = textBlobCache->find(key);
    }

    if (cacheBlob) {
        if (cacheBlob->mustRegenerate(listPaint, glyphRunList.anyRunsSubpixelPositioned(),
                                      blurRec, viewMatrix, origin.x(),origin.y())) {
            // We have to remake the blob because changes may invalidate our masks.
            // TODO we could probably get away reuse most of the time if the pointer is unique,
            // but we'd have to clear the subrun information
            textBlobCache->remove(cacheBlob.get());
            cacheBlob = textBlobCache->makeCachedBlob(
                    glyphRunList, key, blurRec, listPaint, color, grStrikeCache);
            cacheBlob->generateFromGlyphRunList(
                    *context->priv().caps()->shaderCaps(), fOptions,
                    listPaint, scalerContextFlags, viewMatrix, props,
                    glyphRunList, target->glyphPainter());
        } else {
            textBlobCache->makeMRU(cacheBlob.get());

            if (CACHE_SANITY_CHECK) {
                sk_sp<GrTextBlob> sanityBlob(textBlobCache->makeBlob(
                        glyphRunList, color, grStrikeCache));
                sanityBlob->setupKey(key, blurRec, listPaint);
                cacheBlob->generateFromGlyphRunList(
                        *context->priv().caps()->shaderCaps(), fOptions,
                        listPaint, scalerContextFlags, viewMatrix, props, glyphRunList,
                        target->glyphPainter());
                GrTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
            }
        }
    } else {
        if (canCache) {
            cacheBlob = textBlobCache->makeCachedBlob(
                    glyphRunList, key, blurRec, listPaint, color, grStrikeCache);
        } else {
            cacheBlob = textBlobCache->makeBlob(glyphRunList, color, grStrikeCache);
        }
        cacheBlob->generateFromGlyphRunList(
                *context->priv().caps()->shaderCaps(), fOptions, listPaint,
                scalerContextFlags, viewMatrix, props, glyphRunList,
                target->glyphPainter());
    }

    cacheBlob->flush(target, props, fDistanceAdjustTable.get(), listPaint, filteredColor,
                     clip, viewMatrix, origin.x(), origin.y());
}

void GrTextBlob::SubRun::appendGlyph(GrGlyph* glyph, SkRect dstRect) {

    this->joinGlyphBounds(dstRect);

    GrTextBlob* blob = fRun->fBlob;

    bool hasW = this->hasWCoord();
    // glyphs drawn in perspective must always have a w coord.
    SkASSERT(hasW || !blob->fInitialViewMatrix.hasPerspective());
    auto maskFormat = this->maskFormat();
    size_t vertexStride = GetVertexStride(maskFormat, hasW);

    intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices + fVertexEndIndex);

    // We always write the third position component used by SDFs. If it is unused it gets
    // overwritten. Similarly, we always write the color and the blob will later overwrite it
    // with texture coords if it is unused.
    size_t colorOffset = hasW ? sizeof(SkPoint3) : sizeof(SkPoint);
    // V0
    *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fLeft, dstRect.fTop, 1.f};
    *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
    vertex += vertexStride;

    // V1
    *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fLeft, dstRect.fBottom, 1.f};
    *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
    vertex += vertexStride;

    // V2
    *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fRight, dstRect.fTop, 1.f};
    *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
    vertex += vertexStride;

    // V3
    *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fRight, dstRect.fBottom, 1.f};
    *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;

    fVertexEndIndex += vertexStride * kVerticesPerGlyph;
    blob->fGlyphs[fGlyphEndIndex++] = glyph;
}

void GrTextBlob::Run::switchSubRunIfNeededAndAppendGlyph(GrGlyph* glyph,
                                                         const sk_sp<GrTextStrike>& strike,
                                                         const SkRect& destRect,
                                                         bool needsTransform) {
    GrMaskFormat format = glyph->fMaskFormat;

    SubRun* subRun = &fSubRunInfo.back();
    if (fInitialized && subRun->maskFormat() != format) {
        subRun = pushBackSubRun(fDescriptor, fColor);
        subRun->setStrike(strike);
    } else if (!fInitialized) {
        subRun->setStrike(strike);
    }

    fInitialized = true;
    subRun->setMaskFormat(format);
    subRun->setNeedsTransform(needsTransform);
    subRun->appendGlyph(glyph, destRect);
}

void GrTextBlob::Run::appendDeviceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
                                             const SkGlyph& skGlyph, SkPoint origin) {
    if (GrGlyph* glyph = strike->getGlyph(skGlyph)) {

        SkRect glyphRect = glyph->destRect(origin);

        if (!glyphRect.isEmpty()) {
            this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, false);
        }
    }
}

void GrTextBlob::Run::appendSourceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
                                             const SkGlyph& skGlyph,
                                             SkPoint origin,
                                             SkScalar textScale) {
    if (GrGlyph* glyph = strike->getGlyph(skGlyph)) {

        SkRect glyphRect = glyph->destRect(origin, textScale);

        if (!glyphRect.isEmpty()) {
            this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, true);
        }
    }
}

void GrTextBlob::generateFromGlyphRunList(const GrShaderCaps& shaderCaps,
                                          const GrTextContext::Options& options,
                                          const SkPaint& paint,
                                          SkScalerContextFlags scalerContextFlags,
                                          const SkMatrix& viewMatrix,
                                          const SkSurfaceProps& props,
                                          const SkGlyphRunList& glyphRunList,
                                          SkGlyphRunListPainter* glyphPainter) {
    SkPoint origin = glyphRunList.origin();
    const SkPaint& runPaint = glyphRunList.paint();
    this->initReusableBlob(SkPaintPriv::ComputeLuminanceColor(runPaint), viewMatrix,
                           origin.x(), origin.y());

    glyphPainter->processGlyphRunList(glyphRunList,
                                      viewMatrix,
                                      props,
                                      shaderCaps.supportsDistanceFieldText(),
                                      options,
                                      this);
}

GrTextBlob::Run* GrTextBlob::currentRun() {
    return &fRuns[fRunCount - 1];
}

void GrTextBlob::startRun(const SkGlyphRun& glyphRun, bool useSDFT) {
    if (useSDFT) {
        this->setHasDistanceField();
    }
    Run* run = this->pushBackRun();
    run->setRunFontAntiAlias(glyphRun.font().hasSomeAntiAliasing());
}

void GrTextBlob::processDeviceMasks(SkSpan<const SkGlyphPos> masks,
                                    SkStrikeInterface* strike) {
    Run* run = this->currentRun();
    this->setHasBitmap();
    run->setupFont(strike->strikeSpec());
    sk_sp<GrTextStrike> currStrike = fStrikeCache->getStrike(strike->getDescriptor());
    for (const auto& mask : masks) {
        SkPoint pt{SkScalarFloorToScalar(mask.position.fX),
                   SkScalarFloorToScalar(mask.position.fY)};
        run->appendDeviceSpaceGlyph(currStrike, *mask.glyph, pt);
    }
}

void GrTextBlob::processSourcePaths(SkSpan<const SkGlyphPos> paths,
                                    SkStrikeInterface* strike, SkScalar cacheToSourceScale) {
    Run* run = this->currentRun();
    this->setHasBitmap();
    run->setupFont(strike->strikeSpec());
    for (const auto& path : paths) {
        if (const SkPath* glyphPath = path.glyph->path()) {
            run->appendPathGlyph(*glyphPath, path.position, cacheToSourceScale,
                                 false);
        }
    }
}

void GrTextBlob::processDevicePaths(SkSpan<const SkGlyphPos> paths) {
    Run* run = this->currentRun();
    this->setHasBitmap();
    for (const auto& path : paths) {
        SkPoint pt{SkScalarFloorToScalar(path.position.fX),
                   SkScalarFloorToScalar(path.position.fY)};
        // TODO: path should always be set. Remove when proven.
        if (const SkPath* glyphPath = path.glyph->path()) {
            run->appendPathGlyph(*glyphPath, pt, SK_Scalar1, true);
        }
    }
}

void GrTextBlob::processSourceSDFT(SkSpan<const SkGlyphPos> masks,
                                   SkStrikeInterface* strike,
                                   const SkFont& runFont,
                                   SkScalar cacheToSourceScale,
                                   SkScalar minScale,
                                   SkScalar maxScale,
                                   bool hasWCoord) {

    Run* run = this->currentRun();
    run->setSubRunHasDistanceFields(
            runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
            runFont.hasSomeAntiAliasing(),
            hasWCoord);
    this->setMinAndMaxScale(minScale, maxScale);
    run->setupFont(strike->strikeSpec());
    sk_sp<GrTextStrike> currStrike = fStrikeCache->getStrike(strike->getDescriptor());
    for (const auto& mask : masks) {
        run->appendSourceSpaceGlyph(
                currStrike, *mask.glyph, mask.position, cacheToSourceScale);
    }
}

void GrTextBlob::processSourceFallback(SkSpan<const SkGlyphPos> masks,
                                       SkStrikeInterface* strike, SkScalar cacheToSourceScale,
                                       bool hasW) {
    Run* run = this->currentRun();

    auto subRun = run->initARGBFallback();
    sk_sp<GrTextStrike> grStrike =
            fStrikeCache->getStrike(strike->getDescriptor());
    subRun->setStrike(grStrike);
    subRun->setHasWCoord(hasW);

    this->setHasBitmap();
    run->setupFont(strike->strikeSpec());
    for (const auto& mask : masks) {
        run->appendSourceSpaceGlyph
                (grStrike, *mask.glyph, mask.position, cacheToSourceScale);
    }
}

void GrTextBlob::processDeviceFallback(SkSpan<const SkGlyphPos> masks,
                                       SkStrikeInterface* strike) {
    Run* run = this->currentRun();
    this->setHasBitmap();
    sk_sp<GrTextStrike> grStrike = fStrikeCache->getStrike(strike->getDescriptor());
    auto subRun = run->initARGBFallback();
    run->setupFont(strike->strikeSpec());
    subRun->setStrike(grStrike);
    for (const auto& mask : masks) {
        run->appendDeviceSpaceGlyph(grStrike, *mask.glyph, mask.position);
    }
}

#if GR_TEST_UTILS

#include "GrRenderTargetContext.h"
#include "GrRecordingContextPriv.h"

std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrRecordingContext* context,
                                                              GrTextContext* textContext,
                                                              GrRenderTargetContext* rtc,
                                                              const SkPaint& skPaint,
                                                              const SkFont& font,
                                                              const SkMatrix& viewMatrix,
                                                              const char* text,
                                                              int x,
                                                              int y) {
    auto direct = context->priv().asDirectContext();
    if (!direct) {
        return nullptr;
    }

    auto strikeCache = direct->priv().getGrStrikeCache();

    static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);

    size_t textLen = (int)strlen(text);

    SkPMColor4f filteredColor = generate_filtered_color(skPaint, rtc->colorSpaceInfo());
    GrColor color = filteredColor.toBytes_RGBA();

    auto origin = SkPoint::Make(x, y);
    SkGlyphRunBuilder builder;
    builder.drawTextUTF8(skPaint, font, text, textLen, origin);

    auto glyphRunList = builder.useGlyphRunList();
    sk_sp<GrTextBlob> blob;
    if (!glyphRunList.empty()) {
        blob = direct->priv().getTextBlobCache()->makeBlob(glyphRunList, color, strikeCache);
        // Use the text and textLen below, because we don't want to mess with the paint.
        SkScalerContextFlags scalerContextFlags =
                ComputeScalerContextFlags(rtc->colorSpaceInfo());
        blob->generateFromGlyphRunList(
                *context->priv().caps()->shaderCaps(), textContext->fOptions,
                skPaint, scalerContextFlags, viewMatrix, surfaceProps,
                glyphRunList, rtc->textTarget()->glyphPainter());
    }

    return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, skPaint, filteredColor, surfaceProps,
                             textContext->dfAdjustTable(), rtc->textTarget());
}

#endif  // GR_TEST_UTILS
#endif  // SK_SUPPORT_GPU

SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, int size)
        : fPainter{painter} {
    SkASSERT(size >= 0);
    if (fPainter->fMaxRunSize < size) {
        fPainter->fMaxRunSize = size;

        fPainter->fPositions.reset(size);
        fPainter->fGlyphPos.reset(size);
    }
}

SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() {
    fPainter->fPaths.clear();
    fPainter->fARGBGlyphsIDs.clear();
    fPainter->fARGBPositions.clear();

    if (fPainter->fMaxRunSize > 200) {
        fPainter->fMaxRunSize = 0;
        fPainter->fPositions.reset();
        fPainter->fGlyphPos.reset();
        fPainter->fPaths.shrink_to_fit();
        fPainter->fARGBGlyphsIDs.shrink_to_fit();
        fPainter->fARGBPositions.shrink_to_fit();
    }
}