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