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