SkTextBlob plumbing

Add SkTextBlob serialization + drawTextBlob() overrides.

R=mtklein@google.com, reed@google.com, robertphillips@google.com
BUG=269080

Author: fmalita@chromium.org

Review URL: https://codereview.chromium.org/499413002
diff --git a/src/core/SkBBoxRecord.cpp b/src/core/SkBBoxRecord.cpp
index 69139ad..5837a88 100644
--- a/src/core/SkBBoxRecord.cpp
+++ b/src/core/SkBBoxRecord.cpp
@@ -9,6 +9,8 @@
 #include "SkBBoxRecord.h"
 #include "SkPatchUtils.h"
 
+#include "SkTextBlob.h"
+
 SkBBoxRecord::~SkBBoxRecord() {
     fSaveStack.deleteAll();
 }
@@ -272,6 +274,20 @@
     }
 }
 
+void SkBBoxRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                  const SkPaint& paint) {
+    SkRect bbox = blob->bounds();
+    bbox.offset(x, y);
+    // FIXME: implement implicit blob bounds!
+    if (bbox.isEmpty()) {
+        this->getClipBounds(&bbox);
+    }
+
+    if (this->transformBounds(bbox, &paint)) {
+        INHERITED::onDrawTextBlob(blob, x, y, paint);
+    }
+}
+
 void SkBBoxRecord::drawVertices(VertexMode mode, int vertexCount,
                                 const SkPoint vertices[], const SkPoint texs[],
                                 const SkColor colors[], SkXfermode* xfer,
diff --git a/src/core/SkBBoxRecord.h b/src/core/SkBBoxRecord.h
index 1d19b70..4833452 100644
--- a/src/core/SkBBoxRecord.h
+++ b/src/core/SkBBoxRecord.h
@@ -66,6 +66,8 @@
                                 SkScalar constY, const SkPaint&) SK_OVERRIDE;
     virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
                                   const SkMatrix* matrix, const SkPaint&) SK_OVERRIDE;
+    virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                const SkPaint& paint) SK_OVERRIDE;
     virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                              const SkPoint texCoords[4], SkXfermode* xmode,
                              const SkPaint& paint) SK_OVERRIDE;
diff --git a/src/core/SkPictureData.cpp b/src/core/SkPictureData.cpp
index 2bcf779..86a3388 100644
--- a/src/core/SkPictureData.cpp
+++ b/src/core/SkPictureData.cpp
@@ -10,6 +10,7 @@
 #include "SkPictureData.h"
 #include "SkPictureRecord.h"
 #include "SkReadBuffer.h"
+#include "SkTextBlob.h"
 #include "SkTypeface.h"
 #include "SkTSort.h"
 #include "SkWriteBuffer.h"
@@ -76,6 +77,16 @@
             fPictureRefs[i]->ref();
         }
     }
+
+    // templatize to consolidate with similar picture logic?
+    const SkTDArray<const SkTextBlob*>& blobs = record.getTextBlobRefs();
+    fTextBlobCount = blobs.count();
+    if (fTextBlobCount > 0) {
+        fTextBlobRefs = SkNEW_ARRAY(const SkTextBlob*, fTextBlobCount);
+        for (int i = 0; i < fTextBlobCount; ++i) {
+            fTextBlobRefs[i] = SkRef(blobs[i]);
+        }
+    }
 }
 
 #ifdef SK_SUPPORT_LEGACY_PICTURE_CLONE
@@ -139,6 +150,8 @@
     fPaints = NULL;
     fPictureRefs = NULL;
     fPictureCount = 0;
+    fTextBlobRefs = NULL;
+    fTextBlobCount = 0;
     fOpData = NULL;
     fFactoryPlayback = NULL;
     fBoundingHierarchy = NULL;
@@ -158,6 +171,11 @@
     }
     SkDELETE_ARRAY(fPictureRefs);
 
