SkPDF: refactor streams, experimental threading

    All PDF Streams are immediatly serialized, and are never long-lived
    in memory.

    if EXPERIMENTAL fExecutor is set on the Document, streams are
    compressed in parallel.

    Results for PDFBigDocBench:

        without patch                    1807885.01 μs
        with    patch without executor   1802808.35 μs
        with    patch with    executor    246313.72 μs

    SkPDFStreamOut() function replaces SkPDFStream classes.

    Page resources are all serialized early.

    Several Document-level objects are serialzied early.

    SkUUID introduced as top-level object.

    Many {insert|append}ObjRef() converted to memory efficient
    {insert|append}Ref().

Bug: skia:8630
Change-Id: Ic336917d0c8b9ac1c2423b43bfe9b49a3533fbff
Reviewed-on: https://skia-review.googlesource.com/c/176588
Auto-Submit: Hal Canary <halcanary@google.com>
Reviewed-by: Herb Derby <herb@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp
index f612357..5eae967 100644
--- a/bench/PDFBench.cpp
+++ b/bench/PDFBench.cpp
@@ -10,6 +10,7 @@
 #include "Resources.h"
 #include "SkAutoPixmapStorage.h"
 #include "SkData.h"
+#include "SkExecutor.h"
 #include "SkFloatToDecimal.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
@@ -79,20 +80,6 @@
 #include "SkPDFUtils.h"
 
 namespace {
-static void test_pdf_object_serialization(const sk_sp<SkPDFObject> object) {
-    // SkDebugWStream wStream;
-    SkNullWStream wStream;
-    SkPDFObjNumMap objNumMap;
-    objNumMap.addObjectRecursively(object.get());
-    for (size_t i = 0; i < objNumMap.objects().size(); ++i) {
-        SkPDFObject* object = objNumMap.objects()[i].get();
-        wStream.writeDecAsText(i + 1);
-        wStream.writeText(" 0 obj\n");
-        object->emitObject(&wStream);
-        wStream.writeText("\nendobj\n");
-    }
-}
-
 class PDFImageBench : public Benchmark {
 public:
     PDFImageBench() {}
@@ -184,11 +171,11 @@
         SkASSERT(fAsset);
         if (!fAsset) { return; }
         while (loops-- > 0) {
-            sk_sp<SkPDFObject> object =
-                sk_make_sp<SkPDFSharedStream>(
-                        std::unique_ptr<SkStreamAsset>(fAsset->duplicate()));
-            test_pdf_object_serialization(object);
-        }
+            SkNullWStream wStream;
+            SkPDFDocument doc(&wStream, SkPDF::Metadata());
+            doc.beginPage(256, 256);
+            (void)SkPDFStreamOut(nullptr, fAsset->duplicate(), &doc, true);
+       }
     }
 
 private:
@@ -229,8 +216,9 @@
         while (loops-- > 0) {
             SkNullWStream nullStream;
             SkPDFDocument doc(&nullStream, SkPDF::Metadata());
-            sk_sp<SkPDFObject> shader = SkPDFMakeShader(&doc, fShader.get(), SkMatrix::I(),
-                                                        {0, 0, 400, 400}, SK_ColorBLACK);
+            doc.beginPage(256, 256);
+            (void) SkPDFMakeShader(&doc, fShader.get(), SkMatrix::I(),
+                                   {0, 0, 400, 400}, SK_ColorBLACK);
         }
     }
 };
@@ -262,5 +250,125 @@
 DEF_BENCH(return new PDFShaderBench;)
 DEF_BENCH(return new WritePDFTextBenchmark;)
 
