blob: fa8a0282492abad32fc6b71eb1f7ae51525ab5d9 [file] [log] [blame]
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +00001/*
vandebo@chromium.org2a22e102011-01-25 21:01:34 +00002 * Copyright (C) 2011 Google Inc.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +00003 *
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 (*resourceList)[i]->unref();
31 resourceList->removeShuffle(i);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000032 i--;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000033 } else {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000034 catalog->addObject((*resourceList)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000035 }
36 }
37}
38
vandebo@chromium.org73322072011-06-21 21:19:41 +000039SkPDFDocument::SkPDFDocument()
40 : fXRefFileOffset(0),
41 fSecondPageFirstResourceIndex(0) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000042 fDocCatalog = new SkPDFDict("Catalog");
43 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
44 fCatalog.addObject(fDocCatalog.get(), true);
45}
46
47SkPDFDocument::~SkPDFDocument() {
48 fPages.safeUnrefAll();
49
50 // The page tree has both child and parent pointers, so it creates a
51 // reference cycle. We must clear that cycle to properly reclaim memory.
52 for (int i = 0; i < fPageTree.count(); i++)
53 fPageTree[i]->clear();
54 fPageTree.safeUnrefAll();
55 fPageResources.safeUnrefAll();
56}
57
58bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000059 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000060 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000061 }
62 for (int i = 0; i < fPages.count(); i++) {
63 if (fPages[i] == NULL) {
64 return false;
65 }
66 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000067
68 // We haven't emitted the document before if fPageTree is empty.
69 if (fPageTree.count() == 0) {
70 SkPDFDict* pageTreeRoot;
71 SkPDFPage::generatePageTree(fPages, &fCatalog, &fPageTree,
72 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000073 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000074
75 /* TODO(vandebo) output intent
76 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
77 outputIntent->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000078 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000079 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000080 new SkPDFString("sRGB"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000081 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
82 intentArray->unref(); // SkRefPtr and new both took a reference.
83 intentArray->append(outputIntent.get());
84 fDocCatalog->insert("OutputIntent", intentArray.get());
85 */
86
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +000087 bool firstPage = true;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000088 for (int i = 0; i < fPages.count(); i++) {
89 int resourceCount = fPageResources.count();
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +000090 fPages[i]->finalizePage(&fCatalog, firstPage, &fPageResources);
91 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
92 &fCatalog);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000093 if (i == 0) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +000094 firstPage = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +000095 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000096 }
97 }
98
99 // Figure out the size of things and inform the catalog of file offsets.
100 off_t fileOffset = headerSize();
101 fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset);
102 fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset);
103 fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset);
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000104 for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000105 fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000106 // Add the size of resources of substitute objects used on page 1.
107 fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000108 if (fPages.count() > 1) {
109 // TODO(vandebo) For linearized format, save the start of the
110 // first page xref table and calculate the size.
111 }
112
113 for (int i = 0; i < fPageTree.count(); i++)
114 fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset);
115
116 for (int i = 1; i < fPages.count(); i++)
117 fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);
118
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000119 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000120 i < fPageResources.count();
121 i++)
122 fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
123
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000124 fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000125 fXRefFileOffset = fileOffset;
126 }
127
128 emitHeader(stream);
129 fDocCatalog->emitObject(stream, &fCatalog, true);
130 fPages[0]->emitObject(stream, &fCatalog, true);
131 fPages[0]->emitPage(stream, &fCatalog);
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000132 for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000133 fPageResources[i]->emit(stream, &fCatalog, true);
134 fCatalog.emitSubstituteResources(stream, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000135 // TODO(vandebo) support linearized format
136 //if (fPages.size() > 1) {
137 // // TODO(vandebo) save the file offset for the first page xref table.
138 // fCatalog.emitXrefTable(stream, true);
139 //}
140
141 for (int i = 0; i < fPageTree.count(); i++)
142 fPageTree[i]->emitObject(stream, &fCatalog, true);
143
144 for (int i = 1; i < fPages.count(); i++)
145 fPages[i]->emitPage(stream, &fCatalog);
146
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000147 for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++)
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000148 fPageResources[i]->emit(stream, &fCatalog, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000149
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000150 fCatalog.emitSubstituteResources(stream, false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000151 int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
152 emitFooter(stream, objCount);
153 return true;
154}
155
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000156bool SkPDFDocument::setPage(int pageNumber,
157 const SkRefPtr<SkPDFDevice>& pdfDevice) {
158 if (fPageTree.count() != 0) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000159 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000160 }
161
162 pageNumber--;
163 SkASSERT(pageNumber >= 0);
164
165 if (pageNumber > fPages.count()) {
166 int oldSize = fPages.count();
167 fPages.setCount(pageNumber + 1);
168 for (int i = oldSize; i <= pageNumber; i++) {
169 fPages[i] = NULL;
170 }
171 }
172
173 SkPDFPage* page = new SkPDFPage(pdfDevice);
174 SkSafeUnref(fPages[pageNumber]);
175 fPages[pageNumber] = page; // Reference from new passed to fPages.
176 return true;
177}
178
179bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) {
180 if (fPageTree.count() != 0) {
181 return false;
182 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000183
184 SkPDFPage* page = new SkPDFPage(pdfDevice);
185 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000186 return true;
187}
188
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000189const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
190 return fPages;
191}
192
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000193void SkPDFDocument::emitHeader(SkWStream* stream) {
194 stream->writeText("%PDF-1.4\n%");
195 // The PDF spec recommends including a comment with four bytes, all
196 // with their high bits set. This is "Skia" with the high bits set.
197 stream->write32(0xD3EBE9E1);
198 stream->writeText("\n");
199}
200
201size_t SkPDFDocument::headerSize() {
202 SkDynamicMemoryWStream buffer;
203 emitHeader(&buffer);
204 return buffer.getOffset();
205}
206
207void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
208 if (fTrailerDict.get() == NULL) {
209 fTrailerDict = new SkPDFDict();
210 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
211
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000212 // TODO(vandebo) Linearized format will take a Prev entry too.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000213 // TODO(vandebo) PDF/A requires an ID entry.
reed@google.comc789cf12011-07-20 12:14:33 +0000214 fTrailerDict->insertInt("Size", objCount);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000215 fTrailerDict->insert("Root",
216 new SkPDFObjRef(fDocCatalog.get()))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000217 }
218
219 stream->writeText("trailer\n");
220 fTrailerDict->emitObject(stream, &fCatalog, false);
221 stream->writeText("\nstartxref\n");
222 stream->writeBigDecAsText(fXRefFileOffset);
223 stream->writeText("\n%%EOF");
224}