1 /*
2 * Copyright 2014 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 #include "GrStencilAndCoverTextContext.h"
9 #include "GrAtlasTextContext.h"
10 #include "GrContext.h"
11 #include "GrPath.h"
12 #include "GrPathRange.h"
13 #include "GrPipelineBuilder.h"
14 #include "GrRenderTargetContext.h"
15 #include "GrResourceProvider.h"
16 #include "GrTextUtils.h"
17 #include "SkAutoKern.h"
18 #include "SkDraw.h"
19 #include "SkDrawFilter.h"
20 #include "SkDrawProcs.h"
21 #include "SkGlyphCache.h"
22 #include "SkGr.h"
23 #include "SkPath.h"
24 #include "SkTextBlobRunIterator.h"
25 #include "SkTextFormatParams.h"
26 #include "SkTextMapStateProc.h"
27
28 #include "ops/GrDrawPathOp.h"
29
delete_hash_map_entry(const Key &,Val * val)30 template<typename Key, typename Val> static void delete_hash_map_entry(const Key&, Val* val) {
31 SkASSERT(*val);
32 delete *val;
33 }
34
delete_hash_table_entry(T * val)35 template<typename T> static void delete_hash_table_entry(T* val) {
36 SkASSERT(*val);
37 delete *val;
38 }
39
GrStencilAndCoverTextContext(GrAtlasTextContext * fallbackTextContext)40 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(GrAtlasTextContext* fallbackTextContext)
41 : fFallbackTextContext(fallbackTextContext)
42 , fCacheSize(0) {
43 }
44
45 GrStencilAndCoverTextContext*
Create(GrAtlasTextContext * fallbackTextContext)46 GrStencilAndCoverTextContext::Create(GrAtlasTextContext* fallbackTextContext) {
47 return new GrStencilAndCoverTextContext(fallbackTextContext);;
48 }
49
~GrStencilAndCoverTextContext()50 GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
51 fBlobIdCache.foreach(delete_hash_map_entry<uint32_t, TextBlob*>);
52 fBlobKeyCache.foreach(delete_hash_table_entry<TextBlob*>);
53 }
54
internalCanDraw(const SkPaint & skPaint)55 bool GrStencilAndCoverTextContext::internalCanDraw(const SkPaint& skPaint) {
56 if (skPaint.getRasterizer()) {
57 return false;
58 }
59 if (skPaint.getMaskFilter()) {
60 return false;
61 }
62 if (SkPathEffect* pe = skPaint.getPathEffect()) {
63 if (pe->asADash(nullptr) != SkPathEffect::kDash_DashType) {
64 return false;
65 }
66 }
67 // No hairlines. They would require new paths with customized strokes for every new draw matrix.
68 return SkPaint::kStroke_Style != skPaint.getStyle() || 0 != skPaint.getStrokeWidth();
69 }
70
drawText(GrContext * context,GrRenderTargetContext * rtc,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const char text[],size_t byteLength,SkScalar x,SkScalar y,const SkIRect & clipBounds)71 void GrStencilAndCoverTextContext::drawText(GrContext* context, GrRenderTargetContext* rtc,
72 const GrClip& clip, const SkPaint& skPaint,
73 const SkMatrix& viewMatrix, const SkSurfaceProps& props,
74 const char text[], size_t byteLength, SkScalar x,
75 SkScalar y, const SkIRect& clipBounds) {
76 if (context->abandoned()) {
77 return;
78 } else if (this->canDraw(skPaint, viewMatrix)) {
79 if (skPaint.getTextSize() > 0) {
80 TextRun run(skPaint);
81 run.setText(text, byteLength, x, y);
82 run.draw(context, rtc, clip, viewMatrix, props, 0, 0, clipBounds, fFallbackTextContext,
83 skPaint);
84 }
85 return;
86 } else if (fFallbackTextContext->canDraw(skPaint, viewMatrix, props,
87 *context->caps()->shaderCaps())) {
88 fFallbackTextContext->drawText(context, rtc, clip, skPaint, viewMatrix, props, text,
89 byteLength, x, y, clipBounds);
90 return;
91 }
92
93 // fall back to drawing as a path
94 GrTextUtils::DrawTextAsPath(context, rtc, clip, skPaint, viewMatrix, text, byteLength, x, y,
95 clipBounds);
96 }
97
drawPosText(GrContext * context,GrRenderTargetContext * rtc,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 & clipBounds)98 void GrStencilAndCoverTextContext::drawPosText(GrContext* context, GrRenderTargetContext* rtc,
99 const GrClip& clip, const SkPaint& skPaint,
100 const SkMatrix& viewMatrix,
101 const SkSurfaceProps& props, const char text[],
102 size_t byteLength, const SkScalar pos[],
103 int scalarsPerPosition, const SkPoint& offset,
104 const SkIRect& clipBounds) {
105 if (context->abandoned()) {
106 return;
107 } else if (this->canDraw(skPaint, viewMatrix)) {
108 if (skPaint.getTextSize() > 0) {
109 TextRun run(skPaint);
110 run.setPosText(text, byteLength, pos, scalarsPerPosition, offset);
111 run.draw(context, rtc, clip, viewMatrix, props, 0, 0, clipBounds, fFallbackTextContext,
112 skPaint);
113 }
114 return;
115 } else if (fFallbackTextContext->canDraw(skPaint, viewMatrix, props,
116 *context->caps()->shaderCaps())) {
117 fFallbackTextContext->drawPosText(context, rtc, clip, skPaint, viewMatrix, props, text,
118 byteLength, pos, scalarsPerPosition, offset, clipBounds);
119 return;
120 }
121
122 // fall back to drawing as a path
123 GrTextUtils::DrawPosTextAsPath(context, rtc, props, clip, skPaint, viewMatrix, text,
124 byteLength, pos, scalarsPerPosition, offset, clipBounds);
125 }
126
uncachedDrawTextBlob(GrContext * context,GrRenderTargetContext * rtc,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkTextBlob * blob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter,const SkIRect & clipBounds)127 void GrStencilAndCoverTextContext::uncachedDrawTextBlob(GrContext* context,
128 GrRenderTargetContext* rtc,
129 const GrClip& clip,
130 const SkPaint& skPaint,
131 const SkMatrix& viewMatrix,
132 const SkSurfaceProps& props,
133 const SkTextBlob* blob,
134 SkScalar x, SkScalar y,
135 SkDrawFilter* drawFilter,
136 const SkIRect& clipBounds) {
137 GrTextUtils::Paint paint(&skPaint);
138 GrTextUtils::RunPaint runPaint(&paint, drawFilter, props);
139 SkTextBlobRunIterator it(blob);
140 for (;!it.done(); it.next()) {
141 if (!runPaint.modifyForRun(it)) {
142 continue;
143 }
144 size_t textLen = it.glyphCount() * sizeof(uint16_t);
145 const SkPoint& offset = it.offset();
146
147 switch (it.positioning()) {
148 case SkTextBlob::kDefault_Positioning:
149 this->drawText(context, rtc, clip, runPaint, viewMatrix, props,
150 (const char*)it.glyphs(), textLen, x + offset.x(), y + offset.y(),
151 clipBounds);
152 break;
153 case SkTextBlob::kHorizontal_Positioning:
154 this->drawPosText(context, rtc, clip, runPaint, viewMatrix, props,
155 (const char*)it.glyphs(), textLen, it.pos(), 1,
156 SkPoint::Make(x, y + offset.y()), clipBounds);
157 break;
158 case SkTextBlob::kFull_Positioning:
159 this->drawPosText(context, rtc, clip, runPaint, viewMatrix, props,
160 (const char*)it.glyphs(), textLen, it.pos(), 2,
161 SkPoint::Make(x, y), clipBounds);
162 break;
163 }
164 }
165 }
166
drawTextBlob(GrContext * context,GrRenderTargetContext * rtc,const GrClip & clip,const SkPaint & skPaint,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkTextBlob * skBlob,SkScalar x,SkScalar y,SkDrawFilter * drawFilter,const SkIRect & clipBounds)167 void GrStencilAndCoverTextContext::drawTextBlob(GrContext* context, GrRenderTargetContext* rtc,
168 const GrClip& clip, const SkPaint& skPaint,
169 const SkMatrix& viewMatrix,
170 const SkSurfaceProps& props,
171 const SkTextBlob* skBlob, SkScalar x, SkScalar y,
172 SkDrawFilter* drawFilter,
173 const SkIRect& clipBounds) {
174 if (context->abandoned()) {
175 return;
176 }
177
178 if (!this->internalCanDraw(skPaint)) {
179 fFallbackTextContext->drawTextBlob(context, rtc, clip, skPaint, viewMatrix, props, skBlob,
180 x, y, drawFilter, clipBounds);
181 return;
182 }
183
184 if (drawFilter || skPaint.getPathEffect()) {
185 // This draw can't be cached.
186 this->uncachedDrawTextBlob(context, rtc, clip, skPaint, viewMatrix, props, skBlob, x, y,
187 drawFilter, clipBounds);
188 return;
189 }
190
191 const TextBlob& blob = this->findOrCreateTextBlob(skBlob, skPaint);
192
193 TextBlob::Iter iter(blob);
194 for (TextRun *run = iter.get(), *nextRun; run; run = nextRun) {
195 nextRun = iter.next();
196 run->draw(context, rtc, clip, viewMatrix, props, x, y, clipBounds, fFallbackTextContext,
197 skPaint);
198 run->releaseGlyphCache();
199 }
200 }
201
style_key_cnt(const GrStyle & style)202 static inline int style_key_cnt(const GrStyle& style) {
203 int cnt = GrStyle::KeySize(style, GrStyle::Apply::kPathEffectAndStrokeRec);
204 // We should be able to make a key because we filtered out arbitrary path effects.
205 SkASSERT(cnt > 0);
206 return cnt;
207 }
208
write_style_key(uint32_t * dst,const GrStyle & style)209 static inline void write_style_key(uint32_t* dst, const GrStyle& style) {
210 // Pass 1 for the scale since the GPU will apply the style not GrStyle::applyToPath().
211 GrStyle::WriteKey(dst, style, GrStyle::Apply::kPathEffectAndStrokeRec, SK_Scalar1);
212 }
213
214 const GrStencilAndCoverTextContext::TextBlob&
findOrCreateTextBlob(const SkTextBlob * skBlob,const SkPaint & skPaint)215 GrStencilAndCoverTextContext::findOrCreateTextBlob(const SkTextBlob* skBlob,
216 const SkPaint& skPaint) {
217 // The font-related parameters are baked into the text blob and will override this skPaint, so
218 // the only remaining properties that can affect a TextBlob are the ones related to stroke.
219 if (SkPaint::kFill_Style == skPaint.getStyle()) { // Fast path.
220 if (TextBlob** found = fBlobIdCache.find(skBlob->uniqueID())) {
221 fLRUList.remove(*found);
222 fLRUList.addToTail(*found);
223 return **found;
224 }
225 TextBlob* blob = new TextBlob(skBlob->uniqueID(), skBlob, skPaint);
226 this->purgeToFit(*blob);
227 fBlobIdCache.set(skBlob->uniqueID(), blob);
228 fLRUList.addToTail(blob);
229 fCacheSize += blob->cpuMemorySize();
230 return *blob;
231 } else {
232 GrStyle style(skPaint);
233 SkSTArray<4, uint32_t, true> key;
234 key.reset(1 + style_key_cnt(style));
235 key[0] = skBlob->uniqueID();
236 write_style_key(&key[1], style);
237 if (TextBlob** found = fBlobKeyCache.find(key)) {
238 fLRUList.remove(*found);
239 fLRUList.addToTail(*found);
240 return **found;
241 }
242 TextBlob* blob = new TextBlob(key, skBlob, skPaint);
243 this->purgeToFit(*blob);
244 fBlobKeyCache.set(blob);
245 fLRUList.addToTail(blob);
246 fCacheSize += blob->cpuMemorySize();
247 return *blob;
248 }
249 }
250
purgeToFit(const TextBlob & blob)251 void GrStencilAndCoverTextContext::purgeToFit(const TextBlob& blob) {
252 static const size_t maxCacheSize = 4 * 1024 * 1024; // Allow up to 4 MB for caching text blobs.
253
254 size_t maxSizeForNewBlob = maxCacheSize - blob.cpuMemorySize();
255 while (fCacheSize && fCacheSize > maxSizeForNewBlob) {
256 TextBlob* lru = fLRUList.head();
257 if (1 == lru->key().count()) {
258 // 1-length keys are unterstood to be the blob id.
259 fBlobIdCache.remove(lru->key()[0]);
260 } else {
261 fBlobKeyCache.remove(lru->key());
262 }
263 fLRUList.remove(lru);
264 fCacheSize -= lru->cpuMemorySize();
265 delete lru;
266 }
267 }
268
269 ////////////////////////////////////////////////////////////////////////////////////////////////////
270
init(const SkTextBlob * skBlob,const SkPaint & skPaint)271 void GrStencilAndCoverTextContext::TextBlob::init(const SkTextBlob* skBlob,
272 const SkPaint& skPaint) {
273 fCpuMemorySize = sizeof(TextBlob);
274 SkPaint runPaint(skPaint);
275 for (SkTextBlobRunIterator iter(skBlob); !iter.done(); iter.next()) {
276 iter.applyFontToPaint(&runPaint); // No need to re-seed the paint.
277 if (runPaint.getTextSize() <= 0) {
278 continue;
279 }
280 TextRun* run = this->addToTail(runPaint);
281
282 const char* text = reinterpret_cast<const char*>(iter.glyphs());
283 size_t byteLength = sizeof(uint16_t) * iter.glyphCount();
284 const SkPoint& runOffset = iter.offset();
285
286 switch (iter.positioning()) {
287 case SkTextBlob::kDefault_Positioning:
288 run->setText(text, byteLength, runOffset.fX, runOffset.fY);
289 break;
290 case SkTextBlob::kHorizontal_Positioning:
291 run->setPosText(text, byteLength, iter.pos(), 1, SkPoint::Make(0, runOffset.fY));
292 break;
293 case SkTextBlob::kFull_Positioning:
294 run->setPosText(text, byteLength, iter.pos(), 2, SkPoint::Make(0, 0));
295 break;
296 }
297
298 fCpuMemorySize += run->computeSizeInCache();
299 }
300 }
301
302 ////////////////////////////////////////////////////////////////////////////////////////////////////
303
304 class GrStencilAndCoverTextContext::FallbackBlobBuilder {
305 public:
FallbackBlobBuilder()306 FallbackBlobBuilder() : fBuffIdx(0), fCount(0) {}
307
isInitialized() const308 bool isInitialized() const { return fBuilder != nullptr; }
309
310 void init(const SkPaint& font, SkScalar textRatio);
311
312 void appendGlyph(uint16_t glyphId, const SkPoint& pos);
313
314 sk_sp<SkTextBlob> makeIfNeeded(int* count);
315
316 private:
317 enum { kWriteBufferSize = 1024 };
318
319 void flush();
320
321 std::unique_ptr<SkTextBlobBuilder> fBuilder;
322 SkPaint fFont;
323 int fBuffIdx;
324 int fCount;
325 uint16_t fGlyphIds[kWriteBufferSize];
326 SkPoint fPositions[kWriteBufferSize];
327 };
328
329 ////////////////////////////////////////////////////////////////////////////////////////////////////
330
TextRun(const SkPaint & fontAndStroke)331 GrStencilAndCoverTextContext::TextRun::TextRun(const SkPaint& fontAndStroke)
332 : fStyle(fontAndStroke)
333 , fFont(fontAndStroke)
334 , fTotalGlyphCount(0)
335 , fFallbackGlyphCount(0)
336 , fDetachedGlyphCache(nullptr)
337 , fLastDrawnGlyphsID(SK_InvalidUniqueID) {
338 SkASSERT(fFont.getTextSize() > 0);
339 SkASSERT(!fStyle.hasNonDashPathEffect()); // Arbitrary path effects not supported.
340 SkASSERT(!fStyle.isSimpleHairline()); // Hairlines are not supported.
341
342 // Setting to "fill" ensures that no strokes get baked into font outlines. (We use the GPU path
343 // rendering API for stroking).
344 fFont.setStyle(SkPaint::kFill_Style);
345
346 if (fFont.isFakeBoldText() && fStyle.isSimpleFill()) {
347 const SkStrokeRec& stroke = fStyle.strokeRec();
348 // Instead of letting fake bold get baked into the glyph outlines, do it with GPU stroke.
349 SkScalar fakeBoldScale = SkScalarInterpFunc(fFont.getTextSize(),
350 kStdFakeBoldInterpKeys,
351 kStdFakeBoldInterpValues,
352 kStdFakeBoldInterpLength);
353 SkScalar extra = fFont.getTextSize() * fakeBoldScale;
354
355 SkStrokeRec strokeRec(SkStrokeRec::kFill_InitStyle);
356 strokeRec.setStrokeStyle(stroke.needToApply() ? stroke.getWidth() + extra : extra,
357 true /*strokeAndFill*/);
358 fStyle = GrStyle(strokeRec, fStyle.refPathEffect());
359 fFont.setFakeBoldText(false);
360 }
361
362 if (!fFont.getPathEffect() && !fStyle.isDashed()) {
363 const SkStrokeRec& stroke = fStyle.strokeRec();
364 // We can draw the glyphs from canonically sized paths.
365 fTextRatio = fFont.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
366 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fFont.getTextSize();
367
368 // Compensate for the glyphs being scaled by fTextRatio.
369 if (!fStyle.isSimpleFill()) {
370 SkStrokeRec strokeRec(SkStrokeRec::kFill_InitStyle);
371 strokeRec.setStrokeStyle(stroke.getWidth() / fTextRatio,
372 SkStrokeRec::kStrokeAndFill_Style == stroke.getStyle());
373 fStyle = GrStyle(strokeRec, fStyle.refPathEffect());
374 }
375
376 fFont.setLinearText(true);
377 fFont.setLCDRenderText(false);
378 fFont.setAutohinted(false);
379 fFont.setHinting(SkPaint::kNo_Hinting);
380 fFont.setSubpixelText(true);
381 fFont.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
382
383 fUsingRawGlyphPaths = SK_Scalar1 == fFont.getTextScaleX() &&
384 0 == fFont.getTextSkewX() &&
385 !fFont.isFakeBoldText() &&
386 !fFont.isVerticalText();
387 } else {
388 fTextRatio = fTextInverseRatio = 1.0f;
389 fUsingRawGlyphPaths = false;
390 }
391
392 // Generate the key that will be used to cache the GPU glyph path objects.
393 if (fUsingRawGlyphPaths && fStyle.isSimpleFill()) {
394 static const GrUniqueKey::Domain kRawFillPathGlyphDomain = GrUniqueKey::GenerateDomain();
395
396 const SkTypeface* typeface = fFont.getTypeface();
397 GrUniqueKey::Builder builder(&fGlyphPathsKey, kRawFillPathGlyphDomain, 1);
398 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0;
399 } else {
400 static const GrUniqueKey::Domain kPathGlyphDomain = GrUniqueKey::GenerateDomain();
401
402 int styleDataCount = GrStyle::KeySize(fStyle, GrStyle::Apply::kPathEffectAndStrokeRec);
403 // Key should be valid since we opted out of drawing arbitrary path effects.
404 SkASSERT(styleDataCount >= 0);
405 if (fUsingRawGlyphPaths) {
406 const SkTypeface* typeface = fFont.getTypeface();
407 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain, 2 + styleDataCount);
408 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0;
409 reinterpret_cast<uint32_t&>(builder[1]) = styleDataCount;
410 if (styleDataCount) {
411 write_style_key(&builder[2], fStyle);
412 }
413 } else {
414 SkGlyphCache* glyphCache = this->getGlyphCache();
415 const SkTypeface* typeface = glyphCache->getScalerContext()->getTypeface();
416 const SkDescriptor* desc = &glyphCache->getDescriptor();
417 int descDataCount = (desc->getLength() + 3) / 4;
418 GrUniqueKey::Builder builder(&fGlyphPathsKey, kPathGlyphDomain,
419 2 + styleDataCount + descDataCount);
420 reinterpret_cast<uint32_t&>(builder[0]) = typeface ? typeface->uniqueID() : 0;
421 reinterpret_cast<uint32_t&>(builder[1]) = styleDataCount | (descDataCount << 16);
422 if (styleDataCount) {
423 write_style_key(&builder[2], fStyle);
424 }
425 memcpy(&builder[2 + styleDataCount], desc, desc->getLength());
426 }
427 }
428 }
429
~TextRun()430 GrStencilAndCoverTextContext::TextRun::~TextRun() {
431 this->releaseGlyphCache();
432 }
433
setText(const char text[],size_t byteLength,SkScalar x,SkScalar y)434 void GrStencilAndCoverTextContext::TextRun::setText(const char text[], size_t byteLength,
435 SkScalar x, SkScalar y) {
436 SkASSERT(byteLength == 0 || text != nullptr);
437
438 SkGlyphCache* glyphCache = this->getGlyphCache();
439 SkPaint::GlyphCacheProc glyphCacheProc = SkPaint::GetGlyphCacheProc(fFont.getTextEncoding(),
440 fFont.isDevKernText(),
441 true);
442
443 fTotalGlyphCount = fFont.countText(text, byteLength);
444 fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType,
445 fTotalGlyphCount));
446
447 const char* stop = text + byteLength;
448
449 // Measure first if needed.
450 if (fFont.getTextAlign() != SkPaint::kLeft_Align) {
451 SkScalar stopX = 0;
452 SkScalar stopY = 0;
453
454 const char* textPtr = text;
455 while (textPtr < stop) {
456 // We don't need x, y here, since all subpixel variants will have the
457 // same advance.
458 const SkGlyph& glyph = glyphCacheProc(glyphCache, &textPtr);
459
460 stopX += SkFloatToScalar(glyph.fAdvanceX);
461 stopY += SkFloatToScalar(glyph.fAdvanceY);
462 }
463 SkASSERT(textPtr == stop);
464
465 SkScalar alignX = stopX * fTextRatio;
466 SkScalar alignY = stopY * fTextRatio;
467
468 if (fFont.getTextAlign() == SkPaint::kCenter_Align) {
469 alignX = SkScalarHalf(alignX);
470 alignY = SkScalarHalf(alignY);
471 }
472
473 x -= alignX;
474 y -= alignY;
475 }
476
477 SkAutoKern autokern;
478
479 FallbackBlobBuilder fallback;
480 while (text < stop) {
481 const SkGlyph& glyph = glyphCacheProc(glyphCache, &text);
482 x += autokern.adjust(glyph) * fTextRatio;
483 if (glyph.fWidth) {
484 this->appendGlyph(glyph, SkPoint::Make(x, y), &fallback);
485 }
486
487 x += SkFloatToScalar(glyph.fAdvanceX) * fTextRatio;
488 y += SkFloatToScalar(glyph.fAdvanceY) * fTextRatio;
489 }
490
491 fFallbackTextBlob = fallback.makeIfNeeded(&fFallbackGlyphCount);
492 }
493
setPosText(const char text[],size_t byteLength,const SkScalar pos[],int scalarsPerPosition,const SkPoint & offset)494 void GrStencilAndCoverTextContext::TextRun::setPosText(const char text[], size_t byteLength,
495 const SkScalar pos[], int scalarsPerPosition,
496 const SkPoint& offset) {
497 SkASSERT(byteLength == 0 || text != nullptr);
498 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
499
500 SkGlyphCache* glyphCache = this->getGlyphCache();
501 SkPaint::GlyphCacheProc glyphCacheProc = SkPaint::GetGlyphCacheProc(fFont.getTextEncoding(),
502 fFont.isDevKernText(),
503 true);
504
505 fTotalGlyphCount = fFont.countText(text, byteLength);
506 fInstanceData.reset(InstanceData::Alloc(GrPathRendering::kTranslate_PathTransformType,
507 fTotalGlyphCount));
508
509 const char* stop = text + byteLength;
510
511 SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);
512 SkTextAlignProc alignProc(fFont.getTextAlign());
513 FallbackBlobBuilder fallback;
514 while (text < stop) {
515 const SkGlyph& glyph = glyphCacheProc(glyphCache, &text);
516 if (glyph.fWidth) {
517 SkPoint tmsLoc;
518 tmsProc(pos, &tmsLoc);
519 SkPoint loc;
520 alignProc(tmsLoc, glyph, &loc);
521
522 this->appendGlyph(glyph, loc, &fallback);
523 }
524 pos += scalarsPerPosition;
525 }
526
527 fFallbackTextBlob = fallback.makeIfNeeded(&fFallbackGlyphCount);
528 }
529
createGlyphs(GrResourceProvider * resourceProvider) const530 GrPathRange* GrStencilAndCoverTextContext::TextRun::createGlyphs(
531 GrResourceProvider* resourceProvider) const {
532 GrPathRange* glyphs = static_cast<GrPathRange*>(
533 resourceProvider->findAndRefResourceByUniqueKey(fGlyphPathsKey));
534 if (nullptr == glyphs) {
535 if (fUsingRawGlyphPaths) {
536 SkScalerContextEffects noeffects;
537 glyphs = resourceProvider->createGlyphs(fFont.getTypeface(), noeffects,
538 nullptr, fStyle);
539 } else {
540 SkGlyphCache* cache = this->getGlyphCache();
541 glyphs = resourceProvider->createGlyphs(cache->getScalerContext()->getTypeface(),
542 cache->getScalerContext()->getEffects(),
543 &cache->getDescriptor(),
544 fStyle);
545 }
546 resourceProvider->assignUniqueKeyToResource(fGlyphPathsKey, glyphs);
547 }
548 return glyphs;
549 }
550
appendGlyph(const SkGlyph & glyph,const SkPoint & pos,FallbackBlobBuilder * fallback)551 inline void GrStencilAndCoverTextContext::TextRun::appendGlyph(const SkGlyph& glyph,
552 const SkPoint& pos,
553 FallbackBlobBuilder* fallback) {
554 // Stick the glyphs we can't draw into the fallback text blob.
555 if (SkMask::kARGB32_Format == glyph.fMaskFormat) {
556 if (!fallback->isInitialized()) {
557 fallback->init(fFont, fTextRatio);
558 }
559 fallback->appendGlyph(glyph.getGlyphID(), pos);
560 } else {
561 fInstanceData->append(glyph.getGlyphID(), fTextInverseRatio * pos.x(),
562 fTextInverseRatio * pos.y());
563 }
564 }
565
draw(GrContext * ctx,GrRenderTargetContext * renderTargetContext,const GrClip & clip,const SkMatrix & viewMatrix,const SkSurfaceProps & props,SkScalar x,SkScalar y,const SkIRect & clipBounds,GrAtlasTextContext * fallbackTextContext,const SkPaint & originalSkPaint) const566 void GrStencilAndCoverTextContext::TextRun::draw(GrContext* ctx,
567 GrRenderTargetContext* renderTargetContext,
568 const GrClip& clip, const SkMatrix& viewMatrix,
569 const SkSurfaceProps& props, SkScalar x,
570 SkScalar y, const SkIRect& clipBounds,
571 GrAtlasTextContext* fallbackTextContext,
572 const SkPaint& originalSkPaint) const {
573 GrAA runAA = this->isAntiAlias();
574 SkASSERT(fInstanceData);
575 SkASSERT(renderTargetContext->isStencilBufferMultisampled() || GrAA::kNo == runAA);
576
577 if (fInstanceData->count()) {
578 static constexpr GrUserStencilSettings kCoverPass(
579 GrUserStencilSettings::StaticInit<
580 0x0000,
581 GrUserStencilTest::kNotEqual, // Stencil pass accounts for clip.
582 0xffff,
583 GrUserStencilOp::kZero,
584 GrUserStencilOp::kKeep,
585 0xffff>()
586 );
587
588 sk_sp<GrPathRange> glyphs(this->createGlyphs(ctx->resourceProvider()));
589 if (fLastDrawnGlyphsID != glyphs->uniqueID()) {
590 // Either this is the first draw or the glyphs object was purged since last draw.
591 glyphs->loadPathsIfNeeded(fInstanceData->indices(), fInstanceData->count());
592 fLastDrawnGlyphsID = glyphs->uniqueID();
593 }
594
595 GrPaint grPaint;
596 if (!SkPaintToGrPaint(ctx, renderTargetContext, originalSkPaint, viewMatrix, &grPaint)) {
597 return;
598 }
599
600 // Don't compute a bounding box. For dst copy texture, we'll opt instead for it to just copy
601 // the entire dst. Realistically this is a moot point, because any context that supports
602 // NV_path_rendering will also support NV_blend_equation_advanced.
603 // For clipping we'll just skip any optimizations based on the bounds. This does, however,
604 // hurt GrOp combining.
605 const SkRect bounds = SkRect::MakeIWH(renderTargetContext->width(),
606 renderTargetContext->height());
607
608 // The run's "font" overrides the anti-aliasing of the passed in SkPaint!
609 std::unique_ptr<GrDrawOp> op = GrDrawPathRangeOp::Make(
610 viewMatrix, fTextRatio, fTextInverseRatio * x, fTextInverseRatio * y,
611 std::move(grPaint), GrPathRendering::kWinding_FillType, runAA, glyphs.get(),
612 fInstanceData.get(), bounds);
613
614 renderTargetContext->addDrawOp(clip, std::move(op));
615 }
616
617 if (fFallbackTextBlob) {
618 SkPaint fallbackSkPaint(originalSkPaint);
619 fStyle.strokeRec().applyToPaint(&fallbackSkPaint);
620 if (!fStyle.isSimpleFill()) {
621 fallbackSkPaint.setStrokeWidth(fStyle.strokeRec().getWidth() * fTextRatio);
622 }
623
624 fallbackTextContext->drawTextBlob(ctx, renderTargetContext, clip, fallbackSkPaint,
625 viewMatrix, props, fFallbackTextBlob.get(), x, y, nullptr,
626 clipBounds);
627 }
628 }
629
getGlyphCache() const630 SkGlyphCache* GrStencilAndCoverTextContext::TextRun::getGlyphCache() const {
631 if (!fDetachedGlyphCache) {
632 fDetachedGlyphCache = fFont.detachCache(nullptr, SkPaint::kNone_ScalerContextFlags,
633 nullptr);
634 }
635 return fDetachedGlyphCache;
636 }
637
638
releaseGlyphCache() const639 void GrStencilAndCoverTextContext::TextRun::releaseGlyphCache() const {
640 if (fDetachedGlyphCache) {
641 SkGlyphCache::AttachCache(fDetachedGlyphCache);
642 fDetachedGlyphCache = nullptr;
643 }
644 }
645
computeSizeInCache() const646 size_t GrStencilAndCoverTextContext::TextRun::computeSizeInCache() const {
647 size_t size = sizeof(TextRun) + fGlyphPathsKey.size();
648 // The instance data always reserves enough space for every glyph.
649 size += (fTotalGlyphCount + fFallbackGlyphCount) * (sizeof(uint16_t) + 2 * sizeof(float));
650 if (fInstanceData) {
651 size += sizeof(InstanceData);
652 }
653 if (fFallbackTextBlob) {
654 size += sizeof(SkTextBlob);
655 }
656 return size;
657 }
658
659 ////////////////////////////////////////////////////////////////////////////////////////////////////
660
init(const SkPaint & font,SkScalar textRatio)661 void GrStencilAndCoverTextContext::FallbackBlobBuilder::init(const SkPaint& font,
662 SkScalar textRatio) {
663 SkASSERT(!this->isInitialized());
664 fBuilder.reset(new SkTextBlobBuilder);
665 fFont = font;
666 fFont.setTextAlign(SkPaint::kLeft_Align); // The glyph positions will already account for align.
667 fFont.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
668 // No need for subpixel positioning with bitmap glyphs. TODO: revisit if non-bitmap color glyphs
669 // show up and https://code.google.com/p/skia/issues/detail?id=4408 gets resolved.
670 fFont.setSubpixelText(false);
671 fFont.setTextSize(fFont.getTextSize() * textRatio);
672 fBuffIdx = 0;
673 }
674
appendGlyph(uint16_t glyphId,const SkPoint & pos)675 void GrStencilAndCoverTextContext::FallbackBlobBuilder::appendGlyph(uint16_t glyphId,
676 const SkPoint& pos) {
677 SkASSERT(this->isInitialized());
678 if (fBuffIdx >= kWriteBufferSize) {
679 this->flush();
680 }
681 fGlyphIds[fBuffIdx] = glyphId;
682 fPositions[fBuffIdx] = pos;
683 fBuffIdx++;
684 fCount++;
685 }
686
flush()687 void GrStencilAndCoverTextContext::FallbackBlobBuilder::flush() {
688 SkASSERT(this->isInitialized());
689 SkASSERT(fBuffIdx <= kWriteBufferSize);
690 if (!fBuffIdx) {
691 return;
692 }
693 // This will automatically merge with previous runs since we use the same font.
694 const SkTextBlobBuilder::RunBuffer& buff = fBuilder->allocRunPos(fFont, fBuffIdx);
695 memcpy(buff.glyphs, fGlyphIds, fBuffIdx * sizeof(uint16_t));
696 memcpy(buff.pos, fPositions[0].asScalars(), fBuffIdx * 2 * sizeof(SkScalar));
697 fBuffIdx = 0;
698 }
699
makeIfNeeded(int * count)700 sk_sp<SkTextBlob> GrStencilAndCoverTextContext::FallbackBlobBuilder::makeIfNeeded(int *count) {
701 *count = fCount;
702 if (fCount) {
703 this->flush();
704 return fBuilder->make();
705 }
706 return nullptr;
707 }
708