blob: d60512e7acaddd10938552514d629b9714d469bf [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
vandebo@chromium.org421d6442011-07-20 17:39:01 +000017#include "SkPDFCatalog.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000018#include "SkPDFDevice.h"
19#include "SkPDFDocument.h"
20#include "SkPDFPage.h"
21#include "SkStream.h"
22
vandebo@chromium.orga5180862010-10-26 19:48:49 +000023// Add the resources, starting at firstIndex to the catalog, removing any dupes.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000024// A hash table would be really nice here.
vandebo@chromium.orga5180862010-10-26 19:48:49 +000025void addResourcesToCatalog(int firstIndex, bool firstPage,
26 SkTDArray<SkPDFObject*>* resourceList,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000027 SkPDFCatalog* catalog) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000028 for (int i = firstIndex; i < resourceList->count(); i++) {
29 int index = resourceList->find((*resourceList)[i]);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000030 if (index != i) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000031 (*resourceList)[i]->unref();
32 resourceList->removeShuffle(i);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000033 i--;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000034 } else {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000035 catalog->addObject((*resourceList)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000036 }
37 }
38}
39
vandebo@chromium.org421d6442011-07-20 17:39:01 +000040SkPDFDocument::SkPDFDocument(Flags flags)
vandebo@chromium.org73322072011-06-21 21:19:41 +000041 : fXRefFileOffset(0),
42 fSecondPageFirstResourceIndex(0) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +000043 fCatalog.reset(new SkPDFCatalog(flags));
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000044 fDocCatalog = new SkPDFDict("Catalog");
45 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.org421d6442011-07-20 17:39:01 +000046 fCatalog->addObject(fDocCatalog.get(), true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000047}
48
49SkPDFDocument::~SkPDFDocument() {
50 fPages.safeUnrefAll();
51
52 // The page tree has both child and parent pointers, so it creates a
53 // reference cycle. We must clear that cycle to properly reclaim memory.
54 for (int i = 0; i < fPageTree.count(); i++)
55 fPageTree[i]->clear();
56 fPageTree.safeUnrefAll();
57 fPageResources.safeUnrefAll();
58}
59
60bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000061 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000062 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000063 }
64 for (int i = 0; i < fPages.count(); i++) {
65 if (fPages[i] == NULL) {
66 return false;
67 }
68 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000069
70 // We haven't emitted the document before if fPageTree is empty.
71 if (fPageTree.count() == 0) {
72 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +000073 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000074 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000075 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000076
77 /* TODO(vandebo) output intent
78 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
79 outputIntent->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000080 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000081 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000082 new SkPDFString("sRGB"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000083 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
84 intentArray->unref(); // SkRefPtr and new both took a reference.
85 intentArray->append(outputIntent.get());
86 fDocCatalog->insert("OutputIntent", intentArray.get());
87 */
88
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +000089 bool firstPage = true;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000090 for (int i = 0; i < fPages.count(); i++) {
91 int resourceCount = fPageResources.count();
vandebo@chromium.org421d6442011-07-20 17:39:01 +000092 fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +000093 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
vandebo@chromium.org421d6442011-07-20 17:39:01 +000094 fCatalog.get());
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000095 if (i == 0) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +000096 firstPage = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +000097 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000098 }
99 }
100
101 // Figure out the size of things and inform the catalog of file offsets.
102 off_t fileOffset = headerSize();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000103 fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
104 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
105 fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
106 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
107 fileOffset += fCatalog->setFileOffset(fPageResources[i],
108 fileOffset);
109 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000110 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000111 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000112 if (fPages.count() > 1) {
113 // TODO(vandebo) For linearized format, save the start of the
114 // first page xref table and calculate the size.
115 }
116
117 for (int i = 0; i < fPageTree.count(); i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000118 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000119
120 for (int i = 1; i < fPages.count(); i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000121 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000122
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000123 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000124 i < fPageResources.count();
125 i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000126 fileOffset += fCatalog->setFileOffset(fPageResources[i],
127 fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000128
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000129 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
130 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000131 fXRefFileOffset = fileOffset;
132 }
133
134 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000135 fDocCatalog->emitObject(stream, fCatalog.get(), true);
136 fPages[0]->emitObject(stream, fCatalog.get(), true);
137 fPages[0]->emitPage(stream, fCatalog.get());
138 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
139 fPageResources[i]->emit(stream, fCatalog.get(), true);
140 }
141 fCatalog->emitSubstituteResources(stream, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000142 // TODO(vandebo) support linearized format
143 //if (fPages.size() > 1) {
144 // // TODO(vandebo) save the file offset for the first page xref table.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000145 // fCatalog->emitXrefTable(stream, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000146 //}
147
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000148 for (int i = 0; i < fPageTree.count(); i++) {
149 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
150 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000151
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000152 for (int i = 1; i < fPages.count(); i++) {
153 fPages[i]->emitPage(stream, fCatalog.get());
154 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000155
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000156 for (int i = fSecondPageFirstResourceIndex;
157 i < fPageResources.count();
158 i++) {
159 fPageResources[i]->emit(stream, fCatalog.get(), true);
160 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000161
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000162 fCatalog->emitSubstituteResources(stream, false);
163 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000164 emitFooter(stream, objCount);
165 return true;
166}
167
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000168bool SkPDFDocument::setPage(int pageNumber,
169 const SkRefPtr<SkPDFDevice>& pdfDevice) {
170 if (fPageTree.count() != 0) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000171 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000172 }
173
174 pageNumber--;
175 SkASSERT(pageNumber >= 0);
176
177 if (pageNumber > fPages.count()) {
178 int oldSize = fPages.count();
179 fPages.setCount(pageNumber + 1);
180 for (int i = oldSize; i <= pageNumber; i++) {
181 fPages[i] = NULL;
182 }
183 }
184
185 SkPDFPage* page = new SkPDFPage(pdfDevice);
186 SkSafeUnref(fPages[pageNumber]);
187 fPages[pageNumber] = page; // Reference from new passed to fPages.
188 return true;
189}
190
191bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) {
192 if (fPageTree.count() != 0) {
193 return false;
194 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000195
196 SkPDFPage* page = new SkPDFPage(pdfDevice);
197 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000198 return true;
199}
200
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000201const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
202 return fPages;
203}
204
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000205void SkPDFDocument::emitHeader(SkWStream* stream) {
206 stream->writeText("%PDF-1.4\n%");
207 // The PDF spec recommends including a comment with four bytes, all
208 // with their high bits set. This is "Skia" with the high bits set.
209 stream->write32(0xD3EBE9E1);
210 stream->writeText("\n");
211}
212
213size_t SkPDFDocument::headerSize() {
214 SkDynamicMemoryWStream buffer;
215 emitHeader(&buffer);
216 return buffer.getOffset();
217}
218
219void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
220 if (fTrailerDict.get() == NULL) {
221 fTrailerDict = new SkPDFDict();
222 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
223
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000224 // TODO(vandebo) Linearized format will take a Prev entry too.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000225 // TODO(vandebo) PDF/A requires an ID entry.
reed@google.comc789cf12011-07-20 12:14:33 +0000226 fTrailerDict->insertInt("Size", objCount);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000227 fTrailerDict->insert("Root",
228 new SkPDFObjRef(fDocCatalog.get()))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000229 }
230
231 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000232 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000233 stream->writeText("\nstartxref\n");
234 stream->writeBigDecAsText(fXRefFileOffset);
235 stream->writeText("\n%%EOF");
236}