+#if 0
+#include "SkExecutor.h"
+namespace {
+void big_pdf_test(const SkBitmap& background) {
+    static const char* kText[] = {
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do",
+        "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad",
+        "minim veniam, quis nostrud exercitation ullamco laboris nisi ut",
+        "aliquip ex ea commodo consequat. Duis aute irure dolor in",
+        "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla",
+        "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in",
+        "culpa qui officia deserunt mollit anim id est laborum.",
+        "",
+        "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem",
+        "accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae",
+        "ab illo inventore veritatis et quasi architecto beatae vitae dicta",
+        "sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit,",
+        "aspernatur aut odit aut fugit, sed quia consequuntur magni dolores",
+        "eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,",
+        "qui dolorem ipsum, quia dolor sit amet consectetur adipiscing velit,",
+        "sed quia non numquam do eius modi tempora incididunt, ut labore et",
+        "dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,",
+        "quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi",
+        "ut aliquid ex ea commodi consequatur? Quis autem vel eum iure",
+        "reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae",
+        "consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla",
+        "pariatur?",
+        "",
+        "At vero eos et accusamus et iusto odio dignissimos ducimus, qui",
+        "blanditiis praesentium voluptatum deleniti atque corrupti, quos",
+        "dolores et quas molestias excepturi sint, obcaecati cupiditate non",
+        "provident, similique sunt in culpa, qui officia deserunt mollitia",
+        "animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis",
+        "est et expedita distinctio. Nam libero tempore, cum soluta nobis est",
+        "eligendi optio, cumque nihil impedit, quo minus id, quod maxime",
+        "placeat, facere possimus, omnis voluptas assumenda est, omnis dolor",
+        "repellendus. Temporibus autem quibusdam et aut officiis debitis aut",
+        "rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint",
+        "et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente",
+        "delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut",
+        "perferendis doloribus asperiores repellat",
+        "",
+        "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem",
+        "accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae",
+        "ab illo inventore veritatis et quasi architecto beatae vitae dicta",
+        "sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit,",
+        "aspernatur aut odit aut fugit, sed quia consequuntur magni dolores",
+        "eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,",
+        "qui dolorem ipsum, quia dolor sit amet consectetur adipiscing velit,",
+        "sed quia non numquam do eius modi tempora incididunt, ut labore et",
+        "dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,",
+        "quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi",
+        "ut aliquid ex ea commodi consequatur? Quis autem vel eum iure",
+        "reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae",
+        "consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla",
+        "pariatur?",
+        "",
+    };
+    //SkFILEWStream wStream("/tmp/big_pdf.pdf");
+    SkNullWStream wStream;
+    SkPDF::Metadata metadata;
+    //std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool();
+    //metadata.fExecutor = executor.get();
+    sk_sp<SkDocument> doc = SkPDF::MakeDocument(&wStream, metadata);
+
+    SkCanvas* canvas = nullptr;
+    float x = 36;
+    float y = 36;
+    constexpr size_t kLineCount = SK_ARRAY_COUNT(kText);
+    constexpr int kLoopCount = 200;
+    SkPaint paint;
+    paint.setTextEncoding(kUTF8_SkTextEncoding);
+    for (int loop = 0; loop < kLoopCount; ++loop) {
+        for (size_t line = 0; line < kLineCount; ++line) {
+            y += paint.getFontSpacing();
+            if (!canvas || y > 792 - 36) {
+                y = 36 + paint.getFontSpacing();
+                canvas = doc->beginPage(612, 792);
+                background.notifyPixelsChanged();
+                canvas->drawBitmap(background, 0, 0);
+            }
+            canvas->drawText(kText[line], strlen(kText[line]), x, y, paint);
+        }
+    }
+}
+
+SkBitmap make_background() {
+    SkBitmap background;
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(32, 32);
+    bitmap.eraseColor(SK_ColorWHITE);
+    SkCanvas tmp(bitmap);
+    SkPaint gray;
+    gray.setColor(SkColorSetARGB(0xFF, 0xEE, 0xEE, 0xEE));
+    tmp.drawRect({0,0,16,16}, gray);
+    tmp.drawRect({16,16,32,32}, gray);
+    SkPaint shader;
+    shader.setShader(
+            SkShader::MakeBitmapShader(
+                bitmap, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
+    background.allocN32Pixels(612, 792);
+    SkCanvas tmp2(background);
+    tmp2.drawPaint(shader);
+    return background;
+}
+
+struct PDFBigDocBench : public Benchmark {
+    SkBitmap fBackground;
+    void onDelayedSetup() override { fBackground = make_background(); }
+    const char* onGetName() override { return "PDFBigDocBench"; }
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+    void onDraw(int loops, SkCanvas*) override {
+        while (loops-- > 0) { big_pdf_test(fBackground); }
+    }
+};
+}  // namespace
+DEF_BENCH(return new PDFBigDocBench;)
 #endif
 
+#endif // SK_SUPPORT_PDF
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index cd9f099..e8726b4 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1638,6 +1638,10 @@
     metadata.fCreator = "Skia/DM";
     metadata.fRasterDPI = fRasterDpi;
     metadata.fPDFA = fPDFA;
+#if 0
+    std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool();
+    metadata.fExecutor = executor.get();
+#endif
     sk_sp<SkDocument> doc = SkPDF::MakeDocument(dst, metadata);
     if (!doc) {
         return "SkPDF::MakeDocument() returned nullptr";
diff --git a/include/docs/SkPDFDocument.h b/include/docs/SkPDFDocument.h
index 4623c7a..ad0007c 100644
--- a/include/docs/SkPDFDocument.h
+++ b/include/docs/SkPDFDocument.h
@@ -9,6 +9,8 @@
 #include "SkString.h"
 #include "SkTime.h"
 
+class SkExecutor;
+
 namespace SkPDF {
 
 /** Table 333 in PDF 32000-1:2008
@@ -141,6 +143,15 @@
      *  should retain ownership.
      */
     const StructureElementNode* fStructureElementTreeRoot = nullptr;
+
+    /** Executor to handle threaded work within PDF Backend. If this is nullptr,
+        then all work will be done serially on the main thread. To have worker
+        threads assist with various tasks, set this to a valid SkExecutor
+        instance. Currently used for executing Deflate algorithm in parallel.
+
+        Experimental.
+    */
+    SkExecutor* fExecutor = nullptr;
 };
 
 /** Associate a node ID with subsequent drawing commands in an
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp
index a5754ef..0a6e27c 100644
--- a/src/pdf/SkPDFBitmap.cpp
+++ b/src/pdf/SkPDFBitmap.cpp
@@ -10,6 +10,7 @@
 #include "SkColorData.h"
 #include "SkData.h"
 #include "SkDeflate.h"
+#include "SkExecutor.h"
 #include "SkImage.h"
 #include "SkImageInfoPriv.h"
 #include "SkJpegInfo.h"
@@ -120,12 +121,13 @@
     return ref;
 }
 
-static SkPDFIndirectReference do_deflated_image(const SkPixmap& pm,
-                                                SkPDFDocument* doc,
-                                                bool isOpaque) {
+static void  do_deflated_image(const SkPixmap& pm,
+                               SkPDFDocument* doc,
+                               bool isOpaque,
+                               SkPDFIndirectReference ref) {
     SkPDFIndirectReference sMask;
     if (!isOpaque) {
-        sMask = doc->reserve();
+        sMask = doc->reserveRef();
     }
     SkDynamicMemoryWStream buffer;
     SkDeflateWStream deflateWStream(&buffer);
@@ -167,7 +169,6 @@
             deflateWStream.write(byteBuffer, dst - byteBuffer);
     }
     deflateWStream.finalize();
-    SkPDFIndirectReference ref = doc->reserve();
     SkWStream* stream = doc->beginObject(ref);
     emit_dict(stream, pm.info().dimensions(), colorSpace,
               sMask.fValue != -1 ? &sMask : nullptr,
@@ -177,11 +178,10 @@
     if (!isOpaque) {
         do_deflated_alpha(pm, doc, sMask);
     }
-    return ref;
 }
 
 static bool do_jpeg(const SkData& data, SkPDFDocument* doc, SkISize size,
-                    SkPDFIndirectReference* result) {
+                    SkPDFIndirectReference ref) {
     SkISize jpegSize;
     SkEncodedInfo::Color jpegColorType;
     SkEncodedOrigin exifOrientation;
@@ -199,10 +199,8 @@
     #ifdef SK_PDF_IMAGE_STATS
     gJpegImageObjects.fetch_add(1);
     #endif
-    SkPDFIndirectReference ref = doc->reserve();
-    *result = ref;
-    SkWStream* stream = doc->beginObject(ref);
 
+    SkWStream* stream = doc->beginObject(ref);
     SkPDFDict pdfDict("XObject");
     pdfDict.insertName("Subtype", "Image");
     pdfDict.insertInt("Width", jpegSize.width());
@@ -247,26 +245,44 @@
     return bm;
 }
 
-SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
-                                           SkPDFDocument* doc,
-                                           int encodingQuality) {
-    SkPDFIndirectReference result;
+void serialize_image(const SkImage* img,
+                     int encodingQuality,
+                     SkPDFDocument* doc,
+                     SkPDFIndirectReference ref) {
     SkASSERT(img);
     SkASSERT(doc);
     SkASSERT(encodingQuality >= 0);
     SkISize dimensions = img->dimensions();
     sk_sp<SkData> data = img->refEncodedData();
-    if (data && do_jpeg(*data, doc, dimensions, &result)) {
-        return result;
+    if (data && do_jpeg(*data, doc, dimensions, ref)) {
+        return;
     }
     SkBitmap bm = to_pixels(img);
     SkPixmap pm = bm.pixmap();
     bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
     if (encodingQuality <= 100 && isOpaque) {
         sk_sp<SkData> data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
-        if (data && do_jpeg(*data, doc, dimensions, &result)) {
-            return result;
+        if (data && do_jpeg(*data, doc, dimensions, ref)) {
+            return;
         }
     }
-    return do_deflated_image(pm, doc, isOpaque);
+    do_deflated_image(pm, doc, isOpaque, ref);
+}
+
+SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
+                                           SkPDFDocument* doc,
+                                           int encodingQuality) {
+    SkASSERT(img);
+    SkASSERT(doc);
+    SkPDFIndirectReference ref = doc->reserveRef();
+    if (SkExecutor* executor = doc->executor()) {
+        SkRef(img);
+        executor->add([img, encodingQuality, doc, ref]() {
+            serialize_image(img, encodingQuality, doc, ref);
+            SkSafeUnref(img);
+        });
+        return ref;
+    }
+    serialize_image(img, encodingQuality, doc, ref);
+    return ref;
 }
diff --git a/src/pdf/SkPDFCanon.h b/src/pdf/SkPDFCanon.h
index b4782c2..1d93711 100644
--- a/src/pdf/SkPDFCanon.h
+++ b/src/pdf/SkPDFCanon.h
@@ -34,11 +34,11 @@
     SkPDFCanon& operator=(SkPDFCanon&&);
     SkPDFCanon& operator=(const SkPDFCanon&) = delete;
 
-    SkTHashMap<SkPDFImageShaderKey, sk_sp<SkPDFObject>> fImageShaderMap;
+    SkTHashMap<SkPDFImageShaderKey, SkPDFIndirectReference> fImageShaderMap;
 
     SkPDFGradientShader::HashMap fGradientPatternMap;
 
-    SkTHashMap<SkBitmapKey, sk_sp<SkPDFObject>> fPDFBitmapMap;
+    SkTHashMap<SkBitmapKey, SkPDFIndirectReference> fPDFBitmapMap;
 
     SkTHashMap<uint32_t, std::unique_ptr<SkAdvancedTypefaceMetrics>> fTypefaceMetrics;
     SkTHashMap<uint32_t, std::vector<SkString>> fType1GlyphNames;
@@ -47,12 +47,11 @@
     SkTHashMap<uint32_t, SkPDFIndirectReference> fType3FontDescriptors;
     SkTHashMap<uint64_t, SkPDFFont> fFontMap;
 
-    SkTHashMap<SkPDFStrokeGraphicState, sk_sp<SkPDFDict>> fStrokeGSMap;
-    SkTHashMap<SkPDFFillGraphicState, sk_sp<SkPDFDict>> fFillGSMap;
+    SkTHashMap<SkPDFStrokeGraphicState, SkPDFIndirectReference> fStrokeGSMap;
+    SkTHashMap<SkPDFFillGraphicState, SkPDFIndirectReference> fFillGSMap;
 
-    sk_sp<SkPDFStream> fInvertFunction;
-    sk_sp<SkPDFDict> fNoSmaskGraphicState;
-    sk_sp<SkPDFArray> fRangeObject;
+    SkPDFIndirectReference fInvertFunction;
+    SkPDFIndirectReference fNoSmaskGraphicState;
 };
 
 #endif  // SkPDFCanon_DEFINED
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 7475009..95d65d4 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -107,6 +107,11 @@
     return SkImage::MakeFromBitmap(greyBitmap);
 }
 
+static int add_resource(SkTHashSet<SkPDFIndirectReference>& resources, SkPDFIndirectReference ref) {
+    resources.add(ref);
+    return ref.fValue;
+}
+
 static void draw_points(SkCanvas::PointMode mode,
                         size_t count,
                         const SkPoint* points,
@@ -429,7 +434,7 @@
             if (shape->isEmpty()) {
                 shape = nullptr;
             }
-            fDevice->finishContentEntry(fClipStack, fBlendMode, std::move(fDstFormXObject), shape);
+            fDevice->finishContentEntry(fClipStack, fBlendMode, fDstFormXObject, shape);
         }
     }
 
@@ -474,7 +479,7 @@
     SkPDFDevice* fDevice = nullptr;
     SkDynamicMemoryWStream* fContentStream = nullptr;
     SkBlendMode fBlendMode;
-    sk_sp<SkPDFObject> fDstFormXObject;
+    SkPDFIndirectReference fDstFormXObject;
     SkPath fShape;
     const SkClipStack* fClipStack;
 };
@@ -497,9 +502,9 @@
     fLinkToURLs = std::vector<RectWithData>();
     fLinkToDestinations = std::vector<RectWithData>();
     fNamedDestinations = std::vector<NamedDestination>();
-    fGraphicStateResources = std::vector<sk_sp<SkPDFObject>>();
-    fXObjectResources = std::vector<sk_sp<SkPDFObject>>();
-    fShaderResources = std::vector<sk_sp<SkPDFObject>>();
+    fGraphicStateResources.reset();
+    fXObjectResources.reset();
+    fShaderResources.reset();
     fFontResources.reset();
     fContent.reset();
     fActiveStackState = GraphicStackState();
@@ -797,23 +802,24 @@
     return index;
 }
 
-void SkPDFDevice::setGraphicState(sk_sp<SkPDFObject> gs, SkDynamicMemoryWStream* content) {
-    SkPDFUtils::ApplyGraphicState(find_or_add(&fGraphicStateResources, std::move(gs)), content);
+void SkPDFDevice::setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream* content) {
+    SkPDFUtils::ApplyGraphicState(add_resource(fGraphicStateResources, gs), content);
 }
 
 void SkPDFDevice::addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice,
                                        SkDynamicMemoryWStream* contentStream) {
     this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
             maskDevice->makeFormXObjectFromDevice(true), false,
-            SkPDFGraphicState::kLuminosity_SMaskMode, this->getCanon()), contentStream);
+            SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), contentStream);
 }
 
 void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) {
     // The no-softmask graphic state is used to "turn off" the mask for later draw calls.
-    sk_sp<SkPDFDict>& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
+    SkPDFIndirectReference& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
     if (!noSMaskGS) {
-        noSMaskGS = sk_make_sp<SkPDFDict>("ExtGState");
-        noSMaskGS->insertName("SMask", "None");
+        SkPDFDict tmp("ExtGState");
+        tmp.insertName("SMask", "None");
+        noSMaskGS = fDocument->emit(tmp);
     }
     this->setGraphicState(noSMaskGS, contentStream);
 }
@@ -1240,12 +1246,10 @@
                     // Not yet specified font or need to switch font.
                     font = SkPDFFont::GetFontResource(fDocument, glyphCache.get(), typeface, gid);
                     SkASSERT(font);  // All preconditions for SkPDFFont::GetFontResource are met.
-                    SkPDFIndirectReference ref = font->indirectReference();
-                    fFontResources.add(ref);
-
                     glyphPositioner.flush();
                     glyphPositioner.setWideChars(font->multiByteGlyphs());
-                    SkPDFWriteResourceName(out, SkPDFResourceType::kFont, ref.fValue);
+                    SkPDFWriteResourceName(out, SkPDFResourceType::kFont,
+                                           add_resource(fFontResources, font->indirectReference()));
                     out->writeText(" ");
                     SkPDFUtils::AppendScalar(textSize, out);
                     out->writeText(" Tf\n");
@@ -1275,9 +1279,10 @@
     // TODO: implement drawVertices
 }
 
-void SkPDFDevice::drawFormXObject(sk_sp<SkPDFObject> xObject, SkDynamicMemoryWStream* content) {
+void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content) {
+    SkASSERT(xObject);
     SkPDFWriteResourceName(content, SkPDFResourceType::kXObject,
-                           find_or_add(&fXObjectResources, std::move(xObject)));
+                           add_resource(fXObjectResources, xObject));
     content->writeText(" Do\n");
 }
 
@@ -1335,18 +1340,20 @@
     return SkSurface::MakeRaster(info, &props);
 }
 
+static std::vector<SkPDFIndirectReference> sort(const SkTHashSet<SkPDFIndirectReference>& src) {
+    std::vector<SkPDFIndirectReference> dst;
+    dst.reserve(src.count());
+    src.foreach([&dst](SkPDFIndirectReference ref) { dst.push_back(ref); } );
+    std::sort(dst.begin(), dst.end(),
+            [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; });
+    return dst;
+}
 
 sk_sp<SkPDFDict> SkPDFDevice::makeResourceDict() {
-    std::vector<SkPDFIndirectReference> fonts;
-    fonts.reserve(fFontResources.count());
-    fFontResources.foreach([&fonts](SkPDFIndirectReference ref) { fonts.push_back(ref); } );
-    fFontResources.reset();
-    std::sort(fonts.begin(), fonts.end(),
-            [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; });
-    return SkPDFMakeResourceDict(std::move(fGraphicStateResources),
-                                 std::move(fShaderResources),
-                                 std::move(fXObjectResources),
-                                 std::move(fonts));
+    return SkPDFMakeResourceDict(sort(fGraphicStateResources),
+                                 sort(fShaderResources),
+                                 sort(fXObjectResources),
+                                 sort(fFontResources));
 }
 
 std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
@@ -1446,13 +1453,13 @@
     for (const RectWithData& rectWithURL : fLinkToURLs) {
         SkRect r;
         fInitialTransform.mapRect(&r, rectWithURL.rect);
-        array->appendObjRef(create_link_to_url(rectWithURL.data.get(), r));
+        array->appendRef(fDocument->emit(*create_link_to_url(rectWithURL.data.get(), r)));
     }
     for (const RectWithData& linkToDestination : fLinkToDestinations) {
         SkRect r;
         fInitialTransform.mapRect(&r, linkToDestination.rect);
-        array->appendObjRef(
-                create_link_named_dest(linkToDestination.data.get(), r));
+        array->appendRef(
+                fDocument->emit(*create_link_named_dest(linkToDestination.data.get(), r)));
     }
     return array;
 }
@@ -1461,6 +1468,7 @@
     for (const NamedDestination& dest : fNamedDestinations) {
         auto pdfDest = sk_make_sp<SkPDFArray>();
         pdfDest->reserve(5);
+        // TODO(halcanry) reserve an IndirectReference for each page with beginPage()
         pdfDest->appendObjRef(sk_ref_sp(page));
         pdfDest->appendName("XYZ");
         SkPoint p = fInitialTransform.mapXY(dest.point.x(), dest.point.y());
@@ -1472,7 +1480,7 @@
     }
 }
 
-sk_sp<SkPDFObject> SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
+SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
     SkMatrix inverseTransform = SkMatrix::I();
     if (!fInitialTransform.isIdentity()) {
         if (!fInitialTransform.invert(&inverseTransform)) {
@@ -1482,8 +1490,8 @@
     }
     const char* colorSpace = alpha ? "DeviceGray" : nullptr;
 
-    sk_sp<SkPDFObject> xobject =
-        SkPDFMakeFormXObject(this->content(),
+    SkPDFIndirectReference xobject =
+        SkPDFMakeFormXObject(fDocument, this->content(),
                              SkPDFMakeArray(0, 0, this->width(), this->height()),
                              this->makeResourceDict(), inverseTransform, colorSpace);
     // We always draw the form xobjects that we create back into the device, so
@@ -1493,10 +1501,11 @@
     return xobject;
 }
 
-void SkPDFDevice::drawFormXObjectWithMask(sk_sp<SkPDFObject> xObject,
-                                          sk_sp<SkPDFObject> mask,
+void SkPDFDevice::drawFormXObjectWithMask(SkPDFIndirectReference xObject,
+                                          SkPDFIndirectReference sMask,
                                           SkBlendMode mode,
                                           bool invertClip) {
+    SkASSERT(sMask);
     SkPaint paint;
     paint.setBlendMode(mode);
     ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint);
@@ -1504,9 +1513,9 @@
         return;
     }
     this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
-            std::move(mask), invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
-            fDocument->canon()), content.stream());
-    this->drawFormXObject(std::move(xObject), content.stream());
+            sMask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
+            fDocument), content.stream());
+    this->drawFormXObject(xObject, content.stream());
     this->clearMaskOnGraphicState(content.stream());
 }
 
@@ -1519,8 +1528,8 @@
                                                        const SkMatrix& matrix,
                                                        const SkPaint& paint,
                                                        bool hasText,
-                                                       sk_sp<SkPDFObject>* dst) {
-    *dst = nullptr;
+                                                       SkPDFIndirectReference* dst) {
+    SkASSERT(!*dst);
     SkBlendMode blendMode = paint.getBlendMode();
 
     // Dst xfer mode doesn't draw source at all.
@@ -1570,7 +1579,7 @@
 
 void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
                                      SkBlendMode blendMode,
-                                     sk_sp<SkPDFObject> dst,
+                                     SkPDFIndirectReference dst,
                                      SkPath* shape) {
     SkASSERT(blendMode != SkBlendMode::kDst);
     if (treat_as_regular_pdf_blend_mode(blendMode)) {
@@ -1618,7 +1627,7 @@
 
     SkPaint stockPaint;
 
-    sk_sp<SkPDFObject> srcFormXObject;
+    SkPDFIndirectReference srcFormXObject;
     if (this->isContentEmpty()) {
         // If nothing was drawn and there's no shape, then the draw was a
         // no-op, but dst needs to be restored for that to be true.
@@ -1628,7 +1637,7 @@
         if (shape == nullptr || blendMode == SkBlendMode::kDstOut ||
                 blendMode == SkBlendMode::kSrcATop) {
             ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
-            this->drawFormXObject(std::move(dst), content.stream());
+            this->drawFormXObject(dst, content.stream());
             return;
         } else {
             blendMode = SkBlendMode::kClear;
@@ -1691,8 +1700,8 @@
     if (blendMode == SkBlendMode::kSrcIn ||
             blendMode == SkBlendMode::kSrcOut ||
             blendMode == SkBlendMode::kSrcATop) {
-        this->drawFormXObjectWithMask(std::move(srcFormXObject), std::move(dst),
-                                      SkBlendMode::kSrcOver, blendMode == SkBlendMode::kSrcOut);
+        this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver,
+                                      blendMode == SkBlendMode::kSrcOut);
         return;
     } else {
         SkBlendMode mode = SkBlendMode::kSrcOver;
@@ -1700,8 +1709,7 @@
             this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, false);
             mode = SkBlendMode::kMultiply;
         }
-        this->drawFormXObjectWithMask(std::move(dst), std::move(srcFormXObject), mode,
-                                      blendMode == SkBlendMode::kDstOut);
+        this->drawFormXObjectWithMask(dst, srcFormXObject, mode, blendMode == SkBlendMode::kDstOut);
         return;
     }
 }
@@ -1728,7 +1736,6 @@
     entry->fShaderIndex = -1;
 
     // PDF treats a shader as a color, so we only set one or the other.
-    sk_sp<SkPDFObject> pdfShader;
     SkShader* shader = paint.getShader();
     if (shader) {
         if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
@@ -1759,24 +1766,25 @@
             SkIRect bounds;
             clipStackBounds.roundOut(&bounds);
 
-            pdfShader = SkPDFMakeShader(fDocument, shader, transform, bounds, paint.getColor());
+            SkPDFIndirectReference pdfShader
+                = SkPDFMakeShader(fDocument, shader, transform, bounds, paint.getColor());
 
             if (pdfShader) {
                 // pdfShader has been canonicalized so we can directly compare pointers.
-                entry->fShaderIndex = find_or_add(&fShaderResources, std::move(pdfShader));
+                entry->fShaderIndex = add_resource(fShaderResources, pdfShader);
             }
         }
     }
 
-    sk_sp<SkPDFDict> newGraphicState;
+    SkPDFIndirectReference newGraphicState;
     if (color == paint.getColor4f()) {
-        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint);
+        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument, paint);
     } else {
         SkPaint newPaint = paint;
         newPaint.setColor4f(color, nullptr);
-        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), newPaint);
+        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument, newPaint);
     }
-    entry->fGraphicStateIndex = find_or_add(&fGraphicStateResources, std::move(newGraphicState));
+    entry->fGraphicStateIndex = add_resource(fGraphicStateResources, std::move(newGraphicState));
 
     if (hasText) {
         entry->fTextScaleX = paint.getTextScaleX();
@@ -1814,15 +1822,6 @@
            is_integer(r.bottom());
 }
 
-namespace {
-// This struct will go away when fIndirectReference goes away.
-struct PDFObj final : public SkPDFObject {
-    PDFObj(SkPDFIndirectReference ref) { fIndirectReference = ref; }
-    // emitObject() is never called since the Object already has a indirect ref.
-    void emitObject(SkWStream*) const override { SK_ABORT("DO NOT REACH HERE"); }
-};
-} // namespace
-
 void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
                                         const SkRect* src,
                                         const SkRect& dst,
@@ -2046,18 +2045,17 @@
     }
 
     SkBitmapKey key = imageSubset.key();
-    sk_sp<SkPDFObject>* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
-    sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
-    if (!pdfimage) {
+    SkPDFIndirectReference* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
+    SkPDFIndirectReference pdfimage = pdfimagePtr ? *pdfimagePtr : SkPDFIndirectReference();
+    if (!pdfimagePtr) {
         SkASSERT(imageSubset);
-        auto ref = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
+        pdfimage = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
                                        fDocument->metadata().fEncodingQuality);
-        SkASSERT(ref.fValue > 0);
-        pdfimage = sk_make_sp<PDFObj>(ref);
         SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
         fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
     }
-    this->drawFormXObject(std::move(pdfimage), content.stream());
+    SkASSERT(pdfimage != SkPDFIndirectReference());
+    this->drawFormXObject(pdfimage, content.stream());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h
index 86dc4f5..c709d59 100644
--- a/src/pdf/SkPDFDevice.h
+++ b/src/pdf/SkPDFDevice.h
@@ -33,7 +33,6 @@
 class SkPDFDict;
 class SkPDFFont;
 class SkPDFObject;
-class SkPDFStream;
 class SkRRect;
 struct SkPDFIndirectReference;
 
@@ -167,9 +166,9 @@
     std::vector<RectWithData> fLinkToDestinations;
     std::vector<NamedDestination> fNamedDestinations;
 
-    std::vector<sk_sp<SkPDFObject>> fGraphicStateResources;
-    std::vector<sk_sp<SkPDFObject>> fXObjectResources;
-    std::vector<sk_sp<SkPDFObject>> fShaderResources;
+    SkTHashSet<SkPDFIndirectReference> fGraphicStateResources;
+    SkTHashSet<SkPDFIndirectReference> fXObjectResources;
+    SkTHashSet<SkPDFIndirectReference> fShaderResources;
     SkTHashSet<SkPDFIndirectReference> fFontResources;
     int fNodeId;
 
@@ -199,10 +198,10 @@
     SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
 
     // Set alpha to true if making a transparency group form x-objects.
-    sk_sp<SkPDFObject> makeFormXObjectFromDevice(bool alpha = false);
+    SkPDFIndirectReference makeFormXObjectFromDevice(bool alpha = false);
 
-    void drawFormXObjectWithMask(sk_sp<SkPDFObject> xObject,
-                                 sk_sp<SkPDFObject> mask,
+    void drawFormXObjectWithMask(SkPDFIndirectReference xObject,
+                                 SkPDFIndirectReference sMask,
                                  SkBlendMode,
                                  bool invertClip);
 
@@ -211,11 +210,11 @@
     // setUpContentEntry and finishContentEntry can be used directly, but
     // the preferred method is to use the ScopedContentEntry helper class.
     SkDynamicMemoryWStream* setUpContentEntry(const SkClipStack* clipStack,
-                                    const SkMatrix& matrix,
-                                    const SkPaint& paint,
-                                    bool hasText,
-                                    sk_sp<SkPDFObject>* dst);
-    void finishContentEntry(const SkClipStack*, SkBlendMode, sk_sp<SkPDFObject> dst, SkPath* shape);
+                                              const SkMatrix& matrix,
+                                              const SkPaint& paint,
+                                              bool hasText,
+                                              SkPDFIndirectReference* dst);
+    void finishContentEntry(const SkClipStack*, SkBlendMode, SkPDFIndirectReference, SkPath*);
     bool isContentEmpty();
 
     void populateGraphicStateEntryFromPaint(const SkMatrix& matrix,
@@ -248,8 +247,8 @@
 
     void addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice, SkDynamicMemoryWStream*);
     void clearMaskOnGraphicState(SkDynamicMemoryWStream*);
-    void setGraphicState(sk_sp<SkPDFObject>, SkDynamicMemoryWStream*);
-    void drawFormXObject(sk_sp<SkPDFObject>, SkDynamicMemoryWStream*);
+    void setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream*);
+    void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*);
 
     bool hasEmptyClip() const { return this->cs().isEmpty(this->bounds()); }
 
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
index d2bb292..2bf48ee 100644
--- a/src/pdf/SkPDFDocument.cpp
+++ b/src/pdf/SkPDFDocument.cpp
@@ -47,10 +47,6 @@
 
 SkPDFObjectSerializer::~SkPDFObjectSerializer() = default;
 
-SkPDFObjectSerializer::SkPDFObjectSerializer(SkPDFObjectSerializer&&) = default;
-
-SkPDFObjectSerializer& SkPDFObjectSerializer::operator=(SkPDFObjectSerializer&&) = default;
-
 
 #define SKPDF_MAGIC "\xD3\xEB\xE9\xE1"
 #ifndef SK_BUILD_FOR_WIN
@@ -59,16 +55,13 @@
 static_assert((SKPDF_MAGIC[2] & 0x7F) == "Skia"[2], "");
 static_assert((SKPDF_MAGIC[3] & 0x7F) == "Skia"[3], "");
 #endif
-void SkPDFObjectSerializer::serializeHeader(SkWStream* wStream,
-                                            const SkPDF::Metadata& md) {
+void SkPDFObjectSerializer::serializeHeader(SkWStream* wStream) {
     fBaseOffset = wStream->bytesWritten();
     static const char kHeader[] = "%PDF-1.4\n%" SKPDF_MAGIC "\n";
     wStream->writeText(kHeader);
     // The PDF spec recommends including a comment with four
     // bytes, all with their high bits set.  "\xD3\xEB\xE9\xE1" is
     // "Skia" with the high bits set.
-    fInfoDict = SkPDFMetadata::MakeDocumentInformationDict(md);
-    this->serializeObject(fInfoDict, wStream);
 }
 #undef SKPDF_MAGIC
 
@@ -86,8 +79,7 @@
 
 void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object,
                                             SkWStream* wStream) {
-    SkPDFObjNumMap objNumMap;
-    objNumMap.fNextObjectNumber = fNextObjectNumber;
+    SkPDFObjNumMap objNumMap(this);
     objNumMap.addObjectRecursively(object.get());
 
     for (const sk_sp<SkPDFObject>& object : objNumMap.fObjects) {
@@ -96,18 +88,18 @@
         this->endObject(wStream);
         object->drop();
     }
-    fNextObjectNumber = objNumMap.fNextObjectNumber; // save for later.
 }
 
 
 // Xref table and footer
 void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
-                                            const sk_sp<SkPDFObject> docCatalog,
-                                            sk_sp<SkPDFObject> id) {
+                                            SkPDFIndirectReference infoDict,
+                                            SkPDFIndirectReference docCatalog,
+                                            SkUUID uuid) {
     int xRefFileOffset = this->offset(wStream).fValue;
     // Include the special zeroth object in the count.
 
-    int objCount = SkToS32(fNextObjectNumber);
+    int objCount = SkToS32(fNextObjectNumber.load());
     wStream->writeText("xref\n0 ");
     wStream->writeDecAsText(objCount);
     wStream->writeText("\n0000000000 65535 f \n");
@@ -118,12 +110,12 @@
     }
     SkPDFDict trailerDict;
     trailerDict.insertInt("Size", objCount);
-    SkASSERT(docCatalog);
-    trailerDict.insertObjRef("Root", docCatalog);
-    SkASSERT(fInfoDict);
-    trailerDict.insertObjRef("Info", std::move(fInfoDict));
-    if (id) {
-        trailerDict.insertObject("ID", std::move(id));
+    SkASSERT(docCatalog != SkPDFIndirectReference());
+    trailerDict.insertRef("Root", docCatalog);
+    SkASSERT(infoDict != SkPDFIndirectReference());
+    trailerDict.insertRef("Info", infoDict);
+    if (SkUUID() != uuid) {
+        trailerDict.insertObject("ID", SkPDFMetadata::MakePdfId(uuid, uuid));
     }
     wStream->writeText("trailer\n");
     trailerDict.emitObject(wStream);
@@ -216,6 +208,7 @@
     if (fMetadata.fStructureElementTreeRoot) {
         fTagRoot = recursiveBuildTagTree(*fMetadata.fStructureElementTreeRoot, nullptr);
     }
+    fExecutor = metadata.fExecutor;
 }
 
 SkPDFDocument::~SkPDFDocument() {
@@ -225,28 +218,34 @@
 
 SkPDFIndirectReference SkPDFDocument::serialize(const sk_sp<SkPDFObject>& object) {
     SkASSERT(object);
-    fObjectSerializer.serializeObject(object, this->getStream());
+    if (object->fIndirectReference == SkPDFIndirectReference()) {
+        SkAutoMutexAcquire autoMutexAcquire(fMutex);
+        fObjectSerializer.serializeObject(object, this->getStream());
+    }
+    SkASSERT(object->fIndirectReference != SkPDFIndirectReference());
     return object->fIndirectReference;
 }
 
-SkPDFIndirectReference SkPDFDocument::emit(const SkPDFObject& object){
-    SkPDFIndirectReference ref = this->reserve();
+SkPDFIndirectReference SkPDFDocument::emit(const SkPDFObject& object, SkPDFIndirectReference ref){
     object.emitObject(this->beginObject(ref));
     this->endObject();
     return ref;
 }
 
-SkPDFIndirectReference SkPDFDocument::reserve() {
+SkPDFIndirectReference SkPDFDocument::reserveRef() {
     ++fOutstandingRefs;
-    return SkPDFIndirectReference{fObjectSerializer.fNextObjectNumber++};
+    return fObjectSerializer.reserve();
 };
 
 SkWStream* SkPDFDocument::beginObject(SkPDFIndirectReference ref) {
-    --fOutstandingRefs;
+    fMutex.acquire();
     return fObjectSerializer.beginObject(ref, this->getStream());
 };
+
 void SkPDFDocument::endObject() {
     fObjectSerializer.endObject(this->getStream());
+    fMutex.release();
+    fSemaphore.signal();
 };
 
 static SkSize operator*(SkISize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; }
@@ -256,18 +255,22 @@
     SkASSERT(fCanvas.imageInfo().dimensions().isZero());
     if (fPages.empty()) {
         // if this is the first page if the document.
-        fObjectSerializer.serializeHeader(this->getStream(), fMetadata);
+        {
+            SkAutoMutexAcquire autoMutexAcquire(fMutex);
+            fObjectSerializer.serializeHeader(this->getStream());
+        }
+
+        fInfoDict = this->serialize(SkPDFMetadata::MakeDocumentInformationDict(fMetadata));
         fDests = sk_make_sp<SkPDFDict>();
         if (fMetadata.fPDFA) {
-            SkPDFMetadata::UUID uuid = SkPDFMetadata::CreateUUID(fMetadata);
+            fUUID = SkPDFMetadata::CreateUUID(fMetadata);
             // We use the same UUID for Document ID and Instance ID since this
             // is the first revision of this document (and Skia does not
             // support revising existing PDF documents).
             // If we are not in PDF/A mode, don't use a UUID since testing
             // works best with reproducible outputs.
-            fID = SkPDFMetadata::MakePdfId(uuid, uuid);
-            fXMP = SkPDFMetadata::MakeXMPObject(fMetadata, uuid, uuid);
-            fObjectSerializer.serializeObject(fXMP, this->getStream());
+            fXMP = this->serialize(
+                SkPDFMetadata::MakeXMPObject(fMetadata, fUUID, fUUID));
         }
     }
     // By scaling the page at the device level, we will create bitmap layer
@@ -295,7 +298,7 @@
     auto page = sk_make_sp<SkPDFDict>("Page");
 
     SkSize mediaSize = fPageDevice->imageInfo().dimensions() * fInverseRasterScale;
-    auto contentObject = sk_make_sp<SkPDFStream>(fPageDevice->content());
+    std::unique_ptr<SkStreamAsset> pageContent = fPageDevice->content();
     auto resourceDict = fPageDevice->makeResourceDict();
     auto annotations = fPageDevice->getAnnotations();
     fPageDevice->appendDestinations(fDests.get(), page.get());
@@ -307,8 +310,7 @@
     if (annotations) {
         page->insertObject("Annots", std::move(annotations));
     }
-    this->serialize(contentObject);
-    page->insertObjRef("Contents", std::move(contentObject));
+    page->insertRef("Contents", SkPDFStreamOut(nullptr, std::move(pageContent), this));
     // The StructParents unique identifier for each page is just its
     // 0-based page index.
     page->insertInt("StructParents", static_cast<int>(fPages.size()));
@@ -320,14 +322,14 @@
 }
 
 void SkPDFDocument::reset() {
-    fObjectSerializer = SkPDFObjectSerializer();
+    reset_object(&fObjectSerializer);
     fCanon = SkPDFCanon();
     reset_object(&fCanvas);
     fPages = std::vector<sk_sp<SkPDFDict>>();
     fDests = nullptr;
     fPageDevice = nullptr;
-    fID = nullptr;
-    fXMP = nullptr;
+    fUUID = SkUUID();
+    fXMP = SkPDFIndirectReference();
     fMetadata = SkPDF::Metadata();
     fRasterScale = 1;
     fInverseRasterScale = 1;
@@ -447,14 +449,14 @@
     return SkData::MakeWithoutCopy(kProfile, kProfileLength);
 }
 
-static sk_sp<SkPDFStream> make_srgb_color_profile() {
-    sk_sp<SkPDFStream> stream = sk_make_sp<SkPDFStream>(SkSrgbIcm());
-    stream->dict()->insertInt("N", 3);
-    stream->dict()->insertObject("Range", SkPDFMakeArray(0, 1, 0, 1, 0, 1));
-    return stream;
+static SkPDFIndirectReference make_srgb_color_profile(SkPDFDocument* doc) {
+    sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
+    dict->insertInt("N", 3);
+    dict->insertObject("Range", SkPDFMakeArray(0, 1, 0, 1, 0, 1));
+    return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(SkSrgbIcm()), doc, true);
 }
 
-static sk_sp<SkPDFArray> make_srgb_output_intents() {
+static sk_sp<SkPDFArray> make_srgb_output_intents(SkPDFDocument* doc) {
     // sRGB is specified by HTML, CSS, and SVG.
     auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent");
     outputIntent->insertName("S", "GTS_PDFA1");
@@ -462,8 +464,7 @@
     outputIntent->insertString("OutputConditionIdentifier",
                                "Custom");
     outputIntent->insertString("Info","sRGB IEC61966-2.1");
-    outputIntent->insertObjRef("DestOutputProfile",
-                               make_srgb_color_profile());
+    outputIntent->insertRef("DestOutputProfile", make_srgb_color_profile(doc));
     auto intentArray = sk_make_sp<SkPDFArray>();
     intentArray->appendObject(std::move(outputIntent));
     return intentArray;
@@ -533,11 +534,11 @@
     }
     auto docCatalog = sk_make_sp<SkPDFDict>("Catalog");
     if (fMetadata.fPDFA) {
-        SkASSERT(fXMP);
-        docCatalog->insertObjRef("Metadata", fXMP);
+        SkASSERT(fXMP != SkPDFIndirectReference());
+        docCatalog->insertRef("Metadata", fXMP);
         // Don't specify OutputIntents if we are not in PDF/A mode since
         // no one has ever asked for this feature.
-        docCatalog->insertObject("OutputIntents", make_srgb_output_intents());
+        docCatalog->insertObject("OutputIntents", make_srgb_output_intents(this));
     }
 
     sk_sp<SkPDFDict> pageTree = generate_page_tree(fPages);
@@ -593,15 +594,23 @@
             }
         }
     }
-    fObjectSerializer.serializeObject(docCatalog, this->getStream());
+    auto docCatalogRef = this->serialize(docCatalog);
     for (const SkPDFFont* f : get_fonts(fCanon)) {
         f->emitSubset(this);
     }
-    SkASSERT(fOutstandingRefs == 0);
-    fObjectSerializer.serializeFooter(this->getStream(), docCatalog, fID);
+    while (fOutstandingRefs > 0) {
+        fSemaphore.wait();
+        --fOutstandingRefs;
+    }
+
+    {
+        SkAutoMutexAcquire autoMutexAcquire(fMutex);
+        fObjectSerializer.serializeFooter(this->getStream(), fInfoDict, docCatalogRef, fUUID);
+    }
     this->reset();
 }
 
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkPDF::SetNodeId(SkCanvas* canvas, int nodeID) {
diff --git a/src/pdf/SkPDFDocumentPriv.h b/src/pdf/SkPDFDocumentPriv.h
index c23e026..27810dd 100644
--- a/src/pdf/SkPDFDocumentPriv.h
+++ b/src/pdf/SkPDFDocumentPriv.h
@@ -8,13 +8,17 @@
 #define SkPDFDocumentPriv_DEFINED
 
 #include "SkCanvas.h"
-#include "SkPDFDocument.h"
 #include "SkPDFCanon.h"
+#include "SkPDFDocument.h"
 #include "SkPDFFont.h"
 #include "SkPDFMetadata.h"
+#include "SkUUID.h"
+
+#include <atomic>
 
 class SkPDFDevice;
 class SkPDFTag;
+class SkExecutor;
 
 const char* SkPDFGetNodeIdKey();
 
@@ -31,23 +35,26 @@
 // Logically part of SkPDFDocument (like SkPDFCanon), but separate to
 // keep similar functionality together.
 struct SkPDFObjectSerializer {
-    int fNextObjectNumber = 1;
+    std::atomic<int> fNextObjectNumber = {1};
+    SkPDFIndirectReference reserve() { return SkPDFIndirectReference{fNextObjectNumber++}; }
     SkPDFOffsetMap fOffsets;
-    sk_sp<SkPDFObject> fInfoDict;
     size_t fBaseOffset = SIZE_MAX;
 
     SkPDFObjectSerializer();
     ~SkPDFObjectSerializer();
-    SkPDFObjectSerializer(SkPDFObjectSerializer&&);
-    SkPDFObjectSerializer& operator=(SkPDFObjectSerializer&&);
+    SkPDFObjectSerializer(SkPDFObjectSerializer&&) = delete;
+    SkPDFObjectSerializer& operator=(SkPDFObjectSerializer&&) = delete;
     SkPDFObjectSerializer(const SkPDFObjectSerializer&) = delete;
     SkPDFObjectSerializer& operator=(const SkPDFObjectSerializer&) = delete;
 
     SkWStream* beginObject(SkPDFIndirectReference, SkWStream*);
     void endObject(SkWStream*);
-    void serializeHeader(SkWStream*, const SkPDF::Metadata&);
+    void serializeHeader(SkWStream*);
     void serializeObject(const sk_sp<SkPDFObject>&, SkWStream*);
-    void serializeFooter(SkWStream*, const sk_sp<SkPDFObject>, sk_sp<SkPDFObject>);
+    void serializeFooter(SkWStream*,
+                         SkPDFIndirectReference infoDict,
+                         SkPDFIndirectReference docCatalog,
+                         SkUUID uuid);
     SkPDFFileOffset offset(SkWStream*);
 };
 
@@ -73,7 +80,8 @@
        after calling serialize, since those changes will be too late.
      */
     SkPDFIndirectReference serialize(const sk_sp<SkPDFObject>&);
-    SkPDFIndirectReference emit(const SkPDFObject&);
+    SkPDFIndirectReference emit(const SkPDFObject&, SkPDFIndirectReference);
+    SkPDFIndirectReference emit(const SkPDFObject& o) { return this->emit(o, this->reserveRef()); }
     SkPDFCanon* canon() { return &fCanon; }
     const SkPDF::Metadata& metadata() const { return fMetadata; }
 
@@ -81,10 +89,12 @@
     // Returns -1 if no mark ID.
     int getMarkIdForNodeId(int nodeId);
 
-    SkPDFIndirectReference reserve();
+    SkPDFIndirectReference reserveRef();
     SkWStream* beginObject(SkPDFIndirectReference);
     void endObject();
 
+    SkExecutor* executor() const { return fExecutor; }
+
 private:
     sk_sp<SkPDFTag> recursiveBuildTagTree(const SkPDF::StructureElementNode& node,
                                           sk_sp<SkPDFTag> parent);
@@ -95,11 +105,13 @@
     std::vector<sk_sp<SkPDFDict>> fPages;
     sk_sp<SkPDFDict> fDests;
     sk_sp<SkPDFDevice> fPageDevice;
-    sk_sp<SkPDFObject> fID;
-    sk_sp<SkPDFObject> fXMP;
+    SkUUID fUUID;
+    SkPDFIndirectReference fInfoDict;
+    SkPDFIndirectReference fXMP;
     SkPDF::Metadata fMetadata;
     SkScalar fRasterScale = 1;
     SkScalar fInverseRasterScale = 1;
+    SkExecutor* fExecutor = nullptr;
 
     // For tagged PDFs.
 
@@ -110,7 +122,9 @@
     // A mapping from node ID to tag for fast lookup.
     SkTHashMap<int, sk_sp<SkPDFTag>> fNodeIdToTag;
 
-    int fOutstandingRefs = 0;
+    std::atomic<int> fOutstandingRefs = {0};
+    SkMutex fMutex;
+    SkSemaphore fSemaphore;
 
     void reset();
 };
diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp
index 83615f2..9be890b 100644
--- a/src/pdf/SkPDFFont.cpp
+++ b/src/pdf/SkPDFFont.cpp
@@ -225,7 +225,7 @@
         firstNonZeroGlyph = subsetCode;
         lastGlyph = SkToU16(SkTMin<int>((int)lastGlyph, 254 + (int)subsetCode));
     }
-    auto ref = doc->reserve();
+    auto ref = doc->reserveRef();
     return canon->fFontMap.set(
             fontID, SkPDFFont(std::move(typeface), firstNonZeroGlyph, lastGlyph, type, ref));
 }
