Initial PDF backend commit: directories, build rules, primitives

This change establishes and tests the building blocks of the PDF file format.
For now, PDF code is not compiled by default.

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

git-svn-id: http://skia.googlecode.com/svn/trunk@600 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/Makefile b/Makefile
index 970f0b9..65f0b71 100644
--- a/Makefile
+++ b/Makefile
@@ -48,6 +48,13 @@
 include src/utils/utils_files.mk
 SRC_LIST += $(addprefix src/utils/, $(SOURCE))
 
+# pdf backend files
+ifeq ($(SKIA_PDF_SUPPORT),true)
+	C_INCLUDES += -Iinclude/pdf
+	include src/pdf/pdf_files.mk
+	SRC_LIST += $(addprefix src/pdf/, $(SOURCE))
+endif
+
 # extra files we want to build to prevent bit-rot, but not link
 JUST_COMPILE_LIST := src/ports/SkFontHost_tables.cpp
 
@@ -137,6 +144,9 @@
 C_INCLUDES += -Isrc/core
 
 include tests/tests_files.mk
+ifeq ($(SKIA_PDF_SUPPORT),true)
+  SOURCE += PDFPrimitivesTest.cpp
+endif
 TESTS_SRCS := $(addprefix tests/, $(SOURCE))
 
 TESTS_OBJS := $(TESTS_SRCS:.cpp=.o)
@@ -212,4 +222,5 @@
 	@echo "    SKIA_DEBUG=true for debug build"
 	@echo "    SKIA_SCALAR=fixed for fixed-point build"
 	@echo "    SKIA_BUILD_FOR=mac for mac build (e.g. CG for image decoding)"
+	@echo "    SKIA_PDF_SUPPORT=true to enable the pdf generation backend"
 	@echo ""
