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 "SkTextBlobRunIterator.h" 9 10 #include "SkReadBuffer.h" 11 #include "SkTypeface.h" 12 #include "SkWriteBuffer.h" 13 14 namespace { 15 16 // TODO(fmalita): replace with SkFont. 17 class RunFont : SkNoncopyable { 18 public: 19 RunFont(const SkPaint& paint) 20 : fSize(paint.getTextSize()) 21 , fScaleX(paint.getTextScaleX()) 22 , fTypeface(SkSafeRef(paint.getTypeface())) 23 , fSkewX(paint.getTextSkewX()) 24 , fAlign(paint.getTextAlign()) 25 , fHinting(paint.getHinting()) 26 , fFlags(paint.getFlags() & kFlagsMask) { } 27 28 void applyToPaint(SkPaint* paint) const { 29 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); 30 paint->setTypeface(fTypeface.get()); 31 paint->setTextSize(fSize); 32 paint->setTextScaleX(fScaleX); 33 paint->setTextSkewX(fSkewX); 34 paint->setTextAlign(static_cast<SkPaint::Align>(fAlign)); 35 paint->setHinting(static_cast<SkPaint::Hinting>(fHinting)); 36 37 paint->setFlags((paint->getFlags() & ~kFlagsMask) | fFlags); 38 } 39 40 bool operator==(const RunFont& other) const { 41 return fTypeface == other.fTypeface 42 && fSize == other.fSize 43 && fScaleX == other.fScaleX 44 && fSkewX == other.fSkewX 45 && fAlign == other.fAlign 46 && fHinting == other.fHinting 47 && fFlags == other.fFlags; 48 } 49 50 bool operator!=(const RunFont& other) const { 51 return !(*this == other); 52 } 53 54 uint32_t flags() const { return fFlags; } 55 56 private: 57 const static uint32_t kFlagsMask = 58 SkPaint::kAntiAlias_Flag | 59 SkPaint::kUnderlineText_Flag | 60 SkPaint::kStrikeThruText_Flag | 61 SkPaint::kFakeBoldText_Flag | 62 SkPaint::kLinearText_Flag | 63 SkPaint::kSubpixelText_Flag | 64 SkPaint::kDevKernText_Flag | 65 SkPaint::kLCDRenderText_Flag | 66 SkPaint::kEmbeddedBitmapText_Flag | 67 SkPaint::kAutoHinting_Flag | 68 SkPaint::kVerticalText_Flag | 69 SkPaint::kGenA8FromLCD_Flag; 70 71 SkScalar fSize; 72 SkScalar fScaleX; 73 74 // Keep this SkAutoTUnref off the first position, to avoid interfering with SkNoncopyable 75 // empty baseclass optimization (http://code.google.com/p/skia/issues/detail?id=3694). 76 SkAutoTUnref<SkTypeface> fTypeface; 77 SkScalar fSkewX; 78 79 static_assert(SkPaint::kAlignCount < 4, "insufficient_align_bits"); 80 uint32_t fAlign : 2; 81 static_assert(SkPaint::kFull_Hinting < 4, "insufficient_hinting_bits"); 82 uint32_t fHinting : 2; 83 static_assert((kFlagsMask & 0xffff) == kFlagsMask, "insufficient_flags_bits"); 84 uint32_t fFlags : 16; 85 86 typedef SkNoncopyable INHERITED; 87 }; 88 89 struct RunFontStorageEquivalent { 90 SkScalar fSize, fScaleX; 91 void* fTypeface; 92 SkScalar fSkewX; 93 uint32_t fFlags; 94 }; 95 static_assert(sizeof(RunFont) == sizeof(RunFontStorageEquivalent), "runfont_should_stay_packed"); 96 97 } // anonymous namespace 98 99 // 100 // Textblob data is laid out into externally-managed storage as follows: 101 // 102 // ----------------------------------------------------------------------------- 103 // | SkTextBlob | RunRecord | Glyphs[] | Pos[] | RunRecord | Glyphs[] | Pos[] | ... 104 // ----------------------------------------------------------------------------- 105 // 106 // Each run record describes a text blob run, and can be used to determine the (implicit) 107 // location of the following record. 108 109 SkDEBUGCODE(static const unsigned kRunRecordMagic = 0xb10bcafe;) 110 111 class SkTextBlob::RunRecord { 112 public: 113 RunRecord(uint32_t count, const SkPoint& offset, const SkPaint& font, GlyphPositioning pos) 114 : fFont(font) 115 , fCount(count) 116 , fOffset(offset) 117 , fPositioning(pos) { 118 SkDEBUGCODE(fMagic = kRunRecordMagic); 119 } 120 121 uint32_t glyphCount() const { 122 return fCount; 123 } 124 125 const SkPoint& offset() const { 126 return fOffset; 127 } 128 129 const RunFont& font() const { 130 return fFont; 131 } 132 133 GlyphPositioning positioning() const { 134 return fPositioning; 135 } 136 137 uint16_t* glyphBuffer() const { 138 // Glyph are stored immediately following the record. 139 return reinterpret_cast<uint16_t*>(const_cast<RunRecord*>(this) + 1); 140 } 141 142 SkScalar* posBuffer() const { 143 // Position scalars follow the (aligned) glyph buffer. 144 return reinterpret_cast<SkScalar*>(reinterpret_cast<uint8_t*>(this->glyphBuffer()) + 145 SkAlign4(fCount * sizeof(uint16_t))); 146 } 147 148 static size_t StorageSize(int glyphCount, SkTextBlob::GlyphPositioning positioning) { 149 // RunRecord object + (aligned) glyph buffer + position buffer 150 return SkAlignPtr(sizeof(SkTextBlob::RunRecord) 151 + SkAlign4(glyphCount* sizeof(uint16_t)) 152 + glyphCount * sizeof(SkScalar) * ScalarsPerGlyph(positioning)); 153 } 154 155 static const RunRecord* First(const SkTextBlob* blob) { 156 // The first record (if present) is stored following the blob object. 157 return reinterpret_cast<const RunRecord*>(blob + 1); 158 } 159 160 static const RunRecord* Next(const RunRecord* run) { 161 return reinterpret_cast<const RunRecord*>(reinterpret_cast<const uint8_t*>(run) 162 + StorageSize(run->glyphCount(), run->positioning())); 163 } 164 165 void validate(const uint8_t* storageTop) const { 166 SkASSERT(kRunRecordMagic == fMagic); 167 SkASSERT((uint8_t*)Next(this) <= storageTop); 168 SkASSERT(glyphBuffer() + fCount <= (uint16_t*)posBuffer()); 169 SkASSERT(posBuffer() + fCount * ScalarsPerGlyph(fPositioning) <= (SkScalar*)Next(this)); 170 } 171 172 private: 173 friend class SkTextBlobBuilder; 174 175 void grow(uint32_t count) { 176 SkScalar* initialPosBuffer = posBuffer(); 177 uint32_t initialCount = fCount; 178 fCount += count; 179 180 // Move the initial pos scalars to their new location. 181 size_t copySize = initialCount * sizeof(SkScalar) * ScalarsPerGlyph(fPositioning); 182 SkASSERT((uint8_t*)posBuffer() + copySize <= (uint8_t*)Next(this)); 183 184 // memmove, as the buffers may overlap 185 memmove(posBuffer(), initialPosBuffer, copySize); 186 } 187 188 RunFont fFont; 189 uint32_t fCount; 190 SkPoint fOffset; 191 GlyphPositioning fPositioning; 192 193 SkDEBUGCODE(unsigned fMagic;) 194 }; 195 196 static int32_t gNextID = 1; 197 static int32_t next_id() { 198 int32_t id; 199 do { 200 id = sk_atomic_inc(&gNextID); 201 } while (id == SK_InvalidGenID); 202 return id; 203 } 204 205 SkTextBlob::SkTextBlob(int runCount, const SkRect& bounds) 206 : fRunCount(runCount) 207 , fBounds(bounds) 208 , fUniqueID(next_id()) { 209 } 210 211 SkTextBlob::~SkTextBlob() { 212 const RunRecord* run = RunRecord::First(this); 213 for (int i = 0; i < fRunCount; ++i) { 214 const RunRecord* nextRun = RunRecord::Next(run); 215 SkDEBUGCODE(run->validate((uint8_t*)this + fStorageSize);) 216 run->~RunRecord(); 217 run = nextRun; 218 } 219 } 220 221 void SkTextBlob::flatten(SkWriteBuffer& buffer) const { 222 int runCount = fRunCount; 223 224 buffer.write32(runCount); 225 buffer.writeRect(fBounds); 226 227 SkPaint runPaint; 228 SkTextBlobRunIterator it(this); 229 while (!it.done()) { 230 SkASSERT(it.glyphCount() > 0); 231 232 buffer.write32(it.glyphCount()); 233 buffer.write32(it.positioning()); 234 buffer.writePoint(it.offset()); 235 // This should go away when switching to SkFont 236 it.applyFontToPaint(&runPaint); 237 buffer.writePaint(runPaint); 238 239 buffer.writeByteArray(it.glyphs(), it.glyphCount() * sizeof(uint16_t)); 240 buffer.writeByteArray(it.pos(), 241 it.glyphCount() * sizeof(SkScalar) * ScalarsPerGlyph(it.positioning())); 242 243 it.next(); 244 SkDEBUGCODE(runCount--); 245 } 246 SkASSERT(0 == runCount); 247 } 248 249 const SkTextBlob* SkTextBlob::CreateFromBuffer(SkReadBuffer& reader) { 250 int runCount = reader.read32(); 251 if (runCount < 0) { 252 return nullptr; 253 } 254 255 SkRect bounds; 256 reader.readRect(&bounds); 257 258 SkTextBlobBuilder blobBuilder; 259 for (int i = 0; i < runCount; ++i) { 260 int glyphCount = reader.read32(); 261 GlyphPositioning pos = static_cast<GlyphPositioning>(reader.read32()); 262 if (glyphCount <= 0 || pos > kFull_Positioning) { 263 return nullptr; 264 } 265 266 SkPoint offset; 267 reader.readPoint(&offset); 268 SkPaint font; 269 reader.readPaint(&font); 270 271 const SkTextBlobBuilder::RunBuffer* buf = nullptr; 272 switch (pos) { 273 case kDefault_Positioning: 274 buf = &blobBuilder.allocRun(font, glyphCount, offset.x(), offset.y(), &bounds); 275 break; 276 case kHorizontal_Positioning: 277 buf = &blobBuilder.allocRunPosH(font, glyphCount, offset.y(), &bounds); 278 break; 279 case kFull_Positioning: 280 buf = &blobBuilder.allocRunPos(font, glyphCount, &bounds); 281 break; 282 default: 283 return nullptr; 284 } 285 286 if (!reader.readByteArray(buf->glyphs, glyphCount * sizeof(uint16_t)) || 287 !reader.readByteArray(buf->pos, 288 glyphCount * sizeof(SkScalar) * ScalarsPerGlyph(pos))) { 289 return nullptr; 290 } 291 } 292 293 return blobBuilder.build(); 294 } 295 296 unsigned SkTextBlob::ScalarsPerGlyph(GlyphPositioning pos) { 297 // GlyphPositioning values are directly mapped to scalars-per-glyph. 298 SkASSERT(pos <= 2); 299 return pos; 300 } 301 302 SkTextBlobRunIterator::SkTextBlobRunIterator(const SkTextBlob* blob) 303 : fCurrentRun(SkTextBlob::RunRecord::First(blob)) 304 , fRemainingRuns(blob->fRunCount) { 305 SkDEBUGCODE(fStorageTop = (uint8_t*)blob + blob->fStorageSize;) 306 } 307 308 bool SkTextBlobRunIterator::done() const { 309 return fRemainingRuns <= 0; 310 } 311 312 void SkTextBlobRunIterator::next() { 313 SkASSERT(!this->done()); 314 315 if (!this->done()) { 316 SkDEBUGCODE(fCurrentRun->validate(fStorageTop);) 317 fCurrentRun = SkTextBlob::RunRecord::Next(fCurrentRun); 318 fRemainingRuns--; 319 } 320 } 321 322 uint32_t SkTextBlobRunIterator::glyphCount() const { 323 SkASSERT(!this->done()); 324 return fCurrentRun->glyphCount(); 325 } 326 327 const uint16_t* SkTextBlobRunIterator::glyphs() const { 328 SkASSERT(!this->done()); 329 return fCurrentRun->glyphBuffer(); 330 } 331 332 const SkScalar* SkTextBlobRunIterator::pos() const { 333 SkASSERT(!this->done()); 334 return fCurrentRun->posBuffer(); 335 } 336 337 const SkPoint& SkTextBlobRunIterator::offset() const { 338 SkASSERT(!this->done()); 339 return fCurrentRun->offset(); 340 } 341 342 SkTextBlob::GlyphPositioning SkTextBlobRunIterator::positioning() const { 343 SkASSERT(!this->done()); 344 return fCurrentRun->positioning(); 345 } 346 347 void SkTextBlobRunIterator::applyFontToPaint(SkPaint* paint) const { 348 SkASSERT(!this->done()); 349 350 fCurrentRun->font().applyToPaint(paint); 351 } 352 353 bool SkTextBlobRunIterator::isLCD() const { 354 return SkToBool(fCurrentRun->font().flags() & SkPaint::kLCDRenderText_Flag); 355 } 356 357 SkTextBlobBuilder::SkTextBlobBuilder() 358 : fStorageSize(0) 359 , fStorageUsed(0) 360 , fRunCount(0) 361 , fDeferredBounds(false) 362 , fLastRun(0) { 363 fBounds.setEmpty(); 364 } 365 366 SkTextBlobBuilder::~SkTextBlobBuilder() { 367 if (nullptr != fStorage.get()) { 368 // We are abandoning runs and must destruct the associated font data. 369 // The easiest way to accomplish that is to use the blob destructor. 370 build()->unref(); 371 } 372 } 373 374 SkRect SkTextBlobBuilder::TightRunBounds(const SkTextBlob::RunRecord& run) { 375 SkRect bounds; 376 SkPaint paint; 377 run.font().applyToPaint(&paint); 378 379 if (SkTextBlob::kDefault_Positioning == run.positioning()) { 380 paint.measureText(run.glyphBuffer(), run.glyphCount() * sizeof(uint16_t), &bounds); 381 return bounds.makeOffset(run.offset().x(), run.offset().y()); 382 } 383 384 SkAutoSTArray<16, SkRect> glyphBounds(run.glyphCount()); 385 paint.getTextWidths(run.glyphBuffer(), 386 run.glyphCount() * sizeof(uint16_t), 387 NULL, 388 glyphBounds.get()); 389 390 SkASSERT(SkTextBlob::kFull_Positioning == run.positioning() || 391 SkTextBlob::kHorizontal_Positioning == run.positioning()); 392 // kFull_Positioning => [ x, y, x, y... ] 393 // kHorizontal_Positioning => [ x, x, x... ] 394 // (const y applied by runBounds.offset(run->offset()) later) 395 const SkScalar horizontalConstY = 0; 396 const SkScalar* glyphPosX = run.posBuffer(); 397 const SkScalar* glyphPosY = (run.positioning() == SkTextBlob::kFull_Positioning) ? 398 glyphPosX + 1 : &horizontalConstY; 399 const unsigned posXInc = SkTextBlob::ScalarsPerGlyph(run.positioning()); 400 const unsigned posYInc = (run.positioning() == SkTextBlob::kFull_Positioning) ? 401 posXInc : 0; 402 403 bounds.setEmpty(); 404 for (unsigned i = 0; i < run.glyphCount(); ++i) { 405 bounds.join(glyphBounds[i].makeOffset(*glyphPosX, *glyphPosY)); 406 glyphPosX += posXInc; 407 glyphPosY += posYInc; 408 } 409 410 SkASSERT((void*)glyphPosX <= SkTextBlob::RunRecord::Next(&run)); 411 412 return bounds.makeOffset(run.offset().x(), run.offset().y()); 413 } 414 415 SkRect SkTextBlobBuilder::ConservativeRunBounds(const SkTextBlob::RunRecord& run) { 416 SkASSERT(run.glyphCount() > 0); 417 SkASSERT(SkTextBlob::kFull_Positioning == run.positioning() || 418 SkTextBlob::kHorizontal_Positioning == run.positioning()); 419 420 SkPaint paint; 421 run.font().applyToPaint(&paint); 422 const SkRect fontBounds = paint.getFontBounds(); 423 if (fontBounds.isEmpty()) { 424 // Empty font bounds are likely a font bug. TightBounds has a better chance of 425 // producing useful results in this case. 426 return TightRunBounds(run); 427 } 428 429 // Compute the glyph position bbox. 430 SkRect bounds; 431 switch (run.positioning()) { 432 case SkTextBlob::kHorizontal_Positioning: { 433 const SkScalar* glyphPos = run.posBuffer(); 434 SkASSERT((void*)(glyphPos + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run)); 435 436 SkScalar minX = *glyphPos; 437 SkScalar maxX = *glyphPos; 438 for (unsigned i = 1; i < run.glyphCount(); ++i) { 439 SkScalar x = glyphPos[i]; 440 minX = SkMinScalar(x, minX); 441 maxX = SkMaxScalar(x, maxX); 442 } 443 444 bounds.setLTRB(minX, 0, maxX, 0); 445 } break; 446 case SkTextBlob::kFull_Positioning: { 447 const SkPoint* glyphPosPts = reinterpret_cast<const SkPoint*>(run.posBuffer()); 448 SkASSERT((void*)(glyphPosPts + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run)); 449 450 bounds.setBounds(glyphPosPts, run.glyphCount()); 451 } break; 452 default: 453 SkFAIL("unsupported positioning mode"); 454 } 455 456 // Expand by typeface glyph bounds. 457 bounds.fLeft += fontBounds.left(); 458 bounds.fTop += fontBounds.top(); 459 bounds.fRight += fontBounds.right(); 460 bounds.fBottom += fontBounds.bottom(); 461 462 // Offset by run position. 463 return bounds.makeOffset(run.offset().x(), run.offset().y()); 464 } 465 466 void SkTextBlobBuilder::updateDeferredBounds() { 467 SkASSERT(!fDeferredBounds || fRunCount > 0); 468 469 if (!fDeferredBounds) { 470 return; 471 } 472 473 SkASSERT(fLastRun >= sizeof(SkTextBlob)); 474 SkTextBlob::RunRecord* run = reinterpret_cast<SkTextBlob::RunRecord*>(fStorage.get() + 475 fLastRun); 476 477 // FIXME: we should also use conservative bounds for kDefault_Positioning. 478 SkRect runBounds = SkTextBlob::kDefault_Positioning == run->positioning() ? 479 TightRunBounds(*run) : ConservativeRunBounds(*run); 480 fBounds.join(runBounds); 481 fDeferredBounds = false; 482 } 483 484 void SkTextBlobBuilder::reserve(size_t size) { 485 // We don't currently pre-allocate, but maybe someday... 486 if (fStorageUsed + size <= fStorageSize) { 487 return; 488 } 489 490 if (0 == fRunCount) { 491 SkASSERT(nullptr == fStorage.get()); 492 SkASSERT(0 == fStorageSize); 493 SkASSERT(0 == fStorageUsed); 494 495 // the first allocation also includes blob storage 496 fStorageUsed += sizeof(SkTextBlob); 497 } 498 499 fStorageSize = fStorageUsed + size; 500 // FYI: This relies on everything we store being relocatable, particularly SkPaint. 501 fStorage.realloc(fStorageSize); 502 } 503 504 bool SkTextBlobBuilder::mergeRun(const SkPaint &font, SkTextBlob::GlyphPositioning positioning, 505 int count, SkPoint offset) { 506 if (0 == fLastRun) { 507 SkASSERT(0 == fRunCount); 508 return false; 509 } 510 511 SkASSERT(fLastRun >= sizeof(SkTextBlob)); 512 SkTextBlob::RunRecord* run = reinterpret_cast<SkTextBlob::RunRecord*>(fStorage.get() + 513 fLastRun); 514 SkASSERT(run->glyphCount() > 0); 515 516 if (run->positioning() != positioning 517 || run->font() != font 518 || (run->glyphCount() + count < run->glyphCount())) { 519 return false; 520 } 521 522 // we can merge same-font/same-positioning runs in the following cases: 523 // * fully positioned run following another fully positioned run 524 // * horizontally postioned run following another horizontally positioned run with the same 525 // y-offset 526 if (SkTextBlob::kFull_Positioning != positioning 527 && (SkTextBlob::kHorizontal_Positioning != positioning 528 || run->offset().y() != offset.y())) { 529 return false; 530 } 531 532 size_t sizeDelta = SkTextBlob::RunRecord::StorageSize(run->glyphCount() + count, positioning) - 533 SkTextBlob::RunRecord::StorageSize(run->glyphCount(), positioning); 534 this->reserve(sizeDelta); 535 536 // reserve may have realloced 537 run = reinterpret_cast<SkTextBlob::RunRecord*>(fStorage.get() + fLastRun); 538 uint32_t preMergeCount = run->glyphCount(); 539 run->grow(count); 540 541 // Callers expect the buffers to point at the newly added slice, ant not at the beginning. 542 fCurrentRunBuffer.glyphs = run->glyphBuffer() + preMergeCount; 543 fCurrentRunBuffer.pos = run->posBuffer() 544 + preMergeCount * SkTextBlob::ScalarsPerGlyph(positioning); 545 546 fStorageUsed += sizeDelta; 547 548 SkASSERT(fStorageUsed <= fStorageSize); 549 run->validate(fStorage.get() + fStorageUsed); 550 551 return true; 552 } 553 554 void SkTextBlobBuilder::allocInternal(const SkPaint &font, 555 SkTextBlob::GlyphPositioning positioning, 556 int count, SkPoint offset, const SkRect* bounds) { 557 SkASSERT(count > 0); 558 SkASSERT(SkPaint::kGlyphID_TextEncoding == font.getTextEncoding()); 559 560 if (!this->mergeRun(font, positioning, count, offset)) { 561 this->updateDeferredBounds(); 562 563 size_t runSize = SkTextBlob::RunRecord::StorageSize(count, positioning); 564 this->reserve(runSize); 565 566 SkASSERT(fStorageUsed >= sizeof(SkTextBlob)); 567 SkASSERT(fStorageUsed + runSize <= fStorageSize); 568 569 SkTextBlob::RunRecord* run = new (fStorage.get() + fStorageUsed) 570 SkTextBlob::RunRecord(count, offset, font, positioning); 571 572 fCurrentRunBuffer.glyphs = run->glyphBuffer(); 573 fCurrentRunBuffer.pos = run->posBuffer(); 574 575 fLastRun = fStorageUsed; 576 fStorageUsed += runSize; 577 fRunCount++; 578 579 SkASSERT(fStorageUsed <= fStorageSize); 580 run->validate(fStorage.get() + fStorageUsed); 581 } 582 583 if (!fDeferredBounds) { 584 if (bounds) { 585 fBounds.join(*bounds); 586 } else { 587 fDeferredBounds = true; 588 } 589 } 590 } 591 592 const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRun(const SkPaint& font, int count, 593 SkScalar x, SkScalar y, 594 const SkRect* bounds) { 595 this->allocInternal(font, SkTextBlob::kDefault_Positioning, count, SkPoint::Make(x, y), bounds); 596 597 return fCurrentRunBuffer; 598 } 599 600 const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPosH(const SkPaint& font, int count, 601 SkScalar y, 602 const SkRect* bounds) { 603 this->allocInternal(font, SkTextBlob::kHorizontal_Positioning, count, SkPoint::Make(0, y), 604 bounds); 605 606 return fCurrentRunBuffer; 607 } 608 609 const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPos(const SkPaint& font, int count, 610 const SkRect *bounds) { 611 this->allocInternal(font, SkTextBlob::kFull_Positioning, count, SkPoint::Make(0, 0), bounds); 612 613 return fCurrentRunBuffer; 614 } 615 616 const SkTextBlob* SkTextBlobBuilder::build() { 617 SkASSERT((fRunCount > 0) == (nullptr != fStorage.get())); 618 619 this->updateDeferredBounds(); 620 621 if (0 == fRunCount) { 622 SkASSERT(nullptr == fStorage.get()); 623 fStorageUsed = sizeof(SkTextBlob); 624 fStorage.realloc(fStorageUsed); 625 } 626 627 const SkTextBlob* blob = new (fStorage.detach()) SkTextBlob(fRunCount, fBounds); 628 SkDEBUGCODE(const_cast<SkTextBlob*>(blob)->fStorageSize = fStorageSize;) 629 630 SkDEBUGCODE( 631 size_t validateSize = sizeof(SkTextBlob); 632 const SkTextBlob::RunRecord* run = SkTextBlob::RunRecord::First(blob); 633 for (int i = 0; i < fRunCount; ++i) { 634 validateSize += SkTextBlob::RunRecord::StorageSize(run->fCount, run->fPositioning); 635 run->validate(reinterpret_cast<const uint8_t*>(blob) + fStorageUsed); 636 run = SkTextBlob::RunRecord::Next(run); 637 } 638 SkASSERT(validateSize == fStorageUsed); 639 ) 640 641 fStorageUsed = 0; 642 fStorageSize = 0; 643 fRunCount = 0; 644 fLastRun = 0; 645 fBounds.setEmpty(); 646 647 return blob; 648 } 649 650