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

#ifndef SkFindAndPositionGlyph_DEFINED
#define SkFindAndPositionGlyph_DEFINED

#include "SkAutoKern.h"
#include "SkGlyph.h"
#include "SkGlyphCache.h"
#include "SkPaint.h"
#include "SkTemplates.h"
#include "SkUtils.h"
#include <utility>

// Calculate a type with the same size as the max of all the Ts.
// This must be top level because the is no specialization of inner classes.
template<typename... Ts> struct SkMaxSizeOf;

template<>
struct SkMaxSizeOf<> {
    static const size_t value = 0;
};

template<typename H, typename... Ts>
struct SkMaxSizeOf<H, Ts...> {
    static const size_t value =
        sizeof(H) >= SkMaxSizeOf<Ts...>::value ? sizeof(H) : SkMaxSizeOf<Ts...>::value;
};


// This is a temporary helper function to work around a bug in the code generation
// for aarch64 (arm) on GCC 4.9. This bug does not show up on other platforms, so it
// seems to be an aarch64 backend problem.
//
// GCC 4.9 on ARM64 does not generate the proper constructor code for PositionReader or
// GlyphFindAndPlace. The vtable is not set properly without adding the fixme code.
// The implementation is in SkDraw.cpp.
extern void FixGCC49Arm64Bug(int v);

class SkFindAndPlaceGlyph {
public:
    template<typename ProcessOneGlyph>
    static void ProcessText(
        SkPaint::TextEncoding, const char text[], size_t byteLength,
        SkPoint offset, const SkMatrix& matrix, SkPaint::Align textAlignment,
        SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph);
    // ProcessPosText handles all cases for finding and positioning glyphs. It has a very large
    // multiplicity. It figures out the glyph, position and rounding and pass those parameters to
    // processOneGlyph.
    //
    // The routine processOneGlyph passed in by the client has the following signature:
    // void f(const SkGlyph& glyph, SkPoint position, SkPoint rounding);
    //
    // * Sub-pixel positioning (2) - use sub-pixel positioning.
    // * Text alignment (3) - text alignment with respect to the glyph's width.
    // * Matrix type (3) - special cases for translation and X-coordinate scaling.
    // * Components per position (2) - the positions vector can have a common Y with different
    //   Xs, or XY-pairs.
    // * Axis Alignment (for sub-pixel positioning) (3) - when using sub-pixel positioning, round
    //   to a whole coordinate instead of using sub-pixel positioning.
    // The number of variations is 108 for sub-pixel and 36 for full-pixel.
    // This routine handles all of them using inline polymorphic variable (no heap allocation).
    template<typename ProcessOneGlyph>
    static void ProcessPosText(
        SkPaint::TextEncoding, const char text[], size_t byteLength,
        SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
        SkPaint::Align textAlignment,
        SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph);

private:
    // UntaggedVariant is a pile of memory that can hold one of the Ts. It provides a way
    // to initialize that memory in a typesafe way.
    template<typename... Ts>
    class UntaggedVariant {
    public:
        UntaggedVariant() { }

        ~UntaggedVariant() { }
        UntaggedVariant(const UntaggedVariant&) = delete;
        UntaggedVariant& operator=(const UntaggedVariant&) = delete;
        UntaggedVariant(UntaggedVariant&&) = delete;
        UntaggedVariant& operator=(UntaggedVariant&&) = delete;

        template<typename Variant, typename... Args>
        void initialize(Args&&... args) {
            SkASSERT(sizeof(Variant) <= sizeof(fSpace));
        #if defined(_MSC_VER) && _MSC_VER < 1900
            #define alignof __alignof
        #endif
            SkASSERT(alignof(Variant) <= alignof(Space));
            new(&fSpace) Variant(std::forward<Args>(args)...);
        }

    private:
        typedef SkAlignedSStorage<SkMaxSizeOf<Ts...>::value> Space;
        Space fSpace;
    };

    // PolymorphicVariant holds subclasses of Base without slicing. Ts must be subclasses of Base.
    template<typename Base, typename... Ts>
    class PolymorphicVariant {
    public:
        typedef UntaggedVariant<Ts...> Variants;

