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