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