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