        template<typename Initializer>
        PolymorphicVariant(Initializer&& initializer) {
            initializer(&fVariants);
        }
        ~PolymorphicVariant() { get()->~Base(); }
        Base* get() const { return reinterpret_cast<Base*>(&fVariants); }
        Base* operator->() const { return get(); }
        Base& operator*() const { return *get(); }

    private:
        mutable Variants fVariants;
    };

    // GlyphFinderInterface is the polymorphic base for classes that parse a stream of chars into
    // the right UniChar (or GlyphID) and lookup up the glyph on the cache. The concrete
    // implementations are: Utf8GlyphFinder, Utf16GlyphFinder, Utf32GlyphFinder,
    // and GlyphIdGlyphFinder.
    class GlyphFinderInterface {
    public:
        virtual ~GlyphFinderInterface() {}
        virtual const SkGlyph& lookupGlyph(const char** text) = 0;
        virtual const SkGlyph& lookupGlyphXY(const char** text, SkFixed x, SkFixed y) = 0;
    };

    class UtfNGlyphFinder : public GlyphFinderInterface {
    public:
        UtfNGlyphFinder(SkGlyphCache* cache) : fCache(cache) { SkASSERT(cache != nullptr); }

        const SkGlyph& lookupGlyph(const char** text) override {
            SkASSERT(text != nullptr);
            return fCache->getUnicharMetrics(nextUnichar(text));
        }
        const SkGlyph& lookupGlyphXY(const char** text, SkFixed x, SkFixed y) override {
            SkASSERT(text != nullptr);
            return fCache->getUnicharMetrics(nextUnichar(text), x, y);
        }

    private:
        virtual SkUnichar nextUnichar(const char** text) = 0;
        SkGlyphCache* fCache;
    };

    class Utf8GlyphFinder final : public UtfNGlyphFinder {
    public:
        Utf8GlyphFinder(SkGlyphCache* cache) : UtfNGlyphFinder(cache) { }

    private:
        SkUnichar nextUnichar(const char** text) override { return SkUTF8_NextUnichar(text); }
    };

    class Utf16GlyphFinder final : public UtfNGlyphFinder {
    public:
        Utf16GlyphFinder(SkGlyphCache* cache) : UtfNGlyphFinder(cache) { }

    private:
        SkUnichar nextUnichar(const char** text) override {
            return SkUTF16_NextUnichar((const uint16_t**)text);
        }
    };

    class Utf32GlyphFinder final : public UtfNGlyphFinder {
    public:
        Utf32GlyphFinder(SkGlyphCache* cache) : UtfNGlyphFinder(cache) { }

    private:
        SkUnichar nextUnichar(const char** text) override {
            const int32_t* ptr = *(const int32_t**)text;
            SkUnichar uni = *ptr++;
            *text = (const char*)ptr;
            return uni;
        }
    };

    class GlyphIdGlyphFinder final : public GlyphFinderInterface {
    public:
        GlyphIdGlyphFinder(SkGlyphCache* cache) : fCache(cache) { SkASSERT(cache != nullptr); }

        const SkGlyph& lookupGlyph(const char** text) override {
            return fCache->getGlyphIDMetrics(nextGlyphId(text));
        }
        const SkGlyph& lookupGlyphXY(const char** text, SkFixed x, SkFixed y) override {
            return fCache->getGlyphIDMetrics(nextGlyphId(text), x, y);
        }

    private:
        uint16_t nextGlyphId(const char** text) {
            SkASSERT(text != nullptr);

            const uint16_t* ptr = *(const uint16_t**)text;
            uint16_t glyphID = *ptr;
            ptr += 1;
            *text = (const char*)ptr;
            return glyphID;
        }
        SkGlyphCache* fCache;
    };

    typedef PolymorphicVariant<
        GlyphFinderInterface,
        Utf8GlyphFinder,
        Utf16GlyphFinder,
        Utf32GlyphFinder,
        GlyphIdGlyphFinder> LookupGlyphVariant;

