[PDF] Make stream compression optional on a per device basis.

There are a lot of small pieces to make this change work:
- SkPDFDocument (and SkPDFCatalog) take flags to disable compression (and font embedding - not implemented yet, can disable font subsetting for now).
- SkPDFStream now defers compression until the size/emit step.
- Classes that *had* a stream (because they didn't know the stream size at construction time) now *are* streams to make the substitution work correctly.
- The SkPDFShader implementation got pulled apart into two classes, one that is a SkPDFDict, and one that is a SkPDFStream (making the common ancestor SkPDFObject).
- Added helper methods in SkPDFObject for children that have simple resource lists.
- Added an iterator to SkPDFDict so that a substitute SkPDFStream can get a copy of the stream dictionary.
- Change SkPDFDocument to have a pointer to an SkPDFCatalog to remove a new circular header reference.

Review URL: http://codereview.appspot.com/4700045

git-svn-id: http://skia.googlecode.com/svn/trunk@1911 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/core/SkFlate.cpp b/src/core/SkFlate.cpp
index 3bae8a9..a5a7b22 100644
--- a/src/core/SkFlate.cpp
+++ b/src/core/SkFlate.cpp
@@ -26,11 +26,7 @@
 
 // static
 bool SkFlate::HaveFlate() {
-#ifdef SK_DEBUG
-    return false;
-#else
     return true;
-#endif
 }
 
 namespace {
diff --git a/src/pdf/SkPDFCatalog.cpp b/src/pdf/SkPDFCatalog.cpp
index 025c86d..420ce40 100644
--- a/src/pdf/SkPDFCatalog.cpp
+++ b/src/pdf/SkPDFCatalog.cpp
@@ -19,10 +19,11 @@
 #include "SkStream.h"
 #include "SkTypes.h"
 
-SkPDFCatalog::SkPDFCatalog()
+SkPDFCatalog::SkPDFCatalog(SkPDFDocument::Flags flags)
     : fFirstPageCount(0),
       fNextObjNum(1),
-      fNextFirstPageObjNum(0) {
+      fNextFirstPageObjNum(0),
+      fDocumentFlags(flags) {
 }
 
 SkPDFCatalog::~SkPDFCatalog() {
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 7833362..caba822 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -1394,7 +1394,7 @@
     entry->fClipRegion = clipRegion;
 
     // PDF treats a shader as a color, so we only set one or the other.
-    SkRefPtr<SkPDFShader> pdfShader;
+    SkRefPtr<SkPDFObject> pdfShader;
     const SkShader* shader = paint.getShader();
     SkColor color = paint.getColor();
     if (shader) {
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
index 1cfe64d..d60512e 100644
--- a/src/pdf/SkPDFDocument.cpp
+++ b/src/pdf/SkPDFDocument.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "SkPDFCatalog.h"
 #include "SkPDFDevice.h"
 #include "SkPDFDocument.h"
 #include "SkPDFPage.h"
@@ -36,12 +37,13 @@
     }
 }
 
-SkPDFDocument::SkPDFDocument()
+SkPDFDocument::SkPDFDocument(Flags flags)
         : fXRefFileOffset(0),
           fSecondPageFirstResourceIndex(0) {
+    fCatalog.reset(new SkPDFCatalog(flags));
     fDocCatalog = new SkPDFDict("Catalog");
     fDocCatalog->unref();  // SkRefPtr and new both took a reference.
-    fCatalog.addObject(fDocCatalog.get(), true);
+    fCatalog->addObject(fDocCatalog.get(), true);
 }
 
 SkPDFDocument::~SkPDFDocument() {
@@ -68,7 +70,7 @@
     // We haven't emitted the document before if fPageTree is empty.
     if (fPageTree.count() == 0) {
         SkPDFDict* pageTreeRoot;
-        SkPDFPage::GeneratePageTree(fPages, &fCatalog, &fPageTree,
+        SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
                                     &pageTreeRoot);
         fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
 
@@ -87,9 +89,9 @@
         bool firstPage = true;
         for (int i = 0; i < fPages.count(); i++) {
             int resourceCount = fPageResources.count();
-            fPages[i]->finalizePage(&fCatalog, firstPage, &fPageResources);
+            fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
             addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
-                                  &fCatalog);
+                                  fCatalog.get());
             if (i == 0) {
                 firstPage = false;
                 fSecondPageFirstResourceIndex = fPageResources.count();
@@ -98,57 +100,67 @@
 
         // Figure out the size of things and inform the catalog of file offsets.
         off_t fileOffset = headerSize();
-        fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset);
-        fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset);
-        fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset);
-        for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
-            fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
+        fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
+        fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
+        fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
+        for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
+            fileOffset += fCatalog->setFileOffset(fPageResources[i],
+                                                  fileOffset);
+        }
         // Add the size of resources of substitute objects used on page 1.
-        fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, true);
+        fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
         if (fPages.count() > 1) {
             // TODO(vandebo) For linearized format, save the start of the
             // first page xref table and calculate the size.
         }
 
         for (int i = 0; i < fPageTree.count(); i++)
