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