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