-            fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset);
+            fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
 
         for (int i = 1; i < fPages.count(); i++)
-            fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);
+            fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
 
         for (int i = fSecondPageFirstResourceIndex;
                  i < fPageResources.count();
                  i++)
-            fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
+            fileOffset += fCatalog->setFileOffset(fPageResources[i],
+                                                  fileOffset);
 
-        fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, false);
+        fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
+                                                              false);
         fXRefFileOffset = fileOffset;
     }
 
     emitHeader(stream);
-    fDocCatalog->emitObject(stream, &fCatalog, true);
-    fPages[0]->emitObject(stream, &fCatalog, true);
-    fPages[0]->emitPage(stream, &fCatalog);
-    for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
-        fPageResources[i]->emit(stream, &fCatalog, true);
-    fCatalog.emitSubstituteResources(stream, true);
+    fDocCatalog->emitObject(stream, fCatalog.get(), true);
+    fPages[0]->emitObject(stream, fCatalog.get(), true);
+    fPages[0]->emitPage(stream, fCatalog.get());
+    for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
+        fPageResources[i]->emit(stream, fCatalog.get(), true);
+    }
+    fCatalog->emitSubstituteResources(stream, true);
     // TODO(vandebo) support linearized format
     //if (fPages.size() > 1) {
     //    // TODO(vandebo) save the file offset for the first page xref table.
-    //    fCatalog.emitXrefTable(stream, true);
+    //    fCatalog->emitXrefTable(stream, true);
     //}
 
-    for (int i = 0; i < fPageTree.count(); i++)
-        fPageTree[i]->emitObject(stream, &fCatalog, true);
+    for (int i = 0; i < fPageTree.count(); i++) {
+        fPageTree[i]->emitObject(stream, fCatalog.get(), true);
+    }
 
-    for (int i = 1; i < fPages.count(); i++)
-        fPages[i]->emitPage(stream, &fCatalog);
+    for (int i = 1; i < fPages.count(); i++) {
+        fPages[i]->emitPage(stream, fCatalog.get());
+    }
 
-    for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++)
-        fPageResources[i]->emit(stream, &fCatalog, true);
+    for (int i = fSecondPageFirstResourceIndex;
+            i < fPageResources.count();
+            i++) {
+        fPageResources[i]->emit(stream, fCatalog.get(), true);
+    }
 
-    fCatalog.emitSubstituteResources(stream, false);
-    int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
+    fCatalog->emitSubstituteResources(stream, false);
+    int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
     emitFooter(stream, objCount);
     return true;
 }
@@ -217,7 +229,7 @@
     }
 
     stream->writeText("trailer\n");
-    fTrailerDict->emitObject(stream, &fCatalog, false);
+    fTrailerDict->emitObject(stream, fCatalog.get(), false);
     stream->writeText("\nstartxref\n");
     stream->writeBigDecAsText(fXRefFileOffset);
     stream->writeText("\n%%EOF");
diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp
index 4602c68..be16c76 100755
--- a/src/pdf/SkPDFFont.cpp
+++ b/src/pdf/SkPDFFont.cpp
@@ -435,12 +435,7 @@
 }
 
 void SkPDFFont::getResources(SkTDArray<SkPDFObject*>* resourceList) {
-    resourceList->setReserve(resourceList->count() + fResources.count());
-    for (int i = 0; i < fResources.count(); i++) {
-        resourceList->push(fResources[i]);
-        fResources[i]->ref();
-        fResources[i]->getResources(resourceList);
-    }
+    GetResourcesHelper(&fResources, resourceList);
 }
 
 SkTypeface* SkPDFFont::typeface() {
diff --git a/src/pdf/SkPDFFormXObject.cpp b/src/pdf/SkPDFFormXObject.cpp
index 40a5564..a8a3290 100644
--- a/src/pdf/SkPDFFormXObject.cpp
+++ b/src/pdf/SkPDFFormXObject.cpp
@@ -31,8 +31,7 @@
 
     SkRefPtr<SkStream> content = device->content();
     content->unref();  // SkRefPtr and content() both took a reference.
-    fStream = new SkPDFStream(content.get());
-    fStream->unref();  // SkRefPtr and new both took a reference.
+    setData(content.get());
 
     insert("Type", new SkPDFName("XObject"))->unref();
     insert("Subtype", new SkPDFName("Form"))->unref();
@@ -62,33 +61,6 @@
     fResources.unrefAll();
 }
 