@@ -321,11 +321,13 @@
                             stream_to_data(std::move(fontAsset)), font.glyphUsage(),
                             metrics.fFontName.c_str(), ttcIndex);
                     if (subsetFontData) {
-                        size_t len = subsetFontData->size();
-                        sk_sp<SkPDFStream> subsetStream = sk_make_sp<SkPDFStream>(
-                                std::move(subsetFontData));
-                        subsetStream->dict()->insertInt("Length1", SkToInt(len));
-                        descriptor->insertRef("FontFile2", doc->serialize(subsetStream));
+                        sk_sp<SkPDFDict> tmp = sk_make_sp<SkPDFDict>();
+                        tmp->insertInt("Length1", SkToInt(subsetFontData->size()));
+                        descriptor->insertRef(
+                                "FontFile2",
+                                SkPDFStreamOut(std::move(tmp),
+                                               SkMemoryStream::Make(std::move(subsetFontData)),
+                                               doc, true));
                         break;
                     }
                     // If subsetting fails, fall back to original font data.
@@ -335,15 +337,19 @@
                     if (!fontAsset || fontAsset->getLength() == 0) { break; }
                 }
                 #endif  // SK_PDF_SUBSET_SUPPORTED
-                auto fontStream = sk_make_sp<SkPDFSharedStream>(std::move(fontAsset));
-                fontStream->dict()->insertInt("Length1", fontSize);
-                descriptor->insertRef("FontFile2", doc->serialize(fontStream));
+                sk_sp<SkPDFDict> tmp = sk_make_sp<SkPDFDict>();
+                tmp->insertInt("Length1", fontSize);
+                descriptor->insertRef("FontFile2",
+                                      SkPDFStreamOut(std::move(tmp), std::move(fontAsset),
+                                                     doc, true));
                 break;
             }
             case SkAdvancedTypefaceMetrics::kType1CID_Font: {
-                auto fontStream = sk_make_sp<SkPDFSharedStream>(std::move(fontAsset));
-                fontStream->dict()->insertName("Subtype", "CIDFontType0C");
-                descriptor->insertRef("FontFile3", doc->serialize(fontStream));
+                sk_sp<SkPDFDict> tmp = sk_make_sp<SkPDFDict>();
+                tmp->insertName("Subtype", "CIDFontType0C");
+                descriptor->insertRef("FontFile3",
+                                      SkPDFStreamOut(std::move(tmp), std::move(fontAsset),
+                                                     doc, true));
                 break;
             }
             default:
@@ -398,17 +404,15 @@
     const std::vector<SkUnichar>& glyphToUnicode =
         SkPDFFont::GetUnicodeMap(font.typeface(), canon);
     SkASSERT(SkToSizeT(font.typeface()->countGlyphs()) == glyphToUnicode.size());
-    fontDict.insertRef("ToUnicode",
-                    doc->serialize(
-                       SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
-                                              &font.glyphUsage(),
-                                              font.multiByteGlyphs(),
-                                              font.firstGlyphID(),
-                                              font.lastGlyphID())));
+    std::unique_ptr<SkStreamAsset> toUnicode =
+            SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
+                                   &font.glyphUsage(),
+                                   font.multiByteGlyphs(),
+                                   font.firstGlyphID(),
+                                   font.lastGlyphID());
+    fontDict.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicode), doc));
 
-    SkWStream* stream = doc->beginObject(font.indirectReference());
-    fontDict.emitObject(stream);
-    doc->endObject();
+    doc->emit(fontDict, font.indirectReference());
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -431,11 +435,13 @@
             sk_sp<SkData> fontData = SkPDFConvertType1FontStream(std::move(rawFontData),
                                                                  &header, &data, &trailer);
             if (fontData) {
-                SkPDFStream fontStream(std::move(fontData));
-                fontStream.dict()->insertInt("Length1", header);
-                fontStream.dict()->insertInt("Length2", data);
-                fontStream.dict()->insertInt("Length3", trailer);
-                descriptor.insertRef("FontFile", doc->emit(fontStream));
+                sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
+                dict->insertInt("Length1", header);
+                dict->insertInt("Length2", data);
+                dict->insertInt("Length3", trailer);
+                auto fontStream = SkMemoryStream::Make(std::move(fontData));
+                descriptor.insertRef("FontFile", SkPDFStreamOut(std::move(dict),
+                                                                std::move(fontStream), doc, true));
             }
         }
     }
@@ -514,9 +520,7 @@
     encoding->insertObject("Differences", std::move(encDiffs));
     font.insertObject("Encoding", std::move(encoding));
 
-    SkWStream* stream = doc->beginObject(pdfFont.indirectReference());
-    font.emitObject(stream);
-    doc->endObject();
+    doc->emit(font, pdfFont.indirectReference());
 }
 
 void SkPDFFont::GetType1GlyphNames(const SkTypeface& face, SkString* dst) {
@@ -717,7 +721,8 @@
                     content.writeText(" Do\n");
                 }
             }
-            charProcs->insertRef(characterName, doc->emit(SkPDFStream(content.detachAsStream())));
+            charProcs->insertRef(characterName, SkPDFStreamOut(nullptr,
+                                                               content.detachAsStream(), doc));
         }
         encDiffs->appendName(std::move(characterName));
         widthArray->appendScalar(advance);
@@ -750,20 +755,18 @@
 
     const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(typeface, canon);
     SkASSERT(glyphToUnicode.size() == SkToSizeT(typeface->countGlyphs()));
-    sk_sp<SkPDFStream> toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
-                                                              &subset,
-                                                              false,
-                                                              firstGlyphID,
-                                                              lastGlyphID);
-    font.insertRef("ToUnicode", doc->serialize(toUnicodeCmap));
+    auto toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
+                                                &subset,
+                                                false,
+                                                firstGlyphID,
+                                                lastGlyphID);
+    font.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicodeCmap), doc));
     font.insertRef("FontDescriptor", type3_descriptor(doc, typeface, cache.get()));
     font.insertObject("Widths", std::move(widthArray));
     font.insertObject("Encoding", std::move(encoding));
     font.insertObject("CharProcs", std::move(charProcs));
 
