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