-void SkPDFFormXObject::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
-                             bool indirect) {
-    if (indirect)
-        return emitIndirectObject(stream, catalog);
-
-    fStream->emitObject(stream, catalog, indirect);
-}
-
-size_t SkPDFFormXObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
-    if (indirect)
-        return getIndirectOutputSize(catalog);
-
-    return fStream->getOutputSize(catalog, indirect);
-}
-
 void SkPDFFormXObject::getResources(SkTDArray<SkPDFObject*>* resourceList) {
-    resourceList->setReserve(resourceList->count() + fResources.count());
-    for (int i = 0; i < fResources.count(); i++) {
-        resourceList->push(fResources[i]);
-        fResources[i]->ref();
-    }
-}
-
-SkPDFObject* SkPDFFormXObject::insert(SkPDFName* key, SkPDFObject* value) {
-    return fStream->insert(key, value);
-}
-
-SkPDFObject* SkPDFFormXObject::insert(const char key[], SkPDFObject* value) {
-    return fStream->insert(key, value);
+    GetResourcesHelper(&fResources, resourceList);
 }
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
index 59b2817..0e6e230 100644
--- a/src/pdf/SkPDFGraphicState.cpp
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -67,12 +67,7 @@
 }
 
 void SkPDFGraphicState::getResources(SkTDArray<SkPDFObject*>* resourceList) {
-    resourceList->setReserve(resourceList->count() + fResources.count());
-    for (int i = 0; i < fResources.count(); i++) {
-        resourceList->push(fResources[i]);
-        fResources[i]->ref();
-        fResources[i]->getResources(resourceList);
-    }
+    GetResourcesHelper(&fResources, resourceList);
 }
 
 void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
diff --git a/src/pdf/SkPDFImage.cpp b/src/pdf/SkPDFImage.cpp
index be69f7f..ebbcd11 100644
--- a/src/pdf/SkPDFImage.cpp
+++ b/src/pdf/SkPDFImage.cpp
@@ -281,38 +281,14 @@
     return mask;
 }
 
-void SkPDFImage::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
-                             bool indirect) {
-    if (indirect)
-        return emitIndirectObject(stream, catalog);
-
-    fStream->emitObject(stream, catalog, indirect);
-}
-
-size_t SkPDFImage::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
-    if (indirect)
-        return getIndirectOutputSize(catalog);
-
-    return fStream->getOutputSize(catalog, indirect);
-}
-
 void SkPDFImage::getResources(SkTDArray<SkPDFObject*>* resourceList) {
-    if (fResources.count()) {
-        resourceList->setReserve(resourceList->count() + fResources.count());
-        for (int i = 0; i < fResources.count(); i++) {
-            resourceList->push(fResources[i]);
-            fResources[i]->ref();
-            fResources[i]->getResources(resourceList);
-        }
-    }
+    GetResourcesHelper(&fResources, resourceList);
 }
 
 SkPDFImage::SkPDFImage(SkStream* imageData, const SkBitmap& bitmap,
                        const SkIRect& srcRect, bool doingAlpha,
                        const SkPaint& paint) {
-    fStream = new SkPDFStream(imageData);
-    fStream->unref();  // SkRefPtr and new both took a reference.
-
+    this->setData(imageData);
     SkBitmap::Config config = bitmap.getConfig();
     bool alphaOnly = (config == SkBitmap::kA1_Config ||
                       config == SkBitmap::kA8_Config);
@@ -372,11 +348,3 @@
         insert("Decode", decodeValue.get());
     }
 }
-
-SkPDFObject* SkPDFImage::insert(SkPDFName* key, SkPDFObject* value) {
-    return fStream->insert(key, value);
-}
-
-SkPDFObject* SkPDFImage::insert(const char key[], SkPDFObject* value) {
-    return fStream->insert(key, value);
-}
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 95fdb61..ee22517 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -17,6 +17,7 @@
 #include "SkPDFShader.h"
 
 #include "SkCanvas.h"