    class LookupGlyph : public LookupGlyphVariant {
    public:
        LookupGlyph(SkPaint::TextEncoding encoding, SkGlyphCache* cache)
            : LookupGlyphVariant(
            [&](LookupGlyphVariant::Variants* to_init) {
                switch(encoding) {
                    case SkPaint::kUTF8_TextEncoding:
                        to_init->initialize<Utf8GlyphFinder>(cache);
                        break;
                    case SkPaint::kUTF16_TextEncoding:
                        to_init->initialize<Utf16GlyphFinder>(cache);
                        break;
                    case SkPaint::kUTF32_TextEncoding:
                        to_init->initialize<Utf32GlyphFinder>(cache);
                        break;
                    case SkPaint::kGlyphID_TextEncoding:
                        to_init->initialize<GlyphIdGlyphFinder>(cache);
                        break;
                }
            }
        ) { }
    };

    // PositionReaderInterface reads a point from the pos vector.
    // * HorizontalPositions - assumes a common Y for many X values.
    // * ArbitraryPositions - a list of (X,Y) pairs.
    class PositionReaderInterface {
    public:
        virtual ~PositionReaderInterface() { }
        virtual SkPoint nextPoint() = 0;
        // This is only here to fix a GCC 4.9 aarch64 code gen bug.
        // See comment at the top of the file.
        virtual int forceUseForBug() = 0;
    };

    class HorizontalPositions final : public PositionReaderInterface {
    public:
        explicit HorizontalPositions(const SkScalar* positions)
            : fPositions(positions) { }

        SkPoint nextPoint() override {
            SkScalar x = *fPositions++;
            return {x, 0};
        }

        int forceUseForBug() override { return 1; }

    private:
        const SkScalar* fPositions;
    };

    class ArbitraryPositions final : public PositionReaderInterface {
    public:
        explicit ArbitraryPositions(const SkScalar* positions)
            : fPositions(positions) { }

        SkPoint nextPoint() override {
            SkPoint to_return{fPositions[0], fPositions[1]};
            fPositions += 2;
            return to_return;
        }

        int forceUseForBug() override { return 2; }

    private:
        const SkScalar* fPositions;
    };

    typedef PolymorphicVariant<PositionReaderInterface, HorizontalPositions, ArbitraryPositions>
        PositionReader;

    // MapperInterface given a point map it through the matrix. There are several shortcut
    // variants.
    // * TranslationMapper - assumes a translation only matrix.
    // * XScaleMapper - assumes an X scaling and a translation.
    // * GeneralMapper - Does all other matricies.
    class MapperInterface {
    public:
        virtual ~MapperInterface() { }

        virtual SkPoint map(SkPoint position) const = 0;
    };

    class TranslationMapper final : public MapperInterface {
    public:
        TranslationMapper(const SkMatrix& matrix, const SkPoint origin)
            : fTranslate(matrix.mapXY(origin.fX, origin.fY)) { }

        SkPoint map(SkPoint position) const override {
            return position + fTranslate;
        }

    private:
        const SkPoint fTranslate;
    };

    class XScaleMapper final : public MapperInterface {
    public:
        XScaleMapper(const SkMatrix& matrix, const SkPoint origin)
            : fTranslate(matrix.mapXY(origin.fX, origin.fY)), fXScale(matrix.getScaleX()) { }

        SkPoint map(SkPoint position) const override {
            return {fXScale * position.fX + fTranslate.fX, fTranslate.fY};
        }

    private:
        const SkPoint fTranslate;
        const SkScalar fXScale;
    };

    // The caller must keep matrix alive while this class is used.
    class GeneralMapper final : public MapperInterface {
    public:
        GeneralMapper(const SkMatrix& matrix, const SkPoint origin)
            : fOrigin(origin), fMatrix(matrix), fMapProc(matrix.getMapXYProc()) { }

        SkPoint map(SkPoint position) const override {
            SkPoint result;
            fMapProc(fMatrix, position.fX + fOrigin.fX, position.fY + fOrigin.fY, &result);
            return result;
        }

    private:
        const SkPoint fOrigin;
        const SkMatrix& fMatrix;
        const SkMatrix::MapXYProc fMapProc;
    };

    typedef PolymorphicVariant<
        MapperInterface, TranslationMapper, XScaleMapper, GeneralMapper> Mapper;

