Add deflate support to SkPDFStream.

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

git-svn-id: http://skia.googlecode.com/svn/trunk@627 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/config/SkUserConfig.h b/include/config/SkUserConfig.h
index 8f63ce4..a2df745 100644
--- a/include/config/SkUserConfig.h
+++ b/include/config/SkUserConfig.h
@@ -117,6 +117,12 @@
  */
 //#define SK_SUPPORT_LCDTEXT
 
+/*  If zlib is available and you want to support the flate compression
+    algorithm (used in PDF generation), define SK_ZLIB_INCLUDE to be the
+    include path.
+ */
+//#define SK_ZLIB_INCLUDE <zlib.h>
+
 /*  If SK_DEBUG is defined, then you can optionally define SK_SUPPORT_UNITTEST
     which will run additional self-tests at startup. These can take a long time,
     so this flag is optional.
diff --git a/include/core/SkFlate.h b/include/core/SkFlate.h
new file mode 100644
index 0000000..c496b6f
--- /dev/null
+++ b/include/core/SkFlate.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SkFlate_DEFINED
+#define SkFlate_DEFINED
+
+#include "SkTypes.h"
+
+class SkDynamicMemoryWStream;
+class SkStream;
+
+/** \class SkFlate
+    A class to provide access to the flate compression algorithm.
+*/
+class SkFlate {
+public:
+    /** Indicates if the flate algorithm is available.
+     */
+    static bool HaveFlate();
+
+    /** Use the flate compression algorithm to compress the data in src,
+        putting the result into dst.  Returns false if an error occurs.
+     */
+    static bool Deflate(SkStream* src, SkDynamicMemoryWStream* dst);
+
+    /** Use the flate compression algorithm to decompress the data in src,
+        putting the result into dst.  Returns false if an error occurs.
+     */
+    static bool Inflate(SkStream* src, SkDynamicMemoryWStream* dst);
+
+private:
+    static const size_t kBufferSize;
+};
+
+#endif
diff --git a/include/pdf/SkPDFStream.h b/include/pdf/SkPDFStream.h
index 24a9642..10a40b6 100644
--- a/include/pdf/SkPDFStream.h
+++ b/include/pdf/SkPDFStream.h
@@ -19,9 +19,9 @@
 
 #include "SkPDFTypes.h"
 #include "SkRefCnt.h"
+#include "SkStream.h"
 #include "SkTemplates.h"
 
