blob: 38f4f32977c83212ab49f0f9c93fb3c2b53afecf [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"
vandebo@chromium.org98594282011-07-25 22:34:12 +000021#include "SkPDFFont.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000022#include "SkStream.h"
23
vandebo@chromium.orga5180862010-10-26 19:48:49 +000024// Add the resources, starting at firstIndex to the catalog, removing any dupes.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000025// A hash table would be really nice here.
vandebo@chromium.orga5180862010-10-26 19:48:49 +000026void addResourcesToCatalog(int firstIndex, bool firstPage,
27 SkTDArray<SkPDFObject*>* resourceList,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000028 SkPDFCatalog* catalog) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000029 for (int i = firstIndex; i < resourceList->count(); i++) {
30 int index = resourceList->find((*resourceList)[i]);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000031 if (index != i) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000032 (*resourceList)[i]->unref();
33 resourceList->removeShuffle(i);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000034 i--;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000035 } else {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000036 catalog->addObject((*resourceList)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000037 }
38 }
39}
40
vandebo@chromium.org98594282011-07-25 22:34:12 +000041static void perform_font_subsetting(SkPDFCatalog* catalog,
42 const SkTDArray<SkPDFPage*>& pages,
43 SkTDArray<SkPDFObject*>* substitutes) {
44 SkASSERT(catalog);
45 SkASSERT(substitutes);
46
47 SkPDFGlyphSetMap usage;
48 for (int i = 0; i < pages.count(); ++i) {
49 usage.merge(pages[i]->getFontGlyphUsage());
50 }
51 SkPDFGlyphSetMap::F2BIter iterator(usage);
52 SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
53 while (entry) {
54 SkPDFFont* subsetFont =
55 entry->fFont->getFontSubset(entry->fGlyphSet);
56 if (subsetFont) {
57 catalog->setSubstitute(entry->fFont, subsetFont);
58 substitutes->push(subsetFont); // Transfer ownership to substitutes
59 }
60 entry = iterator.next();
61 }
62}
63
vandebo@chromium.org421d6442011-07-20 17:39:01 +000064SkPDFDocument::SkPDFDocument(Flags flags)
vandebo@chromium.org73322072011-06-21 21:19:41 +000065 : fXRefFileOffset(0),
66 fSecondPageFirstResourceIndex(0) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +000067 fCatalog.reset(new SkPDFCatalog(flags));
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000068 fDocCatalog = new SkPDFDict("Catalog");
69 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.org421d6442011-07-20 17:39:01 +000070 fCatalog->addObject(fDocCatalog.get(), true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000071}
72
73SkPDFDocument::~SkPDFDocument() {
74 fPages.safeUnrefAll();
75
76 // The page tree has both child and parent pointers, so it creates a
77 // reference cycle. We must clear that cycle to properly reclaim memory.
78 for (int i = 0; i < fPageTree.count(); i++)
79 fPageTree[i]->clear();
80 fPageTree.safeUnrefAll();
81 fPageResources.safeUnrefAll();
vandebo@chromium.org98594282011-07-25 22:34:12 +000082 fSubstitutes.safeUnrefAll();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000083}
84
85bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000086 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000087 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000088 }
89 for (int i = 0; i < fPages.count(); i++) {
90 if (fPages[i] == NULL) {
91 return false;
92 }
93 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000094
95 // We haven't emitted the document before if fPageTree is empty.
96 if (fPageTree.count() == 0) {
97 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +000098 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000099 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000100 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000101
102 /* TODO(vandebo) output intent
103 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
104 outputIntent->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000105 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000106 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000107 new SkPDFString("sRGB"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000108 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
109 intentArray->unref(); // SkRefPtr and new both took a reference.
110 intentArray->append(outputIntent.get());
111 fDocCatalog->insert("OutputIntent", intentArray.get());
112 */
113
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000114 bool firstPage = true;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000115 for (int i = 0; i < fPages.count(); i++) {
116 int resourceCount = fPageResources.count();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000117 fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000118 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000119 fCatalog.get());
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000120 if (i == 0) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000121 firstPage = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000122 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000123 }
124 }
125
vandebo@chromium.org98594282011-07-25 22:34:12 +0000126 // Build font subsetting info before proceeding.
127 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
128
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000129 // Figure out the size of things and inform the catalog of file offsets.
130 off_t fileOffset = headerSize();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000131 fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
132 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
133 fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
134 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
135 fileOffset += fCatalog->setFileOffset(fPageResources[i],
136 fileOffset);
137 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000138 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000139 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000140 if (fPages.count() > 1) {
141 // TODO(vandebo) For linearized format, save the start of the
142 // first page xref table and calculate the size.
143 }
144
145 for (int i = 0; i < fPageTree.count(); i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000146 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000147
148 for (int i = 1; i < fPages.count(); i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000149 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000150
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000151 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000152 i < fPageResources.count();
153 i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000154 fileOffset += fCatalog->setFileOffset(fPageResources[i],
155 fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000156
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000157 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
158 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000159 fXRefFileOffset = fileOffset;
160 }
161
162 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000163 fDocCatalog->emitObject(stream, fCatalog.get(), true);
164 fPages[0]->emitObject(stream, fCatalog.get(), true);
165 fPages[0]->emitPage(stream, fCatalog.get());
166 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
167 fPageResources[i]->emit(stream, fCatalog.get(), true);
168 }
169 fCatalog->emitSubstituteResources(stream, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000170 // TODO(vandebo) support linearized format
171 //if (fPages.size() > 1) {
172 // // TODO(vandebo) save the file offset for the first page xref table.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000173 // fCatalog->emitXrefTable(stream, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000174 //}
175
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000176 for (int i = 0; i < fPageTree.count(); i++) {
177 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
178 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000179
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000180 for (int i = 1; i < fPages.count(); i++) {
181 fPages[i]->emitPage(stream, fCatalog.get());
182 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000183
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000184 for (int i = fSecondPageFirstResourceIndex;
185 i < fPageResources.count();
186 i++) {
187 fPageResources[i]->emit(stream, fCatalog.get(), true);
188 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000189
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000190 fCatalog->emitSubstituteResources(stream, false);
191 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000192 emitFooter(stream, objCount);
193 return true;
194}
195
reed@google.com1feb3302011-07-20 18:43:19 +0000196bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000197 if (fPageTree.count() != 0) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000198 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000199 }
200
201 pageNumber--;
202 SkASSERT(pageNumber >= 0);
203
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000204 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000205 int oldSize = fPages.count();
206 fPages.setCount(pageNumber + 1);
207 for (int i = oldSize; i <= pageNumber; i++) {
208 fPages[i] = NULL;
209 }
210 }
211
212 SkPDFPage* page = new SkPDFPage(pdfDevice);
213 SkSafeUnref(fPages[pageNumber]);
214 fPages[pageNumber] = page; // Reference from new passed to fPages.
215 return true;
216}
217
reed@google.com1feb3302011-07-20 18:43:19 +0000218bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000219 if (fPageTree.count() != 0) {
220 return false;
221 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000222
223 SkPDFPage* page = new SkPDFPage(pdfDevice);
224 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000225 return true;
226}
227
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000228const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
229 return fPages;
230}
231
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000232void SkPDFDocument::emitHeader(SkWStream* stream) {
233 stream->writeText("%PDF-1.4\n%");
234 // The PDF spec recommends including a comment with four bytes, all
235 // with their high bits set. This is "Skia" with the high bits set.
236 stream->write32(0xD3EBE9E1);
237 stream->writeText("\n");
238}
239
240size_t SkPDFDocument::headerSize() {
241 SkDynamicMemoryWStream buffer;
242 emitHeader(&buffer);
243 return buffer.getOffset();
244}
245
246void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
247 if (fTrailerDict.get() == NULL) {
248 fTrailerDict = new SkPDFDict();
249 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
250
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000251 // TODO(vandebo) Linearized format will take a Prev entry too.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000252 // TODO(vandebo) PDF/A requires an ID entry.
reed@google.comc789cf12011-07-20 12:14:33 +0000253 fTrailerDict->insertInt("Size", objCount);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000254 fTrailerDict->insert("Root",
255 new SkPDFObjRef(fDocCatalog.get()))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000256 }
257
258 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000259 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000260 stream->writeText("\nstartxref\n");
261 stream->writeBigDecAsText(fXRefFileOffset);
262 stream->writeText("\n%%EOF");
263}