blob: 2b1e3f731bc763ffd1f1d78f8af053ce58ae778f [file] [log] [blame]
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +00001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "SkPDFDevice.h"
18#include "SkPDFDocument.h"
19#include "SkPDFPage.h"
20#include "SkStream.h"
21
vandebo@chromium.orga5180862010-10-26 19:48:49 +000022// Add the resources, starting at firstIndex to the catalog, removing any dupes.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000023// A hash table would be really nice here.
vandebo@chromium.orga5180862010-10-26 19:48:49 +000024void addResourcesToCatalog(int firstIndex, bool firstPage,
25 SkTDArray<SkPDFObject*>* resourceList,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000026 SkPDFCatalog* catalog) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000027 for (int i = firstIndex; i < resourceList->count(); i++) {
28 int index = resourceList->find((*resourceList)[i]);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000029 if (index != i) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000030 // The resource lists themselves should already be unique, so the
31 // first page resources shouldn't have any dups (assuming the first
32 // page resources are handled first).
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000033 SkASSERT(!firstPage);
vandebo@chromium.orga5180862010-10-26 19:48:49 +000034 (*resourceList)[i]->unref();
35 resourceList->removeShuffle(i);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000036 } else {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000037 catalog->addObject((*resourceList)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000038 }
39 }
40}
41
42SkPDFDocument::SkPDFDocument() : fXRefFileOffset(0) {
43 fDocCatalog = new SkPDFDict("Catalog");
44 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
45 fCatalog.addObject(fDocCatalog.get(), true);
46}
47
48SkPDFDocument::~SkPDFDocument() {
49 fPages.safeUnrefAll();
50
51 // The page tree has both child and parent pointers, so it creates a
52 // reference cycle. We must clear that cycle to properly reclaim memory.
53 for (int i = 0; i < fPageTree.count(); i++)
54 fPageTree[i]->clear();
55 fPageTree.safeUnrefAll();
56 fPageResources.safeUnrefAll();
57}
58
59bool SkPDFDocument::emitPDF(SkWStream* stream) {
60 if (fPages.isEmpty())
61 return false;
62
63 // We haven't emitted the document before if fPageTree is empty.
64 if (fPageTree.count() == 0) {
65 SkPDFDict* pageTreeRoot;
66 SkPDFPage::generatePageTree(fPages, &fCatalog, &fPageTree,
67 &pageTreeRoot);
68 SkRefPtr<SkPDFObjRef> pageTreeRootRef = new SkPDFObjRef(pageTreeRoot);
69 pageTreeRootRef->unref(); // SkRefPtr and new both took a reference.
70 fDocCatalog->insert("Pages", pageTreeRootRef.get());
71 bool first_page = true;
72 for (int i = 0; i < fPages.count(); i++) {
73 int resourceCount = fPageResources.count();
74 fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources);
vandebo@chromium.orga5180862010-10-26 19:48:49 +000075 addResourcesToCatalog(resourceCount, first_page, &fPageResources,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000076 &fCatalog);
77 if (i == 0) {
78 first_page = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +000079 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000080 }
81 }
82
83 // Figure out the size of things and inform the catalog of file offsets.
84 off_t fileOffset = headerSize();
85 fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset);
86 fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset);
87 fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset);
vandebo@chromium.orga5180862010-10-26 19:48:49 +000088 for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000089 fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
90 if (fPages.count() > 1) {
91 // TODO(vandebo) For linearized format, save the start of the
92 // first page xref table and calculate the size.
93 }
94
95 for (int i = 0; i < fPageTree.count(); i++)
96 fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset);
97
98 for (int i = 1; i < fPages.count(); i++)
99 fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);
100
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000101 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000102 i < fPageResources.count();
103 i++)
104 fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
105
106 fXRefFileOffset = fileOffset;
107 }
108
109 emitHeader(stream);
110 fDocCatalog->emitObject(stream, &fCatalog, true);
111 fPages[0]->emitObject(stream, &fCatalog, true);
112 fPages[0]->emitPage(stream, &fCatalog);
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000113 for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000114 fPageResources[i]->emitObject(stream, &fCatalog, true);
115 // TODO(vandebo) support linearized format
116 //if (fPages.size() > 1) {
117 // // TODO(vandebo) save the file offset for the first page xref table.
118 // fCatalog.emitXrefTable(stream, true);
119 //}
120
121 for (int i = 0; i < fPageTree.count(); i++)
122 fPageTree[i]->emitObject(stream, &fCatalog, true);
123
124 for (int i = 1; i < fPages.count(); i++)
125 fPages[i]->emitPage(stream, &fCatalog);
126
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000127 for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++)
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000128 fPageResources[i]->emitObject(stream, &fCatalog, true);
129
130 int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
131 emitFooter(stream, objCount);
132 return true;
133}
134
135bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) {
136 if (fPageTree.count() != 0)
137 return false;
138
139 SkPDFPage* page = new SkPDFPage(pdfDevice);
140 fPages.push(page); // Reference from new passed to fPages.
141 // The rest of the pages will be added to the catalog along with the rest
142 // of the page tree. But the first page has to be marked as such, so we
143 // handle it here.
144 if (fPages.count() == 1)
145 fCatalog.addObject(page, true);
146 return true;
147}
148
149void SkPDFDocument::emitHeader(SkWStream* stream) {
150 stream->writeText("%PDF-1.4\n%");
151 // The PDF spec recommends including a comment with four bytes, all
152 // with their high bits set. This is "Skia" with the high bits set.
153 stream->write32(0xD3EBE9E1);
154 stream->writeText("\n");
155}
156
157size_t SkPDFDocument::headerSize() {
158 SkDynamicMemoryWStream buffer;
159 emitHeader(&buffer);
160 return buffer.getOffset();
161}
162
163void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
164 if (fTrailerDict.get() == NULL) {
165 fTrailerDict = new SkPDFDict();
166 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
167
168 SkPDFInt* objCountInt = new SkPDFInt(objCount);
169 fTrailerDict->insert("Size", objCountInt);
170 objCountInt->unref(); // insert took a ref and we're done with it.
171
172 // TODO(vandebo) Linearized format will take a Prev entry too.
173
174 SkPDFObjRef* docCatalogRef = new SkPDFObjRef(fDocCatalog.get());
175 fTrailerDict->insert("Root", docCatalogRef);
176 docCatalogRef->unref(); // insert took a ref and we're done with it.
177
178 // TODO(vandebo) PDF/A requires an ID entry.
179 }
180
181 stream->writeText("trailer\n");
182 fTrailerDict->emitObject(stream, &fCatalog, false);
183 stream->writeText("\nstartxref\n");
184 stream->writeBigDecAsText(fXRefFileOffset);
185 stream->writeText("\n%%EOF");
186}