+    for (int i = 0; i < fTextBlobCount; i++) {
+        fTextBlobRefs[i]->unref();
+    }
+    SkDELETE_ARRAY(fTextBlobRefs);
+
     SkDELETE(fFactoryPlayback);
 }
 
@@ -268,6 +286,13 @@
         write_tag_size(buffer, SK_PICT_PATH_BUFFER_TAG, n);
         fPathHeap->flatten(buffer);
     }
+
+    if (fTextBlobCount > 0) {
+        write_tag_size(buffer, SK_PICT_TEXTBLOB_BUFFER_TAG, fTextBlobCount);
+        for (i = 0; i  < fTextBlobCount; ++i) {
+            fTextBlobRefs[i]->flatten(buffer);
+        }
+    }
 }
 
 void SkPictureData::serialize(SkWStream* stream,
@@ -485,6 +510,33 @@
                 fPathHeap.reset(SkNEW_ARGS(SkPathHeap, (buffer)));
             }
             break;
+        case SK_PICT_TEXTBLOB_BUFFER_TAG: {
+            if (!buffer.validate((0 == fTextBlobCount) && (NULL == fTextBlobRefs))) {
+                return false;
+            }
+            fTextBlobCount = size;
+            fTextBlobRefs = SkNEW_ARRAY(const SkTextBlob*, fTextBlobCount);
+            bool success = true;
+            int i = 0;
+            for ( ; i < fTextBlobCount; i++) {
+                fTextBlobRefs[i] = SkTextBlob::CreateFromBuffer(buffer);
+                if (NULL == fTextBlobRefs[i]) {
+                    success = false;
+                    break;
+                }
+            }
+            if (!success) {
+                // Delete all of the blobs that were already created (up to but excluding i):
+                for (int j = 0; j < i; j++) {
+                    fTextBlobRefs[j]->unref();
+                }
+                // Delete the array
+                SkDELETE_ARRAY(fTextBlobRefs);
+                fTextBlobRefs = NULL;
+                fTextBlobCount = 0;
+                return false;
+            }
+        } break;
         case SK_PICT_READER_TAG: {
             SkAutoMalloc storage(size);
             if (!buffer.readByteArray(storage.get(), size) ||
diff --git a/src/core/SkPictureData.h b/src/core/SkPictureData.h
index a6c840a..14c5186 100644
--- a/src/core/SkPictureData.h
+++ b/src/core/SkPictureData.h
@@ -26,6 +26,7 @@
 class SkPath;
 class SkPictureStateTree;
 class SkReadBuffer;
+class SkTextBlob;
 
 struct SkPictInfo {
     enum Flags {
@@ -49,9 +50,10 @@
 // This tag specifies the size of the ReadBuffer, needed for the following tags
 #define SK_PICT_BUFFER_SIZE_TAG     SkSetFourByteTag('a', 'r', 'a', 'y')
 // these are all inside the ARRAYS tag
-#define SK_PICT_BITMAP_BUFFER_TAG  SkSetFourByteTag('b', 't', 'm', 'p')
-#define SK_PICT_PAINT_BUFFER_TAG   SkSetFourByteTag('p', 'n', 't', ' ')
-#define SK_PICT_PATH_BUFFER_TAG    SkSetFourByteTag('p', 't', 'h', ' ')
+#define SK_PICT_BITMAP_BUFFER_TAG   SkSetFourByteTag('b', 't', 'm', 'p')
+#define SK_PICT_PAINT_BUFFER_TAG    SkSetFourByteTag('p', 'n', 't', ' ')
+#define SK_PICT_PATH_BUFFER_TAG     SkSetFourByteTag('p', 't', 'h', ' ')
+#define SK_PICT_TEXTBLOB_BUFFER_TAG SkSetFourByteTag('b', 'l', 'o', 'b')
 
 // Always write this guy last (with no length field afterwards)
 #define SK_PICT_EOF_TAG     SkSetFourByteTag('e', 'o', 'f', ' ')
@@ -132,6 +134,12 @@
         return &(*fPaints)[index - 1];
     }
 
+    const SkTextBlob* getTextBlob(SkReader32* reader) const {
+        int index = reader->readInt();
+        SkASSERT(index > 0 && index <= fTextBlobCount);
+        return fTextBlobRefs[index - 1];
+    }
+
     void initIterator(SkPictureStateTree::Iterator* iter,
                       const SkTDArray<void*>& draws,
                       SkCanvas* canvas) const {
@@ -183,6 +191,8 @@
 
     const SkPicture** fPictureRefs;
     int fPictureCount;
+    const SkTextBlob** fTextBlobRefs;
+    int fTextBlobCount;
 
     SkBBoxHierarchy* fBoundingHierarchy;
     SkPictureStateTree* fStateTree;
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index 536189b..3530f39 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -69,8 +69,9 @@
     
     DRAW_PATCH, // could not add in aphabetical order
     DRAW_PICTURE_MATRIX_PAINT,
+    DRAW_TEXT_BLOB,
 
-    LAST_DRAWTYPE_ENUM = DRAW_PICTURE_MATRIX_PAINT
+    LAST_DRAWTYPE_ENUM = DRAW_TEXT_BLOB
 };
 
 // In the 'match' method, this constant will match any flavor of DRAW_BITMAP*
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 6197d88..a64bf0f 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -12,6 +12,7 @@
 #include "SkPictureRecord.h"
 #include "SkPictureStateTree.h"
 #include "SkReader32.h"
+#include "SkTextBlob.h"
 #include "SkTDArray.h"
 #include "SkTypes.h"
 
@@ -420,6 +421,13 @@
             SkScalar y = reader->readScalar();
             canvas->drawText(text.text(), text.length(), x, y, paint);
         } break;
+        case DRAW_TEXT_BLOB: {
+            const SkPaint& paint = *fPictureData->getPaint(reader);
+            const SkTextBlob* blob = fPictureData->getTextBlob(reader);
+            SkScalar x = reader->readScalar();
+            SkScalar y = reader->readScalar();
+            canvas->drawTextBlob(blob, x, y, paint);
+        } break;
         case DRAW_TEXT_TOP_BOTTOM: {
             const SkPaint& paint = *fPictureData->getPaint(reader);
             TextContainer text;
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index dd80400..67bd9a5 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -12,6 +12,7 @@
 #include "SkPictureStateTree.h"
 #include "SkPixelRef.h"
 #include "SkRRect.h"
+#include "SkTextBlob.h"
 #include "SkTSearch.h"
 
 #define HEAP_BLOCK_SIZE 4096
@@ -59,6 +60,7 @@
     SkSafeUnref(fStateTree);
     fFlattenableHeap.setBitmapStorage(NULL);
     fPictureRefs.unrefAll();
+    fTextBlobRefs.unrefAll();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -114,6 +116,7 @@
         0,  // POP_CULL - no paint
         1,  // DRAW_PATCH - right after op code
         1,  // DRAW_PICTURE_MATRIX_PAINT - right after op code
+        1,  // DRAW_TEXT_BLOB- right after op code
     };
 
     SK_COMPILE_ASSERT(sizeof(gPaintOffsets) == LAST_DRAWTYPE_ENUM + 1,
@@ -462,7 +465,8 @@
     return (op > CONCAT && op < ROTATE)
             || DRAW_DRRECT == op
             || DRAW_PATCH == op
-            || DRAW_PICTURE_MATRIX_PAINT == op;
+            || DRAW_PICTURE_MATRIX_PAINT == op
+            || DRAW_TEXT_BLOB == op;
 }
 
 /*
@@ -1207,6 +1211,22 @@
     this->validate(initialOffset, size);
 }
 
+void SkPictureRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                     const SkPaint& paint) {
+
+    // op + paint index + blob index + x/y
+    size_t size = 3 * kUInt32Size + 2 * sizeof(SkScalar);
+    size_t initialOffset = this->addDraw(DRAW_TEXT_BLOB, &size);
+    SkASSERT(initialOffset + getPaintOffset(DRAW_TEXT_BLOB, size) == fWriter.bytesWritten());
+
+    this->addPaint(paint);
+    this->addTextBlob(blob);
+    this->addScalar(x);
+    this->addScalar(y);
+
+    this->validate(initialOffset, size);
+}
+
 void SkPictureRecord::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                     const SkPaint* paint) {
     // op + picture index
@@ -1523,5 +1543,16 @@
     fWriter.writePad(text, byteLength);
 }
 
+void SkPictureRecord::addTextBlob(const SkTextBlob *blob) {
+    int index = fTextBlobRefs.find(blob);
+    if (index < 0) {    // not found
+        index = fTextBlobRefs.count();
+        *fTextBlobRefs.append() = blob;
+        blob->ref();
+    }
+    // follow the convention of recording a 1-based index
+    this->addInt(index + 1);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index 8fd40bc..f2dd87b 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -71,6 +71,10 @@
         return fPictureRefs;
     }
 
+    const SkTDArray<const SkTextBlob* >& getTextBlobRefs() const {
+        return fTextBlobRefs;
+    }
+
     SkData* opData(bool deepCopy) const {
         this->validate(fWriter.bytesWritten(), 0);
 
@@ -181,6 +185,7 @@
     void addRRect(const SkRRect&);
     void addRegion(const SkRegion& region);
     void addText(const void* text, size_t byteLength);
+    void addTextBlob(const SkTextBlob* blob);
 
     int find(const SkBitmap& bitmap);
 
@@ -213,6 +218,8 @@
                                 SkScalar constY, const SkPaint&) SK_OVERRIDE;
     virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
                                   const SkMatrix* matrix, const SkPaint&) SK_OVERRIDE;
+    virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                const SkPaint& paint) SK_OVERRIDE;
     
     virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                              const SkPoint texCoords[4], SkXfermode* xmode,
@@ -281,7 +288,8 @@
     SkWriter32 fWriter;
 
     // we ref each item in these arrays
-    SkTDArray<const SkPicture*> fPictureRefs;
+    SkTDArray<const SkPicture*>  fPictureRefs;
+    SkTDArray<const SkTextBlob*> fTextBlobRefs;
 
     uint32_t fRecordFlags;
     bool     fOptsEnabled;
diff --git a/src/core/SkRecorder.h b/src/core/SkRecorder.h
index 23ecdf6..683d29b 100644
--- a/src/core/SkRecorder.h
+++ b/src/core/SkRecorder.h
@@ -92,7 +92,7 @@
     void onDrawTextBlob(const SkTextBlob* blob,
                         SkScalar x,
                         SkScalar y,
-                        const SkPaint& paint);
+                        const SkPaint& paint) SK_OVERRIDE;
     void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                      const SkPoint texCoords[4], SkXfermode* xmode,
                      const SkPaint& paint) SK_OVERRIDE;
diff --git a/src/core/SkTextBlob.cpp b/src/core/SkTextBlob.cpp
index ded801a..d928d7b 100644
--- a/src/core/SkTextBlob.cpp
+++ b/src/core/SkTextBlob.cpp
@@ -7,6 +7,9 @@
 
 #include "SkTextBlob.h"
 
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+
 SkTextBlob::SkTextBlob(uint16_t *glyphs, SkScalar *pos, const SkTArray<Run> *runs,
                        const SkRect& bounds)
     : fGlyphBuffer(glyphs)
@@ -26,6 +29,86 @@
     return fUniqueID;
 }
 
+unsigned SkTextBlob::ScalarsPerGlyph(GlyphPositioning pos) {
+    // GlyphPositioning values are directly mapped to scalars-per-glyph.
+    SkASSERT(pos <= 2);
+    return pos;
+}
+
+void SkTextBlob::flatten(SkWriteBuffer& buffer) const {
+    int runCount = (NULL == fRuns.get()) ? 0 : fRuns->count();
+
+    buffer.write32(runCount);
+    buffer.writeRect(fBounds);
+
+    SkPaint runPaint;
+    RunIterator it(this);
+    while (!it.done()) {
+        SkASSERT(it.glyphCount() > 0);
+
+        buffer.write32(it.glyphCount());
+        buffer.write32(it.positioning());
+        buffer.writePoint(it.offset());
+        // This should go away when switching to SkFont
+        it.applyFontToPaint(&runPaint);
+        buffer.writePaint(runPaint);
+
+        buffer.writeByteArray(it.glyphs(), it.glyphCount() * sizeof(uint16_t));
+        buffer.writeByteArray(it.pos(),
+            it.glyphCount() * sizeof(SkScalar) * ScalarsPerGlyph(it.positioning()));
+
+        it.next();
+        SkDEBUGCODE(runCount--);
+    }
+    SkASSERT(0 == runCount);
+}
+
+const SkTextBlob* SkTextBlob::CreateFromBuffer(SkReadBuffer& reader) {
+    int runCount = reader.read32();
+    if (runCount < 0) {
+        return NULL;
+    }
+
+    SkRect bounds;
+    reader.readRect(&bounds);
+
+    SkTextBlobBuilder blobBuilder;
+    for (int i = 0; i < runCount; ++i) {
+        int glyphCount = reader.read32();
+        GlyphPositioning pos = static_cast<GlyphPositioning>(reader.read32());
+        if (glyphCount <= 0 || pos > kFull_Positioning) {
+            return NULL;
+        }
+
+        SkPoint offset;
+        reader.readPoint(&offset);
+        SkPaint font;
+        reader.readPaint(&font);
+
+        const SkTextBlobBuilder::RunBuffer* buf = NULL;
+        switch (pos) {
+        case kDefault_Positioning:
+            buf = &blobBuilder.allocRun(font, glyphCount, offset.x(), offset.y(), &bounds);
+            break;
+        case kHorizontal_Positioning:
+            buf = &blobBuilder.allocRunPosH(font, glyphCount, offset.y(), &bounds);
+            break;
+        case kFull_Positioning:
+            buf = &blobBuilder.allocRunPos(font, glyphCount, &bounds);
+            break;
+        default:
+            return NULL;
+        }
+
+        if (!reader.readByteArray(buf->glyphs, glyphCount * sizeof(uint16_t)) ||
+            !reader.readByteArray(buf->pos, glyphCount * sizeof(SkScalar) * ScalarsPerGlyph(pos))) {
+            return NULL;
+        }
+    }
+
+    return blobBuilder.build();
+}
+
 SkTextBlob::RunIterator::RunIterator(const SkTextBlob* blob)
     : fBlob(blob)
     , fIndex(0) {
@@ -161,9 +244,7 @@
 
     this->ensureRun(font, positioning, offset);
 
-    // SkTextBlob::GlyphPositioning values are directly mapped to scalars-per-glyph.
-    unsigned posScalarsPerGlyph = positioning;
-    SkASSERT(posScalarsPerGlyph <= 2);
+    unsigned posScalarsPerGlyph = SkTextBlob::ScalarsPerGlyph(positioning);
 
     fGlyphBuffer.append(count);
     fPosBuffer.append(count * posScalarsPerGlyph);
diff --git a/src/pipe/SkGPipePriv.h b/src/pipe/SkGPipePriv.h
index 821da0f..121512d 100644
--- a/src/pipe/SkGPipePriv.h
+++ b/src/pipe/SkGPipePriv.h
@@ -58,6 +58,7 @@
     kDrawRRect_DrawOp,
     kDrawSprite_DrawOp,
     kDrawText_DrawOp,
+    kDrawTextBlob_DrawOp,
     kDrawTextOnPath_DrawOp,
     kDrawVertices_DrawOp,
     kRestore_DrawOp,
diff --git a/src/pipe/SkGPipeRead.cpp b/src/pipe/SkGPipeRead.cpp
index 35de638..e48baf3 100644
--- a/src/pipe/SkGPipeRead.cpp
+++ b/src/pipe/SkGPipeRead.cpp
@@ -670,6 +670,10 @@
     UNIMPLEMENTED
 }
 
+static void drawTextBlob_rp(SkCanvas* canvas, SkReader32* reader, uint32_t op32,
+                            SkGPipeState* state) {
+    UNIMPLEMENTED
+}
 ///////////////////////////////////////////////////////////////////////////////
 
 static void paintOp_rp(SkCanvas*, SkReader32* reader, uint32_t op32,
@@ -814,6 +818,7 @@
     drawRRect_rp,
     drawSprite_rp,
     drawText_rp,
+    drawTextBlob_rp,
     drawTextOnPath_rp,
     drawVertices_rp,
     restore_rp,
diff --git a/src/pipe/SkGPipeWrite.cpp b/src/pipe/SkGPipeWrite.cpp
index 94a30a3..32af81d 100644
--- a/src/pipe/SkGPipeWrite.cpp
+++ b/src/pipe/SkGPipeWrite.cpp
@@ -283,6 +283,8 @@
                                 SkScalar constY, const SkPaint&) SK_OVERRIDE;
     virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
                                   const SkMatrix* matrix, const SkPaint&) SK_OVERRIDE;
+    virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                const SkPaint& paint) SK_OVERRIDE;
     virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                              const SkPoint texCoords[4], SkXfermode* xmode,
                              const SkPaint& paint) SK_OVERRIDE;
@@ -935,6 +937,13 @@
     }
 }
 
+void SkGPipeCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                   const SkPaint& paint) {
+    // FIXME: blob serialization only supports SkWriteBuffers
+    //        -- convert to SkWriter32 to avoid unrolling?
+    this->INHERITED::onDrawTextBlob(blob, x, y, paint);
+}
+
 void SkGPipeCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                   const SkPaint* paint) {
     // we want to playback the picture into individual draw calls
diff --git a/src/utils/SkDeferredCanvas.cpp b/src/utils/SkDeferredCanvas.cpp
index de3958a..cb69b4e 100644
--- a/src/utils/SkDeferredCanvas.cpp
+++ b/src/utils/SkDeferredCanvas.cpp
@@ -901,6 +901,13 @@
     this->recordedDrawCommand();
 }
 
+void SkDeferredCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                      const SkPaint& paint) {
+    AutoImmediateDrawIfNeeded autoDraw(*this, &paint);
+    this->drawingCanvas()->drawTextBlob(blob, x, y, paint);
+    this->recordedDrawCommand();
+}
+
 void SkDeferredCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                      const SkPaint* paint) {
     this->drawingCanvas()->drawPicture(picture, matrix, paint);
diff --git a/src/utils/SkDumpCanvas.cpp b/src/utils/SkDumpCanvas.cpp
index 661f0d8..5e3d153 100644
--- a/src/utils/SkDumpCanvas.cpp
+++ b/src/utils/SkDumpCanvas.cpp
@@ -14,6 +14,7 @@
 #include "SkPixelRef.h"
 #include "SkRRect.h"
 #include "SkString.h"
+#include "SkTextBlob.h"
 #include <stdarg.h>
 #include <stdio.h>
 
@@ -423,6 +424,14 @@
                str.c_str(), byteLength);
 }
 
+void SkDumpCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                  const SkPaint& paint) {
+    SkString str;
+    toString(blob->bounds(), &str);
+    this->dump(kDrawText_Verb, &paint, "drawTextBlob(%p) [%s]", blob, str.c_str());
+    // FIXME: dump the actual blob content?
+}
+
 void SkDumpCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                  const SkPaint* paint) {
     this->dump(kDrawPicture_Verb, NULL, "drawPicture(%p) %d:%d", picture,
diff --git a/src/utils/SkLua.cpp b/src/utils/SkLua.cpp
index 9e94775..773af54 100644
--- a/src/utils/SkLua.cpp
+++ b/src/utils/SkLua.cpp
@@ -21,6 +21,7 @@
 #include "SkPixelRef.h"
 #include "SkRRect.h"
 #include "SkString.h"
+#include "SkTextBlob.h"
 #include "SkTypeface.h"
 
 extern "C" {
@@ -45,6 +46,7 @@
 DEF_MTNAME(SkPaint)
 DEF_MTNAME(SkPathEffect)
 DEF_MTNAME(SkShader)
+DEF_MTNAME(SkTextBlob)
 DEF_MTNAME(SkTypeface)
 
 template <typename T> T* push_new(lua_State* L) {
@@ -273,6 +275,11 @@
     CHECK_SETFIELD(key);
 }
 
+void SkLua::pushTextBlob(const SkTextBlob* blob, const char key[]) {
+    push_ref(fL, const_cast<SkTextBlob*>(blob));
+    CHECK_SETFIELD(key);
+}
+
 static const char* element_type(SkClipStack::Element::Type type) {
     switch (type) {
         case SkClipStack::Element::kEmpty_Type:
diff --git a/src/utils/SkLuaCanvas.cpp b/src/utils/SkLuaCanvas.cpp
index 0903ee8..8fe1aa2 100644
--- a/src/utils/SkLuaCanvas.cpp
+++ b/src/utils/SkLuaCanvas.cpp
@@ -268,6 +268,15 @@
     lua.pushPaint(paint, "paint");
 }
 
+void SkLuaCanvas::onDrawTextBlob(const SkTextBlob *blob, SkScalar x, SkScalar y,
+                                 const SkPaint &paint) {
+    AUTO_LUA("drawTextBlob");
+    lua.pushTextBlob(blob, "blob");
+    lua.pushScalar(x, "x");
+    lua.pushScalar(y, "y");
+    lua.pushPaint(paint, "paint");
+}
+
 void SkLuaCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                 const SkPaint* paint) {
     AUTO_LUA("drawPicture");
diff --git a/src/utils/SkNWayCanvas.cpp b/src/utils/SkNWayCanvas.cpp
index d436bd4..90fd017 100644
--- a/src/utils/SkNWayCanvas.cpp
+++ b/src/utils/SkNWayCanvas.cpp
@@ -265,6 +265,14 @@
     }
 }
 
+void SkNWayCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                  const SkPaint &paint) {
+    Iter iter(fList);
+    while (iter.next()) {
+        iter->drawTextBlob(blob, x, y, paint);
+    }
+}
+
 void SkNWayCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                  const SkPaint* paint) {
     Iter iter(fList);
diff --git a/src/utils/SkProxyCanvas.cpp b/src/utils/SkProxyCanvas.cpp
index c15acaa..1677daf 100644
--- a/src/utils/SkProxyCanvas.cpp
+++ b/src/utils/SkProxyCanvas.cpp
@@ -136,6 +136,11 @@
     fProxy->drawTextOnPath(text, byteLength, path, matrix, paint);
 }
 
+void SkProxyCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                   const SkPaint& paint) {
+    fProxy->drawTextBlob(blob, x, y, paint);
+}
+
 void SkProxyCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,
                                   const SkPaint* paint) {
     fProxy->drawPicture(picture, matrix, paint);
diff --git a/src/utils/debugger/SkDebugCanvas.cpp b/src/utils/debugger/SkDebugCanvas.cpp
index 228f25f..2b0eab7 100644
--- a/src/utils/debugger/SkDebugCanvas.cpp
+++ b/src/utils/debugger/SkDebugCanvas.cpp
@@ -571,6 +571,11 @@
         new SkDrawTextOnPathCommand(text, byteLength, path, matrix, paint));
 }
 
+void SkDebugCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                   const SkPaint& paint) {
+    this->addDrawCommand(new SkDrawTextBlobCommand(blob, x, y, paint));
+}
+
 void SkDebugCanvas::drawVertices(VertexMode vmode, int vertexCount,
         const SkPoint vertices[], const SkPoint texs[], const SkColor colors[],
         SkXfermode*, const uint16_t indices[], int indexCount,
diff --git a/src/utils/debugger/SkDebugCanvas.h b/src/utils/debugger/SkDebugCanvas.h
index e4fb0d9..94ad426 100644
--- a/src/utils/debugger/SkDebugCanvas.h
+++ b/src/utils/debugger/SkDebugCanvas.h
@@ -242,6 +242,8 @@
                                 SkScalar constY, const SkPaint&) SK_OVERRIDE;
     virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
                                   const SkMatrix* matrix, const SkPaint&) SK_OVERRIDE;
+    virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                const SkPaint& paint) SK_OVERRIDE;
     virtual void onPushCull(const SkRect& cullRect) SK_OVERRIDE;
     virtual void onPopCull() SK_OVERRIDE;
 
diff --git a/src/utils/debugger/SkDrawCommand.cpp b/src/utils/debugger/SkDrawCommand.cpp
index 26d2a85..3cebca2 100644
--- a/src/utils/debugger/SkDrawCommand.cpp
+++ b/src/utils/debugger/SkDrawCommand.cpp
@@ -10,6 +10,8 @@
 #include "SkDrawCommand.h"
 #include "SkObjectParser.h"
 
+#include "SkTextBlob.h"
+
 // TODO(chudy): Refactor into non subclass model.
 
 SkDrawCommand::SkDrawCommand(DrawType type)
@@ -643,6 +645,26 @@
     canvas->drawPosTextH(fText, fByteLength, fXpos, fConstY, fPaint);
 }
 
+SkDrawTextBlobCommand::SkDrawTextBlobCommand(const SkTextBlob* blob, SkScalar x, SkScalar y,
+                                             const SkPaint& paint)
+    : INHERITED(DRAW_TEXT_BLOB)
+    , fBlob(blob)
+    , fXPos(x)
+    , fYPos(y)
+    , fPaint(paint) {
+
+    blob->ref();
+
+    // FIXME: push blob info
+    fInfo.push(SkObjectParser::ScalarToString(x, "XPOS: "));
+    fInfo.push(SkObjectParser::ScalarToString(x, "YPOS: "));
+    fInfo.push(SkObjectParser::PaintToString(paint));
+}
+
+void SkDrawTextBlobCommand::execute(SkCanvas* canvas) {
+    canvas->drawTextBlob(fBlob, fXPos, fYPos, fPaint);
+}
+
 SkDrawRectCommand::SkDrawRectCommand(const SkRect& rect, const SkPaint& paint)
     : INHERITED(DRAW_RECT) {
     fRect = rect;
diff --git a/src/utils/debugger/SkDrawCommand.h b/src/utils/debugger/SkDrawCommand.h
index ce7b1f5..f3c8cca 100644
--- a/src/utils/debugger/SkDrawCommand.h
+++ b/src/utils/debugger/SkDrawCommand.h
@@ -436,6 +436,21 @@
     typedef SkDrawCommand INHERITED;
 };
 
+class SkDrawTextBlobCommand : public SkDrawCommand {
+public:
+    SkDrawTextBlobCommand(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint);
+
+    virtual void execute(SkCanvas* canvas) SK_OVERRIDE;
+
+private:
+    SkAutoTUnref<const SkTextBlob> fBlob;
+    SkScalar                       fXPos;
+    SkScalar                       fYPos;
+    SkPaint                        fPaint;
+
+    typedef SkDrawCommand INHERITED;
+};
+
 class SkDrawRectCommand : public SkDrawCommand {
 public:
     SkDrawRectCommand(const SkRect& rect, const SkPaint& paint);