+#include "SkData.h"
 #include "SkPDFCatalog.h"
 #include "SkPDFDevice.h"
 #include "SkPDFTypes.h"
@@ -286,65 +287,107 @@
     return function;
 }
 
-SkPDFShader::~SkPDFShader() {
+class SkPDFShader::State {
+public:
+    SkShader::GradientType fType;
+    SkShader::GradientInfo fInfo;
+    SkAutoFree fColorData;
+    SkMatrix fCanvasTransform;
+    SkMatrix fShaderTransform;
+    SkIRect fBBox;
+
+    SkBitmap fImage;
+    uint32_t fPixelGeneration;
+    SkShader::TileMode fImageTileModes[2];
+
+    explicit State(const SkShader& shader, const SkMatrix& canvasTransform,
+                   const SkIRect& bbox);
+    bool operator==(const State& b) const;
+};
+
+class SkPDFFunctionShader : public SkPDFDict, public SkPDFShader {
+public:
+    SkPDFFunctionShader(SkPDFShader::State* state);
+    ~SkPDFFunctionShader() {
+        if (isValid()) {
+            RemoveShader(this);
+        }
+        fResources.unrefAll();
+    }
+
+    bool isValid() { return fResources.count() > 0; }
+
+    void getResources(SkTDArray<SkPDFObject*>* resourceList) {
+        GetResourcesHelper(&fResources, resourceList);
+    }
+
+private:
+    static SkPDFObject* RangeObject();
+
+    SkTDArray<SkPDFObject*> fResources;
+    SkAutoTDelete<const SkPDFShader::State> fState;
+
+    SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain);
+};
+
+class SkPDFImageShader : public SkPDFStream, public SkPDFShader {
+public:
+    SkPDFImageShader(SkPDFShader::State* state);
+    ~SkPDFImageShader() {
+        RemoveShader(this);
+        fResources.unrefAll();
+    }
+
+    void getResources(SkTDArray<SkPDFObject*>* resourceList) {
+        GetResourcesHelper(&fResources, resourceList);
+    }
+
+private:
+    SkTDArray<SkPDFObject*> fResources;
+    SkAutoTDelete<const SkPDFShader::State> fState;
+};
+
+SkPDFShader::SkPDFShader() {}
+
+// static
+void SkPDFShader::RemoveShader(SkPDFObject* shader) {
     SkAutoMutexAcquire lock(CanonicalShadersMutex());
-    ShaderCanonicalEntry entry(this, fState.get());
+    ShaderCanonicalEntry entry(shader, NULL);
     int index = CanonicalShaders().find(entry);
-    if (fContent.get()) {
-        SkASSERT(index >= 0);
-        CanonicalShaders().removeShuffle(index);
-    }
-    fResources.unrefAll();
-}
-
-void SkPDFShader::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
-                             bool indirect) {
-    if (indirect)
-        return emitIndirectObject(stream, catalog);
-
-    fContent->emitObject(stream, catalog, indirect);
-}
-
-size_t SkPDFShader::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
-    if (indirect)
-        return getIndirectOutputSize(catalog);
-
-    return fContent->getOutputSize(catalog, indirect);
-}
-
-void SkPDFShader::getResources(SkTDArray<SkPDFObject*>* resourceList) {
-    resourceList->setReserve(resourceList->count() + fResources.count());
-    for (int i = 0; i < fResources.count(); i++) {
-        resourceList->push(fResources[i]);
-        fResources[i]->ref();
-    }
+    SkASSERT(index >= 0);
+    CanonicalShaders().removeShuffle(index);
 }
 
 // static
