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 #include "GrAtlasTextContext.h"
8 #include "GrContext.h"
9 #include "GrContextPriv.h"
10 #include "GrTextBlobCache.h"
11 #include "SkDistanceFieldGen.h"
12 #include "SkDraw.h"
13 #include "SkDrawFilter.h"
14 #include "SkDrawProcs.h"
15 #include "SkFindAndPlaceGlyph.h"
16 #include "SkGr.h"
17 #include "SkGraphics.h"
18 #include "SkMakeUnique.h"
19 #include "SkMaskFilterBase.h"
20 #include "SkTextMapStateProc.h"
21
22 #include "ops/GrMeshDrawOp.h"
23
24 // DF sizes and thresholds for usage of the small and medium sizes. For example, above
25 // kSmallDFFontLimit we will use the medium size. The large size is used up until the size at
26 // which we switch over to drawing as paths as controlled by Options.
27 static const int kSmallDFFontSize = 32;
28 static const int kSmallDFFontLimit = 32;
29 static const int kMediumDFFontSize = 72;
30 static const int kMediumDFFontLimit = 72;
31 static const int kLargeDFFontSize = 162;
32
33 static const int kDefaultMinDistanceFieldFontSize = 18;
34 #ifdef SK_BUILD_FOR_ANDROID
35 static const int kDefaultMaxDistanceFieldFontSize = 384;
36 #else
37 static const int kDefaultMaxDistanceFieldFontSize = 2 * kLargeDFFontSize;
38 #endif
39
GrAtlasTextContext(const Options & options)40 GrAtlasTextContext::GrAtlasTextContext(const Options& options)
41 : fDistanceAdjustTable(new GrDistanceFieldAdjustTable) {
42 fMaxDistanceFieldFontSize = options.fMaxDistanceFieldFontSize < 0.f
43 ? kDefaultMaxDistanceFieldFontSize
44 : options.fMaxDistanceFieldFontSize;
45 fMinDistanceFieldFontSize = options.fMinDistanceFieldFontSize < 0.f
46 ? kDefaultMinDistanceFieldFontSize
47 : options.fMinDistanceFieldFontSize;
48 fDistanceFieldVerticesAlwaysHaveW = options.fDistanceFieldVerticesAlwaysHaveW;
49 }
50
Make(const Options & options)51 std::unique_ptr<GrAtlasTextContext> GrAtlasTextContext::Make(const Options& options) {
52 return std::unique_ptr<GrAtlasTextContext>(new GrAtlasTextContext(options));
53 }
54
ComputeCanonicalColor(const SkPaint & paint,bool lcd)55 SkColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
56 SkColor canonicalColor = paint.computeLuminanceColor();
57 if (lcd) {
58 // This is the correct computation, but there are tons of cases where LCD can be overridden.
59 // For now we just regenerate if any run in a textblob has LCD.
60 // TODO figure out where all of these overrides are and see if we can incorporate that logic
61 // at a higher level *OR* use sRGB
62 SkASSERT(false);
63 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
64 } else {
65 // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
66 // gamma corrected masks anyways, nor color
67 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
68 SkColorGetG(canonicalColor),
69 SkColorGetB(canonicalColor));
70 // reduce to our finite number of bits
71 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
72 }
73 return canonicalColor;
74 }
75
ComputeScalerContextFlags(const GrColorSpaceInfo & colorSpaceInfo)76 SkScalerContextFlags GrAtlasTextContext::ComputeScalerContextFlags(
77 const GrColorSpaceInfo& colorSpaceInfo) {
78 // If we're doing gamma-correct rendering, then we can disable the gamma hacks.
79 // Otherwise, leave them on. In either case, we still want the contrast boost:
80 if (colorSpaceInfo.isGammaCorrect()) {
81 return SkScalerContextFlags::kBoostContrast;
82 } else {
83 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
84 }
85 }
86
87 // TODO if this function ever shows up in profiling, then we can compute this value when the
88 // textblob is being built and cache it. However, for the time being textblobs mostly only have 1
89 // run so this is not a big deal to compute here.
HasLCD(const SkTextBlob * blob)90 bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) {
91 SkTextBlobRunIterator it(blob);
92 for (; !it.done(); it.next()) {
93 if (it.isLCD()) {
94 return true;
95 }
96 }
97 return false;
98 }
99
drawTextBlob(GrContext * context,GrTextUtils::Target * target,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter,const SkIRect & clipBounds)100 void GrAtlasTextContext::drawTextBlob(GrContext* context, GrTextUtils::Target* target,
101 const GrClip& clip, const SkPaint& skPaint,
102 const SkMatrix& viewMatrix, const SkSurfaceProps& props,
103 const SkTextBlob* blob, SkScalar x, SkScalar y,
104 SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
105 // If we have been abandoned, then don't draw
106 if (context->abandoned()) {
107 return;
108 }
109
110 sk_sp<GrAtlasTextBlob> cacheBlob;
111 SkMaskFilterBase::BlurRec blurRec;
112 GrAtlasTextBlob::Key key;
113 // It might be worth caching these things, but its not clear at this time
114 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
115 const SkMaskFilter* mf = skPaint.getMaskFilter();
116 bool canCache = !(skPaint.getPathEffect() ||
117 (mf && !as_MFB(mf)->asABlur(&blurRec)) ||
118 drawFilter);
119 SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorSpaceInfo());
120
121 auto glyphCache = context->contextPriv().getGlyphCache();
122 auto restrictedAtlasManager = context->contextPriv().getRestrictedAtlasManager();
123 GrTextBlobCache* textBlobCache = context->contextPriv().getTextBlobCache();
124
125 if (canCache) {
126 bool hasLCD = HasLCD(blob);
127
128 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
129 SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
130 kUnknown_SkPixelGeometry;
131
132 // TODO we want to figure out a way to be able to use the canonical color on LCD text,
133 // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
134 // ensure we always match the same key
135 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
136 ComputeCanonicalColor(skPaint, hasLCD);
137
138 key.fPixelGeometry = pixelGeometry;
139 key.fUniqueID = blob->uniqueID();
140 key.fStyle = skPaint.getStyle();
141 key.fHasBlur = SkToBool(mf);
142 key.fCanonicalColor = canonicalColor;
143 key.fScalerContextFlags = scalerContextFlags;
144 cacheBlob = textBlobCache->find(key);
145 }
146
147 GrTextUtils::Paint paint(&skPaint, &target->colorSpaceInfo());
148 if (cacheBlob) {
149 if (cacheBlob->mustRegenerate(paint, blurRec, viewMatrix, x, y)) {
150 // We have to remake the blob because changes may invalidate our masks.
151 // TODO we could probably get away reuse most of the time if the pointer is unique,
152 // but we'd have to clear the subrun information
153 textBlobCache->remove(cacheBlob.get());
154 cacheBlob = textBlobCache->makeCachedBlob(blob, key, blurRec, skPaint);
155 this->regenerateTextBlob(cacheBlob.get(), glyphCache,
156 *context->caps()->shaderCaps(), paint, scalerContextFlags,
157 viewMatrix, props, blob, x, y, drawFilter);
158 } else {
159 textBlobCache->makeMRU(cacheBlob.get());
160
161 if (CACHE_SANITY_CHECK) {
162 int glyphCount = 0;
163 int runCount = 0;
164 GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob);
165 sk_sp<GrAtlasTextBlob> sanityBlob(textBlobCache->makeBlob(glyphCount, runCount));
166 sanityBlob->setupKey(key, blurRec, skPaint);
167 this->regenerateTextBlob(sanityBlob.get(), glyphCache,
168 *context->caps()->shaderCaps(), paint, scalerContextFlags,
169 viewMatrix, props, blob, x, y, drawFilter);
170 GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
171 }
172 }
173 } else {
174 if (canCache) {
175 cacheBlob = textBlobCache->makeCachedBlob(blob, key, blurRec, skPaint);
176 } else {
177 cacheBlob = textBlobCache->makeBlob(blob);
178 }
179 this->regenerateTextBlob(cacheBlob.get(), glyphCache,
180 *context->caps()->shaderCaps(), paint, scalerContextFlags,
181 viewMatrix, props, blob, x, y, drawFilter);
182 }
183
184 cacheBlob->flush(restrictedAtlasManager, target, props, fDistanceAdjustTable.get(), paint,
185 clip, viewMatrix, clipBounds, x, y);
186 }
187
regenerateTextBlob(GrAtlasTextBlob * cacheBlob,GrGlyphCache * glyphCache,const GrShaderCaps & shaderCaps,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter) const188 void GrAtlasTextContext::regenerateTextBlob(GrAtlasTextBlob* cacheBlob,
189 GrGlyphCache* glyphCache,
190 const GrShaderCaps& shaderCaps,
191 const GrTextUtils::Paint& paint,
192 SkScalerContextFlags scalerContextFlags,
193 const SkMatrix& viewMatrix,
194 const SkSurfaceProps& props, const SkTextBlob* blob,
195 SkScalar x, SkScalar y,
196 SkDrawFilter* drawFilter) const {
197 cacheBlob->initReusableBlob(paint.luminanceColor(), viewMatrix, x, y);
198
199 // Regenerate textblob
200 SkTextBlobRunIterator it(blob);
201 GrTextUtils::RunPaint runPaint(&paint, drawFilter, props);
202 for (int run = 0; !it.done(); it.next(), run++) {
203 int glyphCount = it.glyphCount();
204 size_t textLen = glyphCount * sizeof(uint16_t);
205 const SkPoint& offset = it.offset();
206 cacheBlob->push_back_run(run);
207 if (!runPaint.modifyForRun([it](SkPaint* p) { it.applyFontToPaint(p); })) {
208 continue;
209 }
210 cacheBlob->setRunPaintFlags(run, runPaint.skPaint().getFlags());
211
212 if (this->canDrawAsDistanceFields(runPaint, viewMatrix, props, shaderCaps)) {
213 switch (it.positioning()) {
214 case SkTextBlob::kDefault_Positioning: {
215 this->drawDFText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags,
216 viewMatrix, (const char*)it.glyphs(), textLen, x + offset.x(),
217 y + offset.y());
218 break;
219 }
220 case SkTextBlob::kHorizontal_Positioning: {
221 SkPoint dfOffset = SkPoint::Make(x, y + offset.y());
222 this->drawDFPosText(cacheBlob, run, glyphCache, props, runPaint,
223 scalerContextFlags, viewMatrix, (const char*)it.glyphs(),
224 textLen, it.pos(), 1, dfOffset);
225 break;
226 }
227 case SkTextBlob::kFull_Positioning: {
228 SkPoint dfOffset = SkPoint::Make(x, y);
229 this->drawDFPosText(cacheBlob, run, glyphCache, props, runPaint,
230 scalerContextFlags, viewMatrix, (const char*)it.glyphs(),
231 textLen, it.pos(), 2, dfOffset);
232 break;
233 }
234 }
235 } else {
236 switch (it.positioning()) {
237 case SkTextBlob::kDefault_Positioning:
238 DrawBmpText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags,
239 viewMatrix, (const char*)it.glyphs(), textLen, x + offset.x(),
240 y + offset.y());
241 break;
242 case SkTextBlob::kHorizontal_Positioning:
243 DrawBmpPosText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags,
244 viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 1,
245 SkPoint::Make(x, y + offset.y()));
246 break;
247 case SkTextBlob::kFull_Positioning:
248 DrawBmpPosText(cacheBlob, run, glyphCache, props, runPaint, scalerContextFlags,
249 viewMatrix, (const char*)it.glyphs(), textLen, it.pos(), 2,
250 SkPoint::Make(x, y));
251 break;
252 }
253 }
254 }
255 }
256
257 inline sk_sp<GrAtlasTextBlob>
makeDrawTextBlob(GrTextBlobCache * blobCache,GrGlyphCache * glyphCache,const GrShaderCaps & shaderCaps,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,SkScalar x,SkScalar y) const258 GrAtlasTextContext::makeDrawTextBlob(GrTextBlobCache* blobCache,
259 GrGlyphCache* glyphCache,
260 const GrShaderCaps& shaderCaps,
261 const GrTextUtils::Paint& paint,
262 SkScalerContextFlags scalerContextFlags,
263 const SkMatrix& viewMatrix,
264 const SkSurfaceProps& props,
265 const char text[], size_t byteLength,
266 SkScalar x, SkScalar y) const {
267 int glyphCount = paint.skPaint().countText(text, byteLength);
268 if (!glyphCount) {
269 return nullptr;
270 }
271 sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1);
272 blob->initThrowawayBlob(viewMatrix, x, y);
273 blob->setRunPaintFlags(0, paint.skPaint().getFlags());
274
275 if (this->canDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps)) {
276 this->drawDFText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix,
277 text, byteLength, x, y);
278 } else {
279 DrawBmpText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix, text,
280 byteLength, x, y);
281 }
282 return blob;
283 }
284
285 inline sk_sp<GrAtlasTextBlob>
makeDrawPosTextBlob(GrTextBlobCache * blobCache,GrGlyphCache * glyphCache,const GrShaderCaps & shaderCaps,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset) const286 GrAtlasTextContext::makeDrawPosTextBlob(GrTextBlobCache* blobCache,
287 GrGlyphCache* glyphCache,
288 const GrShaderCaps& shaderCaps,
289 const GrTextUtils::Paint& paint,
290 SkScalerContextFlags scalerContextFlags,
291 const SkMatrix& viewMatrix,
292 const SkSurfaceProps& props,
293 const char text[], size_t byteLength,
294 const SkScalar pos[], int scalarsPerPosition, const
295 SkPoint& offset) const {
296 int glyphCount = paint.skPaint().countText(text, byteLength);
297 if (!glyphCount) {
298 return nullptr;
299 }
300
301 sk_sp<GrAtlasTextBlob> blob = blobCache->makeBlob(glyphCount, 1);
302 blob->initThrowawayBlob(viewMatrix, offset.x(), offset.y());
303 blob->setRunPaintFlags(0, paint.skPaint().getFlags());
304
305 if (this->canDrawAsDistanceFields(paint, viewMatrix, props, shaderCaps)) {
306 this->drawDFPosText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix,
307 text, byteLength, pos, scalarsPerPosition, offset);
308 } else {
309 DrawBmpPosText(blob.get(), 0, glyphCache, props, paint, scalerContextFlags, viewMatrix,
310 text, byteLength, pos, scalarsPerPosition, offset);
311 }
312 return blob;
313 }
314
drawText(GrContext * context,GrTextUtils::Target * target,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & regionClipBounds)315 void GrAtlasTextContext::drawText(GrContext* context, GrTextUtils::Target* target,
316 const GrClip& clip, const SkPaint& skPaint,
317 const SkMatrix& viewMatrix, const SkSurfaceProps& props,
318 const char text[], size_t byteLength, SkScalar x, SkScalar y,
319 const SkIRect& regionClipBounds) {
320 if (context->abandoned()) {
321 return;
322 }
323
324 auto glyphCache = context->contextPriv().getGlyphCache();
325 auto restrictedAtlasManager = context->contextPriv().getRestrictedAtlasManager();
326 auto textBlobCache = context->contextPriv().getTextBlobCache();
327
328 GrTextUtils::Paint paint(&skPaint, &target->colorSpaceInfo());
329 sk_sp<GrAtlasTextBlob> blob(
330 this->makeDrawTextBlob(textBlobCache, glyphCache,
331 *context->caps()->shaderCaps(), paint,
332 ComputeScalerContextFlags(target->colorSpaceInfo()),
333 viewMatrix, props, text, byteLength, x, y));
334 if (blob) {
335 blob->flush(restrictedAtlasManager, target, props, fDistanceAdjustTable.get(), paint,
336 clip, viewMatrix, regionClipBounds, x, y);
337 }
338 }
339
drawPosText(GrContext * context,GrTextUtils::Target * target,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset,const SkIRect & regionClipBounds)340 void GrAtlasTextContext::drawPosText(GrContext* context, GrTextUtils::Target* target,
341 const GrClip& clip, const SkPaint& skPaint,
342 const SkMatrix& viewMatrix, const SkSurfaceProps& props,
343 const char text[], size_t byteLength, const SkScalar pos[],
344 int scalarsPerPosition, const SkPoint& offset,
345 const SkIRect& regionClipBounds) {
346 GrTextUtils::Paint paint(&skPaint, &target->colorSpaceInfo());
347 if (context->abandoned()) {
348 return;
349 }
350
351 auto glyphCache = context->contextPriv().getGlyphCache();
352 auto restrictedAtlasManager = context->contextPriv().getRestrictedAtlasManager();
353 auto textBlobCache = context->contextPriv().getTextBlobCache();
354
355 sk_sp<GrAtlasTextBlob> blob(this->makeDrawPosTextBlob(
356 textBlobCache, glyphCache,
357 *context->caps()->shaderCaps(), paint,
358 ComputeScalerContextFlags(target->colorSpaceInfo()), viewMatrix, props, text,
359 byteLength, pos, scalarsPerPosition, offset));
360 if (blob) {
361 blob->flush(restrictedAtlasManager, target, props, fDistanceAdjustTable.get(), paint,
362 clip, viewMatrix, regionClipBounds, offset.fX, offset.fY);
363 }
364 }
365
DrawBmpText(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y)366 void GrAtlasTextContext::DrawBmpText(GrAtlasTextBlob* blob, int runIndex,
367 GrGlyphCache* glyphCache, const SkSurfaceProps& props,
368 const GrTextUtils::Paint& paint,
369 SkScalerContextFlags scalerContextFlags,
370 const SkMatrix& viewMatrix, const char text[],
371 size_t byteLength, SkScalar x, SkScalar y) {
372 SkASSERT(byteLength == 0 || text != nullptr);
373
374 // nothing to draw
375 if (text == nullptr || byteLength == 0) {
376 return;
377 }
378
379 // Ensure the blob is set for bitmaptext
380 blob->setHasBitmap();
381
382 if (SkDraw::ShouldDrawTextAsPaths(paint, viewMatrix)) {
383 DrawBmpTextAsPaths(blob, runIndex, glyphCache, props, paint, scalerContextFlags, viewMatrix,
384 text, byteLength, x, y);
385 return;
386 }
387
388 sk_sp<GrTextStrike> currStrike;
389 SkGlyphCache* cache = blob->setupCache(runIndex, props, scalerContextFlags, paint, &viewMatrix);
390 SkFindAndPlaceGlyph::ProcessText(paint.skPaint().getTextEncoding(), text, byteLength, {x, y},
391 viewMatrix, paint.skPaint().getTextAlign(), cache,
392 [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) {
393 position += rounding;
394 BmpAppendGlyph(blob, runIndex, glyphCache, &currStrike,
395 glyph, SkScalarFloorToScalar(position.fX),
396 SkScalarFloorToScalar(position.fY),
397 paint.filteredPremulColor(), cache,
398 SK_Scalar1);
399 });
400
401 SkGlyphCache::AttachCache(cache);
402 }
403
DrawBmpPosText(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset)404 void GrAtlasTextContext::DrawBmpPosText(GrAtlasTextBlob* blob, int runIndex,
405 GrGlyphCache* glyphCache, const SkSurfaceProps& props,
406 const GrTextUtils::Paint& paint,
407 SkScalerContextFlags scalerContextFlags,
408 const SkMatrix& viewMatrix,
409 const char text[], size_t byteLength, const SkScalar pos[],
410 int scalarsPerPosition, const SkPoint& offset) {
411 SkASSERT(byteLength == 0 || text != nullptr);
412 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
413
414 // nothing to draw
415 if (text == nullptr || byteLength == 0) {
416 return;
417 }
418
419 // Ensure the blob is set for bitmaptext
420 blob->setHasBitmap();
421
422 if (SkDraw::ShouldDrawTextAsPaths(paint, viewMatrix)) {
423 DrawBmpPosTextAsPaths(blob, runIndex, glyphCache, props, paint, scalerContextFlags,
424 viewMatrix, text, byteLength, pos, scalarsPerPosition, offset);
425 return;
426 }
427
428 sk_sp<GrTextStrike> currStrike;
429 SkGlyphCache* cache = blob->setupCache(runIndex, props, scalerContextFlags, paint, &viewMatrix);
430 SkFindAndPlaceGlyph::ProcessPosText(
431 paint.skPaint().getTextEncoding(), text, byteLength, offset, viewMatrix, pos,
432 scalarsPerPosition, paint.skPaint().getTextAlign(), cache,
433 [&](const SkGlyph& glyph, SkPoint position, SkPoint rounding) {
434 position += rounding;
435 BmpAppendGlyph(blob, runIndex, glyphCache, &currStrike, glyph,
436 SkScalarFloorToScalar(position.fX),
437 SkScalarFloorToScalar(position.fY),
438 paint.filteredPremulColor(), cache, SK_Scalar1);
439 });
440
441 SkGlyphCache::AttachCache(cache);
442 }
443
DrawBmpTextAsPaths(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & origPaint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y)444 void GrAtlasTextContext::DrawBmpTextAsPaths(GrAtlasTextBlob* blob, int runIndex,
445 GrGlyphCache* glyphCache,
446 const SkSurfaceProps& props,
447 const GrTextUtils::Paint& origPaint,
448 SkScalerContextFlags scalerContextFlags,
449 const SkMatrix& viewMatrix, const char text[],
450 size_t byteLength, SkScalar x, SkScalar y) {
451 // nothing to draw
452 if (text == nullptr || byteLength == 0) {
453 return;
454 }
455
456 // Temporarily jam in kFill, so we only ever ask for the raw outline from the cache.
457 SkPaint pathPaint(origPaint);
458 pathPaint.setStyle(SkPaint::kFill_Style);
459 pathPaint.setPathEffect(nullptr);
460
461 GrTextUtils::PathTextIter iter(text, byteLength, pathPaint, true);
462 FallbackTextHelper fallbackTextHelper(viewMatrix, pathPaint, glyphCache, iter.getPathScale());
463
464 const SkGlyph* iterGlyph;
465 const SkPath* iterPath;
466 SkScalar xpos = 0;
467 const char* lastText = text;
468 while (iter.next(&iterGlyph, &iterPath, &xpos)) {
469 if (iterGlyph) {
470 SkPoint pos = SkPoint::Make(xpos + x, y);
471 fallbackTextHelper.appendText(*iterGlyph, iter.getText() - lastText, lastText, pos);
472 } else if (iterPath) {
473 blob->appendPathGlyph(runIndex, *iterPath, xpos + x, y, iter.getPathScale(), false);
474 }
475 lastText = iter.getText();
476 }
477
478 fallbackTextHelper.drawText(blob, runIndex, glyphCache, props, origPaint, scalerContextFlags);
479 }
480
DrawBmpPosTextAsPaths(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & origPaint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset)481 void GrAtlasTextContext::DrawBmpPosTextAsPaths(GrAtlasTextBlob* blob, int runIndex,
482 GrGlyphCache* glyphCache,
483 const SkSurfaceProps& props,
484 const GrTextUtils::Paint& origPaint,
485 SkScalerContextFlags scalerContextFlags,
486 const SkMatrix& viewMatrix,
487 const char text[], size_t byteLength,
488 const SkScalar pos[], int scalarsPerPosition,
489 const SkPoint& offset) {
490 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
491
492 // nothing to draw
493 if (text == nullptr || byteLength == 0) {
494 return;
495 }
496
497 // setup our std paint, in hopes of getting hits in the cache
498 SkPaint pathPaint(origPaint);
499 SkScalar matrixScale = pathPaint.setupForAsPaths();
500 FallbackTextHelper fallbackTextHelper(viewMatrix, origPaint, glyphCache, matrixScale);
501
502 // Temporarily jam in kFill, so we only ever ask for the raw outline from the cache.
503 pathPaint.setStyle(SkPaint::kFill_Style);
504 pathPaint.setPathEffect(nullptr);
505
506 SkPaint::GlyphCacheProc glyphCacheProc = SkPaint::GetGlyphCacheProc(pathPaint.getTextEncoding(),
507 pathPaint.isDevKernText(),
508 true);
509 SkAutoGlyphCache autoCache(pathPaint, &props, nullptr);
510 SkGlyphCache* cache = autoCache.getCache();
511
512 const char* stop = text + byteLength;
513 const char* lastText = text;
514 SkTextAlignProc alignProc(pathPaint.getTextAlign());
515 SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);
516
517 while (text < stop) {
518 const SkGlyph& glyph = glyphCacheProc(cache, &text);
519 if (glyph.fWidth) {
520 SkPoint tmsLoc;
521 tmsProc(pos, &tmsLoc);
522 SkPoint loc;
523 alignProc(tmsLoc, glyph, &loc);
524 if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
525 fallbackTextHelper.appendText(glyph, text - lastText, lastText, loc);
526 } else {
527 const SkPath* path = cache->findPath(glyph);
528 if (path) {
529 blob->appendPathGlyph(runIndex, *path, loc.fX, loc.fY, matrixScale, false);
530 }
531 }
532 }
533 lastText = text;
534 pos += scalarsPerPosition;
535 }
536
537 fallbackTextHelper.drawText(blob, runIndex, glyphCache, props, origPaint, scalerContextFlags);
538 }
539
BmpAppendGlyph(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * grGlyphCache,sk_sp<GrTextStrike> * strike,const SkGlyph & skGlyph,SkScalar sx,SkScalar sy,GrColor color,SkGlyphCache * skGlyphCache,SkScalar textRatio)540 void GrAtlasTextContext::BmpAppendGlyph(GrAtlasTextBlob* blob, int runIndex,
541 GrGlyphCache* grGlyphCache,
542 sk_sp<GrTextStrike>* strike,
543 const SkGlyph& skGlyph, SkScalar sx, SkScalar sy,
544 GrColor color, SkGlyphCache* skGlyphCache,
545 SkScalar textRatio) {
546 if (!*strike) {
547 *strike = grGlyphCache->getStrike(skGlyphCache);
548 }
549
550 GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(),
551 skGlyph.getSubXFixed(),
552 skGlyph.getSubYFixed(),
553 GrGlyph::kCoverage_MaskStyle);
554 GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, skGlyphCache);
555 if (!glyph) {
556 return;
557 }
558
559 SkASSERT(skGlyph.fWidth == glyph->width());
560 SkASSERT(skGlyph.fHeight == glyph->height());
561
562 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft);
563 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop);
564 SkScalar width = SkIntToScalar(glyph->fBounds.width());
565 SkScalar height = SkIntToScalar(glyph->fBounds.height());
566
567 dx *= textRatio;
568 dy *= textRatio;
569 width *= textRatio;
570 height *= textRatio;
571
572 SkRect glyphRect = SkRect::MakeXYWH(sx + dx, sy + dy, width, height);
573
574 blob->appendGlyph(runIndex, glyphRect, color, *strike, glyph, skGlyphCache, skGlyph, sx, sy,
575 textRatio, true);
576 }
577
canDrawAsDistanceFields(const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const GrShaderCaps & caps) const578 bool GrAtlasTextContext::canDrawAsDistanceFields(const SkPaint& skPaint, const SkMatrix& viewMatrix,
579 const SkSurfaceProps& props,
580 const GrShaderCaps& caps) const {
581 if (!viewMatrix.hasPerspective()) {
582 SkScalar maxScale = viewMatrix.getMaxScale();
583 SkScalar scaledTextSize = maxScale * skPaint.getTextSize();
584 // Hinted text looks far better at small resolutions
585 // Scaling up beyond 2x yields undesireable artifacts
586 if (scaledTextSize < fMinDistanceFieldFontSize ||
587 scaledTextSize > fMaxDistanceFieldFontSize) {
588 return false;
589 }
590
591 bool useDFT = props.isUseDeviceIndependentFonts();
592 #if SK_FORCE_DISTANCE_FIELD_TEXT
593 useDFT = true;
594 #endif
595
596 if (!useDFT && scaledTextSize < kLargeDFFontSize) {
597 return false;
598 }
599 }
600
601 // mask filters modify alpha, which doesn't translate well to distance
602 if (skPaint.getMaskFilter() || !caps.shaderDerivativeSupport()) {
603 return false;
604 }
605
606 // TODO: add some stroking support
607 if (skPaint.getStyle() != SkPaint::kFill_Style) {
608 return false;
609 }
610
611 return true;
612 }
613
initDistanceFieldPaint(GrAtlasTextBlob * blob,SkPaint * skPaint,SkScalar * textRatio,const SkMatrix & viewMatrix) const614 void GrAtlasTextContext::initDistanceFieldPaint(GrAtlasTextBlob* blob,
615 SkPaint* skPaint,
616 SkScalar* textRatio,
617 const SkMatrix& viewMatrix) const {
618 SkScalar textSize = skPaint->getTextSize();
619 SkScalar scaledTextSize = textSize;
620
621 if (viewMatrix.hasPerspective()) {
622 // for perspective, we simply force to the medium size
623 // TODO: compute a size based on approximate screen area
624 scaledTextSize = kMediumDFFontLimit;
625 } else {
626 SkScalar maxScale = viewMatrix.getMaxScale();
627 // if we have non-unity scale, we need to choose our base text size
628 // based on the SkPaint's text size multiplied by the max scale factor
629 // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
630 if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
631 scaledTextSize *= maxScale;
632 }
633 }
634
635 // We have three sizes of distance field text, and within each size 'bucket' there is a floor
636 // and ceiling. A scale outside of this range would require regenerating the distance fields
637 SkScalar dfMaskScaleFloor;
638 SkScalar dfMaskScaleCeil;
639 if (scaledTextSize <= kSmallDFFontLimit) {
640 dfMaskScaleFloor = fMinDistanceFieldFontSize;
641 dfMaskScaleCeil = kSmallDFFontLimit;
642 *textRatio = textSize / kSmallDFFontSize;
643 skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize));
644 } else if (scaledTextSize <= kMediumDFFontLimit) {
645 dfMaskScaleFloor = kSmallDFFontLimit;
646 dfMaskScaleCeil = kMediumDFFontLimit;
647 *textRatio = textSize / kMediumDFFontSize;
648 skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize));
649 } else {
650 dfMaskScaleFloor = kMediumDFFontLimit;
651 dfMaskScaleCeil = fMaxDistanceFieldFontSize;
652 *textRatio = textSize / kLargeDFFontSize;
653 skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize));
654 }
655
656 // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
657 // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale
658 // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
659 // tolerate before we'd have to move to a large mip size. When we actually test these values
660 // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
661 // against these values to decide if we can reuse or not(ie, will a given scale change our mip
662 // level)
663 SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
664 blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize);
665
666 skPaint->setAntiAlias(true);
667 skPaint->setLCDRenderText(false);
668 skPaint->setAutohinted(false);
669 skPaint->setHinting(SkPaint::kNormal_Hinting);
670 skPaint->setSubpixelText(true);
671 }
672
drawDFText(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const char text[],size_t byteLength,SkScalar x,SkScalar y) const673 void GrAtlasTextContext::drawDFText(GrAtlasTextBlob* blob, int runIndex,
674 GrGlyphCache* glyphCache, const SkSurfaceProps& props,
675 const GrTextUtils::Paint& paint,
676 SkScalerContextFlags scalerContextFlags,
677 const SkMatrix& viewMatrix, const char text[],
678 size_t byteLength, SkScalar x, SkScalar y) const {
679 SkASSERT(byteLength == 0 || text != nullptr);
680
681 // nothing to draw
682 if (text == nullptr || byteLength == 0) {
683 return;
684 }
685
686 const SkPaint& skPaint = paint.skPaint();
687 SkPaint::GlyphCacheProc glyphCacheProc =
688 SkPaint::GetGlyphCacheProc(skPaint.getTextEncoding(), skPaint.isDevKernText(), true);
689 SkAutoDescriptor desc;
690 SkScalerContextEffects effects;
691 // We apply the fake-gamma by altering the distance in the shader, so we ignore the
692 // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
693 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
694 skPaint, &props, SkScalerContextFlags::kNone, nullptr, &desc, &effects);
695 SkGlyphCache* origPaintCache =
696 SkGlyphCache::DetachCache(skPaint.getTypeface(), effects, desc.getDesc());
697
698 SkTArray<SkScalar> positions;
699
700 const char* textPtr = text;
701 SkScalar stopX = 0;
702 SkScalar stopY = 0;
703 SkScalar origin = 0;
704 switch (skPaint.getTextAlign()) {
705 case SkPaint::kRight_Align: origin = SK_Scalar1; break;
706 case SkPaint::kCenter_Align: origin = SK_ScalarHalf; break;
707 case SkPaint::kLeft_Align: origin = 0; break;
708 }
709
710 SkAutoKern autokern;
711 const char* stop = text + byteLength;
712 while (textPtr < stop) {
713 // don't need x, y here, since all subpixel variants will have the
714 // same advance
715 const SkGlyph& glyph = glyphCacheProc(origPaintCache, &textPtr);
716
717 SkScalar width = SkFloatToScalar(glyph.fAdvanceX) + autokern.adjust(glyph);
718 positions.push_back(stopX + origin * width);
719
720 SkScalar height = SkFloatToScalar(glyph.fAdvanceY);
721 positions.push_back(stopY + origin * height);
722
723 stopX += width;
724 stopY += height;
725 }
726 SkASSERT(textPtr == stop);
727
728 SkGlyphCache::AttachCache(origPaintCache);
729
730 // now adjust starting point depending on alignment
731 SkScalar alignX = stopX;
732 SkScalar alignY = stopY;
733 if (skPaint.getTextAlign() == SkPaint::kCenter_Align) {
734 alignX = SkScalarHalf(alignX);
735 alignY = SkScalarHalf(alignY);
736 } else if (skPaint.getTextAlign() == SkPaint::kLeft_Align) {
737 alignX = 0;
738 alignY = 0;
739 }
740 x -= alignX;
741 y -= alignY;
742 SkPoint offset = SkPoint::Make(x, y);
743
744 this->drawDFPosText(blob, runIndex, glyphCache, props, paint, scalerContextFlags, viewMatrix,
745 text, byteLength, positions.begin(), 2, offset);
746 }
747
drawDFPosText(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset) const748 void GrAtlasTextContext::drawDFPosText(GrAtlasTextBlob* blob, int runIndex,
749 GrGlyphCache* glyphCache, const SkSurfaceProps& props,
750 const GrTextUtils::Paint& paint,
751 SkScalerContextFlags scalerContextFlags,
752 const SkMatrix& viewMatrix, const char text[],
753 size_t byteLength, const SkScalar pos[],
754 int scalarsPerPosition, const SkPoint& offset) const {
755 SkASSERT(byteLength == 0 || text != nullptr);
756 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
757
758 // nothing to draw
759 if (text == nullptr || byteLength == 0) {
760 return;
761 }
762
763 bool hasWCoord = viewMatrix.hasPerspective() || fDistanceFieldVerticesAlwaysHaveW;
764
765 // Setup distance field paint and text ratio
766 SkScalar textRatio;
767 SkPaint dfPaint(paint);
768 this->initDistanceFieldPaint(blob, &dfPaint, &textRatio, viewMatrix);
769 blob->setHasDistanceField();
770 blob->setSubRunHasDistanceFields(runIndex, paint.skPaint().isLCDRenderText(),
771 paint.skPaint().isAntiAlias(), hasWCoord);
772
773 FallbackTextHelper fallbackTextHelper(viewMatrix, paint, glyphCache, textRatio);
774
775 sk_sp<GrTextStrike> currStrike;
776
777 // We apply the fake-gamma by altering the distance in the shader, so we ignore the
778 // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
779 SkGlyphCache* cache =
780 blob->setupCache(runIndex, props, SkScalerContextFlags::kNone, dfPaint, nullptr);
781 SkPaint::GlyphCacheProc glyphCacheProc =
782 SkPaint::GetGlyphCacheProc(dfPaint.getTextEncoding(), dfPaint.isDevKernText(), true);
783
784 const char* stop = text + byteLength;
785
786 SkPaint::Align align = dfPaint.getTextAlign();
787 SkScalar alignMul = SkPaint::kCenter_Align == align ? SK_ScalarHalf :
788 (SkPaint::kRight_Align == align ? SK_Scalar1 : 0);
789 while (text < stop) {
790 const char* lastText = text;
791 // the last 2 parameters are ignored
792 const SkGlyph& glyph = glyphCacheProc(cache, &text);
793
794 if (glyph.fWidth) {
795 SkPoint glyphPos(offset);
796 glyphPos.fX += pos[0] - SkFloatToScalar(glyph.fAdvanceX) * alignMul * textRatio;
797 glyphPos.fY += (2 == scalarsPerPosition ? pos[1] : 0) -
798 SkFloatToScalar(glyph.fAdvanceY) * alignMul * textRatio;
799
800 if (glyph.fMaskFormat != SkMask::kARGB32_Format) {
801 DfAppendGlyph(blob, runIndex, glyphCache, &currStrike, glyph, glyphPos.fX,
802 glyphPos.fY, paint.filteredPremulColor(), cache, textRatio);
803 } else {
804 // can't append color glyph to SDF batch, send to fallback
805 fallbackTextHelper.appendText(glyph, SkToInt(text - lastText), lastText, glyphPos);
806 }
807 }
808 pos += scalarsPerPosition;
809 }
810
811 SkGlyphCache::AttachCache(cache);
812
813 fallbackTextHelper.drawText(blob, runIndex, glyphCache, props, paint, scalerContextFlags);
814 }
815
816 // TODO: merge with BmpAppendGlyph
DfAppendGlyph(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * grGlyphCache,sk_sp<GrTextStrike> * strike,const SkGlyph & skGlyph,SkScalar sx,SkScalar sy,GrColor color,SkGlyphCache * skGlyphCache,SkScalar textRatio)817 void GrAtlasTextContext::DfAppendGlyph(GrAtlasTextBlob* blob, int runIndex,
818 GrGlyphCache* grGlyphCache, sk_sp<GrTextStrike>* strike,
819 const SkGlyph& skGlyph, SkScalar sx, SkScalar sy,
820 GrColor color, SkGlyphCache* skGlyphCache,
821 SkScalar textRatio) {
822 if (!*strike) {
823 *strike = grGlyphCache->getStrike(skGlyphCache);
824 }
825
826 GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(),
827 skGlyph.getSubXFixed(),
828 skGlyph.getSubYFixed(),
829 GrGlyph::kDistance_MaskStyle);
830 GrGlyph* glyph = (*strike)->getGlyph(skGlyph, id, skGlyphCache);
831 if (!glyph) {
832 return;
833 }
834
835 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset);
836 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset);
837 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceFieldInset);
838 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFieldInset);
839
840 dx *= textRatio;
841 dy *= textRatio;
842 width *= textRatio;
843 height *= textRatio;
844 SkRect glyphRect = SkRect::MakeXYWH(sx + dx, sy + dy, width, height);
845
846 blob->appendGlyph(runIndex, glyphRect, color, *strike, glyph, skGlyphCache, skGlyph, sx, sy,
847 textRatio, false);
848 }
849
850 ///////////////////////////////////////////////////////////////////////////////////////////////////
851
appendText(const SkGlyph & glyph,int count,const char * text,SkPoint glyphPos)852 void GrAtlasTextContext::FallbackTextHelper::appendText(const SkGlyph& glyph, int count,
853 const char* text, SkPoint glyphPos) {
854 SkScalar maxDim = SkTMax(glyph.fWidth, glyph.fHeight)*fTextRatio;
855 if (!fUseScaledFallback) {
856 SkScalar scaledGlyphSize = maxDim * fMaxScale;
857 if (!fViewMatrix.hasPerspective() && scaledGlyphSize > fMaxTextSize) {
858 fUseScaledFallback = true;
859 }
860 }
861
862 fFallbackTxt.append(count, text);
863 if (fUseScaledFallback) {
864 // If there's a glyph in the font that's particularly large, it's possible
865 // that fScaledFallbackTextSize may end up minimizing too much. We'd rather skip
866 // that glyph than make the others pixelated, so we set a minimum size of half the
867 // maximum text size to avoid this case.
868 SkScalar glyphTextSize = SkTMax(SkScalarFloorToScalar(fMaxTextSize*fTextSize / maxDim),
869 0.5f*fMaxTextSize);
870 fScaledFallbackTextSize = SkTMin(glyphTextSize, fScaledFallbackTextSize);
871 }
872 *fFallbackPos.append() = glyphPos;
873 }
874
drawText(GrAtlasTextBlob * blob,int runIndex,GrGlyphCache * glyphCache,const SkSurfaceProps & props,const GrTextUtils::Paint & paint,SkScalerContextFlags scalerContextFlags)875 void GrAtlasTextContext::FallbackTextHelper::drawText(GrAtlasTextBlob* blob, int runIndex,
876 GrGlyphCache* glyphCache,
877 const SkSurfaceProps& props,
878 const GrTextUtils::Paint& paint,
879 SkScalerContextFlags scalerContextFlags) {
880 if (fFallbackTxt.count()) {
881 blob->initOverride(runIndex);
882 blob->setHasBitmap();
883 SkGlyphCache* cache = nullptr;
884 const SkPaint& skPaint = paint.skPaint();
885 SkPaint::GlyphCacheProc glyphCacheProc =
886 SkPaint::GetGlyphCacheProc(skPaint.getTextEncoding(),
887 skPaint.isDevKernText(), true);
888 SkColor textColor = paint.filteredPremulColor();
889 SkScalar textRatio = SK_Scalar1;
890 fViewMatrix.mapPoints(fFallbackPos.begin(), fFallbackPos.count());
891 if (fUseScaledFallback) {
892 // Set up paint and matrix to scale glyphs
893 SkPaint scaledPaint(skPaint);
894 scaledPaint.setTextSize(fScaledFallbackTextSize);
895 // remove maxScale from viewMatrix and move it into textRatio
896 // this keeps the base glyph size consistent regardless of matrix scale
897 SkMatrix modMatrix(fViewMatrix);
898 SkScalar invScale = SkScalarInvert(fMaxScale);
899 modMatrix.preScale(invScale, invScale);
900 textRatio = fTextSize * fMaxScale / fScaledFallbackTextSize;
901 cache = blob->setupCache(runIndex, props, scalerContextFlags, scaledPaint,
902 &modMatrix);
903 } else {
904 cache = blob->setupCache(runIndex, props, scalerContextFlags, paint,
905 &fViewMatrix);
906 }
907
908 sk_sp<GrTextStrike> currStrike;
909 const char* text = fFallbackTxt.begin();
910 const char* stop = text + fFallbackTxt.count();
911 SkPoint* glyphPos = fFallbackPos.begin();
912 while (text < stop) {
913 const SkGlyph& glyph = glyphCacheProc(cache, &text);
914 GrAtlasTextContext::BmpAppendGlyph(blob, runIndex, glyphCache, &currStrike, glyph,
915 glyphPos->fX, glyphPos->fY, textColor,
916 cache, textRatio);
917 glyphPos++;
918 }
919
920 SkGlyphCache::AttachCache(cache);
921 }
922 }
923
924 ///////////////////////////////////////////////////////////////////////////////////////////////////
925
926 #if GR_TEST_UTILS
927
928 #include "GrRenderTargetContext.h"
929
GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp)930 GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) {
931 static uint32_t gContextID = SK_InvalidGenID;
932 static std::unique_ptr<GrAtlasTextContext> gTextContext;
933 static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
934
935 if (context->uniqueID() != gContextID) {
936 gContextID = context->uniqueID();
937 gTextContext = GrAtlasTextContext::Make(GrAtlasTextContext::Options());
938 }
939
940 // Setup dummy SkPaint / GrPaint / GrRenderTargetContext
941 sk_sp<GrRenderTargetContext> rtc(context->makeDeferredRenderTargetContext(
942 SkBackingFit::kApprox, 1024, 1024, kRGBA_8888_GrPixelConfig, nullptr));
943
944 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
945
946 // Because we the GrTextUtils::Paint requires an SkPaint for font info, we ignore the GrPaint
947 // param.
948 SkPaint skPaint;
949 skPaint.setColor(random->nextU());
950 skPaint.setLCDRenderText(random->nextBool());
951 skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
952 skPaint.setSubpixelText(random->nextBool());
953 GrTextUtils::Paint utilsPaint(&skPaint, &rtc->colorSpaceInfo());
954
955 const char* text = "The quick brown fox jumps over the lazy dog.";
956 int textLen = (int)strlen(text);
957
958 // create some random x/y offsets, including negative offsets
959 static const int kMaxTrans = 1024;
960 int xPos = (random->nextU() % 2) * 2 - 1;
961 int yPos = (random->nextU() % 2) * 2 - 1;
962 int xInt = (random->nextU() % kMaxTrans) * xPos;
963 int yInt = (random->nextU() % kMaxTrans) * yPos;
964 SkScalar x = SkIntToScalar(xInt);
965 SkScalar y = SkIntToScalar(yInt);
966
967 auto glyphCache = context->contextPriv().getGlyphCache();
968 auto restrictedAtlasManager = context->contextPriv().getRestrictedAtlasManager();
969
970 // right now we don't handle textblobs, nor do we handle drawPosText. Since we only intend to
971 // test the text op with this unit test, that is okay.
972 sk_sp<GrAtlasTextBlob> blob(gTextContext->makeDrawTextBlob(
973 context->contextPriv().getTextBlobCache(), glyphCache,
974 *context->caps()->shaderCaps(), utilsPaint,
975 GrAtlasTextContext::kTextBlobOpScalerContextFlags, viewMatrix, gSurfaceProps, text,
976 static_cast<size_t>(textLen), x, y));
977
978 return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, utilsPaint, gSurfaceProps,
979 gTextContext->dfAdjustTable(), restrictedAtlasManager,
980 rtc->textTarget());
981 }
982
983 #endif
984