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)