/* * 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 // 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; }; // 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 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 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 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(std::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; }; // 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(cache); break; case SkPaint::kUTF16_TextEncoding: to_init->initialize(cache); break; case SkPaint::kUTF32_TextEncoding: to_init->initialize(cache); break; case SkPaint::kGlyphID_TextEncoding: to_init->initialize(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 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 {SkFloatToScalar(glyph.fAdvanceX) / 2, SkFloatToScalar(glyph.fAdvanceY) / 2}; case SkPaint::kRight_Align: return {SkFloatToScalar(glyph.fAdvanceX), SkFloatToScalar(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) { // Only the fractional part of position.fX and position.fY matter, because the result of // this function will just be passed to FixedToSub. switch (axisAlignment) { case kX_SkAxisAlignment: return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding), 0}; case kY_SkAxisAlignment: return {0, SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)}; case kNone_SkAxisAlignment: return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding), SkScalarToFixed(SkScalarFraction(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() { }; // 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 class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface { 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{SkFloatToScalar(metricGlyph.fAdvanceX), SkFloatToScalar(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{SkFloatToScalar(renderGlyph.fAdvanceX), SkFloatToScalar(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 class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface { 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 += {fAutoKern.adjust(glyph), 0.0f}; } if (glyph.fWidth > 0) { finalPosition -= TextAlignmentAdjustment(kTextAlignment, glyph); processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf}); } return finalPosition + SkPoint{SkFloatToScalar(glyph.fAdvanceX), SkFloatToScalar(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 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, LookupGlyph& glyphFinder) { switch (axisAlignment) { case kX_SkAxisAlignment: to_init->template initialize>(glyphFinder); break; case kNone_SkAxisAlignment: to_init->template initialize>(glyphFinder); break; case kY_SkAxisAlignment: to_init->template initialize>(glyphFinder); break; } } static SkPoint MeasureText(LookupGlyph& glyphFinder, const char text[], size_t byteLength) { SkScalar 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) + SkFloatToScalar(glyph.fAdvanceX); y += SkFloatToScalar(glyph.fAdvanceY); } SkASSERT(text == stop); return {x, y}; } }; template 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 = cache->getScalerContext()->computeAxisAlignmentForHText(); 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)); } return; } PositionReader positionReader{ [&](PositionReader::Variants* to_init) { if (2 == scalarsPerPosition) { to_init->initialize(pos); } else { to_init->initialize(pos); } positionReader->forceUseForBug(); } }; Mapper mapper{ [&](Mapper::Variants* to_init) { 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()) { switch (textAlignment) { case SkPaint::kLeft_Align: InitSubpixel( to_init, axisAlignment, glyphFinder); break; case SkPaint::kCenter_Align: InitSubpixel( to_init, axisAlignment, glyphFinder); break; case SkPaint::kRight_Align: InitSubpixel( to_init, axisAlignment, glyphFinder); break; } } else { switch (textAlignment) { case SkPaint::kLeft_Align: to_init->template initialize< GlyphFindAndPlaceFullPixel>(glyphFinder); break; case SkPaint::kCenter_Align: to_init->template initialize< GlyphFindAndPlaceFullPixel>(glyphFinder); break; case SkPaint::kRight_Align: to_init->template initialize< GlyphFindAndPlaceFullPixel>(glyphFinder); break; } } } }; const char* stop = text + byteLength; while (text < stop) { SkPoint mappedPoint = mapper->map(positionReader->nextPoint()); findAndPosition->findAndPositionGlyph( &text, mappedPoint, std::forward(processOneGlyph)); } } template 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 findAndPosition{ [&](typename GlyphFindAndPlace::Variants* to_init) { if (cache->isSubpixel()) { SkAxisAlignment axisAlignment = cache->getScalerContext()->computeAxisAlignmentForHText(); InitSubpixel( 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)); } } #endif // SkFindAndPositionGlyph_DEFINED