Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #ifndef SkFindAndPositionGlyph_DEFINED
      9 #define SkFindAndPositionGlyph_DEFINED
     10 
     11 #include "SkArenaAlloc.h"
     12 #include "SkGlyph.h"
     13 #include "SkMatrixPriv.h"
     14 #include "SkPaint.h"
     15 #include "SkStrike.h"
     16 #include "SkTemplates.h"
     17 #include "SkUTF.h"
     18 #include <utility>
     19 
     20 class SkFindAndPlaceGlyph {
     21 public:
     22     // ProcessPosText handles all cases for finding and positioning glyphs. It has a very large
     23     // multiplicity. It figures out the glyph, position and rounding and pass those parameters to
     24     // processOneGlyph.
     25     //
     26     // The routine processOneGlyph passed in by the client has the following signature:
     27     // void f(const SkGlyph& glyph, SkPoint position, SkPoint rounding);
     28     //
     29     // * Sub-pixel positioning (2) - use sub-pixel positioning.
     30     // * Text alignment (3) - text alignment with respect to the glyph's width.
     31     // * Matrix type (3) - special cases for translation and X-coordinate scaling.
     32     // * Components per position (2) - the positions vector can have a common Y with different
     33     //   Xs, or XY-pairs.
     34     // * Axis Alignment (for sub-pixel positioning) (3) - when using sub-pixel positioning, round
     35     //   to a whole coordinate instead of using sub-pixel positioning.
     36     // The number of variations is 108 for sub-pixel and 36 for full-pixel.
     37     // This routine handles all of them using inline polymorphic variable (no heap allocation).
     38     template<typename ProcessOneGlyph>
     39     static void ProcessPosText(
     40         const SkGlyphID[], int count,
     41         SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
     42         SkStrike* cache, ProcessOneGlyph&& processOneGlyph);
     43 
     44     // The SubpixelAlignment function produces a suitable position for the glyph cache to
     45     // produce the correct sub-pixel alignment. If a position is aligned with an axis a shortcut
     46     // of 0 is used for the sub-pixel position.
     47     static SkIPoint SubpixelAlignment(SkAxisAlignment axisAlignment, SkPoint position) {
     48 
     49         if (!SkScalarsAreFinite(position.fX, position.fY)) {
     50             return {0, 0};
     51         }
     52 
     53         // Only the fractional part of position.fX and position.fY matter, because the result of
     54         // this function will just be passed to FixedToSub.
     55         switch (axisAlignment) {
     56             case kX_SkAxisAlignment:
     57                 return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding), 0};
     58             case kY_SkAxisAlignment:
     59                 return {0, SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)};
     60             case kNone_SkAxisAlignment:
     61                 return {SkScalarToFixed(SkScalarFraction(position.fX) + kSubpixelRounding),
     62                         SkScalarToFixed(SkScalarFraction(position.fY) + kSubpixelRounding)};
     63         }
     64         SK_ABORT("Should not get here.");
     65         return {0, 0};
     66     }
     67 
     68     // The SubpixelPositionRounding function returns a point suitable for rounding a sub-pixel
     69     // positioned glyph.
     70     static SkPoint SubpixelPositionRounding(SkAxisAlignment axisAlignment) {
     71         switch (axisAlignment) {
     72             case kX_SkAxisAlignment:
     73                 return {kSubpixelRounding, SK_ScalarHalf};
     74             case kY_SkAxisAlignment:
     75                 return {SK_ScalarHalf, kSubpixelRounding};
     76             case kNone_SkAxisAlignment:
     77                 return {kSubpixelRounding, kSubpixelRounding};
     78         }
     79         SK_ABORT("Should not get here.");
     80         return {0.0f, 0.0f};
     81     }
     82 
     83     // MapperInterface given a point map it through the matrix. There are several shortcut
     84     // variants.
     85     // * TranslationMapper - assumes a translation only matrix.
     86     // * XScaleMapper - assumes an X scaling and a translation.
     87     // * GeneralMapper - Does all other matricies.
     88     class MapperInterface {
     89     public:
     90         virtual ~MapperInterface() {}
     91 
     92         virtual SkPoint map(SkPoint position) const = 0;
     93     };
     94 
     95     static MapperInterface* CreateMapper(const SkMatrix& matrix, const SkPoint& offset,
     96                                          int scalarsPerPosition, SkArenaAlloc* arena) {
     97         auto mtype = matrix.getType();
     98         if (mtype & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask) ||
     99             scalarsPerPosition == 2) {
    100             return arena->make<GeneralMapper>(matrix, offset);
    101         }
    102 
    103         if (mtype & SkMatrix::kScale_Mask) {
    104             return arena->make<XScaleMapper>(matrix, offset);
    105         }
    106 
    107         return arena->make<TranslationMapper>(matrix, offset);
    108     }
    109 
    110 private:
    111     // PositionReaderInterface reads a point from the pos vector.
    112     // * HorizontalPositions - assumes a common Y for many X values.
    113     // * ArbitraryPositions - a list of (X,Y) pairs.
    114     class PositionReaderInterface {
    115     public:
    116         virtual ~PositionReaderInterface() { }
    117         virtual SkPoint nextPoint() = 0;
    118     };
    119 
    120     class HorizontalPositions final : public PositionReaderInterface {
    121     public:
    122         explicit HorizontalPositions(const SkScalar* positions)
    123             : fPositions(positions) { }
    124 
    125         SkPoint nextPoint() override {
    126             SkScalar x = *fPositions++;
    127             return {x, 0};
    128         }
    129 
    130     private:
    131         const SkScalar* fPositions;
    132     };
    133 
    134     class ArbitraryPositions final : public PositionReaderInterface {
    135     public:
    136         explicit ArbitraryPositions(const SkScalar* positions)
    137             : fPositions(positions) { }
    138 
    139         SkPoint nextPoint() override {
    140             SkPoint to_return{fPositions[0], fPositions[1]};
    141             fPositions += 2;
    142             return to_return;
    143         }
    144 
    145     private:
    146         const SkScalar* fPositions;
    147     };
    148 
    149     class TranslationMapper final : public MapperInterface {
    150     public:
    151         TranslationMapper(const SkMatrix& matrix, const SkPoint origin)
    152             : fTranslate(matrix.mapXY(origin.fX, origin.fY)) { }
    153 
    154         SkPoint map(SkPoint position) const override {
    155             return position + fTranslate;
    156         }
    157 
    158     private:
    159         const SkPoint fTranslate;
    160     };
    161 
    162     class XScaleMapper final : public MapperInterface {
    163     public:
    164         XScaleMapper(const SkMatrix& matrix, const SkPoint origin)
    165             : fTranslate(matrix.mapXY(origin.fX, origin.fY)), fXScale(matrix.getScaleX()) { }
    166 
    167         SkPoint map(SkPoint position) const override {
    168             return {fXScale * position.fX + fTranslate.fX, fTranslate.fY};
    169         }
    170 
    171     private:
    172         const SkPoint fTranslate;
    173         const SkScalar fXScale;
    174     };
    175 
    176     // The caller must keep matrix alive while this class is used.
    177     class GeneralMapper final : public MapperInterface {
    178     public:
    179         GeneralMapper(const SkMatrix& matrix, const SkPoint origin)
    180             : fOrigin(origin), fMatrix(matrix), fMapProc(SkMatrixPriv::GetMapXYProc(matrix)) { }
    181 
    182         SkPoint map(SkPoint position) const override {
    183             SkPoint result;
    184             fMapProc(fMatrix, position.fX + fOrigin.fX, position.fY + fOrigin.fY, &result);
    185             return result;
    186         }
    187 
    188     private:
    189         const SkPoint fOrigin;
    190         const SkMatrix& fMatrix;
    191         const SkMatrixPriv::MapXYProc fMapProc;
    192     };
    193 
    194     // The "call" to SkFixedToScalar is actually a macro. It's macros all the way down.
    195     // Needs to be a macro because you can't have a const float unless you make it constexpr.
    196     static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound);
    197 
    198     // GlyphFindAndPlaceInterface given the text and position finds the correct glyph and does
    199     // glyph specific position adjustment. The findAndPositionGlyph method takes text and
    200     // position and calls processOneGlyph with the correct glyph, final position and rounding
    201     // terms. The final position is not rounded yet and is the responsibility of processOneGlyph.
    202     template<typename ProcessOneGlyph>
    203     class GlyphFindAndPlaceInterface : SkNoncopyable {
    204     public:
    205         virtual ~GlyphFindAndPlaceInterface() { }
    206 
    207         // findAndPositionGlyph calculates the position of the glyph, finds the glyph, and
    208         // returns the position of where the next glyph will be using the glyph's advance. The
    209         // returned position is used by drawText, but ignored by drawPosText.
    210         // The compiler should prune all this calculation if the return value is not used.
    211         //
    212         // This should be a pure virtual, but some versions of GCC <= 4.8 have a bug that causes a
    213         // compile error.
    214         // See GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60277
    215         virtual SkPoint findAndPositionGlyph(
    216             SkGlyphID, SkPoint position,
    217             ProcessOneGlyph&& processOneGlyph) {
    218             SK_ABORT("Should never get here.");
    219             return {0.0f, 0.0f};
    220         }
    221     };
    222 
    223     // GlyphFindAndPlaceSubpixel handles finding and placing glyphs when sub-pixel positioning is
    224     // requested. After it has found and placed the glyph it calls the templated function
    225     // ProcessOneGlyph in order to actually perform an action.
    226     template<typename ProcessOneGlyph, SkAxisAlignment kAxisAlignment>
    227     class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
    228     public:
    229         explicit GlyphFindAndPlaceSubpixel(SkStrike* cache) : fCache(cache) {}
    230 
    231         SkPoint findAndPositionGlyph(SkGlyphID glyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
    232             // Find the glyph.
    233             SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, position);
    234             const SkGlyph& renderGlyph = fCache->getGlyphIDMetrics(glyphID, lookupPosition.fX, lookupPosition.fY);
    235 
    236             // If the glyph has no width (no pixels) then don't bother processing it.
    237             if (renderGlyph.fWidth > 0) {
    238                 processOneGlyph(renderGlyph, position,
    239                                 SubpixelPositionRounding(kAxisAlignment));
    240             }
    241             return position + SkPoint{SkFloatToScalar(renderGlyph.fAdvanceX),
    242                                       SkFloatToScalar(renderGlyph.fAdvanceY)};
    243         }
    244 
    245     private:
    246         SkStrike* fCache;
    247     };
    248 
    249     // GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel
    250     // positioning is requested.
    251     template<typename ProcessOneGlyph>
    252     class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
    253     public:
    254         explicit GlyphFindAndPlaceFullPixel(SkStrike* cache) : fCache(cache) {}
    255 
    256         SkPoint findAndPositionGlyph(
    257             SkGlyphID glyphID, SkPoint position,
    258             ProcessOneGlyph&& processOneGlyph) override {
    259             SkPoint finalPosition = position;
    260             const SkGlyph& glyph = fCache->getGlyphIDMetrics(glyphID);
    261 
    262             if (glyph.fWidth > 0) {
    263                 processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf});
    264             }
    265             return finalPosition + SkPoint{SkFloatToScalar(glyph.fAdvanceX),
    266                                            SkFloatToScalar(glyph.fAdvanceY)};
    267         }
    268 
    269     private:
    270         SkStrike* fCache;
    271     };
    272 
    273     template <typename ProcessOneGlyph>
    274     static GlyphFindAndPlaceInterface<ProcessOneGlyph>* getSubpixel(
    275         SkArenaAlloc* arena, SkAxisAlignment axisAlignment, SkStrike* cache)
    276     {
    277         switch (axisAlignment) {
    278             case kX_SkAxisAlignment:
    279                 return arena->make<GlyphFindAndPlaceSubpixel<
    280                     ProcessOneGlyph, kX_SkAxisAlignment>>(cache);
    281             case kNone_SkAxisAlignment:
    282                 return arena->make<GlyphFindAndPlaceSubpixel<
    283                     ProcessOneGlyph, kNone_SkAxisAlignment>>(cache);
    284             case kY_SkAxisAlignment:
    285                 return arena->make<GlyphFindAndPlaceSubpixel<
    286                     ProcessOneGlyph, kY_SkAxisAlignment>>(cache);
    287         }
    288         SK_ABORT("Should never get here.");
    289         return nullptr;
    290     }
    291 };
    292 
    293 template<typename ProcessOneGlyph>
    294 inline void SkFindAndPlaceGlyph::ProcessPosText(
    295     const SkGlyphID glyphs[], int count,
    296     SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
    297     SkStrike* cache, ProcessOneGlyph&& processOneGlyph) {
    298 
    299     SkAxisAlignment axisAlignment = cache->getScalerContext()->computeAxisAlignmentForHText();
    300     uint32_t mtype = matrix.getType();
    301 
    302     // Specialized code for handling the most common case for blink.
    303     if (axisAlignment == kX_SkAxisAlignment
    304         && cache->isSubpixel()
    305         && mtype <= SkMatrix::kTranslate_Mask)
    306     {
    307         using Positioner =
    308             GlyphFindAndPlaceSubpixel <
    309                 ProcessOneGlyph, kX_SkAxisAlignment>;
    310         HorizontalPositions hPositions{pos};
    311         ArbitraryPositions  aPositions{pos};
    312         PositionReaderInterface* positions = nullptr;
    313         if (scalarsPerPosition == 2) {
    314             positions = &aPositions;
    315         } else {
    316             positions = &hPositions;
    317         }
    318         TranslationMapper mapper{matrix, offset};
    319         Positioner positioner(cache);
    320         for (int i = 0; i < count; ++i) {
    321             SkPoint mappedPoint = mapper.TranslationMapper::map(positions->nextPoint());
    322             positioner.Positioner::findAndPositionGlyph(
    323                 glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
    324         }
    325         return;
    326     }
    327 
    328     SkSTArenaAlloc<120> arena;
    329 
    330     PositionReaderInterface* positionReader = nullptr;
    331     if (2 == scalarsPerPosition) {
    332         positionReader = arena.make<ArbitraryPositions>(pos);
    333     } else {
    334         positionReader = arena.make<HorizontalPositions>(pos);
    335     }
    336 
    337     MapperInterface* mapper = CreateMapper(matrix, offset, scalarsPerPosition, &arena);
    338     GlyphFindAndPlaceInterface<ProcessOneGlyph>* findAndPosition = nullptr;
    339     if (cache->isSubpixel()) {
    340         findAndPosition = getSubpixel<ProcessOneGlyph>(&arena, axisAlignment, cache);
    341     } else {
    342         findAndPosition = arena.make<GlyphFindAndPlaceFullPixel<ProcessOneGlyph>>(cache);
    343     }
    344 
    345     for (int i = 0; i < count; ++i) {
    346         SkPoint mappedPoint = mapper->map(positionReader->nextPoint());
    347         findAndPosition->findAndPositionGlyph(
    348             glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
    349     }
    350 }
    351 
    352 #endif  // SkFindAndPositionGlyph_DEFINED
    353