-    SkWStream* stream = doc->beginObject(pdfFont.indirectReference());
-    font.emitObject(stream);
-    doc->endObject();
+    doc->emit(font, pdfFont.indirectReference());
 }
 
 
diff --git a/src/pdf/SkPDFFormXObject.cpp b/src/pdf/SkPDFFormXObject.cpp
index 99146a2..7dfa562 100644
--- a/src/pdf/SkPDFFormXObject.cpp
+++ b/src/pdf/SkPDFFormXObject.cpp
@@ -9,20 +9,20 @@
 #include "SkPDFFormXObject.h"
 #include "SkPDFUtils.h"
 
-sk_sp<SkPDFObject> SkPDFMakeFormXObject(std::unique_ptr<SkStreamAsset> content,
-                                        sk_sp<SkPDFArray> mediaBox,
-                                        sk_sp<SkPDFDict> resourceDict,
-                                        const SkMatrix& inverseTransform,
-                                        const char* colorSpace) {
-    auto form = sk_make_sp<SkPDFStream>(std::move(content));
-    form->dict()->insertName("Type", "XObject");
-    form->dict()->insertName("Subtype", "Form");
+SkPDFIndirectReference SkPDFMakeFormXObject(SkPDFDocument* doc,
+                                            std::unique_ptr<SkStreamAsset> content,
+                                            sk_sp<SkPDFArray> mediaBox,
+                                            sk_sp<SkPDFDict> resourceDict,
+                                            const SkMatrix& inverseTransform,
+                                            const char* colorSpace) {
+    sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
+    dict->insertName("Type", "XObject");
+    dict->insertName("Subtype", "Form");
     if (!inverseTransform.isIdentity()) {
-        sk_sp<SkPDFObject> mat(SkPDFUtils::MatrixToArray(inverseTransform));
-        form->dict()->insertObject("Matrix", std::move(mat));
+        dict->insertObject("Matrix", SkPDFUtils::MatrixToArray(inverseTransform));
     }
-    form->dict()->insertObject("Resources", std::move(resourceDict));
-    form->dict()->insertObject("BBox", std::move(mediaBox));
+    dict->insertObject("Resources", std::move(resourceDict));
+    dict->insertObject("BBox", std::move(mediaBox));
 
     // Right now FormXObject is only used for saveLayer, which implies
     // isolated blending.  Do this conditionally if that changes.
@@ -34,6 +34,6 @@
         group->insertName("CS", colorSpace);
     }
     group->insertBool("I", true);  // Isolated.
-    form->dict()->insertObject("Group", std::move(group));
-    return std::move(form);
+    dict->insertObject("Group", std::move(group));
+    return SkPDFStreamOut(std::move(dict), std::move(content), doc);
 }
diff --git a/src/pdf/SkPDFFormXObject.h b/src/pdf/SkPDFFormXObject.h
index e62b69c..5fa52c9 100644
--- a/src/pdf/SkPDFFormXObject.h
+++ b/src/pdf/SkPDFFormXObject.h
@@ -12,14 +12,17 @@
 #include "SkPDFDevice.h"
 #include "SkPDFTypes.h"
 
+class SkPDFDocument;
+
 /** A form XObject is a self contained description of a graphics
     object.  A form XObject is a page object with slightly different
     syntax, that can be drawn into a page content stream, just like a
     bitmap XObject can be drawn into a page content stream.
 */
-sk_sp<SkPDFObject> SkPDFMakeFormXObject(std::unique_ptr<SkStreamAsset> content,
-                                        sk_sp<SkPDFArray> mediaBox,
-                                        sk_sp<SkPDFDict> resourceDict,
-                                        const SkMatrix& inverseTransform,
-                                        const char* colorSpace);
+SkPDFIndirectReference SkPDFMakeFormXObject(SkPDFDocument* doc,
+                                            std::unique_ptr<SkStreamAsset> content,
+                                            sk_sp<SkPDFArray> mediaBox,
+                                            sk_sp<SkPDFDict> resourceDict,
+                                            const SkMatrix& inverseTransform,
+                                            const char* colorSpace);
 #endif
diff --git a/src/pdf/SkPDFGradientShader.cpp b/src/pdf/SkPDFGradientShader.cpp
index d61b5f1..7bebb3c 100644
--- a/src/pdf/SkPDFGradientShader.cpp
+++ b/src/pdf/SkPDFGradientShader.cpp
@@ -13,6 +13,7 @@
 #include "SkPDFFormXObject.h"
 #include "SkPDFGraphicState.h"
 #include "SkPDFResourceDict.h"
+#include "SkPDFTypes.h"
 #include "SkPDFUtils.h"
 
 static uint32_t hash(const SkShader::GradientInfo& v) {
@@ -573,20 +574,19 @@
     return true;
 }
 
-static sk_sp<SkPDFStream> make_ps_function(
-        std::unique_ptr<SkStreamAsset> psCode,
-        sk_sp<SkPDFArray> domain,
-        sk_sp<SkPDFObject> range) {
-    auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
-    result->dict()->insertInt("FunctionType", 4);
-    result->dict()->insertObject("Domain", std::move(domain));
-    result->dict()->insertObject("Range", std::move(range));
-    return result;
+static SkPDFIndirectReference make_ps_function(std::unique_ptr<SkStreamAsset> psCode,
+                                               sk_sp<SkPDFArray> domain,
+                                               sk_sp<SkPDFObject> range,
+                                               SkPDFDocument* doc) {
+    sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
+    dict->insertInt("FunctionType", 4);
+    dict->insertObject("Domain", std::move(domain));
+    dict->insertObject("Range", std::move(range));
+    return SkPDFStreamOut(std::move(dict), std::move(psCode), doc);
 }
 
-
-static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
-                                             const SkPDFGradientShader::Key& state) {
+static SkPDFIndirectReference make_function_shader(SkPDFDocument* doc,
+                                                   const SkPDFGradientShader::Key& state) {
     SkPoint transformPoints[2];
     const SkShader::GradientInfo& info = state.fInfo;
     SkMatrix finalMatrix = state.fCanvasTransform;
@@ -670,7 +670,7 @@
             case SkShader::kColor_GradientType:
             case SkShader::kNone_GradientType:
             default:
-                return nullptr;
+                return SkPDFIndirectReference();
         }
 
         // Move any scaling (assuming a unit gradient) or translation
@@ -691,14 +691,14 @@
         if (finalMatrix.hasPerspective()) {
             if (!split_perspective(finalMatrix,
                                    &finalMatrix, &perspectiveInverseOnly)) {
-                return nullptr;
+                return SkPDFIndirectReference();
             }
         }
 
         SkRect bbox;
         bbox.set(state.fBBox);
         if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) {
-            return nullptr;
+            return SkPDFIndirectReference();
         }
         SkDynamicMemoryWStream functionCode;
 
@@ -707,7 +707,7 @@
         if (state.fType == SkShader::kConical_GradientType) {
             SkMatrix inverseMapperMatrix;
             if (!mapperMatrix.invert(&inverseMapperMatrix)) {
-                return nullptr;
+                return SkPDFIndirectReference();
             }
             inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2);
             infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]);
@@ -735,13 +735,10 @@
                                      bbox.bottom());
         pdfShader->insertObject("Domain", domain);
 
-        sk_sp<SkPDFArray>& rangeObject = canon->fRangeObject;
-        if (!rangeObject) {
-            rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1);
-        }
-        pdfShader->insertObjRef("Function",
-                                make_ps_function(functionCode.detachAsStream(), std::move(domain),
-                                                 rangeObject));
+        sk_sp<SkPDFArray> rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1);
+        pdfShader->insertRef("Function",
+                             make_ps_function(functionCode.detachAsStream(), std::move(domain),
+                                              std::move(rangeObject), doc));
     }
 
     pdfShader->insertInt("ShadingType", shadingType);
@@ -752,38 +749,40 @@
     pdfFunctionShader->insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix));
     pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
 
-    return pdfFunctionShader;
+    return doc->serialize(pdfFunctionShader);
 }
 
-static sk_sp<SkPDFObject> find_pdf_shader(SkPDFDocument* doc,
-                                          SkPDFGradientShader::Key key,
-                                          bool keyHasAlpha);
+static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
+                                              SkPDFGradientShader::Key key,
+                                              bool keyHasAlpha);
 
-static sk_sp<SkPDFDict> get_gradient_resource_dict(sk_sp<SkPDFObject> functionShader,
-                                                   sk_sp<SkPDFObject> gState) {
-    std::vector<sk_sp<SkPDFObject>> patternShaders;
-    if (functionShader) {
-        patternShaders.push_back(std::move(functionShader));
+static sk_sp<SkPDFDict> get_gradient_resource_dict(SkPDFIndirectReference functionShader,
+                                                   SkPDFIndirectReference gState) {
+    std::vector<SkPDFIndirectReference> patternShaders;
+    if (functionShader != SkPDFIndirectReference()) {
+        patternShaders.push_back(functionShader);
     }
-    std::vector<sk_sp<SkPDFObject>> graphicStates;
-    if (gState) {
-        graphicStates.push_back(std::move(gState));
+    std::vector<SkPDFIndirectReference> graphicStates;
+    if (gState != SkPDFIndirectReference()) {
+        graphicStates.push_back(gState);
     }
     return SkPDFMakeResourceDict(std::move(graphicStates),
                                  std::move(patternShaders),
-                                 std::vector<sk_sp<SkPDFObject>>(),
+                                 std::vector<SkPDFIndirectReference>(),
                                  std::vector<SkPDFIndirectReference>());
 }
 
 // Creates a content stream which fills the pattern P0 across bounds.
 // @param gsIndex A graphics state resource index to apply, or <0 if no
 // graphics state to apply.
-static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex, SkRect& bounds) {
+static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex,
+                                                                  int patternIndex,
+                                                                  SkRect& bounds) {
     SkDynamicMemoryWStream content;
     if (gsIndex >= 0) {
         SkPDFUtils::ApplyGraphicState(gsIndex, &content);
     }
-    SkPDFUtils::ApplyPattern(0, &content);
+    SkPDFUtils::ApplyPattern(patternIndex, &content);
     SkPDFUtils::AppendRectangle(bounds, &content);
     SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, &content);
     return content.detachAsStream();
@@ -818,7 +817,7 @@
     return clone;
 }
 
-static sk_sp<SkPDFObject> create_smask_graphic_state(SkPDFDocument* doc,
+static SkPDFIndirectReference create_smask_graphic_state(SkPDFDocument* doc,
                                                      const SkPDFGradientShader::Key& state) {
     SkASSERT(state.fType != SkShader::kNone_GradientType);
     SkPDFGradientShader::Key luminosityState = clone_key(state);
@@ -829,21 +828,23 @@
     luminosityState.fHash = hash(luminosityState);
 
     SkASSERT(!gradient_has_alpha(luminosityState));
-    sk_sp<SkPDFObject> luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false);
-    sk_sp<SkPDFDict> resources = get_gradient_resource_dict(std::move(luminosityShader), nullptr);
+    SkPDFIndirectReference luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false);
+    sk_sp<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader,
+                                                            SkPDFIndirectReference());
     SkRect bbox = SkRect::Make(state.fBBox);
-    sk_sp<SkPDFObject> alphaMask = SkPDFMakeFormXObject(create_pattern_fill_content(-1, bbox),
-                                                        SkPDFUtils::RectToArray(bbox),
-                                                        std::move(resources),
-                                                        SkMatrix::I(),
-                                                        "DeviceRGB");
+    SkPDFIndirectReference alphaMask =
+            SkPDFMakeFormXObject(doc,
+                                 create_pattern_fill_content(-1, luminosityShader.fValue, bbox),
+                                 SkPDFUtils::RectToArray(bbox),
+                                 std::move(resources),
+                                 SkMatrix::I(),
+                                 "DeviceRGB");
     return SkPDFGraphicState::GetSMaskGraphicState(
-            std::move(alphaMask), false,
-            SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
+            alphaMask, false, SkPDFGraphicState::kLuminosity_SMaskMode, doc);
 }
 
-static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
-                                                     const SkPDFGradientShader::Key& state) {
+static SkPDFIndirectReference make_alpha_function_shader(SkPDFDocument* doc,
+                                                         const SkPDFGradientShader::Key& state) {
     SkASSERT(state.fType != SkShader::kNone_GradientType);
     SkPDFGradientShader::Key opaqueState = clone_key(state);
     for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) {
@@ -853,24 +854,22 @@
 
     SkASSERT(!gradient_has_alpha(opaqueState));
     SkRect bbox = SkRect::Make(state.fBBox);
-    sk_sp<SkPDFObject> colorShader = find_pdf_shader(doc, std::move(opaqueState), false);
+    SkPDFIndirectReference colorShader = find_pdf_shader(doc, std::move(opaqueState), false);
     if (!colorShader) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
-
     // Create resource dict with alpha graphics state as G0 and
     // pattern shader as P0, then write content stream.
-    sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, state);
+    SkPDFIndirectReference alphaGsRef = create_smask_graphic_state(doc, state);
 
-    sk_sp<SkPDFDict> resourceDict =
-            get_gradient_resource_dict(std::move(colorShader), std::move(alphaGs));
+    sk_sp<SkPDFDict> resourceDict = get_gradient_resource_dict(colorShader, alphaGsRef);
 
-    std::unique_ptr<SkStreamAsset> colorStream(create_pattern_fill_content(0, bbox));
-    auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream));
-
-    SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader->dict(), bbox,
+    std::unique_ptr<SkStreamAsset> colorStream =
+            create_pattern_fill_content(alphaGsRef.fValue, colorShader.fValue, bbox);
+    sk_sp<SkPDFDict> alphaFunctionShader = sk_make_sp<SkPDFDict>();
+    SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader.get(), bbox,
                                  std::move(resourceDict), SkMatrix::I());
-    return alphaFunctionShader;
+    return SkPDFStreamOut(std::move(alphaFunctionShader), std::move(colorStream), doc);
 }
 
 static SkPDFGradientShader::Key make_key(const SkShader* shader,
@@ -896,25 +895,25 @@
     return key;
 }
 
