Extract the glyph picking and placing code.
authorherb <herb@google.com>
Mon, 9 Nov 2015 16:51:56 +0000 (08:51 -0800)
committerCommit bot <commit-bot@chromium.org>
Mon, 9 Nov 2015 16:51:56 +0000 (08:51 -0800)
There is a common piece of code which finds and positions glyphs and is used in four places. Some places copied the code, some places added callbacks. Here is a list of code:

SkDraw::drawPosText
GrAtlasTextContext::internalDrawBMPPosText
GrAtlasTextContext::internalDrawDFPosText
SkXPSDevice::drawPosText

This only extracts the code from SkDraw::drawPosText. I would like to use it in the other three places. I think this code is performance neutral.

BUG=skia:

Review URL: https://codereview.chromium.org/1420973005

src/core/SkDraw.cpp

index d8d6fcf..f884d3b 100644 (file)
@@ -14,6 +14,7 @@
 #include "SkDeviceLooper.h"
 #include "SkFixed.h"
 #include "SkMaskFilter.h"
+#include "SkMatrix.h"
 #include "SkPaint.h"
 #include "SkPathEffect.h"
 #include "SkRasterClip.h"
 #include "SkString.h"
 #include "SkStroke.h"
 #include "SkStrokeRec.h"
+#include "SkTemplates.h"
 #include "SkTextMapStateProc.h"
 #include "SkTLazy.h"
+#include "SkUtility.h"
 #include "SkUtils.h"
 #include "SkVertState.h"
 
