aboutsummaryrefslogtreecommitdiffstats
path: root/src/pdf/SkPDFDocument.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdf/SkPDFDocument.cpp')
-rw-r--r--src/pdf/SkPDFDocument.cpp192
1 files changed, 192 insertions, 0 deletions
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
new file mode 100644
index 0000000..95370b4
--- /dev/null
+++ b/src/pdf/SkPDFDocument.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * 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 resources, starting at firstIndex to the catalog, removing any dupes.
+// A hash table would be really nice here.
+void addResourcesToCatalog(int firstIndex, bool firstPage,
+ SkTDArray<SkPDFObject*>* resourceList,
+ SkPDFCatalog* catalog) {
+ for (int i = firstIndex; i < resourceList->count(); i++) {
+ int index = resourceList->find((*resourceList)[i]);
+ if (index != i) {
+ (*resourceList)[i]->unref();
+ resourceList->removeShuffle(i);
+ i--;
+ } else {
+ catalog->addObject((*resourceList)[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);
+ fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
+
+ /* TODO(vandebo) output intent
+ SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
+ outputIntent->unref(); // SkRefPtr and new both took a reference.
+ outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
+ outputIntent->insert("OutputConditionIdentifier",
+ new SkPDFString("sRGB"))->unref();
+ SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
+ intentArray->unref(); // SkRefPtr and new both took a reference.
+ intentArray->append(outputIntent.get());
+ fDocCatalog->insert("OutputIntent", intentArray.get());
+ */
+
+ bool first_page = true;
+ for (int i = 0; i < fPages.count(); i++) {
+ int resourceCount = fPageResources.count();
+ fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources);
+ addResourcesToCatalog(resourceCount, first_page, &fPageResources,
+ &fCatalog);
+ if (i == 0) {
+ first_page = false;
+ fSecondPageFirstResourceIndex = 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 < fSecondPageFirstResourceIndex; 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 = fSecondPageFirstResourceIndex;
+ 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 < fSecondPageFirstResourceIndex; 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 = fSecondPageFirstResourceIndex; 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;
+}
+
+const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
+ return fPages;
+}
+
+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.
+
+ // TODO(vandebo) Linearized format will take a Prev entry too.
+ // TODO(vandebo) PDF/A requires an ID entry.
+ fTrailerDict->insert("Size", new SkPDFInt(objCount))->unref();
+ fTrailerDict->insert("Root",
+ new SkPDFObjRef(fDocCatalog.get()))->unref();
+ }
+
+ stream->writeText("trailer\n");
+ fTrailerDict->emitObject(stream, &fCatalog, false);
+ stream->writeText("\nstartxref\n");
+ stream->writeBigDecAsText(fXRefFileOffset);
+ stream->writeText("\n%%EOF");
+}