-static sk_sp<SkPDFObject> find_pdf_shader(SkPDFDocument* doc,
-                                          SkPDFGradientShader::Key key,
-                                          bool keyHasAlpha) {
+static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
+                                              SkPDFGradientShader::Key key,
+                                              bool keyHasAlpha) {
     SkASSERT(gradient_has_alpha(key) == keyHasAlpha);
-    SkPDFCanon* canon = doc->canon();
-    if (sk_sp<SkPDFObject>* ptr = canon->fGradientPatternMap.find(key)) {
+    auto& gradientPatternMap = doc->canon()->fGradientPatternMap;
+    if (SkPDFIndirectReference* ptr = gradientPatternMap.find(key)) {
         return *ptr;
     }
-    sk_sp<SkPDFObject> pdfShader;
+    SkPDFIndirectReference pdfShader;
     if (keyHasAlpha) {
         pdfShader = make_alpha_function_shader(doc, key);
     } else {
-        pdfShader = make_function_shader(canon, key);
+        pdfShader = make_function_shader(doc, key);
     }
-    canon->fGradientPatternMap.set(std::move(key), pdfShader);
+    gradientPatternMap.set(std::move(key), pdfShader);
     return pdfShader;
 }
 
-sk_sp<SkPDFObject> SkPDFGradientShader::Make(SkPDFDocument* doc,
+SkPDFIndirectReference SkPDFGradientShader::Make(SkPDFDocument* doc,
                                              SkShader* shader,
                                              const SkMatrix& canvasTransform,
                                              const SkIRect& bbox) {
diff --git a/src/pdf/SkPDFGradientShader.h b/src/pdf/SkPDFGradientShader.h
index 0cc059c..7050e09 100644
--- a/src/pdf/SkPDFGradientShader.h
+++ b/src/pdf/SkPDFGradientShader.h
@@ -17,10 +17,10 @@
 
 namespace SkPDFGradientShader {
 
-sk_sp<SkPDFObject> Make(SkPDFDocument* doc,
-                        SkShader* shader,
-                        const SkMatrix& matrix,
-                        const SkIRect& surfaceBBox);
+SkPDFIndirectReference Make(SkPDFDocument* doc,
+                            SkShader* shader,
+                            const SkMatrix& matrix,
+                            const SkIRect& surfaceBBox);
 
 struct Key {
     SkShader::GradientType fType;
@@ -37,7 +37,7 @@
     uint32_t operator()(const Key& k) const { return k.fHash; }
 };
 
-using HashMap = SkTHashMap<Key, sk_sp<SkPDFObject>, KeyHash>;
+using HashMap = SkTHashMap<Key, SkPDFIndirectReference, KeyHash>;
 
 inline bool operator==(const SkShader::GradientInfo& u, const SkShader::GradientInfo& v) {
     return u.fColorCount    == v.fColorCount
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
index b9a5eb3..7bf4a92 100644
--- a/src/pdf/SkPDFGraphicState.cpp
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -9,6 +9,7 @@
 
 #include "SkData.h"
 #include "SkPDFCanon.h"
+#include "SkPDFDocumentPriv.h"
 #include "SkPDFFormXObject.h"
 #include "SkPDFUtils.h"
 #include "SkPaint.h"
@@ -52,21 +53,23 @@
     return SkToU8((unsigned)mode);
 }
 
-sk_sp<SkPDFDict> SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon,
-                                                            const SkPaint& p) {
+SkPDFIndirectReference SkPDFGraphicState::GetGraphicStateForPaint(SkPDFDocument* doc,
+                                                                  const SkPaint& p) {
+    SkPDFCanon* canon = doc->canon();
     SkASSERT(canon);
     if (SkPaint::kFill_Style == p.getStyle()) {
         SkPDFFillGraphicState fillKey = {p.getColor4f().fA, pdf_blend_mode(p.getBlendMode())};
         auto& fillMap = canon->fFillGSMap;
-        if (sk_sp<SkPDFDict>* statePtr = fillMap.find(fillKey)) {
+        if (SkPDFIndirectReference* statePtr = fillMap.find(fillKey)) {
             return *statePtr;
         }
-        auto state = sk_make_sp<SkPDFDict>();
-        state->reserve(2);
-        state->insertColorComponentF("ca", fillKey.fAlpha);
-        state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode));
-        fillMap.set(fillKey, state);
-        return state;
+        SkPDFDict state;
+        state.reserve(2);
+        state.insertColorComponentF("ca", fillKey.fAlpha);
+        state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode));
+        SkPDFIndirectReference ref = doc->emit(state);
+        fillMap.set(fillKey, ref);
+        return ref;
     } else {
         SkPDFStrokeGraphicState strokeKey = {
             p.getStrokeWidth(),
@@ -77,65 +80,63 @@
             pdf_blend_mode(p.getBlendMode())
         };
         auto& sMap = canon->fStrokeGSMap;
-        if (sk_sp<SkPDFDict>* statePtr = sMap.find(strokeKey)) {
+        if (SkPDFIndirectReference* statePtr = sMap.find(strokeKey)) {
             return *statePtr;
         }
-        auto state = sk_make_sp<SkPDFDict>();
-        state->reserve(8);
-        state->insertColorComponentF("CA", strokeKey.fAlpha);
-        state->insertColorComponentF("ca", strokeKey.fAlpha);
-        state->insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap));
-        state->insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin));
-        state->insertScalar("LW", strokeKey.fStrokeWidth);
-        state->insertScalar("ML", strokeKey.fStrokeMiter);
-        state->insertBool("SA", true);  // SA = Auto stroke adjustment.
-        state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode));
-        sMap.set(strokeKey, state);
-        return state;
+        SkPDFDict state;
+        state.reserve(8);
+        state.insertColorComponentF("CA", strokeKey.fAlpha);
+        state.insertColorComponentF("ca", strokeKey.fAlpha);
+        state.insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap));
+        state.insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin));
+        state.insertScalar("LW", strokeKey.fStrokeWidth);
+        state.insertScalar("ML", strokeKey.fStrokeMiter);
+        state.insertBool("SA", true);  // SA = Auto stroke adjustment.
+        state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode));
+        SkPDFIndirectReference ref = doc->emit(state);
+        sMap.set(strokeKey, ref);
+        return ref;
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-static sk_sp<SkPDFStream> make_invert_function() {
+static SkPDFIndirectReference make_invert_function(SkPDFDocument* doc) {
     // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
     // a type 2 function, so we use a type 4 function.
-    auto domainAndRange = SkPDFMakeArray(0, 1);
-
     static const char psInvert[] = "{1 exch sub}";
     // Do not copy the trailing '\0' into the SkData.
-    auto invertFunction = sk_make_sp<SkPDFStream>(
-            SkData::MakeWithoutCopy(psInvert, strlen(psInvert)));
-    invertFunction->dict()->insertInt("FunctionType", 4);
-    invertFunction->dict()->insertObject("Domain", domainAndRange);
-    invertFunction->dict()->insertObject("Range", std::move(domainAndRange));
-    return invertFunction;
+    auto invertFunction = SkData::MakeWithoutCopy(psInvert, strlen(psInvert));
+
+    sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
+    dict->insertInt("FunctionType", 4);
+    dict->insertObject("Domain", SkPDFMakeArray(0, 1));
+    dict->insertObject("Range", SkPDFMakeArray(0, 1));
+    return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(std::move(invertFunction)), doc);
 }
 
-sk_sp<SkPDFDict> SkPDFGraphicState::GetSMaskGraphicState(
-        sk_sp<SkPDFObject> sMask,
-        bool invert,
-        SkPDFSMaskMode sMaskMode,
-        SkPDFCanon* canon) {
+SkPDFIndirectReference SkPDFGraphicState::GetSMaskGraphicState(SkPDFIndirectReference sMask,
+                                                               bool invert,
+                                                               SkPDFSMaskMode sMaskMode,
+                                                               SkPDFDocument* doc) {
     // The practical chances of using the same mask more than once are unlikely
     // enough that it's not worth canonicalizing.
+    SkPDFCanon* canon = doc->canon();
     auto sMaskDict = sk_make_sp<SkPDFDict>("Mask");
     if (sMaskMode == kAlpha_SMaskMode) {
         sMaskDict->insertName("S", "Alpha");
     } else if (sMaskMode == kLuminosity_SMaskMode) {
         sMaskDict->insertName("S", "Luminosity");
     }
-    sMaskDict->insertObjRef("G", std::move(sMask));
+    sMaskDict->insertRef("G", sMask);
     if (invert) {
-        // Instead of calling SkPDFGraphicState::MakeInvertFunction,
         // let the canon deduplicate this object.
-        sk_sp<SkPDFStream>& invertFunction = canon->fInvertFunction;
-        if (!invertFunction) {
-            invertFunction = make_invert_function();
+        if (canon->fInvertFunction == SkPDFIndirectReference()) {
+            canon->fInvertFunction = make_invert_function(doc);
         }
-        sMaskDict->insertObjRef("TR", invertFunction);
+        sMaskDict->insertRef("TR", canon->fInvertFunction);
     }
-    auto result = sk_make_sp<SkPDFDict>("ExtGState");
-    result->insertObject("SMask", std::move(sMaskDict));
-    return result;
+    SkPDFDict result("ExtGState");
+    result.insertObject("SMask", std::move(sMaskDict));
+    return doc->emit(result);
 }
diff --git a/src/pdf/SkPDFGraphicState.h b/src/pdf/SkPDFGraphicState.h
index 86f4eab..9c22e17 100644
--- a/src/pdf/SkPDFGraphicState.h
+++ b/src/pdf/SkPDFGraphicState.h
@@ -29,7 +29,7 @@
 
     /** Get the graphic state for the passed SkPaint.
      */
-    sk_sp<SkPDFDict> GetGraphicStateForPaint(SkPDFCanon*, const SkPaint&);
+    SkPDFIndirectReference GetGraphicStateForPaint(SkPDFDocument*, const SkPaint&);
 
     /** Make a graphic state that only sets the passed soft mask.
      *  @param sMask     The form xobject to use as a soft mask.
@@ -38,12 +38,10 @@
      *
      *  These are not de-duped.
      */
-    sk_sp<SkPDFDict> GetSMaskGraphicState(sk_sp<SkPDFObject> sMask,
-                                          bool invert,
-                                          SkPDFSMaskMode sMaskMode,
-                                          SkPDFCanon* canon);
-
-    sk_sp<SkPDFStream> MakeInvertFunction();
+    SkPDFIndirectReference GetSMaskGraphicState(SkPDFIndirectReference sMask,
+                                                bool invert,
+                                                SkPDFSMaskMode sMaskMode,
+                                                SkPDFDocument* doc);
 }
 
 SK_BEGIN_REQUIRE_DENSE
diff --git a/src/pdf/SkPDFMakeToUnicodeCmap.cpp b/src/pdf/SkPDFMakeToUnicodeCmap.cpp
index a329547..73cdc1d 100644
--- a/src/pdf/SkPDFMakeToUnicodeCmap.cpp
+++ b/src/pdf/SkPDFMakeToUnicodeCmap.cpp
@@ -205,7 +205,7 @@
     append_bfrange_section(bfrangeEntries, multiByteGlyphs, cmap);
 }
 
-sk_sp<SkPDFStream> SkPDFMakeToUnicodeCmap(
+std::unique_ptr<SkStreamAsset> SkPDFMakeToUnicodeCmap(
         const SkUnichar* glyphToUnicode,
         const SkPDFGlyphUse* subset,
         bool multiByteGlyphs,
@@ -216,6 +216,5 @@
     SkPDFAppendCmapSections(glyphToUnicode, subset, &cmap, multiByteGlyphs,
                             firstGlyphID, lastGlyphID);
     append_cmap_footer(&cmap);
-    return sk_make_sp<SkPDFStream>(
-            std::unique_ptr<SkStreamAsset>(cmap.detachAsStream()));
+    return cmap.detachAsStream();
 }
diff --git a/src/pdf/SkPDFMakeToUnicodeCmap.h b/src/pdf/SkPDFMakeToUnicodeCmap.h
index f52862f..6614c01 100644
--- a/src/pdf/SkPDFMakeToUnicodeCmap.h
+++ b/src/pdf/SkPDFMakeToUnicodeCmap.h
@@ -10,7 +10,7 @@
 #include "SkPDFFont.h"
 #include "SkStream.h"
 
-sk_sp<SkPDFStream> SkPDFMakeToUnicodeCmap(
+std::unique_ptr<SkStreamAsset> SkPDFMakeToUnicodeCmap(
         const SkUnichar* glyphToUnicode,
         const SkPDFGlyphUse* subset,
         bool multiByteGlyphs,
diff --git a/src/pdf/SkPDFMetadata.cpp b/src/pdf/SkPDFMetadata.cpp
index 06d7471..b18df79 100644
--- a/src/pdf/SkPDFMetadata.cpp
+++ b/src/pdf/SkPDFMetadata.cpp
@@ -150,8 +150,7 @@
     return std::move(dict);
 }
 
-SkPDFMetadata::UUID SkPDFMetadata::CreateUUID(
-        const SkPDF::Metadata& metadata) {
+SkUUID SkPDFMetadata::CreateUUID(const SkPDF::Metadata& metadata) {
     // The main requirement is for the UUID to be unique; the exact
     // format of the data that will be hashed is not important.
     SkMD5 md5;
@@ -177,22 +176,22 @@
     // See RFC 4122, page 6-7.
     digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
     digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
-    static_assert(sizeof(digest) == sizeof(UUID), "uuid_size");
-    SkPDFMetadata::UUID uuid;
+    static_assert(sizeof(digest) == sizeof(SkUUID), "uuid_size");
+    SkUUID uuid;
     memcpy(&uuid, &digest, sizeof(digest));
     return uuid;
 }
 
-sk_sp<SkPDFObject> SkPDFMetadata::MakePdfId(const UUID& doc,
-                                            const UUID& instance) {
+sk_sp<SkPDFObject> SkPDFMetadata::MakePdfId(const SkUUID& doc,
+                                            const SkUUID& instance) {
     // /ID [ <81b14aafa313db63dbd6f981e49f94f4>
     //       <81b14aafa313db63dbd6f981e49f94f4> ]
     auto array = sk_make_sp<SkPDFArray>();
-    static_assert(sizeof(SkPDFMetadata::UUID) == 16, "uuid_size");
+    static_assert(sizeof(SkUUID) == 16, "uuid_size");
     array->appendString(
-            SkString(reinterpret_cast<const char*>(&doc), sizeof(UUID)));
+            SkString(reinterpret_cast<const char*>(&doc), sizeof(SkUUID)));
     array->appendString(
-            SkString(reinterpret_cast<const char*>(&instance), sizeof(UUID)));
+            SkString(reinterpret_cast<const char*>(&instance), sizeof(SkUUID)));
     return std::move(array);
 }
 
@@ -208,7 +207,7 @@
     }
 }
 