    // TextAlignmentAdjustment handles shifting the glyph based on its width.
    static SkPoint TextAlignmentAdjustment(SkPaint::Align textAlignment, const SkGlyph& glyph) {
        switch (textAlignment) {
            case SkPaint::kLeft_Align:
                return {0.0f, 0.0f};
            case SkPaint::kCenter_Align:
                return {SkFixedToScalar(glyph.fAdvanceX >> 1),
                        SkFixedToScalar(glyph.fAdvanceY >> 1)};
            case SkPaint::kRight_Align:
                return {SkFixedToScalar(glyph.fAdvanceX),
                        SkFixedToScalar(glyph.fAdvanceY)};
        }
        // Even though the entire enum is covered above, MVSC doesn't think so. Make it happy.
        SkFAIL("Should never get here.");
        return {0.0f, 0.0f};
    }

    // The "call" to SkFixedToScalar is actually a macro. It's macros all the way down.
    // Needs to be a macro because you can't have a const float unless you make it constexpr.
    #define kSubpixelRounding (SkFixedToScalar(SkGlyph::kSubpixelRound))

    // The SubpixelPositionRounding function returns a point suitable for rounding a sub-pixel
    // positioned glyph.
    static SkPoint SubpixelPositionRounding(SkAxisAlignment axisAlignment) {
        switch (axisAlignment) {
            case kX_SkAxisAlignment:
                return {kSubpixelRounding, SK_ScalarHalf};
            case kY_SkAxisAlignment:
                return {SK_ScalarHalf, kSubpixelRounding};
            case kNone_SkAxisAlignment:
                return {kSubpixelRounding, kSubpixelRounding};
        }
        SkFAIL("Should not get here.");
        return {0.0f, 0.0f};
    }

    // The SubpixelAlignment function produces a suitable position for the glyph cache to
    // produce the correct sub-pixel alignment. If a position is aligned with an axis a shortcut
    // of 0 is used for the sub-pixel position.
    static SkIPoint SubpixelAlignment(SkAxisAlignment axisAlignment, SkPoint position) {
        switch (axisAlignment) {
            case kX_SkAxisAlignment:
                return {SkScalarToFixed(position.fX + kSubpixelRounding), 0};
            case kY_SkAxisAlignment:
                return {0, SkScalarToFixed(position.fY + kSubpixelRounding)};
            case kNone_SkAxisAlignment:
                return {SkScalarToFixed(position.fX + kSubpixelRounding),
                        SkScalarToFixed(position.fY + kSubpixelRounding)};
        }
        SkFAIL("Should not get here.");
        return {0, 0};
    }

    #undef kSubpixelRounding

    // GlyphFindAndPlaceInterface given the text and position finds the correct glyph and does
    // glyph specific position adjustment. The findAndPositionGlyph method takes text and
    // position and calls processOneGlyph with the correct glyph, final position and rounding
    // terms. The final position is not rounded yet and is the responsibility of processOneGlyph.
    template<typename ProcessOneGlyph>
    class GlyphFindAndPlaceInterface : SkNoncopyable {
    public:
        virtual ~GlyphFindAndPlaceInterface() { };

        // findAndPositionGlyph calculates the position of the glyph, finds the glyph, and
        // returns the position of where the next glyph will be using the glyph's advance and
        // possibly kerning. The returned position is used by drawText, but ignored by drawPosText.
        // The compiler should prune all this calculation if the return value is not used.
        //
        // This should be a pure virtual, but some versions of GCC <= 4.8 have a bug that causes a
        // compile error.
        // See GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60277
        virtual SkPoint findAndPositionGlyph(
            const char** text, SkPoint position, ProcessOneGlyph&& processOneGlyph) {
            SkFAIL("Should never get here.");
            return {0.0f, 0.0f};
        };
    };

    // GlyphFindAndPlaceSubpixel handles finding and placing glyphs when sub-pixel positioning is
    // requested. After it has found and placed the glyph it calls the templated function
    // ProcessOneGlyph in order to actually perform an action.
    template<typename ProcessOneGlyph, SkPaint::Align kTextAlignment,
             SkAxisAlignment kAxisAlignment>
    class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
    public:
        GlyphFindAndPlaceSubpixel(LookupGlyph& glyphFinder)
            : fGlyphFinder(glyphFinder) {
            FixGCC49Arm64Bug(1);
        }

