High level pdf classes and pdf specific interface.
The guts of the implementation will be in SkPDFDevice and below. This is a first implementation of everything above that point.
Review URL: http://codereview.appspot.com/2342043
git-svn-id: http://skia.googlecode.com/svn/trunk@602 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkStream.h b/include/core/SkStream.h
index 3a82991..487c7d6 100644
--- a/include/core/SkStream.h
+++ b/include/core/SkStream.h
@@ -91,6 +91,7 @@
bool writeText(const char text[]);
bool writeDecAsText(int32_t);
+ bool writeBigDecAsText(int64_t, int minDigits = 0);
bool writeHexAsText(uint32_t, int minDigits = 0);
bool writeScalarAsText(SkScalar);
@@ -308,4 +309,3 @@
typedef SkFILEStream SkURLStream;
#endif
-
diff --git a/include/core/SkString.h b/include/core/SkString.h
index 5ecfb1e..4498bba 100644
--- a/include/core/SkString.h
+++ b/include/core/SkString.h
@@ -28,6 +28,8 @@
#define SkStrAppendS32_MaxSize 11
char* SkStrAppendS32(char buffer[], int32_t);
+#define SkStrAppendS64_MaxSize 20
+char* SkStrAppendS64(char buffer[], int64_t, int minDigits);
#define SkStrAppendScalar_MaxSize 11
char* SkStrAppendScalar(char buffer[], SkScalar);
@@ -93,6 +95,7 @@
void insert(size_t offset, const char text[], size_t len);
void insertUnichar(size_t offset, SkUnichar);
void insertS32(size_t offset, int32_t value);
+ void insertS64(size_t offset, int64_t value, int minDigits = 0);
void insertHex(size_t offset, uint32_t value, int minDigits = 0);
void insertScalar(size_t offset, SkScalar);
@@ -101,6 +104,7 @@
void append(const char text[], size_t len) { this->insert((size_t)-1, text, len); }
void appendUnichar(SkUnichar uni) { this->insertUnichar((size_t)-1, uni); }
void appendS32(int32_t value) { this->insertS32((size_t)-1, value); }
+ void appendS64(int64_t value, int minDigits = 0) { this->insertS64((size_t)-1, value, minDigits); }
void appendHex(uint32_t value, int minDigits = 0) { this->insertHex((size_t)-1, value, minDigits); }
void appendScalar(SkScalar value) { this->insertScalar((size_t)-1, value); }
@@ -109,6 +113,7 @@
void prepend(const char text[], size_t len) { this->insert(0, text, len); }
void prependUnichar(SkUnichar uni) { this->insertUnichar(0, uni); }
void prependS32(int32_t value) { this->insertS32(0, value); }
+ void prependS64(int32_t value, int minDigits = 0) { this->insertS64(0, value, minDigits); }
void prependHex(uint32_t value, int minDigits = 0) { this->insertHex(0, value, minDigits); }
void prependScalar(SkScalar value) { this->insertScalar((size_t)-1, value); }
@@ -165,4 +170,3 @@
};
#endif
-
diff --git a/include/pdf/SkPDFCatalog.h b/include/pdf/SkPDFCatalog.h
index 932f1c8..6e530db 100644
--- a/include/pdf/SkPDFCatalog.h
+++ b/include/pdf/SkPDFCatalog.h
@@ -34,23 +34,39 @@
~SkPDFCatalog();
/** Add the passed object to the catalog.
- * @param obj The object to add.
- * @param onFirstPage Is the object on the first page.
+ * @param obj The object to add.
+ * @param onFirstPage Is the object on the first page.
*/
void addObject(SkPDFObject* obj, bool onFirstPage);
+ /** Inform the catalog of the object's position in the final stream.
+ * The object should already have been added to the catalog. Returns
+ * the object's size.
+ * @param obj The object to add.
+ * @param offset The byte offset in the output stream of this object.
+ */
+ size_t setFileOffset(SkPDFObject* obj, size_t offset);
+
/** 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.
+ * @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
+ * @param obj The object of interest
*/
size_t getObjectNumberSize(SkPDFObject* obj);
+ /** Output the cross reference table for objects in the catalog.
+ * Returns the total number of objects.
+ * @param stream The writable output stream to send the output to.
+ * @param firstPage If true, include first page objects only, otherwise
+ * include all objects not on the first page.
+ */
+ int32_t emitXrefTable(SkWStream* stream, bool firstPage);
+
private:
struct Rec {
Rec(SkPDFObject* object, bool onFirstPage)
@@ -68,9 +84,12 @@
// TODO(vandebo) Make this a hash if it's a performance problem.
SkTDArray<struct Rec> fCatalog;
+ // Number of objects on the first page.
+ uint32_t fFirstPageCount;
+ // Next object number to assign (on page > 1).
uint32_t fNextObjNum;
- bool fStartedAssigningObjNums;
- bool fAssigningFirstPageObjNums;
+ // Next object number to assign on the first page.
+ uint32_t fNextFirstPageObjNum;
int findObjectIndex(SkPDFObject* obj) const;
diff --git a/include/pdf/SkPDFDocument.h b/include/pdf/SkPDFDocument.h
new file mode 100644
index 0000000..91dd379
--- /dev/null
+++ b/include/pdf/SkPDFDocument.h
@@ -0,0 +1,80 @@
+/*
+ * 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 SkPDFDocument_DEFINED
+#define SkPDFDocument_DEFINED
+
+#include "SkPDFCatalog.h"
+#include "SkPDFTypes.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+
+class SkPDFDevice;
+class SkPDFPage;
+class SkWSteam;
+
+/** \class SkPDFDocument
+
+ A SkPDFDocument assembles pages together and generates the final PDF file.
+*/
+class SkPDFDocument {
+public:
+ /** Create a PDF document.
+ */
+ SkPDFDocument();
+ ~SkPDFDocument();
+
+ /** Output the PDF to the passed stream.
+ * @param stream The writable output stream to send the PDF to.
+ */
+ bool emitPDF(SkWStream* stream);
+
+ /** Append the passed pdf device to the document as a new page. Returns
+ * true if successful. Will fail if the document has already been emitted.
+ *
+ * @param pdfDevice The page to add to this document.
+ */
+ bool appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice);
+
+private:
+ SkPDFCatalog fCatalog;
+ int64_t fXRefFileOffset;
+
+ SkTDArray<SkPDFPage*> fPages;
+ SkTDArray<SkPDFDict*> fPageTree;
+ SkRefPtr<SkPDFDict> fDocCatalog;
+ SkTDArray<SkPDFObject*> fPageResources;
+ int fSecondPageFirstResouceIndex;
+
+ SkRefPtr<SkPDFDict> fTrailerDict;
+
+ /** Output the PDF header to the passed stream.
+ * @param stream The writable output stream to send the header to.
+ */
+ void emitHeader(SkWStream* stream);
+
+ /** Get the size of the header.
+ */
+ size_t headerSize();
+
+ /** Output the PDF footer to the passed stream.
+ * @param stream The writable output stream to send the footer to.
+ * @param objCount The number of objects in the PDF.
+ */
+ void emitFooter(SkWStream* stream, int64_t objCount);
+};
+
+#endif
diff --git a/include/pdf/SkPDFPage.h b/include/pdf/SkPDFPage.h
new file mode 100644
index 0000000..7e96212
--- /dev/null
+++ b/include/pdf/SkPDFPage.h
@@ -0,0 +1,98 @@
+/*
+ * 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 SkPDFPage_DEFINED
+#define SkPDFPage_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkPDFStream.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+
+class SkPDFCatalog;
+class SkPDFDevice;
+class SkWStream;
+
+/** \class SkPDFPage
+
+ A SkPDFPage contains meta information about a page, is used in the page
+ tree and points to the content of the page.
+*/
+class SkPDFPage : public SkPDFDict {
+public:
+ /** Create a PDF page with the passed PDF device. The device need not
+ * have content on it yet.
+ * @param content The page content.
+ */
+ explicit SkPDFPage(const SkRefPtr<SkPDFDevice>& content);
+ ~SkPDFPage();
+
+ /** Before a page and its contents can be sized and emitted, it must
+ * be finalized. No changes to the PDFDevice will be honored after
+ * finalizePage has been called. This function adds the page content
+ * to the passed catalog, so it must be called for each document
+ * that the page is part of.
+ * @param catalog The catalog to add page content objects to.
+ * @param firstPage Indicate if this is the first page of a document.
+ * @param resouceObjects The resource objects used on the page are added
+ * to this array. This gives the caller a chance
+ * to deduplicate resources across pages.
+ */
+ void finalizePage(SkPDFCatalog* catalog, bool firstPage,
+ SkTDArray<SkPDFObject*>* resourceObjects);
+
+ /** Determine the size of the page content and store to the catalog
+ * the offsets of all nonresource-indirect objects that make up the page
+ * content. This must be called before emitPage(), but after finalizePage.
+ * @param catalog The catalog to add the object offsets to.
+ * @param fileOffset The file offset where the page content will be
+ * emitted.
+ */
+ off_t getPageSize(SkPDFCatalog* catalog, off_t fileOffset);
+
+ /** Output the page content to the passed stream.
+ * @param stream The writable output stream to send the content to.
+ * @param catalog The active object catalog.
+ */
+ void emitPage(SkWStream* stream, SkPDFCatalog* catalog);
+
+ /** Generate a page tree for the passed vector of pages. New objects are
+ * added to the catalog. The pageTree vector is populated with all of
+ * the 'Pages' dictionaries as well as the 'Page' objects. Page trees
+ * have both parent and children links, creating reference cycles, so
+ * it must be torn down explicitly. The first page is not added to
+ * the pageTree dictionary array so the caller can handle it specially.
+ * @param pages The ordered vector of page objects.
+ * @param catalog The catalog to add new objects into.
+ * @param pageTree An output vector with all of the internal and leaf
+ * nodes of the pageTree.
+ * @param rootNode An output parameter set to the root node.
+ */
+ static void generatePageTree(const SkTDArray<SkPDFPage*>& pages,
+ SkPDFCatalog* catalog,
+ SkTDArray<SkPDFDict*>* pageTree,
+ SkPDFDict** rootNode);
+
+private:
+ // Multiple pages may reference the content.
+ SkRefPtr<SkPDFDevice> fDevice;
+
+ SkString fContent;
+ // Once the content is finalized, put it into a stream for output.
+ SkRefPtr<SkPDFStream> fContentStream;
+};
+
+#endif
diff --git a/include/pdf/SkPDFStream.h b/include/pdf/SkPDFStream.h
index b83aa59..24a9642 100644
--- a/include/pdf/SkPDFStream.h
+++ b/include/pdf/SkPDFStream.h
@@ -44,11 +44,17 @@
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.
+ * @param key The key for this dictionary entry.
+ * @param value The value for this dictionary entry.
*/
void insert(SkPDFName* key, SkPDFObject* value);
+ /** Add the value to the stream dictionary with the given key.
+ * @param key The text of the key for this dictionary entry.
+ * @param value The value for this dictionary entry.
+ */
+ void insert(const char key[], SkPDFObject* value);
+
private:
SkPDFDict fDict;
SkRefPtr<SkStream> fData;
diff --git a/include/pdf/SkPDFTypes.h b/include/pdf/SkPDFTypes.h
index 9324595..e0b1041 100644
--- a/include/pdf/SkPDFTypes.h
+++ b/include/pdf/SkPDFTypes.h
@@ -147,7 +147,7 @@
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
private:
- static const uint32_t kMaxLen = 65535;
+ static const size_t kMaxLen = 65535;
const SkString fValue;
@@ -173,7 +173,7 @@
virtual size_t getOutputSize(SkPDFCatalog* catalog, bool indirect);
private:
- static const uint32_t kMaxLen = 127;
+ static const size_t kMaxLen = 127;
const SkString fValue;
@@ -235,6 +235,12 @@
/** Create a PDF dictionary. Maximum number of entries is 4095.
*/
SkPDFDict();
+
+ /** Create a PDF dictionary with a Type entry.
+ * @param type The value of the Type entry.
+ */
+ explicit SkPDFDict(const char type[]);
+
virtual ~SkPDFDict();
// The SkPDFObject interface.
@@ -252,6 +258,17 @@
*/
void insert(SkPDFName* key, SkPDFObject* value);
+ /** Add the value to the dictionary with the given key. The method will
+ * create the SkPDFName object.
+ * @param key The text of the key for this dictionary entry.
+ * @param value The value for this dictionary entry.
+ */
+ void insert(const char key[], SkPDFObject* value);
+
+ /** Remove all entries from the dictionary.
+ */
+ void clear();
+
private:
static const int kMaxLen = 4095;
diff --git a/src/core/SkStream.cpp b/src/core/SkStream.cpp
index 6187f81..cbe3eb4 100644
--- a/src/core/SkStream.cpp
+++ b/src/core/SkStream.cpp
@@ -123,6 +123,13 @@
return this->write(tmp.c_str(), tmp.size());
}
+bool SkWStream::writeBigDecAsText(int64_t dec, int minDigits)
+{
+ SkString tmp;
+ tmp.appendS64(dec, minDigits);
+ return this->write(tmp.c_str(), tmp.size());
+}
+
bool SkWStream::writeHexAsText(uint32_t hex, int digits)
{
SkString tmp;
@@ -725,4 +732,3 @@
#endif
return true;
}
-
diff --git a/src/core/SkString.cpp b/src/core/SkString.cpp
index d595d51..cdce160 100644
--- a/src/core/SkString.cpp
+++ b/src/core/SkString.cpp
@@ -79,6 +79,39 @@
return string;
}
+char* SkStrAppendS64(char string[], int64_t dec, int minDigits)
+{
+ SkDEBUGCODE(char* start = string;)
+
+ char buffer[SkStrAppendS64_MaxSize];
+ char* p = buffer + sizeof(buffer);
+ bool neg = false;
+
+ if (dec < 0) {
+ neg = true;
+ dec = -dec;
+ }
+ do {
+ *--p = SkToU8('0' + dec % 10);
+ dec /= 10;
+ minDigits--;
+ } while (dec != 0);
+ while (minDigits > 0) {
+ *--p = '0';
+ minDigits--;
+ }
+ if (neg)
+ *--p = '-';
+
+ SkASSERT(p >= buffer);
+ size_t cp_len = buffer + sizeof(buffer) - p;
+ memcpy(string, p, cp_len);
+ string += cp_len;
+
+ SkASSERT(string - start <= SkStrAppendS64_MaxSize);
+ return string;
+}
+
char* SkStrAppendScalar(char string[], SkScalar value)
{
SkDEBUGCODE(char* start = string;)
@@ -440,6 +473,13 @@
this->insert(offset, buffer, stop - buffer);
}
+void SkString::insertS64(size_t offset, int64_t dec, int minDigits)
+{
+ char buffer[SkStrAppendS64_MaxSize];
+ char* stop = SkStrAppendS64(buffer, dec, minDigits);
+ this->insert(offset, buffer, stop - buffer);
+}
+
void SkString::insertHex(size_t offset, uint32_t hex, int minDigits)
{
minDigits = SkPin32(minDigits, 0, 8);
@@ -580,4 +620,3 @@
{
delete[] fUCS2;
}
-
diff --git a/src/pdf/SkPDFCatalog.cpp b/src/pdf/SkPDFCatalog.cpp
index 1349da1..7979b70 100644
--- a/src/pdf/SkPDFCatalog.cpp
+++ b/src/pdf/SkPDFCatalog.cpp
@@ -17,23 +17,35 @@
#include "SkPDFCatalog.h"
#include "SkPDFTypes.h"
#include "SkStream.h"
+#include "SkTypes.h"
SkPDFCatalog::SkPDFCatalog()
- : fNextObjNum(1),
- fStartedAssigningObjNums(false),
- fAssigningFirstPageObjNums(false) {
+ : fFirstPageCount(0),
+ fNextObjNum(1),
+ fNextFirstPageObjNum(0) {
}
SkPDFCatalog::~SkPDFCatalog() {}
void SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) {
SkASSERT(findObjectIndex(obj) == -1);
- SkASSERT(!fStartedAssigningObjNums);
+ SkASSERT(fNextFirstPageObjNum == 0);
+ if (onFirstPage)
+ fFirstPageCount++;
struct Rec newEntry(obj, onFirstPage);
fCatalog.append(1, &newEntry);
}
+size_t SkPDFCatalog::setFileOffset(SkPDFObject* obj, size_t offset) {
+ int objIndex = assignObjNum(obj) - 1;
+ SkASSERT(fCatalog[objIndex].fObjNumAssigned);
+ SkASSERT(fCatalog[objIndex].fFileOffset == 0);
+ fCatalog[objIndex].fFileOffset = offset;
+
+ return obj->getOutputSize(this, true);
+}
+
void SkPDFCatalog::emitObjectNumber(SkWStream* stream, SkPDFObject* obj) {
stream->writeDecAsText(assignObjNum(obj));
stream->writeText(" 0"); // Generation number is always 0.
@@ -60,21 +72,54 @@
if (fCatalog[currentIndex].fObjNumAssigned)
return currentIndex + 1;
- fStartedAssigningObjNums = true;
+ // First assignment.
+ if (fNextFirstPageObjNum == 0)
+ fNextFirstPageObjNum = fCatalog.count() - fFirstPageCount + 1;
+
+ uint32_t objNum;
if (fCatalog[currentIndex].fOnFirstPage) {
- fAssigningFirstPageObjNums = true;
+ objNum = fNextFirstPageObjNum;
+ fNextFirstPageObjNum++;
} else {
- SkASSERT(!fAssigningFirstPageObjNums);
+ objNum = fNextObjNum;
+ fNextObjNum++;
}
// 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;
+ SkASSERT(!fCatalog[objNum - 1].fObjNumAssigned);
+ if (objNum - 1 != currentIndex)
+ SkTSwap(fCatalog[objNum - 1], fCatalog[currentIndex]);
+ fCatalog[objNum - 1].fObjNumAssigned = true;
+ return objNum;
+}
+
+int32_t SkPDFCatalog::emitXrefTable(SkWStream* stream, bool firstPage) {
+ int first = -1;
+ int last = fCatalog.count() - 1;
+ // TODO(vandebo) support linearized format.
+ //int last = fCatalog.count() - fFirstPageCount - 1;
+ //if (firstPage) {
+ // first = fCatalog.count() - fFirstPageCount;
+ // last = fCatalog.count() - 1;
+ //}
+
+ stream->writeText("xref\n");
+ stream->writeDecAsText(first + 1);
+ stream->writeText(" ");
+ stream->writeDecAsText(last - first + 1);
+ stream->writeText("\n");
+
+ if (first == -1) {
+ stream->writeText("0000000000 65535 f \n");
+ first++;
}
- fCatalog[fNextObjNum - 1].fObjNumAssigned = true;
- fNextObjNum++;
- return fNextObjNum - 1;
+ for (int i = first; i <= last; i++) {
+ SkASSERT(fCatalog[i].fFileOffset > 0);
+ SkASSERT(fCatalog[i].fFileOffset <= 9999999999LL);
+ stream->writeBigDecAsText(fCatalog[i].fFileOffset, 10);
+ stream->writeText(" 00000 n \n");
+ }
+
+ return fCatalog.count() + 1;
}
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
new file mode 100644
index 0000000..8597d7d
--- /dev/null
+++ b/src/pdf/SkPDFDocument.cpp
@@ -0,0 +1,186 @@
+/*
+ * 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 "SkPDFDevice.h"
+#include "SkPDFDocument.h"
+#include "SkPDFPage.h"
+#include "SkStream.h"
+
+// Add the resouces, starting at firstIndex to the catalog, removing any dupes.
+// A hash table would be really nice here.
+void addResoucesToCatalog(int firstIndex, bool firstPage,
+ SkTDArray<SkPDFObject*>* resouceList,
+ SkPDFCatalog* catalog) {
+ for (int i = firstIndex; i < resouceList->count(); i++) {
+ int index = resouceList->find((*resouceList)[i]);
+ if (index != i) {
+ // The resouce lists themselves should already be unique, so the
+ // first page resouces shouldn't have any dups (assuming the first
+ // page resouces are handled first).
+ SkASSERT(!firstPage);
+ (*resouceList)[i]->unref();
+ resouceList->removeShuffle(i);
+ } else {
+ catalog->addObject((*resouceList)[i], firstPage);
+ }
+ }
+}
+
+SkPDFDocument::SkPDFDocument() : fXRefFileOffset(0) {
+ fDocCatalog = new SkPDFDict("Catalog");
+ fDocCatalog->unref(); // SkRefPtr and new both took a reference.
+ fCatalog.addObject(fDocCatalog.get(), true);
+}
+
+SkPDFDocument::~SkPDFDocument() {
+ fPages.safeUnrefAll();
+
+ // The page tree has both child and parent pointers, so it creates a
+ // reference cycle. We must clear that cycle to properly reclaim memory.
+ for (int i = 0; i < fPageTree.count(); i++)
+ fPageTree[i]->clear();
+ fPageTree.safeUnrefAll();
+ fPageResources.safeUnrefAll();
+}
+
+bool SkPDFDocument::emitPDF(SkWStream* stream) {
+ if (fPages.isEmpty())
+ return false;
+
+ // We haven't emitted the document before if fPageTree is empty.
+ if (fPageTree.count() == 0) {
+ SkPDFDict* pageTreeRoot;
+ SkPDFPage::generatePageTree(fPages, &fCatalog, &fPageTree,
+ &pageTreeRoot);
+ SkRefPtr<SkPDFObjRef> pageTreeRootRef = new SkPDFObjRef(pageTreeRoot);
+ pageTreeRootRef->unref(); // SkRefPtr and new both took a reference.
+ fDocCatalog->insert("Pages", pageTreeRootRef.get());
+ bool first_page = true;
+ for (int i = 0; i < fPages.count(); i++) {
+ int resourceCount = fPageResources.count();
+ fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources);
+ addResoucesToCatalog(resourceCount, first_page, &fPageResources,
+ &fCatalog);
+ if (i == 0) {
+ first_page = false;
+ fSecondPageFirstResouceIndex = fPageResources.count();
+ }
+ }
+
+ // 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 < fSecondPageFirstResouceIndex; i++)
+ fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
+ 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);
+
+ for (int i = 1; i < fPages.count(); i++)
+ fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);
+
+ for (int i = fSecondPageFirstResouceIndex;
+ i < fPageResources.count();
+ i++)
+ fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
+
+ 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 < fSecondPageFirstResouceIndex; i++)
+ fPageResources[i]->emitObject(stream, &fCatalog, 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);
+ //}
+
+ for (int i = 0; i < fPageTree.count(); i++)
+ fPageTree[i]->emitObject(stream, &fCatalog, true);
+
+ for (int i = 1; i < fPages.count(); i++)
+ fPages[i]->emitPage(stream, &fCatalog);
+
+ for (int i = fSecondPageFirstResouceIndex; i < fPageResources.count(); i++)
+ fPageResources[i]->emitObject(stream, &fCatalog, true);
+
+ int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
+ emitFooter(stream, objCount);
+ return true;
+}
+
+bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) {
+ if (fPageTree.count() != 0)
+ return false;
+
+ SkPDFPage* page = new SkPDFPage(pdfDevice);
+ fPages.push(page); // Reference from new passed to fPages.
+ // The rest of the pages will be added to the catalog along with the rest
+ // of the page tree. But the first page has to be marked as such, so we
+ // handle it here.
+ if (fPages.count() == 1)
+ fCatalog.addObject(page, true);
+ return true;
+}
+
+void SkPDFDocument::emitHeader(SkWStream* stream) {
+ stream->writeText("%PDF-1.4\n%");
+ // The PDF spec recommends including a comment with four bytes, all
+ // with their high bits set. This is "Skia" with the high bits set.
+ stream->write32(0xD3EBE9E1);
+ stream->writeText("\n");
+}
+
+size_t SkPDFDocument::headerSize() {
+ SkDynamicMemoryWStream buffer;
+ emitHeader(&buffer);
+ return buffer.getOffset();
+}
+
+void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
+ if (fTrailerDict.get() == NULL) {
+ fTrailerDict = new SkPDFDict();
+ fTrailerDict->unref(); // SkRefPtr and new both took a reference.
+
+ SkPDFInt* objCountInt = new SkPDFInt(objCount);
+ fTrailerDict->insert("Size", objCountInt);
+ objCountInt->unref(); // insert took a ref and we're done with it.
+
+ // TODO(vandebo) Linearized format will take a Prev entry too.
+
+ SkPDFObjRef* docCatalogRef = new SkPDFObjRef(fDocCatalog.get());
+ fTrailerDict->insert("Root", docCatalogRef);
+ docCatalogRef->unref(); // insert took a ref and we're done with it.
+
+ // TODO(vandebo) PDF/A requires an ID entry.
+ }
+
+ stream->writeText("trailer\n");
+ fTrailerDict->emitObject(stream, &fCatalog, false);
+ stream->writeText("\nstartxref\n");
+ stream->writeBigDecAsText(fXRefFileOffset);
+ stream->writeText("\n%%EOF");
+}
diff --git a/src/pdf/SkPDFPage.cpp b/src/pdf/SkPDFPage.cpp
new file mode 100644
index 0000000..4183387
--- /dev/null
+++ b/src/pdf/SkPDFPage.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 "SkPDFDevice.h"
+#include "SkPDFPage.h"
+#include "SkStream.h"
+
+SkPDFPage::SkPDFPage(const SkRefPtr<SkPDFDevice>& content)
+ : SkPDFDict("Page"),
+ fDevice(content) {
+}
+
+SkPDFPage::~SkPDFPage() {}
+
+void SkPDFPage::finalizePage(SkPDFCatalog* catalog, bool firstPage,
+ SkTDArray<SkPDFObject*>* resourceObjects) {
+ if (fContentStream.get() == NULL) {
+ insert("Resources", fDevice->getResourceDict().get());
+ insert("MediaBox", fDevice->getMediaBox().get());
+
+ fContent = fDevice->content();
+ SkRefPtr<SkMemoryStream> contentStream = new SkMemoryStream(
+ fContent.c_str(), fContent.size());
+ contentStream->unref(); // SkRefPtr and new both took a reference.
+ fContentStream = new SkPDFStream(contentStream.get());
+ fContentStream->unref(); // SkRefPtr and new both took a reference.
+ SkRefPtr<SkPDFObjRef> contentRef =
+ new SkPDFObjRef(fContentStream.get());
+ contentRef->unref(); // SkRefPtr and new both took a reference.
+ insert("Contents", contentRef.get());
+ }
+ catalog->addObject(fContentStream.get(), firstPage);
+ fDevice->getResouces(resourceObjects);
+}
+
+off_t SkPDFPage::getPageSize(SkPDFCatalog* catalog, off_t fileOffset) {
+ SkASSERT(fContentStream.get() != NULL);
+ catalog->setFileOffset(fContentStream.get(), fileOffset);
+ return fContentStream->getOutputSize(catalog, true);
+}
+
+void SkPDFPage::emitPage(SkWStream* stream, SkPDFCatalog* catalog) {
+ SkASSERT(fContentStream.get() != NULL);
+ fContentStream->emitObject(stream, catalog, true);
+}
+
+// static
+void SkPDFPage::generatePageTree(const SkTDArray<SkPDFPage*>& pages,
+ SkPDFCatalog* catalog,
+ SkTDArray<SkPDFDict*>* pageTree,
+ SkPDFDict** rootNode) {
+ static const int kNodeSize = 8;
+
+ SkRefPtr<SkPDFName> kidsName = new SkPDFName("Kids");
+ kidsName->unref(); // SkRefPtr and new both took a reference.
+ SkRefPtr<SkPDFName> countName = new SkPDFName("Count");
+ countName->unref(); // SkRefPtr and new both took a reference.
+ SkRefPtr<SkPDFName> parentName = new SkPDFName("Parent");
+ parentName->unref(); // SkRefPtr and new both took a reference.
+
+ // curNodes takes a reference to its items, which it passes to pageTree.
+ SkTDArray<SkPDFDict*> curNodes;
+ curNodes.setReserve(pages.count());
+ for (int i = 0; i < pages.count(); i++) {
+ pages[i]->safeRef();
+ curNodes.push(pages[i]);
+ }
+
+ // nextRoundNodes passes its references to nodes on to curNodes.
+ SkTDArray<SkPDFDict*> nextRoundNodes;
+ nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize);
+
+ do {
+ for (int i = 0; i < curNodes.count(); ) {
+ if (i > 0 && i + 1 == curNodes.count()) {
+ nextRoundNodes.push(curNodes[i]);
+ break;
+ }
+
+ SkPDFDict* newNode = new SkPDFDict("Pages");
+ SkRefPtr<SkPDFObjRef> newNodeRef = new SkPDFObjRef(newNode);
+ newNodeRef->unref(); // SkRefPtr and new both took a reference.
+
+ SkRefPtr<SkPDFArray> kids = new SkPDFArray;
+ kids->unref(); // SkRefPtr and new both took a reference.
+ kids->reserve(kNodeSize);
+
+ int count = 0;
+ for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
+ curNodes[i]->insert(parentName.get(), newNodeRef.get());
+ SkRefPtr<SkPDFObjRef> nodeRef = new SkPDFObjRef(curNodes[i]);
+ nodeRef->unref(); // SkRefPtr and new both took a reference.
+ kids->append(nodeRef.get());
+
+ // TODO(vandebo) put the objects in strict access order.
+ // Probably doesn't matter because they are so small.
+ if (curNodes[i] != pages[0]) {
+ pageTree->push(curNodes[i]); // Transfer reference.
+ catalog->addObject(curNodes[i], false);
+ } else {
+ curNodes[i]->safeUnref();
+ }
+ }
+
+ newNode->insert(kidsName.get(), kids.get());
+ SkRefPtr<SkPDFInt> countVal = new SkPDFInt(count);
+ countVal->unref(); // SkRefPtr and new both took a reference.
+ newNode->insert(countName.get(), countVal.get());
+ nextRoundNodes.push(newNode); // Transfer reference.
+ }
+
+ curNodes = nextRoundNodes;
+ nextRoundNodes.rewind();
+ } while(curNodes.count() > 1);
+
+ pageTree->push(curNodes[0]); // Transfer reference.
+ catalog->addObject(curNodes[0], false);
+ if (rootNode)
+ *rootNode = curNodes[0];
+}
diff --git a/src/pdf/SkPDFStream.cpp b/src/pdf/SkPDFStream.cpp
index a7fbc62..b880d4a 100644
--- a/src/pdf/SkPDFStream.cpp
+++ b/src/pdf/SkPDFStream.cpp
@@ -19,11 +19,9 @@
#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());
+ fDict.insert("Length", lenValue.get());
}
SkPDFStream::~SkPDFStream() {
@@ -37,7 +35,7 @@
fDict.emitObject(stream, catalog, false);
stream->writeText(" stream\n");
stream->write(fData->getMemoryBase(), fData->read(NULL, 0));
- stream->writeText("endstream\n");
+ stream->writeText("endstream");
}
size_t SkPDFStream::getOutputSize(SkPDFCatalog* catalog, bool indirect) {
@@ -45,9 +43,13 @@
return getIndirectOutputSize(catalog);
return fDict.getOutputSize(catalog, false) +
- strlen(" stream\nendstream\n") + fData->read(NULL, 0);
+ strlen(" stream\nendstream") + fData->read(NULL, 0);
}
void SkPDFStream::insert(SkPDFName* key, SkPDFObject* value) {
fDict.insert(key, value);
}
+
+void SkPDFStream::insert(const char key[], SkPDFObject* value) {
+ fDict.insert(key, value);
+}
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index f5f23ca..a01fcba 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -211,11 +211,15 @@
}
SkPDFDict::SkPDFDict() {}
+
+SkPDFDict::SkPDFDict(const char type[]) {
+ SkRefPtr<SkPDFName> typeName = new SkPDFName(type);
+ typeName->unref(); // SkRefPtr and new both took a reference.
+ insert("Type", typeName.get());
+}
+
SkPDFDict::~SkPDFDict() {
- for (int i = 0; i < fValue.count(); i++) {
- SkSafeUnref(fValue[i].key);
- SkSafeUnref(fValue[i].value);
- }
+ clear();
}
void SkPDFDict::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
@@ -252,3 +256,17 @@
newEntry->value = value;
SkSafeRef(newEntry->value);
}
+
+void SkPDFDict::insert(const char key[], SkPDFObject* value) {
+ SkRefPtr<SkPDFName> keyName = new SkPDFName(key);
+ keyName->unref(); // SkRefPtr and new both took a reference.
+ insert(keyName.get(), value);
+}
+
+void SkPDFDict::clear() {
+ for (int i = 0; i < fValue.count(); i++) {
+ fValue[i].key->safeUnref();
+ fValue[i].value->safeUnref();
+ }
+ fValue.reset();
+}
diff --git a/src/pdf/pdf_files.mk b/src/pdf/pdf_files.mk
index 5e41eea..a28957f 100644
--- a/src/pdf/pdf_files.mk
+++ b/src/pdf/pdf_files.mk
@@ -1,5 +1,6 @@
SOURCE := \
SkPDFCatalog.cpp \
+ SkPDFDocument.cpp \
+ SkPDFPage.cpp \
SkPDFStream.cpp \
SkPDFTypes.cpp \
-
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 7e4465b..5dcb73c 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -175,12 +175,12 @@
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",
+ "<</Length 12\n>> stream\nTest\nFoo\tBarendstream",
true);
stream->insert(n1.get(), int42.get());
CheckObjectOutput(reporter, stream.get(),
"<</Length 12\n/n1 42\n>> stream\nTest\nFoo\tBar"
- "endstream\n",
+ "endstream",
true);
TestCatalog(reporter);
diff --git a/tests/StringTest.cpp b/tests/StringTest.cpp
index 344c752..2e691f4 100644
--- a/tests/StringTest.cpp
+++ b/tests/StringTest.cpp
@@ -48,6 +48,22 @@
a.set("ab");
a.set("abc");
a.set("abcd");
+
+ a.set("");
+ a.appendS64(72036854775808LL, 0);
+ REPORTER_ASSERT(reporter, a.equals("72036854775808"));
+
+ a.set("");
+ a.appendS64(-1844674407370LL, 0);
+ REPORTER_ASSERT(reporter, a.equals("-1844674407370"));
+
+ a.set("");
+ a.appendS64(73709551616LL, 15);
+ REPORTER_ASSERT(reporter, a.equals("000073709551616"));
+
+ a.set("");
+ a.appendS64(-429496729612LL, 15);
+ REPORTER_ASSERT(reporter, a.equals("-000429496729612"));
}
#include "TestClassDef.h"