@@ -1439,7 +1442,7 @@ void SkDraw::drawText_asPaths(const char text[], size_t byteLength,
 #pragma warning ( disable : 4701 )
 #endif
 
-//////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////
 
 static void D1G_RectClip(const SkDraw1Glyph& state, Sk48Dot16 fx, Sk48Dot16 fy, const SkGlyph& glyph) {
     // Prevent glyphs from being drawn outside of or straddling the edge of device space.
@@ -1726,6 +1729,491 @@ void SkDraw::drawPosText_asPaths(const char text[], size_t byteLength,
     }
 }
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Calculate a type with the same size as the max of all the Ts.
+template <typename... Ts> struct MaxSizeOf;
+
+template <> struct MaxSizeOf<> { static const size_t value = 0; };
+
+template <typename H, typename... Ts> struct MaxSizeOf<H, Ts...> {
+    static const size_t value =
+        sizeof(H) >= MaxSizeOf<Ts...>::value ? sizeof(H) : MaxSizeOf<Ts...>::value;
+};
+
+// 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(skstd::forward<Args>(args)...);
+    }
+
+private:
+    typedef SkAlignedSStorage<MaxSizeOf<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;
+};
+
+// 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 : SkNoncopyable {
+public:
+    virtual ~PositionReaderInterface() { }
+    virtual SkPoint nextPoint() = 0;
+};
+
+class HorizontalPositions final : public PositionReaderInterface {
+public:
+    HorizontalPositions(const SkScalar* positions)
+        : fPositions(positions) { }
+
+    SkPoint nextPoint() override {
+        SkScalar x = *fPositions++;
+        return {x, 0};
+    }
+private:
+    const SkScalar* fPositions;
+};
+
+class ArbitraryPositions final : public PositionReaderInterface {
+public:
+    ArbitraryPositions(const SkScalar* positions)
+        : fPositions(positions) { }
+    SkPoint nextPoint() override {
+        SkPoint to_return {fPositions[0], fPositions[1]};
+        fPositions += 2;
+        return to_return;
+    }
+
+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 : SkNoncopyable {
+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;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Text alignment handles shifting the glyph based on its width.
+static SkPoint text_alignment_adjustment(SkPaint::Align textAlignment, const SkGlyph& glyph) {
+    switch (textAlignment) {
+        case SkPaint::kLeft_Align:
+            return {0.0f, 0.0f};
+            break;
+        case SkPaint::kCenter_Align:
+            return {SkFixedToScalar(glyph.fAdvanceX >> 1),
+                    SkFixedToScalar(glyph.fAdvanceY >> 1)};
+            break;
+        case SkPaint::kRight_Align:
+            return {SkFixedToScalar(glyph.fAdvanceX),
+                    SkFixedToScalar(glyph.fAdvanceY)};
+            break;
+    }
+    // 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.
+static const SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Functions for handling sub-pixel aligned positions.
+// The subpixel_position_rounding function returns a point suitable for rounding a sub-pixel
+// positioned glyph.
+static SkPoint subpixel_position_rounding(SkAxisAlignment axisAlignment) {
+    switch (axisAlignment) {
+        case kX_SkAxisAlignment:
+            return {SkFixedToScalar(SkGlyph::kSubpixelRound), 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 subpixel_position_alignment 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 subpixel_position_alignment(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};
+}
+
+// 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:
+    struct Result {
+        const SkGlyph* fGlyph;
+        Sk48Dot16 fX;
+        Sk48Dot16 fY;
+    };
+    virtual ~GlyphFindAndPlaceInterface() { };
+    // 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 void findAndPositionGlyph(const char** text, SkPoint position,
+                                      ProcessOneGlyph&& processOneGlyph) {};
+};
+
+// GlyphFindAndPlaceForSubpixel 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 GlyphFindAndPlaceForSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
+public:
+    GlyphFindAndPlaceForSubpixel(SkGlyphCache* cache, SkDrawCacheProc glyphCacheProc)
+        : fCache(cache)
+        , fGlyphCacheProc(glyphCacheProc) {
+    }
+    void 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 = fGlyphCacheProc(fCache, &tempText, 0, 0);
+
+            if (metricGlyph.fWidth <= 0) {
+                return;
+            }
+
+            // Adjust the final position by the alignment adjustment.
+            finalPosition -= text_alignment_adjustment(kTextAlignment, metricGlyph);
+        }
+
+        // Find the glyph.
+        SkIPoint lookupPosition = subpixel_position_alignment(kAxisAlignment, finalPosition);
+        const SkGlyph& renderGlyph = fGlyphCacheProc(
+            fCache, 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, subpixel_position_rounding(kAxisAlignment));
+        }
+    }
+
+private:
+    SkGlyphCache* const fCache;
+    SkDrawCacheProc fGlyphCacheProc;
+};
+
+// GlyphFindAndPlaceForFullPixel handles finding and placing glyphs when no sub-pixel positioning
+// is requested.
+template <typename ProcessOneGlyph, SkPaint::Align kTextAlignment>
+class GlyphFindAndPlaceForFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
+public:
+    GlyphFindAndPlaceForFullPixel(SkGlyphCache* cache, SkDrawCacheProc glyphCacheProc)
+        : fCache(cache)
+        , fGlyphCacheProc(glyphCacheProc) { }
+    void findAndPositionGlyph(const char** text, SkPoint position,
+                              ProcessOneGlyph&& processOneGlyph) override {
+        SkPoint finalPosition = position;
+        const SkGlyph& glyph = fGlyphCacheProc(fCache, text, 0, 0);
+        if (glyph.fWidth <= 0) {
+            return;
+        }
+        finalPosition -= text_alignment_adjustment(kTextAlignment, glyph);
+        processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf});
+    }
+
+private:
+    SkGlyphCache* const fCache;
+    SkDrawCacheProc fGlyphCacheProc;
+};
+
+// 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
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kLeft_Align,   kNone_SkAxisAlignment>,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kLeft_Align,   kX_SkAxisAlignment   >,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kLeft_Align,   kY_SkAxisAlignment   >,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kCenter_Align, kNone_SkAxisAlignment>,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kCenter_Align, kX_SkAxisAlignment   >,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kCenter_Align, kY_SkAxisAlignment   >,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kRight_Align,  kNone_SkAxisAlignment>,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kRight_Align,  kX_SkAxisAlignment   >,
+        GlyphFindAndPlaceForSubpixel<ProcessOneGlyph, SkPaint::kRight_Align,  kY_SkAxisAlignment   >,
+        // Full pixel
+        GlyphFindAndPlaceForFullPixel<ProcessOneGlyph, SkPaint::kLeft_Align  >,
+        GlyphFindAndPlaceForFullPixel<ProcessOneGlyph, SkPaint::kCenter_Align>,
+        GlyphFindAndPlaceForFullPixel<ProcessOneGlyph, SkPaint::kRight_Align >
+    >;
+
+// init_subpixel is a helper function for initializing all the variants of
+// GlyphFindAndPlaceForSubpixel.
+template <typename ProcessOneGlyph, SkPaint::Align kTextAlignment>
+static void init_subpixel(
+    typename GlyphFindAndPlace<ProcessOneGlyph>::Variants* to_init,
+    SkAxisAlignment axisAlignment,
+    SkGlyphCache* cache,
+    SkDrawCacheProc glyphCacheProc) {
+    switch (axisAlignment) {
+        case kX_SkAxisAlignment:
+            to_init->template initialize<GlyphFindAndPlaceForSubpixel<
+                ProcessOneGlyph, kTextAlignment, kX_SkAxisAlignment>>(
+                cache, glyphCacheProc);
+            break;
+        case kNone_SkAxisAlignment:
+            to_init->template initialize<GlyphFindAndPlaceForSubpixel<
+                ProcessOneGlyph, kTextAlignment, kNone_SkAxisAlignment>>(
+                cache, glyphCacheProc);
+            break;
+        case kY_SkAxisAlignment:
+            to_init->template initialize<GlyphFindAndPlaceForSubpixel<
+                ProcessOneGlyph, kTextAlignment, kY_SkAxisAlignment>>(
+                cache, glyphCacheProc);
+            break;
+    }
+}
+
+// specialized_process_pos_text is a version of ProcessPosText that de-virtualizes the different
+// components used. It returns true if it can handle the situation, otherwise it returns false.
+// This allows greater inlining freedom to the compiler. Currently, there is only one specialized
+// variant: sub-pixel position, left-aligned, x-axis-aligned, translation, and one scalar per
+// position entry.
+// * This is by far the most common type of text Blink draws.
+template <typename ProcessOneGlyph>
+static bool specialized_process_pos_text(const char* const text, size_t byteLength,
+                                         const SkPoint& offset, const SkMatrix& matrix,
+                                         const SkScalar pos[], int scalarsPerPosition,
+                                         SkPaint::Align textAlignment,
+                                         SkDrawCacheProc& glyphCacheProc,
+                                         SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph) {
+    SkAxisAlignment axisAlignment = SkComputeAxisAlignmentForHText(matrix);
+    uint32_t mtype = matrix.getType();
+    if (scalarsPerPosition == 1
+        && textAlignment == SkPaint::kLeft_Align
+        && axisAlignment == kX_SkAxisAlignment
+        && cache->isSubpixel()
+        && mtype <= SkMatrix::kTranslate_Mask) {
+        typedef GlyphFindAndPlaceForSubpixel<
+            ProcessOneGlyph, SkPaint::kLeft_Align, kX_SkAxisAlignment> Positioner;
+        HorizontalPositions positions{pos};
+        TranslationMapper mapper{matrix, offset};
+        Positioner positioner(cache, glyphCacheProc);
+        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, skstd::forward<ProcessOneGlyph>(processOneGlyph));
+        }
+        return true;
+    }
+    return false;
+}
+
+// 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 process_pos_text(const char text[], size_t byteLength,
+                             const SkPoint& offset, const SkMatrix& matrix,
+                             const SkScalar pos[], int scalarsPerPosition,
+                             SkPaint::Align textAlignment, SkDrawCacheProc& glyphCacheProc,
+                             SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph) {
+
+    PositionReader positionReader {
+        [&](PositionReader::Variants* to_init) {
+            if (2 == scalarsPerPosition) {
+                to_init->initialize<ArbitraryPositions>(pos);
+            } else {
+                to_init->initialize<HorizontalPositions>(pos);
+            }
+        }
+    };
+
+    Mapper mapper {
+        [&] (Mapper::Variants* to_init) {
+            uint32_t mtype = matrix.getType();
+            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()) {
+                SkAxisAlignment axisAlignment = SkComputeAxisAlignmentForHText(matrix);
+                switch (textAlignment) {
+                    case SkPaint::kLeft_Align:
+                        init_subpixel<ProcessOneGlyph, SkPaint::kLeft_Align>(
+                            to_init, axisAlignment, cache, glyphCacheProc);
+                        break;
+                    case SkPaint::kCenter_Align:
+                        init_subpixel<ProcessOneGlyph, SkPaint::kCenter_Align>(
+                            to_init, axisAlignment, cache, glyphCacheProc);
+                        break;
+                    case SkPaint::kRight_Align:
+                        init_subpixel<ProcessOneGlyph, SkPaint::kRight_Align>(
+                            to_init, axisAlignment, cache, glyphCacheProc);
+                        break;
+                }
+            } else {
+                switch (textAlignment) {
+                    case SkPaint::kLeft_Align:
+                        to_init->template initialize<GlyphFindAndPlaceForFullPixel<ProcessOneGlyph,
+                            SkPaint::kLeft_Align>>(
+                            cache, glyphCacheProc);
+                        break;
+                    case SkPaint::kCenter_Align:
+                        to_init->template initialize<GlyphFindAndPlaceForFullPixel<ProcessOneGlyph,
+                            SkPaint::kCenter_Align>>(
+                            cache, glyphCacheProc);
+                        break;
+                    case SkPaint::kRight_Align:
+                        to_init->template initialize<GlyphFindAndPlaceForFullPixel<ProcessOneGlyph,
+                            SkPaint::kRight_Align>>(
+                            cache, glyphCacheProc);
+                        break;
+                }
+            }
+        }
+    };
+
+    const char* stop = text + byteLength;
+    while (text < stop) {
+        SkPoint mappedPoint = mapper->map(positionReader->nextPoint());
+        findAndPosition->findAndPositionGlyph(
+            &text, mappedPoint, skstd::forward<ProcessOneGlyph>(processOneGlyph));
+    }
+}
+
 void SkDraw::drawPosText(const char text[], size_t byteLength,
                          const SkScalar pos[], int scalarsPerPosition,
                          const SkPoint& offset, const SkPaint& paint) const {
@@ -1760,108 +2248,20 @@ void SkDraw::drawPosText(const char text[], size_t byteLength,
         }
     }
 
