Ensure blob typeface information survives SkGPipe serialization.

When flattening text blobs to the temp buffer, we need to collect
typeface info and ship it across the pipe explicitly.

R=mtklein@google.com, reed@google.com, robertphillips@google.com, bungeman@google.com
BUG=412445

Author: fmalita@chromium.org

Review URL: https://codereview.chromium.org/563783003
diff --git a/src/core/SkPtrRecorder.h b/src/core/SkPtrRecorder.h
index 06e14ab..83200f5 100644
--- a/src/core/SkPtrRecorder.h
+++ b/src/core/SkPtrRecorder.h
@@ -58,6 +58,27 @@
      */
     void reset();
 
+    /**
+     * Set iterator.
+     */
+    class Iter {
+    public:
+        Iter(const SkPtrSet& set)
+            : fSet(set)
+            , fIndex(0) {}
+
+        /**
+         * Return the next ptr in the set or null if the end was reached.
+         */
+        void* next() {
+            return fIndex < fSet.fList.count() ? fSet.fList[fIndex++].fPtr : NULL;
+        }
+
+    private:
+        const SkPtrSet& fSet;
+        int             fIndex;
+    };
+
 protected:
     virtual void incPtr(void*) {}
     virtual void decPtr(void*) {}
diff --git a/src/pipe/SkGPipeRead.cpp b/src/pipe/SkGPipeRead.cpp
index 4bd4fa6..8cb0e34 100644
--- a/src/pipe/SkGPipeRead.cpp
+++ b/src/pipe/SkGPipeRead.cpp
@@ -194,8 +194,8 @@
         *fTypefaces.append() = SkTypeface::Deserialize(&stream);
     }
 
-    void setTypeface(SkPaint* paint, unsigned id) {
-        paint->setTypeface(id ? fTypefaces[id - 1] : NULL);
+    SkTypeface* getTypeface(unsigned id) const {
+        return id ? fTypefaces[id - 1] : NULL;
     }
 
 private:
@@ -676,11 +676,22 @@
     SkScalar x = reader->readScalar();
     SkScalar y = reader->readScalar();
 
+    int typefaceCount = reader->readU32();
+    SkAutoSTMalloc<16, SkTypeface*> typefaceArray(typefaceCount);
+    if (state->getFlags() & SkGPipeWriter::kCrossProcess_Flag) {
+        for (int i = 0; i < typefaceCount; ++i) {
+            typefaceArray[i] = state->getTypeface(reader->readU32());
+        }
+    } else {
+        reader->read(typefaceArray.get(), typefaceCount * sizeof(SkTypeface*));
+    }
+
     size_t blobSize = reader->readU32();
     const void* data = reader->skip(SkAlign4(blobSize));
 
     if (state->shouldDraw()) {
         SkReadBuffer blobBuffer(data, blobSize);
+        blobBuffer.setTypefaceArray(typefaceArray.get(), typefaceCount);
         SkAutoTUnref<const SkTextBlob> blob(SkTextBlob::CreateFromBuffer(blobBuffer));
         SkASSERT(blob.get());
 
@@ -731,7 +742,8 @@
             case kTypeface_PaintOp:
                 SkASSERT(SkToBool(state->getFlags() &
                                   SkGPipeWriter::kCrossProcess_Flag));
-                state->setTypeface(p, data); break;
+                p->setTypeface(state->getTypeface(data));
+                break;
             default: SkDEBUGFAIL("bad paintop"); return;
         }
         SkASSERT(reader->offset() <= stop);
diff --git a/src/pipe/SkGPipeWrite.cpp b/src/pipe/SkGPipeWrite.cpp
index f361e5e..41b0234 100644
--- a/src/pipe/SkGPipeWrite.cpp
+++ b/src/pipe/SkGPipeWrite.cpp
@@ -22,6 +22,7 @@
 #include "SkPatchUtils.h"
 #include "SkPathEffect.h"
 #include "SkPictureFlat.h"
+#include "SkPtrRecorder.h"
 #include "SkRasterizer.h"
 #include "SkRRect.h"
 #include "SkShader.h"
@@ -35,7 +36,7 @@
     kSizeOfFlatRRect = sizeof(SkRect) + 4 * sizeof(SkVector)
 };
 
-static bool isCrossProcess(uint32_t flags) {
+static bool is_cross_process(uint32_t flags) {
     return SkToBool(flags & SkGPipeWriter::kCrossProcess_Flag);
 }
 
@@ -338,6 +339,10 @@
         }
     }
 
+    typedef SkAutoSTMalloc<128, uint8_t> TypefaceBuffer;
+    size_t getInProcessTypefaces(const SkRefCntSet& typefaceSet, TypefaceBuffer*);
+    size_t getCrossProcessTypefaces(const SkRefCntSet& typefaceSet, TypefaceBuffer*);
+
     // Should be called after any calls to an SkFlatDictionary::findAndReplace
     // if a new SkFlatData was added when in cross process mode
     void flattenFactoryNames();