        SkPoint findAndPositionGlyph(
            const char** text, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
            SkPoint finalPosition = position;
            if (kTextAlignment != SkPaint::kLeft_Align) {
                // Get the width of an un-sub-pixel positioned glyph for calculating the
                // alignment. This is not needed for kLeftAlign because its adjustment is
                // always {0, 0}.
                const char* tempText = *text;
                const SkGlyph &metricGlyph = fGlyphFinder->lookupGlyph(&tempText);

                if (metricGlyph.fWidth <= 0) {
                    // Exiting early, be sure to update text pointer.
                    *text = tempText;
                    return finalPosition + SkPoint{SkFixedToScalar(metricGlyph.fAdvanceX),
                                                   SkFixedToScalar(metricGlyph.fAdvanceY)};
                }

                // Adjust the final position by the alignment adjustment.
                finalPosition -= TextAlignmentAdjustment(kTextAlignment, metricGlyph);
            }

            // Find the glyph.
            SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, finalPosition);
            const SkGlyph& renderGlyph =
                fGlyphFinder->lookupGlyphXY(text, lookupPosition.fX, lookupPosition.fY);

            // If the glyph has no width (no pixels) then don't bother processing it.
            if (renderGlyph.fWidth > 0) {
                processOneGlyph(renderGlyph, finalPosition,
                                SubpixelPositionRounding(kAxisAlignment));
            }
            return finalPosition + SkPoint{SkFixedToScalar(renderGlyph.fAdvanceX),
                                           SkFixedToScalar(renderGlyph.fAdvanceY)};
        }

    private:
        LookupGlyph& fGlyphFinder;
    };

    enum SelectKerning {
        kNoKerning = false,
        kUseKerning = true
    };

    // GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel
    // positioning is requested. The kUseKerning argument should be true for drawText, and false
    // for drawPosText.
    template<typename ProcessOneGlyph, SkPaint::Align kTextAlignment, SelectKerning kUseKerning>
    class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
    public:
        GlyphFindAndPlaceFullPixel(LookupGlyph& glyphFinder)
            : fGlyphFinder(glyphFinder) {
            FixGCC49Arm64Bug(2);
            // Kerning can only be used with SkPaint::kLeft_Align
            static_assert(!kUseKerning || SkPaint::kLeft_Align == kTextAlignment,
                          "Kerning can only be used with left aligned text.");
        }

        SkPoint findAndPositionGlyph(
            const char** text, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
            SkPoint finalPosition = position;
            const SkGlyph& glyph = fGlyphFinder->lookupGlyph(text);
            if (kUseKerning) {
                finalPosition += {SkFixedToScalar(fAutoKern.adjust(glyph)), 0.0f};
            }
            if (glyph.fWidth > 0) {
                finalPosition -= TextAlignmentAdjustment(kTextAlignment, glyph);
                processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf});
            }
            return finalPosition + SkPoint{SkFixedToScalar(glyph.fAdvanceX),
                                           SkFixedToScalar(glyph.fAdvanceY)};
        }

    private:
        LookupGlyph& fGlyphFinder;

        SkAutoKern fAutoKern;
    };

    // GlyphFindAndPlace is a large variant that encapsulates the multiple types of finding and
    // placing a glyph. There are three factors that go into the different factors.
    // * Is sub-pixel positioned - a boolean that says whether to use sub-pixel positioning.
    // * Text alignment - indicates if the glyph should be placed to the right, centered or left
    //   of a given position.
    // * Axis alignment - indicates if the glyphs final sub-pixel position should be rounded to a
    //   whole pixel if the glyph is aligned with an axis. This is only used for sub-pixel
    //   positioning and allows the baseline to look crisp.
    template<typename ProcessOneGlyph>
    using GlyphFindAndPlace = PolymorphicVariant<
        GlyphFindAndPlaceInterface<ProcessOneGlyph>,
        // Subpixel
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kLeft_Align,   kNone_SkAxisAlignment>,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kLeft_Align,   kX_SkAxisAlignment   >,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kLeft_Align,   kY_SkAxisAlignment   >,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kCenter_Align, kNone_SkAxisAlignment>,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kCenter_Align, kX_SkAxisAlignment   >,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kCenter_Align, kY_SkAxisAlignment   >,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kRight_Align,  kNone_SkAxisAlignment>,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kRight_Align,  kX_SkAxisAlignment   >,
        GlyphFindAndPlaceSubpixel<ProcessOneGlyph,  SkPaint::kRight_Align,  kY_SkAxisAlignment   >,
        // Full pixel
        GlyphFindAndPlaceFullPixel<ProcessOneGlyph, SkPaint::kLeft_Align,   kNoKerning>,
        GlyphFindAndPlaceFullPixel<ProcessOneGlyph, SkPaint::kCenter_Align, kNoKerning>,
        GlyphFindAndPlaceFullPixel<ProcessOneGlyph, SkPaint::kRight_Align,  kNoKerning>
    >;

    // InitSubpixel is a helper function for initializing all the variants of
    // GlyphFindAndPlaceSubpixel.
    template<typename ProcessOneGlyph, SkPaint::Align kTextAlignment>
    static void InitSubpixel(
        typename GlyphFindAndPlace<ProcessOneGlyph>::Variants* to_init,
        SkAxisAlignment axisAlignment,
        LookupGlyph& glyphFinder) {
        switch (axisAlignment) {
            case kX_SkAxisAlignment:
                to_init->template initialize<GlyphFindAndPlaceSubpixel<
                    ProcessOneGlyph, kTextAlignment, kX_SkAxisAlignment>>(glyphFinder);
                break;
            case kNone_SkAxisAlignment:
                to_init->template initialize<GlyphFindAndPlaceSubpixel<
                    ProcessOneGlyph, kTextAlignment, kNone_SkAxisAlignment>>(glyphFinder);
                break;
            case kY_SkAxisAlignment:
                to_init->template initialize<GlyphFindAndPlaceSubpixel<
                    ProcessOneGlyph, kTextAlignment, kY_SkAxisAlignment>>(glyphFinder);
                break;
        }
    }

    static SkPoint MeasureText(LookupGlyph& glyphFinder, const char text[], size_t byteLength) {
        SkFixed     x = 0, y = 0;
        const char* stop = text + byteLength;

        SkAutoKern  autokern;

        while (text < stop) {
            // don't need x, y here, since all subpixel variants will have the
            // same advance
            const SkGlyph& glyph = glyphFinder->lookupGlyph(&text);

            x += autokern.adjust(glyph) + glyph.fAdvanceX;
            y += glyph.fAdvanceY;
        }
        SkASSERT(text == stop);
        return {SkFixedToScalar(x), SkFixedToScalar(y)};
    }
};