-    const char*        stop = text + byteLength;
-    SkTextAlignProc    alignProc(paint.getTextAlign());
     SkDraw1Glyph       d1g;
     SkDraw1Glyph::Proc proc = d1g.init(this, blitter, cache, paint);
-    SkTextMapStateProc tmsProc(*fMatrix, offset, scalarsPerPosition);
-
-    if (cache->isSubpixel()) {
-        // maybe we should skip the rounding if linearText is set
-        SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(*fMatrix);
-
-        SkFixed fxMask = ~0;
-        SkFixed fyMask = ~0;
-        if (kX_SkAxisAlignment == baseline) {
-            fyMask = 0;
-            d1g.fHalfSampleY = SK_ScalarHalf;
-        } else if (kY_SkAxisAlignment == baseline) {
-            fxMask = 0;
-            d1g.fHalfSampleX = SK_ScalarHalf;
-        }
 
-        if (SkPaint::kLeft_Align == paint.getTextAlign()) {
-            while (text < stop) {
-                SkPoint tmsLoc;
-                tmsProc(pos, &tmsLoc);
-
-                Sk48Dot16 fx = SkScalarTo48Dot16(tmsLoc.fX + d1g.fHalfSampleX);
-                Sk48Dot16 fy = SkScalarTo48Dot16(tmsLoc.fY + d1g.fHalfSampleY);
-
-                const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
-
-                if (glyph.fWidth) {
-                    proc(d1g, fx, fy, glyph);
-                }
-                pos += scalarsPerPosition;
-            }
-        } else {
-            while (text < stop) {
-                const char* currentText = text;
-                const SkGlyph& metricGlyph = glyphCacheProc(cache, &text, 0, 0);
-
-                if (metricGlyph.fWidth) {
-                    SkDEBUGCODE(SkFixed prevAdvX = metricGlyph.fAdvanceX;)
-                    SkDEBUGCODE(SkFixed prevAdvY = metricGlyph.fAdvanceY;)
-                    SkPoint tmsLoc;
-                    tmsProc(pos, &tmsLoc);
-
-                    SkPoint alignLoc;
-                    alignProc(tmsLoc, metricGlyph, &alignLoc);
-
-                    Sk48Dot16 fx = SkScalarTo48Dot16(alignLoc.fX + d1g.fHalfSampleX);
-                    Sk48Dot16 fy = SkScalarTo48Dot16(alignLoc.fY + d1g.fHalfSampleY);
-
-                    // have to call again, now that we've been "aligned"
-                    const SkGlyph& glyph = glyphCacheProc(cache, &currentText,
-                                                          fx & fxMask, fy & fyMask);
-                    // the assumption is that the metrics haven't changed
-                    SkASSERT(prevAdvX == glyph.fAdvanceX);
-                    SkASSERT(prevAdvY == glyph.fAdvanceY);
-                    SkASSERT(glyph.fWidth);
-
-                    proc(d1g, fx, fy, glyph);
-                }
-                pos += scalarsPerPosition;
-            }
-        }
-    } else {    // not subpixel
-        if (SkPaint::kLeft_Align == paint.getTextAlign()) {
-            while (text < stop) {
-                // the last 2 parameters are ignored
-                const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
-
-                if (glyph.fWidth) {
-                    SkPoint tmsLoc;
-                    tmsProc(pos, &tmsLoc);
-
-                    proc(d1g,
-                         SkScalarTo48Dot16(tmsLoc.fX + SK_ScalarHalf), //d1g.fHalfSampleX,
-                         SkScalarTo48Dot16(tmsLoc.fY + SK_ScalarHalf), //d1g.fHalfSampleY,
-                         glyph);
-                }
-                pos += scalarsPerPosition;
-            }
-        } else {
-            while (text < stop) {
-                // the last 2 parameters are ignored
-                const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
-
-                if (glyph.fWidth) {
-                    SkPoint tmsLoc;
-                    tmsProc(pos, &tmsLoc);
-
-                    SkPoint alignLoc;
-                    alignProc(tmsLoc, glyph, &alignLoc);
-
-                    proc(d1g,
-                         SkScalarTo48Dot16(alignLoc.fX + SK_ScalarHalf), //d1g.fHalfSampleX,
-                         SkScalarTo48Dot16(alignLoc.fY + SK_ScalarHalf), //d1g.fHalfSampleY,
-                         glyph);
-                }
-                pos += scalarsPerPosition;
-            }
-        }
+    auto processOneGlyph =
+        [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) {
+            position += rounding;
+            proc(d1g, SkScalarToFixed(position.fX), SkScalarToFixed(position.fY), glyph);
+        };
+
+    SkPaint::Align textAlignment = paint.getTextAlign();
+    if (!specialized_process_pos_text(text, byteLength, offset, *fMatrix, pos, scalarsPerPosition,
+            textAlignment, glyphCacheProc, cache, processOneGlyph)) {
+        process_pos_text(text, byteLength, offset, *fMatrix, pos, scalarsPerPosition,
+            textAlignment, glyphCacheProc, cache, processOneGlyph);
     }
 }