Deliberately call typefaceproc only once per face in pictures

Before this CL, the new test would write out 2 copies of each typeface,
since each one is referenced twice in the picture.

Bug: skia:
Change-Id: I6ebfe6b5ea0bb0cca2869ef0cccad21a51e48411
Reviewed-on: https://skia-review.googlesource.com/151667
Auto-Submit: Mike Reed <reed@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Mike Reed <reed@google.com>
diff --git a/src/core/SkPictureData.cpp b/src/core/SkPictureData.cpp
index 14a6d94..877bbdc 100644
--- a/src/core/SkPictureData.cpp
+++ b/src/core/SkPictureData.cpp
@@ -121,7 +121,8 @@
     SkASSERT(size == (stream->bytesWritten() - start));
 }
 
-void SkPictureData::WriteTypefaces(SkWStream* stream, const SkRefCntSet& rec) {
+void SkPictureData::WriteTypefaces(SkWStream* stream, const SkRefCntSet& rec,
+                                   const SkSerialProcs& procs) {
     int count = rec.count();
 
     write_tag_size(stream, SK_PICT_TYPEFACE_TAG, count);
@@ -131,6 +132,14 @@
     rec.copyToArray((SkRefCnt**)array);
 
     for (int i = 0; i < count; i++) {
+        SkTypeface* tf = array[i];
+        if (procs.fTypefaceProc) {
+            auto data = procs.fTypefaceProc(tf, procs.fTypefaceCtx);
+            if (data) {
+                stream->write(data->data(), data->size());
+                continue;
+            }
+        }
         array[i]->serialize(stream);
     }
 }
@@ -175,6 +184,18 @@
     }
 }
 
+// SkPictureData::serialize() will write out paints, and then write out an array of typefaces
+// (unique set). However, paint's serializer will respect SerialProcs, which can cause us to
+// call that custom typefaceproc on *every* typeface, not just on the unique ones. To avoid this,
+// we ignore the custom proc (here) when we serialize the paints, and then do respect it when
+// we serialize the typefaces.
+static SkSerialProcs skip_typeface_proc(const SkSerialProcs& procs) {
+    SkSerialProcs newProcs = procs;
+    newProcs.fTypefaceProc = nullptr;
+    newProcs.fTypefaceCtx = nullptr;
+    return newProcs;
+}
+
 void SkPictureData::serialize(SkWStream* stream, const SkSerialProcs& procs,
                               SkRefCntSet* topLevelTypeFaceSet) const {
     // This can happen at pretty much any time, so might as well do it first.
@@ -190,7 +211,7 @@
     SkFactorySet factSet;  // buffer refs factSet, so factSet must come first.
     SkBinaryWriteBuffer buffer;
     buffer.setFactoryRecorder(sk_ref_sp(&factSet));
-    buffer.setSerialProcs(procs);
+    buffer.setSerialProcs(skip_typeface_proc(procs));
     buffer.setTypefaceRecorder(sk_ref_sp(typefaceSet));
     this->flattenToBuffer(buffer);
 
@@ -210,7 +231,10 @@
     // We need to write typefaces before we write the buffer or any sub-picture.
     WriteFactories(stream, factSet);
     if (typefaceSet == &localTypefaceSet) {
-        WriteTypefaces(stream, *typefaceSet);
+        // Pass the original typefaceproc (if any) now that we're ready to actually serialize the
+        // typefaces. We skipped this proc before, when we were serializing paints, so that the
+        // paints would just write indices into our typeface set.
+        WriteTypefaces(stream, *typefaceSet, procs);
     }
 
     // Write the buffer.
diff --git a/src/core/SkPictureData.h b/src/core/SkPictureData.h
index 659b9ca..bef438c 100644
--- a/src/core/SkPictureData.h
+++ b/src/core/SkPictureData.h
@@ -162,7 +162,7 @@
     const SkPictInfo fInfo;
 
     static void WriteFactories(SkWStream* stream, const SkFactorySet& rec);
-    static void WriteTypefaces(SkWStream* stream, const SkRefCntSet& rec);
+    static void WriteTypefaces(SkWStream* stream, const SkRefCntSet& rec, const SkSerialProcs&);
 
     void initForPlayback() const;
 };
diff --git a/tests/SerialProcsTest.cpp b/tests/SerialProcsTest.cpp
index fd82079..01ae6b7 100644
--- a/tests/SerialProcsTest.cpp
+++ b/tests/SerialProcsTest.cpp
@@ -178,3 +178,46 @@
     test_pictures(reporter, p0, 1, true);
 }
 
+static sk_sp<SkPicture> make_picture(sk_sp<SkTypeface> tf0, sk_sp<SkTypeface> tf1) {
+    SkPictureRecorder rec;
+    SkCanvas* canvas = rec.beginRecording(100, 100);
+    SkPaint paint;
+    paint.setTypeface(tf0); canvas->drawText("hello", 5, 0, 0, paint);
+    paint.setTypeface(tf1); canvas->drawText("hello", 5, 0, 0, paint);
+    paint.setTypeface(tf0); canvas->drawText("hello", 5, 0, 0, paint);
+    paint.setTypeface(tf1); canvas->drawText("hello", 5, 0, 0, paint);
+    return rec.finishRecordingAsPicture();
+}
+
+DEF_TEST(serial_typeface, reporter) {
+    auto tf0 = MakeResourceAsTypeface("fonts/hintgasp.ttf");
+    auto tf1 = MakeResourceAsTypeface("fonts/Roboto2-Regular_NoEmbed.ttf");
+    if (!tf0 || !tf1 || tf0.get() == tf1.get()) {
+        return; // need two different typefaces for this test to make sense.
+    }
+
+#ifdef SK_DEBUG
+    REPORTER_ASSERT(reporter, tf0->getRefCnt() == 1);
+    REPORTER_ASSERT(reporter, tf1->getRefCnt() == 1);
+#endif
+    auto pic = make_picture(tf0, tf1);
+#ifdef SK_DEBUG
+    // picture should add 2 more references to each typeface
+    REPORTER_ASSERT(reporter, tf0->getRefCnt() == 3);
+    REPORTER_ASSERT(reporter, tf1->getRefCnt() == 3);
+#endif
+
+    int counter = 0;
+    SkSerialProcs procs;
+    procs.fTypefaceProc = [](SkTypeface* tf, void* ctx) -> sk_sp<SkData> {
+        *(int*)ctx += 1;
+        return nullptr;
+    };
+    procs.fTypefaceCtx = &counter;
+    auto data = pic->serialize(&procs);
+
+    // The picture has 2 references to each typeface, but we want the serialized picture to
+    // only have written the data 1 time per typeface.
+    REPORTER_ASSERT(reporter, counter == 2);
+}
+