diff --git a/include/pdf/SkPDFCatalog.h b/include/pdf/SkPDFCatalog.h
new file mode 100644
index 0000000..fa15938
--- /dev/null
+++ b/include/pdf/SkPDFCatalog.h
@@ -0,0 +1,84 @@
+/*
+ * 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 SkPDFCatalog_DEFINED
+#define SkPDFCatalog_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+
+/** \class SkPDFCatalog
+
+    The PDF catalog object manages object numbers and when emitted to the
+    PDF stream, indexes all the objects in the file by offset.
+*/
+class SkPDFCatalog {
+public:
+    /** Create a PDF catalog.
+     */
+    SkPDFCatalog()
+        : fNextObjNum(1),
+          fStartedAssigningObjNums(false),
+          fAssigningFirstPageObjNums(false) {
+    }
+    virtual ~SkPDFCatalog() {}
+
+    /** Add the passed object to the catalog.
+     *  @param obj        The object to add.
+     *  @param onFirstPage  Is the object on the first page.
+     */
+    void addObject(SkPDFObject* obj, bool onFirstPage);
+
+    /** Output the object number for the passed object.
+     *  @param obj        The object of interest.
+     *  @param stream     The writable output stream to send the output to.
+     */
+    void emitObjectNumber(SkWStream* stream, SkPDFObject* obj);
+
+    /** Return the number of bytes that would be emitted for the passed
+     *  object's object number.
+     *  @param obj        The object of interest
+     */
+    size_t getObjectNumberSize(SkPDFObject* obj);
+
+private:
+    struct Rec {
+        Rec(SkPDFObject* object, bool onFirstPage)
+            : fObject(object),
+              fFileOffset(0),
+              fObjNumAssigned(false),
+              fOnFirstPage(onFirstPage) {
+        }
+        SkPDFObject* fObject;
+        off_t fFileOffset;
+        bool fObjNumAssigned;
+        bool fOnFirstPage;
+    };
+
+    // TODO(vandebo) Make this a hash if it's a performance problem.
+    SkTDArray<struct Rec> fCatalog;
+
+    uint32_t fNextObjNum;
+    bool fStartedAssigningObjNums;
+    bool fAssigningFirstPageObjNums;
+
+    int findObjectIndex(SkPDFObject* obj) const;
+
+    int assignObjNum(SkPDFObject* obj);
+};
+
+#endif
diff --git a/include/pdf/SkPDFStream.h b/include/pdf/SkPDFStream.h
new file mode 100644
index 0000000..b83aa59
--- /dev/null
+++ b/include/pdf/SkPDFStream.h
@@ -0,0 +1,57 @@
+/*
+ * 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 SkPDFStream_DEFINED
+#define SkPDFStream_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+#include "SkTemplates.h"
+
+class SkStream;
+class SkPDFCatalog;
+
+/** \class SkPDFStream
+
+    A stream object in a PDF.
+*/
+// TODO(vandebo) This should handle filters as well.
+class SkPDFStream : public SkPDFObject {
+public:
+    /** Create a PDF stream. A Length entry is automatically added to the
+     *  stream dictionary.
+     *  @param stream The data part of the stream.
+     */
+    explicit SkPDFStream(SkStream* stream);
+    virtual ~SkPDFStream();
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+    /** Add the value to the stream dictionary with the given key.
+     *  @param index The index into the array to set.
+     *  @param value The value to add to the array.
+     */
+    void insert(SkPDFName* key, SkPDFObject* value);
+
+private:
+    SkPDFDict fDict;
+    SkRefPtr<SkStream> fData;
+};
+
+#endif
diff --git a/include/pdf/SkPDFTypes.h b/include/pdf/SkPDFTypes.h
new file mode 100644
index 0000000..247bd37
--- /dev/null
+++ b/include/pdf/SkPDFTypes.h
@@ -0,0 +1,266 @@
+/*
+ * 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 SkPDFTypes_DEFINED
+#define SkPDFTypes_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkScalar.h"
+#include "SkString.h"
+#include "SkTDArray.h"
+
+class SkPDFCatalog;
+class SkWStream;
+
+/** \class SkPDFObject
+
+    A PDF Object is the base class for primitive elements in a PDF file.  A
+    common subtype is used to ease the use of indirect object references,
+    which are common in the PDF format.
+*/
+class SkPDFObject : public SkRefCnt {
+public:
+    /** Create a PDF object.
+     */
+    SkPDFObject() {}
+    virtual ~SkPDFObject() {}
+
+    /** Subclasses must implement this method to print the object to the
+     *  PDF file.
+     *  @param catalog  The object catalog to use.
+     *  @param indirect If true, output an object identifier with the object.
+     *  @param stream   The writable output stream to send the output to.
+     */
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect) = 0;
+
+    /** Return the size (number of bytes) of this object in the final output
+     *  file. Compound objects or objects that are computationally intensive
+     *  to output should override this method.
+     *  @param catalog  The object catalog to use.
+     *  @param indirect If true, output an object identifier with the object.
+     */
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+    /** Helper function to output an indirect object.
+     *  @param catalog The object catalog to use.
+     *  @param stream  The writable output stream to send the output to.
+     */
+    void emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog);
+
+    /** Helper function to find the size of an indirect object.
+     *  @param catalog The object catalog to use.
+     */
+    size_t getIndirectOutputSize(SkPDFCatalog* catalog);
+};
+
+/** \class SkPDFObjRef
+
+    An indirect reference to a PDF object.
+*/
+class SkPDFObjRef : public SkPDFObject {
+public:
+    /** Create a reference to an existing SkPDFObject.
+     *  @param obj The object to reference.
+     */
+    explicit SkPDFObjRef(SkPDFObject* obj) : fObj(obj) {}
+    virtual ~SkPDFObjRef() {}
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+    SkRefPtr<SkPDFObject> fObj;
+};
+
+/** \class SkPDFInt
+
+    An integer object in a PDF.
+*/
+class SkPDFInt : public SkPDFObject {
+public:
+    /** Create a PDF integer (usually for indirect reference purposes).
+     *  @param value An integer value between 2^31 - 1 and -2^31.
+     */
+    SkPDFInt(int32_t value) : fValue(value) {}
+    virtual ~SkPDFInt() {}
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+
+private:
+    int32_t fValue;
+};
+
+/** \class SkPDFScalar
+
+    A real number object in a PDF.
+*/
+class SkPDFScalar : public SkPDFObject {
+public:
+    /** Create a PDF real number.
+     *  @param value A real value.
+     */
+    SkPDFScalar(SkScalar value) : fValue(value) {}
+    virtual ~SkPDFScalar() {}
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+
+private:
+    SkScalar fValue;
+};
+
+/** \class SkPDFString
+
+    A string object in a PDF.
+*/
+class SkPDFString : public SkPDFObject {
+public:
+    /** Create a PDF string. Maximum length (in bytes) is 65,535.
+     *  @param value A string value.
+     */
+    SkPDFString(const char value[]);
+    SkPDFString(const SkString& value);
+    virtual ~SkPDFString() {}
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+    static const int kMaxLen = 65535;
+
+    const SkString fValue;
+
+    SkString formatString(const SkString& input);
+};
+
+/** \class SkPDFName
+
+    A name object in a PDF.
+*/
+class SkPDFName : public SkPDFObject {
+public:
+    /** Create a PDF name object. Maximum length is 127 bytes.
+     *  @param value The name.
+     */
+    SkPDFName(const char name[]);
+    SkPDFName(const SkString& name);
+    virtual ~SkPDFName() {}
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+private:
+    static const int kMaxLen = 127;
+
+    const SkString fValue;
+
+    SkString formatName(const SkString& input);
+};
+
+/** \class SkPDFArray
+
+    An array object in a PDF.
+*/
+class SkPDFArray : public SkPDFObject {
+public:
+    /** Create a PDF array. Maximum length is 8191.
+     */
+    SkPDFArray() {}
+    virtual ~SkPDFArray();
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+    /** The size of the array.
+     */
+    int size() { return fValue.count(); }
+
+    /** Preallocate space for the given number of entries.
+     *  @param length The number of array slots to preallocate.
+     */
+    void reserve(int length);
+
+    /** Returns the object at the given offset in the array.
+     *  @param index The index into the array to retrieve.
+     */
+    SkPDFObject* getAt(int index) { return fValue[index]; }
+
+    /** Set the object at the given offset in the array.
+     *  @param index The index into the array to set.
+     *  @param value The value to add to the array.
+     */
+    void setAt(int index, SkPDFObject* value);
+
+    /** Append the object to the end of the array.
+     *  @param value The value to add to the array.
+     */
+    void append(SkPDFObject* value);
+
+private:
+    static const int kMaxLen = 8191;
+    SkTDArray<SkPDFObject*> fValue;
+};
+
+/** \class SkPDFDict
+
+    A dictionary object in a PDF.
+*/
+class SkPDFDict : public SkPDFObject {
+public:
+    /** Create a PDF dictionary. Maximum number of entries is 4095.
+     */
+    SkPDFDict() {}
+    virtual ~SkPDFDict();
+
+    // The SkPDFObject interface.
+    virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect);
+    virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
+
+    /** The size of the dictionary.
+     */
+    int size() { return fValue.count(); }
+
+    /** Add the value to the dictionary with the given key.
+     *  @param key   The key for this dictionary entry.
+     *  @param value The value for this dictionary entry.
+     */
+    void insert(SkPDFName* key, SkPDFObject* value);
+
+private:
+    static const int kMaxLen = 4095;
+
+    struct Rec {
+      SkPDFName* key;
+      SkPDFObject* value;
+    };
+
+    SkTDArray<struct Rec> fValue;
+};
+
+#endif
diff --git a/src/pdf/SkPDFCatalog.cpp b/src/pdf/SkPDFCatalog.cpp
new file mode 100644
index 0000000..dd3cd7e
--- /dev/null
+++ b/src/pdf/SkPDFCatalog.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 "SkPDFCatalog.h"
+#include "SkPDFTypes.h"
+#include "SkStream.h"
+
+void SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) {
+    SkASSERT(findObjectIndex(obj) == -1);
+    SkASSERT(!fStartedAssigningObjNums);
+
+    struct Rec newEntry(obj, onFirstPage);
+    fCatalog.append(1, &newEntry);
+}
+
+void SkPDFCatalog::emitObjectNumber(SkWStream* stream, SkPDFObject* obj) {
+    stream->writeDecAsText(assignObjNum(obj));
+    stream->writeText(" 0");  // Generation number is always 0.
+}
+
+size_t SkPDFCatalog::getObjectNumberSize(SkPDFObject* obj) {
+    SkDynamicMemoryWStream buffer;
+    emitObjectNumber(&buffer, obj);
+    return buffer.getOffset();
+}
+
+int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const {
+    for (int i = 0; i < fCatalog.count(); i++) {
+        if (fCatalog[i].fObject == obj)
+            return i;
+    }
+    return -1;
+}
+
+int SkPDFCatalog::assignObjNum(SkPDFObject* obj) {
+    int pos = findObjectIndex(obj);
+    SkASSERT(pos >= 0);
+    uint32_t currentIndex = pos;
+    if (fCatalog[currentIndex].fObjNumAssigned)
+        return currentIndex + 1;
+
+    fStartedAssigningObjNums = true;
+    if (fCatalog[currentIndex].fOnFirstPage) {
+        fAssigningFirstPageObjNums = true;
+    } else {
+        SkASSERT(!fAssigningFirstPageObjNums);
+    }
+
+    // When we assign an object an object number, we put it in that array
+    // offset (minus 1 because object number 0 is reserved).
+    if (fNextObjNum - 1 != currentIndex) {
+        Rec other = fCatalog[fNextObjNum - 1];
+        fCatalog[fNextObjNum - 1] = fCatalog[currentIndex];
+        fCatalog[currentIndex] = other;
+    }
+    fCatalog[fNextObjNum - 1].fObjNumAssigned = true;
+    fNextObjNum++;
+    return fNextObjNum - 1;
+}
diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp
new file mode 100644
index 0000000..a7fbc62
--- /dev/null
+++ b/src/pdf/SkPDFStream.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 "SkPDFCatalog.h"
+#include "SkPDFStream.h"
+#include "SkStream.h"
+
+SkPDFStream::SkPDFStream(SkStream* stream) : fData(stream) {
+    SkRefPtr<SkPDFName> lenKey = new SkPDFName("Length");
+    lenKey->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> lenValue = new SkPDFInt(fData->read(NULL, 0));
+    lenValue->unref();  // SkRefPtr and new both took a reference.
+    fDict.insert(lenKey.get(), lenValue.get());
+}
+
+SkPDFStream::~SkPDFStream() {
+}
+
+void SkPDFStream::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+
+    fDict.emitObject(stream, catalog, false);
+    stream->writeText(" stream\n");
+    stream->write(fData->getMemoryBase(), fData->read(NULL, 0));
+    stream->writeText("endstream\n");
+}
+
+size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+
+    return fDict.getOutputSize(catalog, false) +
+        strlen(" stream\nendstream\n") + fData->read(NULL, 0);
+}
+
+void SkPDFStream::insert(SkPDFName* key, SkPDFObject* value) {
+    fDict.insert(key, value);
+}
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
new file mode 100644
index 0000000..83f133e
--- /dev/null
+++ b/src/pdf/SkPDFTypes.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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 "SkPDFCatalog.h"
+#include "SkPDFTypes.h"
+#include "SkStream.h"
+
+size_t SkPDFObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    SkDynamicMemoryWStream buffer;
+    emitObject(&buffer, catalog, indirect);
+    return buffer.getOffset();
+}
+
+void SkPDFObject::emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog) {
+    catalog->emitObjectNumber(stream, this);
+    stream->writeText(" obj\n");
+    emitObject(stream, catalog, false);
+    stream->writeText("\nendobj\n");
+}
+
+size_t SkPDFObject::getIndirectOutputSize(SkPDFCatalog* catalog) {
+    return catalog->getObjectNumberSize(this) + strlen(" obj\n") +
+        this->getOutputSize(catalog, false) + strlen("\nendobj\n");
+}
+
+void SkPDFObjRef::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    SkASSERT(!indirect);
+    catalog->emitObjectNumber(stream, fObj.get());
+    stream->writeText(" R");
+}
+
+size_t SkPDFObjRef::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    SkASSERT(!indirect);
+    return catalog->getObjectNumberSize(fObj.get()) + strlen(" R");
+}
+
+void SkPDFInt::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                          bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+    stream->writeDecAsText(fValue);
+}
+
+void SkPDFScalar::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+    stream->writeScalarAsText(fValue);
+}
+
+SkPDFString::SkPDFString(const char value[])
+    : fValue(formatString(SkString(value))) {
+}
+
+SkPDFString::SkPDFString(const SkString& value)
+    : fValue(formatString(value)) {
+}
+
+void SkPDFString::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                             bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+    stream->write(fValue.c_str(), fValue.size());
+}
+
+size_t SkPDFString::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+    return fValue.size();
+}
+
+SkString SkPDFString::formatString(const SkString& input) {
+    SkASSERT(input.size() <= kMaxLen);
+
+    // 7-bit clean is a heuristic to decide what string format to use;
+    // a 7-bit clean string should require little escaping.
+    bool sevenBitClean = true;
+    for (size_t i = 0; i < input.size(); i++) {
+        if (input[i] > 0x7F || input[i] < ' ') {
+            sevenBitClean = false;
+            break;
+        }
+    }
+
+    SkString result;
+    if (sevenBitClean) {
+        result.append("(");
+        for (size_t i = 0; i < input.size(); i++) {
+            if (input[i] == '\\' || input[i] == '(' || input[i] == ')')
+                result.append("\\");
+            result.append(input.c_str() + i, 1);
+        }
+        result.append(")");
+    } else {
+        result.append("<");
+        for (size_t i = 0; i < input.size(); i++)
+            result.appendHex(input[i], 2);
+        result.append(">");
+    }
+
+    return result;
+}
+
+SkPDFName::SkPDFName(const char name[]) : fValue(formatName(SkString(name))) {}
+SkPDFName::SkPDFName(const SkString& name) : fValue(formatName(name)) {}
+
+void SkPDFName::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                           bool indirect) {
+    SkASSERT(!indirect);
+    stream->write(fValue.c_str(), fValue.size());
+}
+
+size_t SkPDFName::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    SkASSERT(!indirect);
+    return fValue.size();
+}
+
+SkString SkPDFName::formatName(const SkString& input) {
+    SkASSERT(input.size() <= kMaxLen);
+
+    SkString result("/");
+    for (size_t i = 0; i < input.size(); i++) {
+        if (input[i] > 0x7F || input[i] < '!' || input[i] == '#') {
+            result.append("#");
+            result.appendHex(input[i], 2);
+        } else {
+            result.append(input.c_str() + i, 1);
+        }
+    }
+
+    return result;
+}
+
+SkPDFArray::~SkPDFArray() {
+    fValue.safeUnrefAll();
+}
+
+void SkPDFArray::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                            bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+
+    stream->writeText("[");
+    for (int i = 0; i < fValue.count(); i++) {
+        fValue[i]->emitObject(stream, catalog, false);
+        if (i + 1 < fValue.count())
+            stream->writeText(" ");
+    }
+    stream->writeText("]");
+}
+
+size_t SkPDFArray::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+
+    size_t result = strlen("[]");
+    if (fValue.count())
+        result += fValue.count() - 1;
+    for (int i = 0; i < fValue.count(); i++)
+        result += fValue[i]->getOutputSize(catalog, false);
+    return result;
+}
+
+void SkPDFArray::reserve(int length) {
+    SkASSERT(length <= kMaxLen);
+    fValue.setReserve(length);
+}
+
+void SkPDFArray::setAt(int offset, SkPDFObject* value) {
+    SkASSERT(offset < fValue.count());
+    SkSafeUnref(fValue[offset]);
+    fValue[offset] = value;
+    SkSafeRef(fValue[offset]);
+}
+
+void SkPDFArray::append(SkPDFObject* value) {
+    SkASSERT(fValue.count() < kMaxLen);
+    SkSafeRef(value);
+    fValue.push(value);
+}
+
+SkPDFDict::~SkPDFDict() {
+    for (int i = 0; i < fValue.count(); i++) {
+        SkSafeUnref(fValue[i].key);
+        SkSafeUnref(fValue[i].value);
+    }
+}
+
+void SkPDFDict::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
+                           bool indirect) {
+    if (indirect)
+        return emitIndirectObject(stream, catalog);
+
+    stream->writeText("<<");
+    for (int i = 0; i < fValue.count(); i++) {
+        fValue[i].key->emitObject(stream, catalog, false);
+        stream->writeText(" ");
+        fValue[i].value->emitObject(stream, catalog, false);
+        stream->writeText("\n");
+    }
+    stream->writeText(">>");
+}
+
+size_t SkPDFDict::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
+    if (indirect)
+        return getIndirectOutputSize(catalog);
+
+    size_t result = strlen("<<>>") + (fValue.count() * 2);
+    for (int i = 0; i < fValue.count(); i++) {
+        result += fValue[i].key->getOutputSize(catalog, false);
+        result += fValue[i].value->getOutputSize(catalog, false);
+    }
+    return result;
+}
+
+void SkPDFDict::insert(SkPDFName* key, SkPDFObject* value) {
+    struct Rec* newEntry = fValue.append();
+    newEntry->key = key;
+    SkSafeRef(newEntry->key);
+    newEntry->value = value;
+    SkSafeRef(newEntry->value);
+}
diff --git a/src/pdf/pdf_files.mk b/src/pdf/pdf_files.mk
new file mode 100644
index 0000000..5e41eea
--- /dev/null
+++ b/src/pdf/pdf_files.mk
@@ -0,0 +1,5 @@
+SOURCE := \
+    SkPDFCatalog.cpp \
+    SkPDFStream.cpp \
+    SkPDFTypes.cpp \
+
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
new file mode 100644
index 0000000..81d8c0a
--- /dev/null
+++ b/tests/PDFPrimitivesTest.cpp
@@ -0,0 +1,176 @@
+#include <string>
+
+#include "Test.h"
+#include "SkPDFCatalog.h"
+#include "SkPDFStream.h"
+#include "SkPDFTypes.h"
+#include "SkScalar.h"
+#include "SkStream.h"
+
+static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
+                              const std::string& representation,
+                              bool indirect) {
+    size_t directSize = obj->getOutputSize(NULL, false);
+    REPORTER_ASSERT(reporter, directSize == representation.size());
+
+    SkDynamicMemoryWStream buffer;
+    obj->emitObject(&buffer, NULL, false);
+    REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
+    REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), representation.c_str(),
+                                     directSize) == 0);
+
+    if (indirect) {
+        // Indirect output.
+        static char header[] = "1 0 obj\n";
+        static size_t headerLen = strlen(header);
+        static char footer[] = "\nendobj\n";
+        static size_t footerLen = strlen(footer);
+
+        SkPDFCatalog catalog;
+        catalog.addObject(obj, false);
+
+        size_t indirectSize = obj->getOutputSize(&catalog, true);
+        REPORTER_ASSERT(reporter,
+                        indirectSize == directSize + headerLen + footerLen);
+
+        buffer.reset();
+        obj->emitObject(&buffer, &catalog, true);
+        REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset());
+        REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), header,
+                                         headerLen) == 0);
+        REPORTER_ASSERT(reporter,
+                        memcmp(buffer.getStream() + headerLen,
+                               representation.c_str(), directSize) == 0);
+        REPORTER_ASSERT(reporter,
+                        memcmp(buffer.getStream() + headerLen + directSize,
+                               footer, footerLen) == 0);
+    }
+}
+
+static void TestCatalog(skiatest::Reporter* reporter) {
+    SkPDFCatalog catalog;
+    SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
+    int1->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> int2 = new SkPDFInt(2);
+    int2->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> int3 = new SkPDFInt(3);
+    int3->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> int1Again(int1.get());
+
+    catalog.addObject(int1.get(), false);
+    catalog.addObject(int2.get(), false);
+    catalog.addObject(int3.get(), false);
+
+    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
+    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
+    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int3.get()) == 3);
+
+    SkDynamicMemoryWStream buffer;
+    catalog.emitObjectNumber(&buffer, int1.get());
+    catalog.emitObjectNumber(&buffer, int2.get());
+    catalog.emitObjectNumber(&buffer, int3.get());
+    catalog.emitObjectNumber(&buffer, int1Again.get());
+    char expectedResult[] = "1 02 03 01 0";
+    REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), expectedResult,
+                                     strlen(expectedResult)) == 0);
+}
+
+static void TestObjectRef(skiatest::Reporter* reporter) {
+    SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
+    int1->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFInt> int2 = new SkPDFInt(2);
+    int2->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFObjRef> int2ref = new SkPDFObjRef(int2.get());
+    int2ref->unref();  // SkRefPtr and new both took a reference.
+
+    SkPDFCatalog catalog;
+    catalog.addObject(int1.get(), false);
+    catalog.addObject(int2.get(), false);
+    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
+    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
+
+    char expectedResult[] = "2 0 R";
+    SkDynamicMemoryWStream buffer;
+    int2ref->emitObject(&buffer, &catalog, false);
+    REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
+    REPORTER_ASSERT(reporter, memcmp(buffer.getStream(), expectedResult,
+                                     buffer.getOffset()) == 0);
+}
+
+static void TestPDFPrimitives(skiatest::Reporter* reporter) {
+    SkRefPtr<SkPDFInt> int42 = new SkPDFInt(42);
+    int42->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, int42.get(), "42", true);
+
+    SkRefPtr<SkPDFScalar> realHalf = new SkPDFScalar(SK_ScalarHalf);
+    realHalf->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, realHalf.get(), "0.5", true);
+
+    SkRefPtr<SkPDFString> stringSimple = new SkPDFString("test ) string ( foo");
+    stringSimple->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, stringSimple.get(), "(test \\) string \\( foo)",
+                      true);
+    SkRefPtr<SkPDFString> stringComplex =
+        new SkPDFString("\ttest ) string ( foo");
+    stringComplex->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, stringComplex.get(),
+                      "<0974657374202920737472696E67202820666F6F>", true);
+
+    SkRefPtr<SkPDFName> name = new SkPDFName("Test name\twith#tab");
+    name->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, name.get(), "/Test#20name#09with#23tab", false);
+
+    SkRefPtr<SkPDFArray> array = new SkPDFArray;
+    array->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, array.get(), "[]", true);
+    array->append(int42.get());
+    CheckObjectOutput(reporter, array.get(), "[42]", true);
+    array->append(realHalf.get());
+    CheckObjectOutput(reporter, array.get(), "[42 0.5]", true);
+    SkRefPtr<SkPDFInt> int0 = new SkPDFInt(0);
+    int0->unref();  // SkRefPtr and new both took a reference.
+    array->append(int0.get());
+    CheckObjectOutput(reporter, array.get(), "[42 0.5 0]", true);
+    SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
+    int1->unref();  // SkRefPtr and new both took a reference.
+    array->setAt(0, int1.get());
+    CheckObjectOutput(reporter, array.get(), "[1 0.5 0]", true);
+
+    SkRefPtr<SkPDFDict> dict = new SkPDFDict;
+    dict->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, dict.get(), "<<>>", true);
+    SkRefPtr<SkPDFName> n1 = new SkPDFName("n1");
+    n1->unref();  // SkRefPtr and new both took a reference.
+    dict->insert(n1.get(), int42.get());
+    CheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>", true);
+    SkRefPtr<SkPDFName> n2 = new SkPDFName("n2");
+    n2->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFName> n3 = new SkPDFName("n3");
+    n3->unref();  // SkRefPtr and new both took a reference.
+    dict->insert(n2.get(), realHalf.get());
+    dict->insert(n3.get(), array.get());
+    CheckObjectOutput(reporter, dict.get(),
+                      "<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>", true);
+
+    char streamBytes[] = "Test\nFoo\tBar";
+    SkRefPtr<SkMemoryStream> streamData = new SkMemoryStream(
+        streamBytes, strlen(streamBytes), true);
+    streamData->unref();  // SkRefPtr and new both took a reference.
+    SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData.get());
+    stream->unref();  // SkRefPtr and new both took a reference.
+    CheckObjectOutput(reporter, stream.get(),
+                      "<</Length 12\n>> stream\nTest\nFoo\tBarendstream\n",
+                      true);
+    stream->insert(n1.get(), int42.get());
+    CheckObjectOutput(reporter, stream.get(),
+                      "<</Length 12\n/n1 42\n>> stream\nTest\nFoo\tBar"
+                      "endstream\n",
+                      true);
+
+    TestCatalog(reporter);
+
+    TestObjectRef(reporter);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PDFPrimitives", PDFPrimitivesTestClass, TestPDFPrimitives)