-SkPDFShader* SkPDFShader::GetPDFShader(const SkShader& shader,
+SkPDFObject* SkPDFShader::GetPDFShader(const SkShader& shader,
                                        const SkMatrix& matrix,
                                        const SkIRect& surfaceBBox) {
-    SkRefPtr<SkPDFShader> pdfShader;
+    SkPDFObject* result;
     SkAutoMutexAcquire lock(CanonicalShadersMutex());
     SkAutoTDelete<State> shaderState(new State(shader, matrix, surfaceBBox));
 
     ShaderCanonicalEntry entry(NULL, shaderState.get());
     int index = CanonicalShaders().find(entry);
     if (index >= 0) {
-        SkPDFShader* result = CanonicalShaders()[index].fPDFShader;
+        result = CanonicalShaders()[index].fPDFShader;
         result->ref();
         return result;
     }
     // The PDFShader takes ownership of the shaderSate.
-    pdfShader = new SkPDFShader(shaderState.detach());
-    // Check for a valid shader.
-    if (pdfShader->fContent.get() == NULL) {
-        pdfShader->unref();
-        return NULL;
+    if (shaderState.get()->fType == SkShader::kNone_GradientType) {
+        result = new SkPDFImageShader(shaderState.detach());
+    } else {
+        SkPDFFunctionShader* functionShader =
+            new SkPDFFunctionShader(shaderState.detach());
+        if (!functionShader->isValid()) {
+            delete functionShader;
+            return NULL;
+        }
+        result = functionShader;
     }
-    entry.fPDFShader = pdfShader.get();
+    entry.fPDFShader = result;
     CanonicalShaders().push(entry);
-    return pdfShader.get();  // return the reference that came from new.
+    return result;  // return the reference that came from new.
 }
 
 // static
@@ -362,7 +405,7 @@
 }
 
 // static
-SkPDFObject* SkPDFShader::RangeObject() {
+SkPDFObject* SkPDFFunctionShader::RangeObject() {
     // This initialization is only thread safe with gcc.
     static SkPDFArray* range = NULL;
     // This method is only used with CanonicalShadersMutex, so it's safe to
@@ -380,15 +423,9 @@
     return range;
 }
 
-SkPDFShader::SkPDFShader(State* state) : fState(state) {
-    if (fState.get()->fType == SkShader::kNone_GradientType) {
-        doImageShader();
-    } else {
-        doFunctionShader();
-    }
-}
-
-void SkPDFShader::doFunctionShader() {
+SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
+        : SkPDFDict("Pattern"),
+          fState(state) {
     SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
     SkPoint transformPoints[2];
 
@@ -407,9 +444,8 @@
             codeFunction = &radialCode;
             break;
         case SkShader::kRadial2_GradientType: {
-            // Bail out if the radii are the same.  Not setting fContent will
-            // cause the higher level code to detect the resulting object
-            // as invalid.
+            // Bail out if the radii are the same.  Empty fResources signals
+            // an error and isValid will return false.
             if (info->fRadius[0] == info->fRadius[1]) {
                 return;
             }
@@ -479,15 +515,12 @@
     pdfShader->insert("Domain", domain.get());
     pdfShader->insert("Function", new SkPDFObjRef(function.get()))->unref();
 
-    fContent = new SkPDFDict("Pattern");
-    fContent->unref();  // SkRefPtr and new both took a reference.
-    fContent->insertInt("PatternType", 2);
-    fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
-    fContent->insert("Shading", pdfShader.get());
+    insertInt("PatternType", 2);
+    insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
+    insert("Shading", pdfShader.get());
 }
 
-// SkShader* shader, SkMatrix matrix, const SkRect& surfaceBBox
-void SkPDFShader::doImageShader() {
+SkPDFImageShader::SkPDFImageShader(SkPDFShader::State* state) : fState(state) {
     fState.get()->fImage.lockPixels();
 
     SkMatrix finalMatrix = fState.get()->fCanvasTransform;
@@ -666,34 +699,43 @@
     content->unref();  // SkRefPtr and content() both took a reference.
     pattern.getResources(&fResources);
 
-    fContent = new SkPDFStream(content.get());
-    fContent->unref();  // SkRefPtr and new both took a reference.
-    fContent->insertName("Type", "Pattern");
-    fContent->insertInt("PatternType", 1);
-    fContent->insertInt("PaintType", 1);
-    fContent->insertInt("TilingType", 1);
-    fContent->insert("BBox", patternBBoxArray.get());
-    fContent->insertScalar("XStep", patternBBox.width());
-    fContent->insertScalar("YStep", patternBBox.height());
-    fContent->insert("Resources", pattern.getResourceDict().get());
-    fContent->insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
+    setData(content.get());
+    insertName("Type", "Pattern");
+    insertInt("PatternType", 1);
+    insertInt("PaintType", 1);
+    insertInt("TilingType", 1);
+    insert("BBox", patternBBoxArray.get());
+    insertScalar("XStep", patternBBox.width());
+    insertScalar("YStep", patternBBox.height());
+    insert("Resources", pattern.getResourceDict().get());
+    insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
 
     fState.get()->fImage.unlockPixels();
 }
 
-SkPDFStream* SkPDFShader::makePSFunction(const SkString& psCode,
-                                         SkPDFArray* domain) {
-    SkRefPtr<SkMemoryStream> funcStream =
-        new SkMemoryStream(psCode.c_str(), psCode.size(), true);
-    funcStream->unref();  // SkRefPtr and new both took a reference.
-
-    SkPDFStream* result = new SkPDFStream(funcStream.get());
+SkPDFStream* SkPDFFunctionShader::makePSFunction(const SkString& psCode,
+                                                 SkPDFArray* domain) {
+    SkAutoDataUnref funcData(SkData::NewWithCopy(psCode.c_str(),
+                                                 psCode.size()));
+    SkPDFStream* result = new SkPDFStream(funcData.get());
     result->insertInt("FunctionType", 4);
     result->insert("Domain", domain);
     result->insert("Range", RangeObject());
     return result;
 }
 
+SkPDFShader::ShaderCanonicalEntry::ShaderCanonicalEntry(SkPDFObject* pdfShader,
+                                                        const State* state)
+    : fPDFShader(pdfShader),
+      fState(state) {
+}
+
+bool SkPDFShader::ShaderCanonicalEntry::operator==(
+        const ShaderCanonicalEntry& b) const {
+    return fPDFShader == b.fPDFShader ||
+           (fState != NULL && b.fState != NULL && *fState == *b.fState);
+}
+
 bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
     if (fType != b.fType ||
             fCanvasTransform != b.fCanvasTransform ||
diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp
index 790cfae..0ff7b49 100644
--- a/src/pdf/SkPDFStream.cpp
+++ b/src/pdf/SkPDFStream.cpp
@@ -20,45 +20,105 @@
 #include "SkPDFStream.h"
 #include "SkStream.h"
 
-SkPDFStream::SkPDFStream(SkStream* stream) {
-    if (SkFlate::HaveFlate())
-        SkAssertResult(SkFlate::Deflate(stream, &fCompressedData));
+static bool skip_compression(SkPDFCatalog* catalog) {
+    return catalog->getDocumentFlags() & SkPDFDocument::kNoCompression_Flag;
+}
 
-    if (SkFlate::HaveFlate() &&
-            fCompressedData.getOffset() < stream->getLength()) {
-        fLength = fCompressedData.getOffset();
-        insert("Filter", new SkPDFName("FlateDecode"))->unref();
-    } else {
-        fCompressedData.reset();
-        fPlainData = stream;
-        fLength = fPlainData->getLength();
+SkPDFStream::SkPDFStream(SkStream* stream)
+    : fState(kUnused_State),
+      fData(stream) {
+}
+
+SkPDFStream::SkPDFStream(SkData* data) : fState(kUnused_State) {
+    SkMemoryStream* stream = new SkMemoryStream;
+    stream->setData(data);
+    fData = stream;
+    fData->unref();  // SkRefPtr and new both took a reference.
+}
+
+SkPDFStream::SkPDFStream(const SkPDFStream& pdfStream)
+        : SkPDFDict(),
+          fState(kUnused_State),
+          fData(pdfStream.fData) {
+    bool removeLength = true;
+    // Don't uncompress an already compressed stream, but we could.
+    if (pdfStream.fState == kCompressed_State) {
+        fState = kCompressed_State;
+        removeLength = false;
     }
-    insertInt("Length", fLength);
+    SkPDFDict::Iter dict(pdfStream);
+    SkPDFName* key;
+    SkPDFObject* value;
+    SkPDFName lengthName("Length");
+    for (key = dict.next(&value); key != NULL; key = dict.next(&value)) {
+        if (removeLength && *key == lengthName) {
+            continue;
+        }
+        this->insert(key, value);
+    }
 }
 
-SkPDFStream::~SkPDFStream() {
-}
+SkPDFStream::~SkPDFStream() {}
 
 void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
                              bool indirect) {
-    if (indirect)
+    if (indirect) {
         return emitIndirectObject(stream, catalog);
+    }
+    if (!this->populate(catalog)) {
+        return fSubstitute->emitObject(stream, catalog, indirect);
+    }
 
     this->INHERITED::emitObject(stream, catalog, false);
     stream->writeText(" stream\n");
-    if (fPlainData.get()) {
-        stream->write(fPlainData->getMemoryBase(), fLength);
-    } else {
-        SkAutoDataUnref data(fCompressedData.copyToData());
-        stream->write(data.data(), fLength);
-    }
+    stream->write(fData->getMemoryBase(), fData->getLength());
     stream->writeText("\nendstream");
 }
 
 size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
-    if (indirect)
+    if (indirect) {
         return getIndirectOutputSize(catalog);
+    }
+    if (!this->populate(catalog)) {
+        return fSubstitute->getOutputSize(catalog, indirect);
+    }
 
     return this->INHERITED::getOutputSize(catalog, false) +
-        strlen(" stream\n\nendstream") + fLength;
+        strlen(" stream\n\nendstream") + fData->getLength();
+}
+
+SkPDFStream::SkPDFStream() : fState(kUnused_State) {}
+
+void SkPDFStream::setData(SkStream* stream) {
+    fData = stream;
+}
+
+bool SkPDFStream::populate(SkPDFCatalog* catalog) {
+    if (fState == kUnused_State) {
+        if (!skip_compression(catalog) && SkFlate::HaveFlate()) {
+            SkDynamicMemoryWStream compressedData;
+
+            SkAssertResult(SkFlate::Deflate(fData.get(), &compressedData));
+            if (compressedData.getOffset() < fData->getLength()) {
+                SkMemoryStream* stream = new SkMemoryStream;
+                stream->setData(compressedData.copyToData());
+                fData = stream;
+                fData->unref();  // SkRefPtr and new both took a reference.
+                insertName("Filter", "FlateDecode");
+            }
+            fState = kCompressed_State;
+        } else {
+            fState = kNoCompression_State;
+        }
+        insertInt("Length", fData->getLength());
+    } else if (fState == kNoCompression_State && !skip_compression(catalog) &&
+               SkFlate::HaveFlate()) {
+        if (!fSubstitute.get()) {
+            fSubstitute = new SkPDFStream(*this);
+            fSubstitute->unref();  // SkRefPtr and new both took a reference.
+            catalog->setSubstitute(this, fSubstitute.get());
+        }
+        return false;
+    }
+    return true;
 }
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index 810d860..a026d69 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -48,14 +48,32 @@
     stream->writeText("\nendobj\n");
 }
 
-SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {}
-SkPDFObjRef::~SkPDFObjRef() {}
-
 size_t SkPDFObject::getIndirectOutputSize(SkPDFCatalog* catalog) {
     return catalog->getObjectNumberSize(this) + strlen(" obj\n") +
         this->getOutputSize(catalog, false) + strlen("\nendobj\n");
 }
 
+void SkPDFObject::AddResourceHelper(SkPDFObject* resource,
+                                    SkTDArray<SkPDFObject*>* list) {
+    list->push(resource);
+    resource->ref();
+}
+
+void SkPDFObject::GetResourcesHelper(SkTDArray<SkPDFObject*>* resources,
+                                     SkTDArray<SkPDFObject*>* result) {
+    if (resources->count()) {
+        result->setReserve(result->count() + resources->count());
+        for (int i = 0; i < resources->count(); i++) {
+            result->push((*resources)[i]);
+            (*resources)[i]->ref();
+            (*resources)[i]->getResources(result);
+        }
+    }
+}
+
+SkPDFObjRef::SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {}
+SkPDFObjRef::~SkPDFObjRef() {}
+
 void SkPDFObjRef::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
                              bool indirect) {
     SkASSERT(!indirect);
@@ -258,6 +276,10 @@
 SkPDFName::SkPDFName(const SkString& name) : fValue(FormatName(name)) {}
 SkPDFName::~SkPDFName() {}
 
+bool SkPDFName::operator==(const SkPDFName& b) const {
+    return fValue == b.fValue;
+}
+
 void SkPDFName::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
                            bool indirect) {
     SkASSERT(!indirect);
@@ -433,3 +455,19 @@
     }
     fValue.reset();
 }
+
+SkPDFDict::Iter::Iter(const SkPDFDict& dict)
+    : fIter(dict.fValue.begin()),
+      fStop(dict.fValue.end()) {
+}
+
+SkPDFName* SkPDFDict::Iter::next(SkPDFObject** value) {
+    if (fIter != fStop) {
+        Rec* cur = fIter;
+        fIter++;
+        *value = cur->value;
+        return cur->key;
+    }
+    *value = NULL;
+    return NULL;
+}