-static SkString uuid_to_string(const SkPDFMetadata::UUID& uuid) {
+static SkString uuid_to_string(const SkUUID& uuid) {
     //  8-4-4-4-12
     char buffer[36];  // [32 + 4]
     char* ptr = buffer;
@@ -306,8 +305,8 @@
 
 sk_sp<SkPDFObject> SkPDFMetadata::MakeXMPObject(
         const SkPDF::Metadata& metadata,
-        const UUID& doc,
-        const UUID& instance) {
+        const SkUUID& doc,
+        const SkUUID& instance) {
     static const char templateString[] =
             "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n"
diff --git a/src/pdf/SkPDFMetadata.h b/src/pdf/SkPDFMetadata.h
index 314b57e..70b96e0 100644
--- a/src/pdf/SkPDFMetadata.h
+++ b/src/pdf/SkPDFMetadata.h
@@ -9,22 +9,19 @@
 #define SkPDFMetadata_DEFINED
 
 #include "SkPDFDocument.h"
+#include "SkUUID.h"
 
 class SkPDFObject;
 
 namespace SkPDFMetadata {
 sk_sp<SkPDFObject> MakeDocumentInformationDict(const SkPDF::Metadata&);
 
-struct UUID {
-    uint8_t fData[16];
-};
+SkUUID CreateUUID(const SkPDF::Metadata&);
 
-UUID CreateUUID(const SkPDF::Metadata&);
-
-sk_sp<SkPDFObject> MakePdfId(const UUID& doc, const UUID& instance);
+sk_sp<SkPDFObject> MakePdfId(const SkUUID& doc, const SkUUID& instance);
 
 sk_sp<SkPDFObject> MakeXMPObject(const SkPDF::Metadata&,
-                                 const UUID& doc,
-                                 const UUID& instance);
+                                 const SkUUID& doc,
+                                 const SkUUID& instance);
 }
 #endif  // SkPDFMetadata_DEFINED
diff --git a/src/pdf/SkPDFResourceDict.cpp b/src/pdf/SkPDFResourceDict.cpp
index fbd5492..545d801 100644
--- a/src/pdf/SkPDFResourceDict.cpp
+++ b/src/pdf/SkPDFResourceDict.cpp
@@ -59,18 +59,6 @@
     return SkString(buffer, (size_t)(end - buffer));
 }
 
-static void add_subdict(std::vector<sk_sp<SkPDFObject>> resourceList,
-                        SkPDFResourceType type,
-                        SkPDFDict* dst) {
-    if (!resourceList.empty()) {
-        auto resources = sk_make_sp<SkPDFDict>();
-        for (size_t i = 0; i < resourceList.size(); i++) {
-            resources->insertObjRef(resource(type, SkToInt(i)), std::move(resourceList[i]));
-        }
-        dst->insertObject(resource_name(type), std::move(resources));
-    }
-}
-
 static void add_subdict(const std::vector<SkPDFIndirectReference>& resourceList,
                         SkPDFResourceType type,
                         SkPDFDict* dst) {
@@ -83,14 +71,14 @@
     }
 }
 