template<typename ProcessOneGlyph>
inline void SkFindAndPlaceGlyph::ProcessPosText(
    SkPaint::TextEncoding textEncoding, const char text[], size_t byteLength,
    SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
    SkPaint::Align textAlignment,
    SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph) {

    SkAxisAlignment axisAlignment = SkComputeAxisAlignmentForHText(matrix);
    uint32_t mtype = matrix.getType();

    LookupGlyph glyphFinder(textEncoding, cache);

    // Specialized code for handling the most common case for blink. The while loop is totally
    // de-virtualized.
    if (scalarsPerPosition == 1
        && textAlignment == SkPaint::kLeft_Align
        && axisAlignment == kX_SkAxisAlignment
        && cache->isSubpixel()
        && mtype <= SkMatrix::kTranslate_Mask) {
        typedef GlyphFindAndPlaceSubpixel<
            ProcessOneGlyph, SkPaint::kLeft_Align, kX_SkAxisAlignment> Positioner;
        HorizontalPositions positions{pos};
        TranslationMapper mapper{matrix, offset};
        Positioner positioner(glyphFinder);
        const char* cursor = text;
        const char* stop = text + byteLength;
        while (cursor < stop) {
            SkPoint mappedPoint = mapper.TranslationMapper::map(
                positions.HorizontalPositions::nextPoint());
            positioner.Positioner::findAndPositionGlyph(
                &cursor, mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
        }
        return;
    }

    PositionReader positionReader{
        [&](PositionReader::Variants* to_init) {
            if (2 == scalarsPerPosition) {
                to_init->initialize<ArbitraryPositions>(pos);
            } else {
                to_init->initialize<HorizontalPositions>(pos);
            }
            positionReader->forceUseForBug();
        }
    };

    Mapper mapper{
        [&](Mapper::Variants* to_init) {
            if (mtype & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)
                || scalarsPerPosition == 2) {
                to_init->initialize<GeneralMapper>(matrix, offset);
            } else if (mtype & SkMatrix::kScale_Mask) {
                to_init->initialize<XScaleMapper>(matrix, offset);
            } else {
                to_init->initialize<TranslationMapper>(matrix, offset);
            }
        }
    };

    GlyphFindAndPlace<ProcessOneGlyph> findAndPosition {
        [&](typename GlyphFindAndPlace<ProcessOneGlyph>::Variants* to_init) {
            if (cache->isSubpixel()) {
                switch (textAlignment) {
                    case SkPaint::kLeft_Align:
                        InitSubpixel<ProcessOneGlyph, SkPaint::kLeft_Align>(
                            to_init, axisAlignment, glyphFinder);
                        break;
                    case SkPaint::kCenter_Align:
                        InitSubpixel<ProcessOneGlyph, SkPaint::kCenter_Align>(
                            to_init, axisAlignment, glyphFinder);
                        break;
                    case SkPaint::kRight_Align:
                        InitSubpixel<ProcessOneGlyph, SkPaint::kRight_Align>(
                            to_init, axisAlignment, glyphFinder);
                        break;
                }
            } else {
                switch (textAlignment) {
                    case SkPaint::kLeft_Align:
                        to_init->template initialize<
                            GlyphFindAndPlaceFullPixel<ProcessOneGlyph,
                            SkPaint::kLeft_Align, kNoKerning>>(glyphFinder);
                        break;
                    case SkPaint::kCenter_Align:
                        to_init->template initialize<
                            GlyphFindAndPlaceFullPixel<ProcessOneGlyph,
                            SkPaint::kCenter_Align, kNoKerning>>(glyphFinder);
                        break;
                    case SkPaint::kRight_Align:
                        to_init->template initialize<
                            GlyphFindAndPlaceFullPixel<ProcessOneGlyph,
                            SkPaint::kRight_Align, kNoKerning>>(glyphFinder);
                        break;
                }
            }
        }
    };

    const char* stop = text + byteLength;
    while (text < stop) {
        SkPoint mappedPoint = mapper->map(positionReader->nextPoint());
        findAndPosition->findAndPositionGlyph(
            &text, mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
    }
}

template<typename ProcessOneGlyph>
inline void SkFindAndPlaceGlyph::ProcessText(
    SkPaint::TextEncoding textEncoding, const char text[], size_t byteLength,
    SkPoint offset, const SkMatrix& matrix, SkPaint::Align textAlignment,
    SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph) {

    // transform the starting point
    matrix.mapPoints(&offset, 1);

    LookupGlyph glyphFinder(textEncoding, cache);

    // need to measure first
    if (textAlignment != SkPaint::kLeft_Align) {
        SkVector stop = MeasureText(glyphFinder, text, byteLength);

        if (textAlignment == SkPaint::kCenter_Align) {
            stop *= SK_ScalarHalf;
        }
        offset -= stop;
    }

    GlyphFindAndPlace<ProcessOneGlyph> findAndPosition{
        [&](typename GlyphFindAndPlace<ProcessOneGlyph>::Variants* to_init) {
            if (cache->isSubpixel()) {
                SkAxisAlignment axisAlignment = SkComputeAxisAlignmentForHText(matrix);
                InitSubpixel<ProcessOneGlyph, SkPaint::kLeft_Align>(
                    to_init, axisAlignment, glyphFinder);
            } else {
                to_init->template initialize<
                    GlyphFindAndPlaceFullPixel<
                        ProcessOneGlyph, SkPaint::kLeft_Align, kUseKerning>>(glyphFinder);
            }
        }
    };

    const char* stop = text + byteLength;
    SkPoint current = offset;
    while (text < stop) {
        current =
            findAndPosition->findAndPositionGlyph(
                &text, current, std::forward<ProcessOneGlyph>(processOneGlyph));

    }
}

#endif  // SkFindAndPositionGlyph_DEFINED