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.
SubpixelAlignment(SkAxisAlignment axisAlignment,SkPoint 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.
SubpixelPositionRounding(SkAxisAlignment axisAlignment)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:
~MapperInterface()90         virtual ~MapperInterface() {}
91 
92         virtual SkPoint map(SkPoint position) const = 0;
93     };
94 
CreateMapper(const SkMatrix & matrix,const SkPoint & offset,int scalarsPerPosition,SkArenaAlloc * arena)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:
~PositionReaderInterface()116         virtual ~PositionReaderInterface() { }
117         virtual SkPoint nextPoint() = 0;
118     };
119 
120     class HorizontalPositions final : public PositionReaderInterface {
121     public:
HorizontalPositions(const SkScalar * positions)122         explicit HorizontalPositions(const SkScalar* positions)
123             : fPositions(positions) { }
124 
nextPoint()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:
ArbitraryPositions(const SkScalar * positions)136         explicit ArbitraryPositions(const SkScalar* positions)
137             : fPositions(positions) { }
138 
nextPoint()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:
TranslationMapper(const SkMatrix & matrix,const SkPoint origin)151         TranslationMapper(const SkMatrix& matrix, const SkPoint origin)
152             : fTranslate(matrix.mapXY(origin.fX, origin.fY)) { }
153 
map(SkPoint position)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:
XScaleMapper(const SkMatrix & matrix,const SkPoint origin)164         XScaleMapper(const SkMatrix& matrix, const SkPoint origin)
165             : fTranslate(matrix.mapXY(origin.fX, origin.fY)), fXScale(matrix.getScaleX()) { }
166 
map(SkPoint position)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:
GeneralMapper(const SkMatrix & matrix,const SkPoint origin)179         GeneralMapper(const SkMatrix& matrix, const SkPoint origin)
180             : fOrigin(origin), fMatrix(matrix), fMapProc(SkMatrixPriv::GetMapXYProc(matrix)) { }
181 
map(SkPoint position)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:
~GlyphFindAndPlaceInterface()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
findAndPositionGlyph(SkGlyphID,SkPoint position,ProcessOneGlyph && processOneGlyph)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:
GlyphFindAndPlaceSubpixel(SkStrike * cache)229         explicit GlyphFindAndPlaceSubpixel(SkStrike* cache) : fCache(cache) {}
230 
findAndPositionGlyph(SkGlyphID glyphID,SkPoint position,ProcessOneGlyph && processOneGlyph)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:
GlyphFindAndPlaceFullPixel(SkStrike * cache)254         explicit GlyphFindAndPlaceFullPixel(SkStrike* cache) : fCache(cache) {}
255 
findAndPositionGlyph(SkGlyphID glyphID,SkPoint position,ProcessOneGlyph && processOneGlyph)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>
getSubpixel(SkArenaAlloc * arena,SkAxisAlignment axisAlignment,SkStrike * cache)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>
ProcessPosText(const SkGlyphID glyphs[],int count,SkPoint offset,const SkMatrix & matrix,const SkScalar pos[],int scalarsPerPosition,SkStrike * cache,ProcessOneGlyph && 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