-class SkStream;
 class SkPDFCatalog;
 
 /** \class SkPDFStream
@@ -57,7 +57,10 @@
 
 private:
     SkPDFDict fDict;
-    SkRefPtr<SkStream> fData;
+    size_t fLength;
+    // Only one of the two streams will be valid.
+    SkRefPtr<SkStream> fPlainData;
+    SkDynamicMemoryWStream fCompressedData;
 };
 
 #endif
diff --git a/src/core/SkFlate.cpp b/src/core/SkFlate.cpp
new file mode 100644
index 0000000..a9adece
--- /dev/null
+++ b/src/core/SkFlate.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SkFlate.h"
+#include "SkStream.h"
+
+#ifndef SK_ZLIB_INCLUDE
+bool SkFlate::HaveFlate() { return false; }
+bool SkFlate::Deflate(SkStream*, SkDynamicMemoryWStream*) { return false; }
+bool SkFlate::Inflate(SkStream*, SkDynamicMemoryWStream*) { return false; }
+#else
+
+// static
+const size_t SkFlate::kBufferSize = 1024;
+
+// static
+bool SkFlate::HaveFlate() {
+    return true;
+}
+
+namespace {
+
+#include SK_ZLIB_INCLUDE
+
+bool doFlate(bool compress, const size_t kBufferSize, SkStream* src,
+             SkDynamicMemoryWStream* dst) {
+    uint8_t inputBuffer[kBufferSize];
+    uint8_t outputBuffer[kBufferSize];
+    z_stream flateData;
+    flateData.zalloc = NULL;
+    flateData.zfree = NULL;
+    flateData.next_in = NULL;
+    flateData.avail_in = 0;
+    flateData.next_out = outputBuffer;
+    flateData.avail_out = kBufferSize;
+    int rc;
+    if (compress)
+        rc = deflateInit(&flateData, Z_DEFAULT_COMPRESSION);
+    else
+        rc = inflateInit(&flateData);
+    if (rc != Z_OK)
+        return false;
+
+    uint8_t* input = (uint8_t*)src->getMemoryBase();
+    size_t inputLength = src->getLength();
+    if (input == NULL || inputLength < 0) {
+        input = NULL;
+        flateData.next_in = inputBuffer;
+        flateData.avail_in = 0;
+    } else {
+        flateData.next_in = input;
+        flateData.avail_in = inputLength;
+    }
+
+    rc = Z_OK;
+    while (true) {
+        if (flateData.avail_out < kBufferSize) {
+            if (!dst->write(outputBuffer, kBufferSize - flateData.avail_out)) {
+                rc = Z_BUF_ERROR;
+                break;
+            }
+            flateData.next_out = outputBuffer;
+            flateData.avail_out = kBufferSize;
+        }
+        if (flateData.avail_in == 0) {
+            if (input != NULL)
+                break;
+            size_t read = src->read(&inputBuffer, kBufferSize);
+            if (read == 0)
+                break;
+            flateData.next_in = inputBuffer;
+            flateData.avail_in = read;
+        }
+        if (compress)
+            rc = deflate(&flateData, Z_NO_FLUSH);
+        else
+            rc = inflate(&flateData, Z_NO_FLUSH);
+        if (rc != Z_OK)
+            break;
+    }
+    while (rc == Z_OK) {
+        if (compress)
+            rc = deflate(&flateData, Z_FINISH);
+        else
+            rc = inflate(&flateData, Z_FINISH);
+        if (flateData.avail_out > 0) {
+            if (!dst->write(outputBuffer, kBufferSize - flateData.avail_out))
+                return false;
+            flateData.next_out = outputBuffer;
+            flateData.avail_out = kBufferSize;
+        }
+    }
+
+    if (compress)
+        deflateEnd(&flateData);
+    else
+        inflateEnd(&flateData);
+    if (rc == Z_STREAM_END)
+        return true;
+    return false;
+}
+
+}
+
+// static
+bool SkFlate::Deflate(SkStream* src, SkDynamicMemoryWStream* dst) {
+    return doFlate(true, kBufferSize, src, dst);
+}
+
+// static
+bool SkFlate::Inflate(SkStream* src, SkDynamicMemoryWStream* dst) {
+    return doFlate(false, kBufferSize, src, dst);
+}
+
+#endif
+
diff --git a/src/core/core_files.mk b/src/core/core_files.mk
index e84365a..af7b876 100644
--- a/src/core/core_files.mk
+++ b/src/core/core_files.mk
@@ -37,6 +37,7 @@
     SkEdgeBuilder.cpp \
     SkEdgeClipper.cpp \
     SkFilterProc.cpp \
+    SkFlate.cpp \
     SkFlattenable.cpp \
     SkFloat.cpp \
     SkFloatBits.cpp \
diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp
index 8be4f11..6947ae4 100644
--- a/src/pdf/SkPDFStream.cpp
+++ b/src/pdf/SkPDFStream.cpp
@@ -14,12 +14,25 @@
  * limitations under the License.
  */
 
+#include "SkFlate.h"
 #include "SkPDFCatalog.h"
 #include "SkPDFStream.h"
 #include "SkStream.h"
 
-SkPDFStream::SkPDFStream(SkStream* stream) : fData(stream) {
-    SkRefPtr<SkPDFInt> lenValue = new SkPDFInt(fData->read(NULL, 0));
+SkPDFStream::SkPDFStream(SkStream* stream) {
+    if (SkFlate::HaveFlate()) {
+        SkAssertResult(SkFlate::Deflate(stream, &fCompressedData));
+        fLength = fCompressedData.getOffset();
+
+        SkRefPtr<SkPDFName> flateFilter = new SkPDFName("FlateDecode");
+        flateFilter->unref();  // SkRefPtr and new both took a reference.
+        fDict.insert("Filter", flateFilter.get());
+    } else {
+        fPlainData = stream;
+        fLength = fPlainData->getLength();
+    }
+
+    SkRefPtr<SkPDFInt> lenValue = new SkPDFInt(fLength);
     lenValue->unref();  // SkRefPtr and new both took a reference.
     fDict.insert("Length", lenValue.get());
 }
@@ -34,7 +47,10 @@
 
     fDict.emitObject(stream, catalog, false);
     stream->writeText(" stream\n");
-    stream->write(fData->getMemoryBase(), fData->read(NULL, 0));
+    if (fPlainData.get())
+        stream->write(fPlainData->getMemoryBase(), fLength);
+    else
+        stream->write(fCompressedData.getStream(), fLength);
     stream->writeText("\nendstream");
 }
 
@@ -43,7 +59,7 @@
         return getIndirectOutputSize(catalog);
 
     return fDict.getOutputSize(catalog, false) +
-        strlen(" stream\n\nendstream") + fData->read(NULL, 0);
+        strlen(" stream\n\nendstream") + fLength;
 }
 
 void SkPDFStream::insert(SkPDFName* key, SkPDFObject* value) {