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