@@ -411,7 +416,7 @@
     fBitmapHeap->endAddingOwnersDeferral(added);
     int index = flat->index();
     if (added) {
-        if (isCrossProcess(fFlags)) {
+        if (is_cross_process(fFlags)) {
             this->flattenFactoryNames();
         }
         size_t flatSize = flat->flatSize();
@@ -436,10 +441,10 @@
                              SkWriter32* writer, uint32_t flags,
                              uint32_t width, uint32_t height)
     : SkCanvas(width, height)
-    , fFactorySet(isCrossProcess(flags) ? SkNEW(SkNamedFactorySet) : NULL)
+    , fFactorySet(is_cross_process(flags) ? SkNEW(SkNamedFactorySet) : NULL)
     , fWriter(*writer)
     , fFlags(flags)
-    , fFlattenableHeap(FLATTENABLES_TO_KEEP, fFactorySet, isCrossProcess(flags))
+    , fFlattenableHeap(FLATTENABLES_TO_KEEP, fFactorySet, is_cross_process(flags))
     , fFlatDictionary(&fFlattenableHeap)
 {
     fController = controller;
@@ -938,23 +943,76 @@
     }
 }
 
+size_t SkGPipeCanvas::getInProcessTypefaces(const SkRefCntSet& typefaceSet,
+                                            TypefaceBuffer* buffer) {
+    // When in-process, we simply write out the typeface pointers.
+    size_t size = typefaceSet.count() * sizeof(SkTypeface*);
+    buffer->reset(size);
+    typefaceSet.copyToArray(reinterpret_cast<SkRefCnt**>(buffer->get()));
+
+    return size;
+}
+
+size_t SkGPipeCanvas::getCrossProcessTypefaces(const SkRefCntSet& typefaceSet,
+                                               TypefaceBuffer* buffer) {
+    // For cross-process we use typeface IDs.
+    size_t size = typefaceSet.count() * sizeof(uint32_t);
+    buffer->reset(size);
+
+    uint32_t* idBuffer = reinterpret_cast<uint32_t*>(buffer->get());
+    SkRefCntSet::Iter iter(typefaceSet);
+    int i = 0;
+
+    for (void* setPtr = iter.next(); setPtr; setPtr = iter.next()) {
+        idBuffer[i++] = this->getTypefaceID(reinterpret_cast<SkTypeface*>(setPtr));
+    }
+
+    SkASSERT(i == typefaceSet.count());
+
+    return size;
+}
+
 void SkGPipeCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                    const SkPaint& paint) {
     NOTIFY_SETUP(this);
     this->writePaint(paint);
 
     // FIXME: this is inefficient but avoids duplicating the blob serialization logic.
+    SkRefCntSet typefaceSet;
     SkWriteBuffer blobBuffer;
+    blobBuffer.setTypefaceRecorder(&typefaceSet);
     blob->flatten(blobBuffer);
 
-    size_t size = sizeof(uint32_t) + 2 * sizeof(SkScalar) + blobBuffer.bytesWritten();
+    // Unlike most draw ops (which only use one paint/typeface), text blobs may reference
+    // an arbitrary number of typefaces. Since the one-paint-per-op model is not applicable,
+    // we need to serialize these explicitly.
+    TypefaceBuffer typefaceBuffer;
+    size_t typefaceSize = is_cross_process(fFlags)
+        ? this->getCrossProcessTypefaces(typefaceSet, &typefaceBuffer)
+        : this->getInProcessTypefaces(typefaceSet, &typefaceBuffer);
+
+    // blob byte count + typeface count + x + y + blob data + an index (cross-process)
+    // or pointer (in-process) for each typeface
+    size_t size = 2 * sizeof(uint32_t)
+                + 2 * sizeof(SkScalar)
+                + blobBuffer.bytesWritten()
+                + typefaceSize;
+
     if (this->needOpBytes(size)) {
         this->writeOp(kDrawTextBlob_DrawOp);
+        SkDEBUGCODE(size_t initialOffset = fWriter.bytesWritten();)
+
         fWriter.writeScalar(x);
         fWriter.writeScalar(y);
+
+        fWriter.write32(typefaceSet.count());
+        fWriter.write(typefaceBuffer.get(), typefaceSize);
+
         fWriter.write32(SkToU32(blobBuffer.bytesWritten()));
         uint32_t* pad = fWriter.reservePad(blobBuffer.bytesWritten());
         blobBuffer.writeToMemory(pad);
+
+        SkASSERT(initialOffset + size == fWriter.bytesWritten());
     }
 }
 
@@ -1197,7 +1255,7 @@
     }
 
     if (!SkTypeface::Equal(base.getTypeface(), paint.getTypeface())) {
-        if (isCrossProcess(fFlags)) {
+        if (is_cross_process(fFlags)) {
             uint32_t id = this->getTypefaceID(paint.getTypeface());
             *ptr++ = PaintOp_packOpData(kTypeface_PaintOp, id);
         } else if (this->needOpBytes(sizeof(void*))) {