From e5911c9c5b859d60208f4b9ac4bf2a638f4bc35f Mon Sep 17 00:00:00 2001 From: herb Date: Mon, 9 Nov 2015 13:15:21 -0800 Subject: [PATCH] Extract glyph find and position code in preparation to use it in XPS and GPU code. BUG=skia: Review URL: https://codereview.chromium.org/1409123010 --- gyp/core.gypi | 1 + src/core/SkDraw.cpp | 494 +------------------------------ src/core/SkFindAndPlaceGlyph.h | 523 +++++++++++++++++++++++++++++++++ 3 files changed, 530 insertions(+), 488 deletions(-) create mode 100644 src/core/SkFindAndPlaceGlyph.h diff --git a/gyp/core.gypi b/gyp/core.gypi index 85bc351372..f54998e28c 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -112,6 +112,7 @@ '<(skia_src_path)/core/SkFilterProc.cpp', '<(skia_src_path)/core/SkFilterProc.h', '<(skia_src_path)/core/SkFilterShader.cpp', + '<(skia_src_path)/core/SkFindAndPlaceGlyph.h', '<(skia_src_path)/core/SkFlattenable.cpp', '<(skia_src_path)/core/SkFlattenableSerialization.cpp', '<(skia_src_path)/core/SkFloatBits.cpp', diff --git a/src/core/SkDraw.cpp b/src/core/SkDraw.cpp index e83bdb74b9..0b650bea44 100644 --- a/src/core/SkDraw.cpp +++ b/src/core/SkDraw.cpp @@ -12,6 +12,7 @@ #include "SkColorPriv.h" #include "SkDevice.h" #include "SkDeviceLooper.h" +#include "SkFindAndPlaceGlyph.h" #include "SkFixed.h" #include "SkMaskFilter.h" #include "SkMatrix.h" @@ -1729,491 +1730,6 @@ 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 struct MaxSizeOf; - -template <> struct MaxSizeOf<> { static const size_t value = 0; }; - -template struct MaxSizeOf { - static const size_t value = - sizeof(H) >= MaxSizeOf::value ? sizeof(H) : MaxSizeOf::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 -class UntaggedVariant { -public: - UntaggedVariant() {} - ~UntaggedVariant() {} - UntaggedVariant(const UntaggedVariant&) = delete; - UntaggedVariant& operator=(const UntaggedVariant&) = delete; - UntaggedVariant(UntaggedVariant&&) = delete; - UntaggedVariant& operator=(UntaggedVariant&&) = delete; - - template - 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)...); - } - -private: - typedef SkAlignedSStorage::value> Space; - Space fSpace; -}; - -// PolymorphicVariant holds subclasses of Base without slicing. Ts must be subclasses of Base. -template -class PolymorphicVariant { -public: - typedef UntaggedVariant Variants; - template - PolymorphicVariant(Initializer&& initializer) { - initializer(&fVariants); - } - ~PolymorphicVariant() { get()->~Base(); } - Base* get() const { return reinterpret_cast(&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 { -public: - virtual ~PositionReaderInterface() { } - virtual SkPoint nextPoint() = 0; -}; - -class HorizontalPositions final : public PositionReaderInterface { -public: - explicit 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: - explicit 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 - 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 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 -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 -class GlyphFindAndPlaceForSubpixel final : public GlyphFindAndPlaceInterface { -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 -class GlyphFindAndPlaceForFullPixel final : public GlyphFindAndPlaceInterface { -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 -using GlyphFindAndPlace = - PolymorphicVariant< - GlyphFindAndPlaceInterface, - // Subpixel - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - GlyphFindAndPlaceForSubpixel, - // Full pixel - GlyphFindAndPlaceForFullPixel, - GlyphFindAndPlaceForFullPixel, - GlyphFindAndPlaceForFullPixel - >; - -// init_subpixel is a helper function for initializing all the variants of -// GlyphFindAndPlaceForSubpixel. -template -static void init_subpixel( - typename GlyphFindAndPlace::Variants* to_init, - SkAxisAlignment axisAlignment, - SkGlyphCache* cache, - SkDrawCacheProc glyphCacheProc) { - switch (axisAlignment) { - case kX_SkAxisAlignment: - to_init->template initialize>( - cache, glyphCacheProc); - break; - case kNone_SkAxisAlignment: - to_init->template initialize>( - cache, glyphCacheProc); - break; - case kY_SkAxisAlignment: - to_init->template initialize>( - 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 -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)); - } - 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 -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(pos); - } else { - to_init->initialize(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(matrix, offset); - } else if (mtype & SkMatrix::kScale_Mask) { - to_init->initialize(matrix, offset); - } else { - to_init->initialize(matrix, offset); - } - } - }; - - GlyphFindAndPlace findAndPosition{ - [&](typename GlyphFindAndPlace::Variants* to_init) { - if (cache->isSubpixel()) { - SkAxisAlignment axisAlignment = SkComputeAxisAlignmentForHText(matrix); - switch (textAlignment) { - case SkPaint::kLeft_Align: - init_subpixel( - to_init, axisAlignment, cache, glyphCacheProc); - break; - case SkPaint::kCenter_Align: - init_subpixel( - to_init, axisAlignment, cache, glyphCacheProc); - break; - case SkPaint::kRight_Align: - init_subpixel( - to_init, axisAlignment, cache, glyphCacheProc); - break; - } - } else { - switch (textAlignment) { - case SkPaint::kLeft_Align: - to_init->template initialize>( - cache, glyphCacheProc); - break; - case SkPaint::kCenter_Align: - to_init->template initialize>( - cache, glyphCacheProc); - break; - case SkPaint::kRight_Align: - to_init->template initialize>( - cache, glyphCacheProc); - break; - } - } - } - }; - - const char* stop = text + byteLength; - while (text < stop) { - SkPoint mappedPoint = mapper->map(positionReader->nextPoint()); - findAndPosition->findAndPositionGlyph( - &text, mappedPoint, skstd::forward(processOneGlyph)); - } -} - void SkDraw::drawPosText(const char text[], size_t byteLength, const SkScalar pos[], int scalarsPerPosition, const SkPoint& offset, const SkPaint& paint) const { @@ -2258,9 +1774,11 @@ void SkDraw::drawPosText(const char text[], size_t byteLength, }; 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, + if (!SkFindAndPlaceGlyph::SpecializedProcessPosText( + text, byteLength, offset, *fMatrix, pos, scalarsPerPosition, + textAlignment, glyphCacheProc, cache, processOneGlyph)) { + SkFindAndPlaceGlyph::ProcessPosText( + text, byteLength, offset, *fMatrix, pos, scalarsPerPosition, textAlignment, glyphCacheProc, cache, processOneGlyph); } } diff --git a/src/core/SkFindAndPlaceGlyph.h b/src/core/SkFindAndPlaceGlyph.h new file mode 100644 index 0000000000..320caf44f7 --- /dev/null +++ b/src/core/SkFindAndPlaceGlyph.h @@ -0,0 +1,523 @@ +/* + * 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 "SkGlyph.h" +#include "SkGlyphCache.h" +#include "SkPaint.h" +#include "SkTemplates.h" + +// 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 struct SkMaxSizeOf; + +template<> +struct SkMaxSizeOf<> { + static const size_t value = 0; +}; + +template +struct SkMaxSizeOf { + static const size_t value = + sizeof(H) >= SkMaxSizeOf::value ? sizeof(H) : SkMaxSizeOf::value; +}; + +class SkFindAndPlaceGlyph { +public: + // 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 + static void ProcessPosText(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); + + // SpecializedProcessPosText 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 + static bool SpecializedProcessPosText(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); + +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 + class UntaggedVariant { + public: + UntaggedVariant() { } + + ~UntaggedVariant() { } + UntaggedVariant(const UntaggedVariant&) = delete; + UntaggedVariant& operator=(const UntaggedVariant&) = delete; + UntaggedVariant(UntaggedVariant&&) = delete; + UntaggedVariant& operator=(UntaggedVariant&&) = delete; + + template + 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)...); + } + + private: + typedef SkAlignedSStorage::value> Space; + Space fSpace; + }; + + // PolymorphicVariant holds subclasses of Base without slicing. Ts must be subclasses of Base. + template + class PolymorphicVariant { + public: + typedef UntaggedVariant Variants; + + template + PolymorphicVariant(Initializer&& initializer) { + initializer(&fVariants); + } + ~PolymorphicVariant() { get()->~Base(); } + Base* get() const { return reinterpret_cast(&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 { + public: + virtual ~PositionReaderInterface() { } + virtual SkPoint nextPoint() = 0; + }; + + class HorizontalPositions final : public PositionReaderInterface { + public: + explicit 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: + explicit 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 + 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; + + // Text alignment 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)) + + // 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 SubpixelPositionRounding(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 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 + class GlyphFindAndPlaceInterface : SkNoncopyable { + public: + 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) { }; + }; + + // 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 + class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface { + public: + GlyphFindAndPlaceSubpixel(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 -= TextAlignmentAdjustment(kTextAlignment, metricGlyph); + } + + // Find the glyph. + SkIPoint lookupPosition = SubpixelAlignment(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, + SubpixelPositionRounding(kAxisAlignment)); + } + } + + private: + SkGlyphCache* const fCache; + SkDrawCacheProc fGlyphCacheProc; + }; + + // GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel + // positioning is requested. + template + class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface { + public: + GlyphFindAndPlaceFullPixel(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 -= TextAlignmentAdjustment(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 + using GlyphFindAndPlace = PolymorphicVariant< + GlyphFindAndPlaceInterface, + // Subpixel + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + GlyphFindAndPlaceSubpixel, + // Full pixel + GlyphFindAndPlaceFullPixel, + GlyphFindAndPlaceFullPixel, + GlyphFindAndPlaceFullPixel + >; + + // InitSubpixel is a helper function for initializing all the variants of + // GlyphFindAndPlaceSubpixel. + template + static void InitSubpixel( + typename GlyphFindAndPlace::Variants* to_init, + SkAxisAlignment axisAlignment, + SkGlyphCache* cache, + SkDrawCacheProc glyphCacheProc) { + switch (axisAlignment) { + case kX_SkAxisAlignment: + to_init->template initialize>( + cache, glyphCacheProc); + break; + case kNone_SkAxisAlignment: + to_init->template initialize>( + cache, glyphCacheProc); + break; + case kY_SkAxisAlignment: + to_init->template initialize>( + cache, glyphCacheProc); + break; + } + } +}; + +template +inline void SkFindAndPlaceGlyph::ProcessPosText( + 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(pos); + } else { + to_init->initialize(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(matrix, offset); + } else if (mtype & SkMatrix::kScale_Mask) { + to_init->initialize(matrix, offset); + } else { + to_init->initialize(matrix, offset); + } + } + }; + + GlyphFindAndPlace findAndPosition{ + [&](typename GlyphFindAndPlace::Variants* to_init) { + if (cache->isSubpixel()) { + SkAxisAlignment axisAlignment = SkComputeAxisAlignmentForHText(matrix); + switch (textAlignment) { + case SkPaint::kLeft_Align: + InitSubpixel( + to_init, axisAlignment, cache, glyphCacheProc); + break; + case SkPaint::kCenter_Align: + InitSubpixel( + to_init, axisAlignment, cache, glyphCacheProc); + break; + case SkPaint::kRight_Align: + InitSubpixel( + to_init, axisAlignment, cache, glyphCacheProc); + break; + } + } else { + switch (textAlignment) { + case SkPaint::kLeft_Align: + to_init->template initialize< + GlyphFindAndPlaceFullPixel>(cache, glyphCacheProc); + break; + case SkPaint::kCenter_Align: + to_init->template initialize< + GlyphFindAndPlaceFullPixel>(cache, glyphCacheProc); + break; + case SkPaint::kRight_Align: + to_init->template initialize< + GlyphFindAndPlaceFullPixel>(cache, glyphCacheProc); + break; + } + } + } + }; + + const char* stop = text + byteLength; + while (text < stop) { + SkPoint mappedPoint = mapper->map(positionReader->nextPoint()); + findAndPosition->findAndPositionGlyph( + &text, mappedPoint, skstd::forward(processOneGlyph)); + } +} + +template +inline bool SkFindAndPlaceGlyph::SpecializedProcessPosText( + 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 GlyphFindAndPlaceSubpixel< + 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)); + } + return true; + } + return false; +} + + +#endif // SkFindAndPositionGlyph_DEFINED