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