/* * 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 "SkArenaAlloc.h" #include "SkGlyph.h" #include "SkMatrixPriv.h" #include "SkPaint.h" #include "SkStrike.h" #include "SkTemplates.h" #include "SkUTF.h" #include <utility> 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<typename ProcessOneGlyph> static void ProcessPosText( const SkGlyphID[], int count, SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition, SkStrike* cache, ProcessOneGlyph&& processOneGlyph); // 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) { if (!SkScalarsAreFinite(position.fX, position.fY)) { return {0, 0}; } // 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)}; } SK_ABORT("Should not get here."); return {0, 0}; } // 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}; } SK_ABORT("Should not get here."); return {0.0f, 0.0f}; } // 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; }; static MapperInterface* CreateMapper(const SkMatrix& matrix, const SkPoint& offset, int scalarsPerPosition, SkArenaAlloc* arena) { auto mtype = matrix.getType(); if (mtype & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask) || scalarsPerPosition == 2) { return arena->make<GeneralMapper>(matrix, offset); } if (mtype & SkMatrix::kScale_Mask) { return arena->make<XScaleMapper>(matrix, offset); } return arena->make<TranslationMapper>(matrix, offset); } private: // 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; }; 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(SkMatrixPriv::GetMapXYProc(matrix)) { } 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 SkMatrixPriv::MapXYProc fMapProc; }; // 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. static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound); // 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. 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( SkGlyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) { SK_ABORT("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, SkAxisAlignment kAxisAlignment> class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> { public: explicit GlyphFindAndPlaceSubpixel(SkStrike* cache) : fCache(cache) {} SkPoint findAndPositionGlyph(SkGlyphID glyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) override { // Find the glyph. SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, position); const SkGlyph& renderGlyph = fCache->getGlyphIDMetrics(glyphID, lookupPosition.fX, lookupPosition.fY); // If the glyph has no width (no pixels) then don't bother processing it. if (renderGlyph.fWidth > 0) { processOneGlyph(renderGlyph, position, SubpixelPositionRounding(kAxisAlignment)); } return position + SkPoint{SkFloatToScalar(renderGlyph.fAdvanceX), SkFloatToScalar(renderGlyph.fAdvanceY)}; } private: SkStrike* fCache; }; // GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel // positioning is requested. template<typename ProcessOneGlyph> class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> { public: explicit GlyphFindAndPlaceFullPixel(SkStrike* cache) : fCache(cache) {} SkPoint findAndPositionGlyph( SkGlyphID glyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) override { SkPoint finalPosition = position; const SkGlyph& glyph = fCache->getGlyphIDMetrics(glyphID); if (glyph.fWidth > 0) { processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf}); } return finalPosition + SkPoint{SkFloatToScalar(glyph.fAdvanceX), SkFloatToScalar(glyph.fAdvanceY)}; } private: SkStrike* fCache; }; template <typename ProcessOneGlyph> static GlyphFindAndPlaceInterface<ProcessOneGlyph>* getSubpixel( SkArenaAlloc* arena, SkAxisAlignment axisAlignment, SkStrike* cache) { switch (axisAlignment) { case kX_SkAxisAlignment: return arena->make<GlyphFindAndPlaceSubpixel< ProcessOneGlyph, kX_SkAxisAlignment>>(cache); case kNone_SkAxisAlignment: return arena->make<GlyphFindAndPlaceSubpixel< ProcessOneGlyph, kNone_SkAxisAlignment>>(cache); case kY_SkAxisAlignment: return arena->make<GlyphFindAndPlaceSubpixel< ProcessOneGlyph, kY_SkAxisAlignment>>(cache); } SK_ABORT("Should never get here."); return nullptr; } }; template<typename ProcessOneGlyph> inline void SkFindAndPlaceGlyph::ProcessPosText( const SkGlyphID glyphs[], int count, SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition, SkStrike* cache, ProcessOneGlyph&& processOneGlyph) { SkAxisAlignment axisAlignment = cache->getScalerContext()->computeAxisAlignmentForHText(); uint32_t mtype = matrix.getType(); // Specialized code for handling the most common case for blink. if (axisAlignment == kX_SkAxisAlignment && cache->isSubpixel() && mtype <= SkMatrix::kTranslate_Mask) { using Positioner = GlyphFindAndPlaceSubpixel < ProcessOneGlyph, kX_SkAxisAlignment>; HorizontalPositions hPositions{pos}; ArbitraryPositions aPositions{pos}; PositionReaderInterface* positions = nullptr; if (scalarsPerPosition == 2) { positions = &aPositions; } else { positions = &hPositions; } TranslationMapper mapper{matrix, offset}; Positioner positioner(cache); for (int i = 0; i < count; ++i) { SkPoint mappedPoint = mapper.TranslationMapper::map(positions->nextPoint()); positioner.Positioner::findAndPositionGlyph( glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph)); } return; } SkSTArenaAlloc<120> arena; PositionReaderInterface* positionReader = nullptr; if (2 == scalarsPerPosition) { positionReader = arena.make<ArbitraryPositions>(pos); } else { positionReader = arena.make<HorizontalPositions>(pos); } MapperInterface* mapper = CreateMapper(matrix, offset, scalarsPerPosition, &arena); GlyphFindAndPlaceInterface<ProcessOneGlyph>* findAndPosition = nullptr; if (cache->isSubpixel()) { findAndPosition = getSubpixel<ProcessOneGlyph>(&arena, axisAlignment, cache); } else { findAndPosition = arena.make<GlyphFindAndPlaceFullPixel<ProcessOneGlyph>>(cache); } for (int i = 0; i < count; ++i) { SkPoint mappedPoint = mapper->map(positionReader->nextPoint()); findAndPosition->findAndPositionGlyph( glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph)); } } #endif // SkFindAndPositionGlyph_DEFINED