-sk_sp<SkPDFDict> SkPDFMakeResourceDict(std::vector<sk_sp<SkPDFObject>> graphicStateResources,
-                                       std::vector<sk_sp<SkPDFObject>> shaderResources,
-                                       std::vector<sk_sp<SkPDFObject>> xObjectResources,
+sk_sp<SkPDFDict> SkPDFMakeResourceDict(std::vector<SkPDFIndirectReference> graphicStateResources,
+                                       std::vector<SkPDFIndirectReference> shaderResources,
+                                       std::vector<SkPDFIndirectReference> xObjectResources,
                                        std::vector<SkPDFIndirectReference> fontResources) {
     auto dict = sk_make_sp<SkPDFDict>();
-    add_subdict(std::move(graphicStateResources), SkPDFResourceType::kExtGState, dict.get());
-    add_subdict(std::move(shaderResources),       SkPDFResourceType::kPattern,   dict.get());
-    add_subdict(std::move(xObjectResources),      SkPDFResourceType::kXObject,   dict.get());
-    add_subdict(fontResources,                    SkPDFResourceType::kFont,      dict.get());
+    add_subdict(graphicStateResources, SkPDFResourceType::kExtGState, dict.get());
+    add_subdict(shaderResources,       SkPDFResourceType::kPattern,   dict.get());
+    add_subdict(xObjectResources,      SkPDFResourceType::kXObject,   dict.get());
+    add_subdict(fontResources,         SkPDFResourceType::kFont,      dict.get());
     return dict;
 }
diff --git a/src/pdf/SkPDFResourceDict.h b/src/pdf/SkPDFResourceDict.h
index 40a17f9..a278b0c 100644
--- a/src/pdf/SkPDFResourceDict.h
+++ b/src/pdf/SkPDFResourceDict.h
@@ -32,9 +32,9 @@
  *
  *  Any arguments can be nullptr.
  */
-sk_sp<SkPDFDict> SkPDFMakeResourceDict(std::vector<sk_sp<SkPDFObject>> graphicStateResources,
-                                       std::vector<sk_sp<SkPDFObject>> shaderResources,
-                                       std::vector<sk_sp<SkPDFObject>> xObjectResources,
+sk_sp<SkPDFDict> SkPDFMakeResourceDict(std::vector<SkPDFIndirectReference> graphicStateResources,
+                                       std::vector<SkPDFIndirectReference> shaderResources,
+                                       std::vector<SkPDFIndirectReference> xObjectResources,
                                        std::vector<SkPDFIndirectReference> fontResources);
 
 /**
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 7448d86..168805b 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -37,9 +37,9 @@
     canvas->drawBitmap(bm, 0, 0, &paint);
 }
 
-static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
-                                            const SkPDFImageShaderKey& key,
-                                            SkImage* image) {
+static SkPDFIndirectReference make_image_shader(SkPDFDocument* doc,
+                                                const SkPDFImageShaderKey& key,
+                                                SkImage* image) {
     SkASSERT(image);
 
     // The image shader pattern cell will be drawn into a separate device
@@ -52,7 +52,7 @@
     finalMatrix.preConcat(key.fShaderTransform);
     SkRect deviceBounds = SkRect::Make(key.fBBox);
     if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
 
     SkRect bitmapBounds = SkRect::Make(image->bounds());
@@ -244,22 +244,23 @@
         }
     }
 
-    auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content());
+    auto imageShader = patternDevice->content();
     sk_sp<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
-    SkPDFUtils::PopulateTilingPatternDict(imageShader->dict(), patternBBox,
+    sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
+    SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox,
                                           std::move(resourceDict), finalMatrix);
-    return imageShader;
+    return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc);
 }
 
 // Generic fallback for unsupported shaders:
 //  * allocate a surfaceBBox-sized bitmap
 //  * shade the whole area
 //  * use the result as a bitmap shader
-static sk_sp<SkPDFObject> make_fallback_shader(SkPDFDocument* doc,
-                                               SkShader* shader,
-                                               const SkMatrix& canvasTransform,
-                                               const SkIRect& surfaceBBox,
-                                               SkColor paintColor) {
+static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc,
+                                                   SkShader* shader,
+                                                   const SkMatrix& canvasTransform,
+                                                   const SkIRect& surfaceBBox,
+                                                   SkColor paintColor) {
     // TODO(vandebo) This drops SKComposeShader on the floor.  We could
     // handle compose shader by pulling things up to a layer, drawing with
     // the first shader, applying the xfer mode and drawing again with the
@@ -280,7 +281,7 @@
     // MakeImageShader's behavior).
     SkRect shaderRect = SkRect::Make(surfaceBBox);
     if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
     // Clamp the bitmap size to about 1M pixels
     static const SkScalar kMaxBitmapArea = 1024 * 1024;
@@ -324,18 +325,18 @@
     return paintColor & SK_ColorBLACK;
 }
 
-sk_sp<SkPDFObject> SkPDFMakeShader(SkPDFDocument* doc,
-                                  SkShader* shader,
-                                  const SkMatrix& canvasTransform,
-                                  const SkIRect& surfaceBBox,
-                                  SkColor paintColor) {
+SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
+                                       SkShader* shader,
+                                       const SkMatrix& canvasTransform,
+                                       const SkIRect& surfaceBBox,
+                                       SkColor paintColor) {
     SkASSERT(shader);
     SkASSERT(doc);
     if (SkShader::kNone_GradientType != shader->asAGradient(nullptr)) {
         return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
     }
     if (surfaceBBox.isEmpty()) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
     SkBitmap image;
     SkPDFImageShaderKey key = {
@@ -350,11 +351,11 @@
     if (SkImage* skimg = shader->isAImage(&key.fShaderTransform, key.fImageTileModes)) {
         key.fBitmapKey = SkBitmapKeyFromImage(skimg);
         SkPDFCanon* canon = doc->canon();
-        sk_sp<SkPDFObject>* shaderPtr = canon->fImageShaderMap.find(key);
+        SkPDFIndirectReference* shaderPtr = canon->fImageShaderMap.find(key);
         if (shaderPtr) {
             return *shaderPtr;
         }
-        sk_sp<SkPDFObject> pdfShader = make_image_shader(doc, key, skimg);
+        SkPDFIndirectReference pdfShader = make_image_shader(doc, key, skimg);
         canon->fImageShaderMap.set(std::move(key), pdfShader);
         return pdfShader;
     }
diff --git a/src/pdf/SkPDFShader.h b/src/pdf/SkPDFShader.h
index b003e34..2a150c3 100644
--- a/src/pdf/SkPDFShader.h
+++ b/src/pdf/SkPDFShader.h
@@ -38,11 +38,11 @@
  *  @param paintColor  Color+Alpha of the paint.  Color is usually ignored,
  *                     unless it is a alpha shader.
  */
-sk_sp<SkPDFObject> SkPDFMakeShader(SkPDFDocument* doc,
-                                  SkShader* shader,
-                                  const SkMatrix& ctm,
-                                  const SkIRect& surfaceBBox,
-                                  SkColor paintColor);
+SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
+                                       SkShader* shader,
+                                       const SkMatrix& ctm,
+                                       const SkIRect& surfaceBBox,
+                                       SkColor paintColor);
 
 SK_BEGIN_REQUIRE_DENSE
 struct SkPDFImageShaderKey {
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index 4d9a71f..f210fc9 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -9,7 +9,9 @@
 
 #include "SkData.h"
 #include "SkDeflate.h"
+#include "SkExecutor.h"
 #include "SkMakeUnique.h"
+#include "SkPDFDocumentPriv.h"
 #include "SkPDFUtils.h"
 #include "SkStream.h"
 #include "SkStreamPriv.h"
@@ -203,6 +205,7 @@
             fObject->emitObject(stream);
             return;
         case Type::kRef:
+            SkASSERT(fIntValue >= 0);
             stream->writeDecAsText(fIntValue);
             stream->writeText(" 0 R");  // Generation number is always 0.
             return;
@@ -496,143 +499,71 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFSharedStream::SkPDFSharedStream(std::unique_ptr<SkStreamAsset> data)
-    : fAsset(std::move(data)) {
-    SkASSERT(fAsset);
-}
-
-SkPDFSharedStream::~SkPDFSharedStream() { this->drop(); }
-
-void SkPDFSharedStream::drop() {
-    fAsset = nullptr;
-    fDict.drop();
-}
-
-#ifdef SK_PDF_LESS_COMPRESSION
-void SkPDFSharedStream::emitObject(SkWStream* stream) const {
-    SkASSERT(fAsset);
-    std::unique_ptr<SkStreamAsset> dup(fAsset->duplicate());
-    SkASSERT(dup && dup->hasLength());
-    size_t length = dup->getLength();
-    stream->writeText("<<");
-    fDict.emitAll(stream);
-    stream->writeText("\n");
-    SkPDFUnion::Name("Length").emitObject(stream);
-    stream->writeText(" ");
-    SkPDFUnion::Int(length).emitObject(stream);
-    stream->writeText("\n>>stream\n");
-    SkStreamCopy(stream, dup.get());
-    stream->writeText("\nendstream");
-}
-#else
-void SkPDFSharedStream::emitObject(SkWStream* stream) const {
-    SkASSERT(fAsset);
-    SkDynamicMemoryWStream buffer;
-    SkDeflateWStream deflateWStream(&buffer);
-    // Since emitObject is const, this function doesn't change the dictionary.
-    std::unique_ptr<SkStreamAsset> dup(fAsset->duplicate());  // Cheap copy
-    SkASSERT(dup);
-    SkStreamCopy(&deflateWStream, dup.get());
-    deflateWStream.finalize();
-    size_t length = buffer.bytesWritten();
-    stream->writeText("<<");
-    fDict.emitAll(stream);
-    stream->writeText("\n");
-    SkPDFUnion::Name("Length").emitObject(stream);
-    stream->writeText(" ");
-    SkPDFUnion::Int(length).emitObject(stream);
-    stream->writeText("\n");
-    SkPDFUnion::Name("Filter").emitObject(stream);
-    stream->writeText(" ");
-    SkPDFUnion::Name("FlateDecode").emitObject(stream);
-    stream->writeText(">>");
-    stream->writeText(" stream\n");
-    buffer.writeToAndReset(stream);
-    stream->writeText("\nendstream");
-}
-#endif
-
-void SkPDFSharedStream::addResources(
-        SkPDFObjNumMap* catalog) const {
-    SkASSERT(fAsset);
-    fDict.addResources(catalog);
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-
-SkPDFStream::SkPDFStream(sk_sp<SkData> data) {
-    this->setData(skstd::make_unique<SkMemoryStream>(std::move(data)));
-}
-
-SkPDFStream::SkPDFStream(std::unique_ptr<SkStreamAsset> stream) {
-    this->setData(std::move(stream));
-}
-
-SkPDFStream::SkPDFStream() {}
-
-SkPDFStream::~SkPDFStream() {}
-
-void SkPDFStream::addResources(SkPDFObjNumMap* catalog) const {
-    SkASSERT(fCompressedData);
-    fDict.addResources(catalog);
-}
-
-void SkPDFStream::drop() {
-    fCompressedData.reset(nullptr);
-    fDict.drop();
-}
-
-void SkPDFStream::emitObject(SkWStream* stream) const {
-    SkASSERT(fCompressedData);
-    fDict.emitObject(stream);
-    // duplicate (a cheap operation) preserves const on fCompressedData.
-    std::unique_ptr<SkStreamAsset> dup(fCompressedData->duplicate());
-    SkASSERT(dup);
-    SkASSERT(dup->hasLength());
-    stream->writeText(" stream\n");
-    stream->writeStream(dup.get(), dup->getLength());
-    stream->writeText("\nendstream");
-}
-
-void SkPDFStream::setData(std::unique_ptr<SkStreamAsset> stream) {
-    SkASSERT(!fCompressedData);  // Only call this function once.
-    SkASSERT(stream);
+static void serialize_stream(const SkPDFDict* origDict,
+                             SkStreamAsset* stream,
+                             bool deflate,
+                             SkPDFDocument* doc,
+                             SkPDFIndirectReference ref) {
     // Code assumes that the stream starts at the beginning.
+    SkASSERT(stream && stream->hasLength());
 
-    #ifdef SK_PDF_LESS_COMPRESSION
-    fCompressedData = std::move(stream);
-    SkASSERT(fCompressedData && fCompressedData->hasLength());
-    fDict.insertInt("Length", fCompressedData->getLength());
-    #else
-
-    SkASSERT(stream->hasLength());
-    SkDynamicMemoryWStream compressedData;
-    SkDeflateWStream deflateWStream(&compressedData);
-    if (stream->getLength() > 0) {
-        SkStreamCopy(&deflateWStream, stream.get());
+    std::unique_ptr<SkStreamAsset> tmp;
+    SkPDFDict dict;
+    static const size_t kMinimumSavings = strlen("/Filter_/FlateDecode_");
+    if (deflate && stream->getLength() > kMinimumSavings) {
+        SkDynamicMemoryWStream compressedData;
+        SkDeflateWStream deflateWStream(&compressedData);
+        SkStreamCopy(&deflateWStream, stream);
+        deflateWStream.finalize();
+        if (stream->getLength() > compressedData.bytesWritten() + kMinimumSavings) {
+            tmp = compressedData.detachAsStream();
+            stream = tmp.get();
+            dict.insertName("Filter", "FlateDecode");
+        } else {
+            SkAssertResult(stream->rewind());
+        }
     }
-    deflateWStream.finalize();
-    size_t compressedLength = compressedData.bytesWritten();
-    size_t originalLength = stream->getLength();
+    dict.insertInt("Length", stream->getLength());
 
-    if (originalLength <= compressedLength + strlen("/Filter_/FlateDecode_")) {
-        SkAssertResult(stream->rewind());
-        fCompressedData = std::move(stream);
-        fDict.insertInt("Length", originalLength);
-        return;
+    SkWStream* dst = doc->beginObject(ref);
+    dst->writeText("<<");
+    if (origDict) {
+        origDict->emitAll(dst);
     }
-    fCompressedData = compressedData.detachAsStream();
-    fDict.insertName("Filter", "FlateDecode");
-    fDict.insertInt("Length", compressedLength);
-    #endif
+    dict.emitAll(dst);
+    dst->writeText(">> stream\n");
+    dst->writeStream(stream, stream->getLength());
+    dst->writeText("\nendstream");
+    doc->endObject();
+}
+
+SkPDFIndirectReference SkPDFStreamOut(sk_sp<SkPDFDict> dict,
+                                      std::unique_ptr<SkStreamAsset> content,
+                                      SkPDFDocument* doc,
+                                      bool deflate) {
+    SkPDFIndirectReference ref = doc->reserveRef();
+    if (SkExecutor* executor = doc->executor()) {
+        SkPDFDict* dictPtr = dict.release();
+        SkStreamAsset* contentPtr = content.release();
+        // Pass ownership of both pointers into a std::function, which should
+        // only be executed once.
+        executor->add([dictPtr, contentPtr, deflate, doc, ref]() {
+            serialize_stream(dictPtr, contentPtr, deflate, doc, ref);
+            SkSafeUnref(dictPtr);
+            delete contentPtr;
+        });
+        return ref;
+    }
+    serialize_stream(dict.get(), content.get(), deflate, doc, ref);
+    return ref;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 void SkPDFObjNumMap::addObjectRecursively(SkPDFObject* obj) {
+    SkASSERT(fIndirectReferenceSource != nullptr);
     if (obj && obj->fIndirectReference.fValue == -1) {
-        obj->fIndirectReference.fValue = fNextObjectNumber++;
+        obj->fIndirectReference = fIndirectReferenceSource->reserve();
         fObjects.emplace_back(sk_ref_sp(obj));
         obj->addResources(this);
     }
diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h
index 935d1b7..3dd5bfc 100644
--- a/src/pdf/SkPDFTypes.h
+++ b/src/pdf/SkPDFTypes.h
@@ -27,6 +27,7 @@
 class SkStreamAsset;
 class SkString;
 class SkWStream;
+struct SkPDFObjectSerializer;
 
 #ifdef SK_PDF_IMAGE_STATS
     #include <atomic>
@@ -34,6 +35,7 @@
 
 struct SkPDFIndirectReference {
     int fValue = -1;
+    explicit operator bool() { return fValue != -1; }
 };
 
 inline static bool operator==(SkPDFIndirectReference u, SkPDFIndirectReference v) {
@@ -44,6 +46,7 @@
     return u.fValue != v.fValue;
 }
 
+
 /** \class SkPDFObject
 
     A PDF Object is the base class for primitive elements in a PDF file.  A
@@ -352,67 +355,16 @@
     SkDEBUGCODE(bool fDumped;)
 };
 
-/** \class SkPDFSharedStream
+#ifdef SK_PDF_LESS_COMPRESSION
+    static constexpr bool kSkPDFDefaultDoDeflate = false;
+#else
+    static constexpr bool kSkPDFDefaultDoDeflate = true;
+#endif
 
-    This class takes an asset and assumes that it is backed by
-    long-lived shared data (for example, an open file
-    descriptor). That is: no memory savings can be made by holding on
-    to a compressed version instead.
- */
-class SkPDFSharedStream final : public SkPDFObject {
-public:
-    SkPDFSharedStream(std::unique_ptr<SkStreamAsset> data);
-    ~SkPDFSharedStream() override;
-    SkPDFDict* dict() { return &fDict; }
-    void emitObject(SkWStream*) const override;
-    void addResources(SkPDFObjNumMap*) const override;
-    void drop() override;
-
-private:
-    std::unique_ptr<SkStreamAsset> fAsset;
-    SkPDFDict fDict;
-    typedef SkPDFObject INHERITED;
-};
-
-/** \class SkPDFStream
-
-    This class takes an asset and assumes that it is the only owner of
-    the asset's data.  It immediately compresses the asset to save
-    memory.
- */
-
-class SkPDFStream final : public SkPDFObject {
-
-public:
-    /** Create a PDF stream. A Length entry is automatically added to the
-     *  stream dictionary.
-     *  @param data   The data part of the stream.
-     *  @param stream The data part of the stream. */
-    explicit SkPDFStream(sk_sp<SkData> data);
-    explicit SkPDFStream(std::unique_ptr<SkStreamAsset> stream);
-    ~SkPDFStream() override;
-
-    SkPDFDict* dict() { return &fDict; }
-
-    // The SkPDFObject interface.
-    void emitObject(SkWStream* stream) const override;
-    void addResources(SkPDFObjNumMap*) const final;
-    void drop() override;
-
-protected:
-    /* Create a PDF stream with no data.  The setData method must be called to
-     * set the data. */
-    SkPDFStream();
-
-    /** Only call this function once. */
-    void setData(std::unique_ptr<SkStreamAsset> stream);
-
-private:
-    std::unique_ptr<SkStreamAsset> fCompressedData;
-    SkPDFDict fDict;
-
-    typedef SkPDFDict INHERITED;
-};
+SkPDFIndirectReference SkPDFStreamOut(sk_sp<SkPDFDict> dict,
+                                      std::unique_ptr<SkStreamAsset> stream,
+                                      SkPDFDocument* doc,
+                                      bool deflate = kSkPDFDefaultDoDeflate);
 
 ////////////////////////////////////////////////////////////////////////////////
 
@@ -423,6 +375,8 @@
 */
 class SkPDFObjNumMap : SkNoncopyable {
 public:
+    SkPDFObjNumMap(SkPDFObjectSerializer* s) : fIndirectReferenceSource(s) {}
+
     /** Add the passed object to the catalog, as well as all its dependencies.
      *  @param obj   The object to add.  If nullptr, this is a noop.
      */
@@ -438,8 +392,8 @@
 
 private:
     friend struct SkPDFObjectSerializer;
+    SkPDFObjectSerializer* fIndirectReferenceSource;
     std::vector<sk_sp<SkPDFObject>> fObjects;
-    int fNextObjectNumber = 1;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/pdf/SkUUID.h b/src/pdf/SkUUID.h
new file mode 100644
index 0000000..3d81865
--- /dev/null
+++ b/src/pdf/SkUUID.h
@@ -0,0 +1,18 @@
+// Copyright 2018 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef SkUUID_DEFINED
+#define SkUUID_DEFINED
+
+#include <cstdint>
+#include <cstring>
+
+struct SkUUID {
+    uint8_t fData[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+};
+
+static inline bool operator==(const SkUUID& u, const SkUUID& v) {
+    return 0 == memcmp(u.fData, v.fData, sizeof(u.fData));
+}
+static inline bool operator!=(const SkUUID& u, const SkUUID& v) { return !(u == v); }
+
+#endif  // SkUUID_DEFINED
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index cbb706d..efb2ab2 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -23,6 +23,7 @@
 #include "SkPDFCanon.h"
 #include "SkPDFDevice.h"
 #include "SkPDFDocument.h"
+#include "SkPDFDocumentPriv.h"
 #include "SkPDFFont.h"
 #include "SkPDFTypes.h"
 #include "SkPDFUtils.h"
@@ -77,51 +78,11 @@
     assert_eq(reporter, result, string);
 }
 
-static void TestPDFStream(skiatest::Reporter* reporter) {
-    char streamBytes[] = "Test\nFoo\tBar";
-    auto streamData = skstd::make_unique<SkMemoryStream>(
-            streamBytes, strlen(streamBytes), true);
-    auto stream = sk_make_sp<SkPDFStream>(std::move(streamData));
-    assert_emit_eq(reporter,
-                   *stream,
-                   "<</Length 12>> stream\nTest\nFoo\tBar\nendstream");
-    stream->dict()->insertInt("Attribute", 42);
-    assert_emit_eq(reporter,
-                   *stream,
-                   "<</Length 12\n/Attribute 42>> stream\n"
-                   "Test\nFoo\tBar\nendstream");
-
-    {
-        char streamBytes2[] = "This is a longer string, so that compression "
-                              "can do something with it. With shorter strings, "
-                              "the short circuit logic cuts in and we end up "
-                              "with an uncompressed string.";
-        auto stream = sk_make_sp<SkPDFStream>(
-                SkData::MakeWithCopy(streamBytes2, strlen(streamBytes2)));
-
-        SkDynamicMemoryWStream compressedByteStream;
-        SkDeflateWStream deflateWStream(&compressedByteStream);
-        deflateWStream.write(streamBytes2, strlen(streamBytes2));
-        deflateWStream.finalize();
-
-        SkDynamicMemoryWStream expected;
-        expected.writeText("<</Filter /FlateDecode\n/Length 116>> stream\n");
-        compressedByteStream.writeToStream(&expected);
-        compressedByteStream.reset();
-        expected.writeText("\nendstream");
-        sk_sp<SkData> expectedResultData2(expected.detachAsData());
-        SkString result = emit_to_string(*stream);
-        #ifndef SK_PDF_LESS_COMPRESSION
-        assert_eql(reporter,
-                   result,
-                   (const char*)expectedResultData2->data(),
-                   expectedResultData2->size());
-        #endif
-    }
-}
-
 static void TestObjectNumberMap(skiatest::Reporter* reporter) {
-    SkPDFObjNumMap objNumMap;
+    SkPDFObjectSerializer pdfObjectSerializer;
+    SkNullWStream nullstream;
+    pdfObjectSerializer.serializeHeader(&nullstream);
+    SkPDFObjNumMap objNumMap(&pdfObjectSerializer);
     sk_sp<SkPDFArray> a1(new SkPDFArray);
     sk_sp<SkPDFArray> a2(new SkPDFArray);
     sk_sp<SkPDFArray> a3(new SkPDFArray);
@@ -145,7 +106,10 @@
     sk_sp<SkPDFArray> a2(new SkPDFArray);
     a2->appendObjRef(a1);
 
-    SkPDFObjNumMap catalog;
+    SkPDFObjectSerializer pdfObjectSerializer;
+    SkNullWStream nullstream;
+    pdfObjectSerializer.serializeHeader(&nullstream);
+    SkPDFObjNumMap catalog(&pdfObjectSerializer);
     catalog.addObjectRecursively(a1.get());
     REPORTER_ASSERT(reporter, catalog.getObjectNumber(a1.get()) == 1);
 
@@ -264,7 +228,10 @@
                    "(Another String) [-1]]");
 
     sk_sp<SkPDFArray> referencedArray(new SkPDFArray);
-    SkPDFObjNumMap catalog;
+    SkPDFObjectSerializer pdfObjectSerializer;
+    SkNullWStream nullstream;
+    pdfObjectSerializer.serializeHeader(&nullstream);
+    SkPDFObjNumMap catalog(&pdfObjectSerializer);
     catalog.addObjectRecursively(referencedArray.get());
     REPORTER_ASSERT(reporter, catalog.getObjectNumber(
                             referencedArray.get()) == 1);
@@ -328,8 +295,12 @@
     assert_emit_eq(reporter, *dict, "<</Type /DType>>");
 
     sk_sp<SkPDFArray> referencedArray(new SkPDFArray);
-    SkPDFObjNumMap catalog;
+    SkPDFObjectSerializer pdfObjectSerializer;
+    SkNullWStream nullstream;
+    pdfObjectSerializer.serializeHeader(&nullstream);
+    SkPDFObjNumMap catalog(&pdfObjectSerializer);
     catalog.addObjectRecursively(referencedArray.get());
+
     REPORTER_ASSERT(reporter, catalog.getObjectNumber(
                             referencedArray.get()) == 1);
     dict->insertObjRef("n1", std::move(referencedArray));
@@ -341,7 +312,6 @@
     TestPDFUnion(reporter);
     TestPDFArray(reporter);
     TestPDFDict(reporter);
-    TestPDFStream(reporter);
     TestObjectNumberMap(reporter);
     TestObjectRef(reporter);
     test_issue1083();