blob: e566856d6d1457c30b3e1f45b0701bb0588f967c [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +00002/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00003 * Copyright 2011 Google Inc.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +00004 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00005 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +00007 */
8
epoger@google.comec3ed6a2011-07-28 14:26:00 +00009
vandebo@chromium.org421d6442011-07-20 17:39:01 +000010#include "SkPDFCatalog.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000011#include "SkPDFDevice.h"
12#include "SkPDFDocument.h"
13#include "SkPDFPage.h"
vandebo@chromium.org98594282011-07-25 22:34:12 +000014#include "SkPDFFont.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000015#include "SkStream.h"
16
vandebo@chromium.orga5180862010-10-26 19:48:49 +000017// Add the resources, starting at firstIndex to the catalog, removing any dupes.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000018// A hash table would be really nice here.
vandebo@chromium.orga5180862010-10-26 19:48:49 +000019void addResourcesToCatalog(int firstIndex, bool firstPage,
20 SkTDArray<SkPDFObject*>* resourceList,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000021 SkPDFCatalog* catalog) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000022 for (int i = firstIndex; i < resourceList->count(); i++) {
23 int index = resourceList->find((*resourceList)[i]);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000024 if (index != i) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000025 (*resourceList)[i]->unref();
26 resourceList->removeShuffle(i);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000027 i--;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000028 } else {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000029 catalog->addObject((*resourceList)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000030 }
31 }
32}
33
vandebo@chromium.org98594282011-07-25 22:34:12 +000034static void perform_font_subsetting(SkPDFCatalog* catalog,
35 const SkTDArray<SkPDFPage*>& pages,
36 SkTDArray<SkPDFObject*>* substitutes) {
37 SkASSERT(catalog);
38 SkASSERT(substitutes);
39
40 SkPDFGlyphSetMap usage;
41 for (int i = 0; i < pages.count(); ++i) {
42 usage.merge(pages[i]->getFontGlyphUsage());
43 }
44 SkPDFGlyphSetMap::F2BIter iterator(usage);
45 SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
46 while (entry) {
47 SkPDFFont* subsetFont =
48 entry->fFont->getFontSubset(entry->fGlyphSet);
49 if (subsetFont) {
50 catalog->setSubstitute(entry->fFont, subsetFont);
51 substitutes->push(subsetFont); // Transfer ownership to substitutes
52 }
53 entry = iterator.next();
54 }
55}
56
vandebo@chromium.org421d6442011-07-20 17:39:01 +000057SkPDFDocument::SkPDFDocument(Flags flags)
vandebo@chromium.org73322072011-06-21 21:19:41 +000058 : fXRefFileOffset(0),
59 fSecondPageFirstResourceIndex(0) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +000060 fCatalog.reset(new SkPDFCatalog(flags));
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000061 fDocCatalog = new SkPDFDict("Catalog");
62 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.org421d6442011-07-20 17:39:01 +000063 fCatalog->addObject(fDocCatalog.get(), true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000064}
65
66SkPDFDocument::~SkPDFDocument() {
67 fPages.safeUnrefAll();
68
69 // The page tree has both child and parent pointers, so it creates a
70 // reference cycle. We must clear that cycle to properly reclaim memory.
71 for (int i = 0; i < fPageTree.count(); i++)
72 fPageTree[i]->clear();
73 fPageTree.safeUnrefAll();
74 fPageResources.safeUnrefAll();
vandebo@chromium.org98594282011-07-25 22:34:12 +000075 fSubstitutes.safeUnrefAll();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000076}
77
78bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000079 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000080 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000081 }
82 for (int i = 0; i < fPages.count(); i++) {
83 if (fPages[i] == NULL) {
84 return false;
85 }
86 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000087
88 // We haven't emitted the document before if fPageTree is empty.
89 if (fPageTree.count() == 0) {
90 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +000091 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000092 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000093 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000094
95 /* TODO(vandebo) output intent
96 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
97 outputIntent->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000098 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000099 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000100 new SkPDFString("sRGB"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000101 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
102 intentArray->unref(); // SkRefPtr and new both took a reference.
103 intentArray->append(outputIntent.get());
104 fDocCatalog->insert("OutputIntent", intentArray.get());
105 */
106
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000107 bool firstPage = true;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000108 for (int i = 0; i < fPages.count(); i++) {
109 int resourceCount = fPageResources.count();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000110 fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000111 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000112 fCatalog.get());
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000113 if (i == 0) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000114 firstPage = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000115 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000116 }
117 }
118
vandebo@chromium.org98594282011-07-25 22:34:12 +0000119 // Build font subsetting info before proceeding.
120 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
121
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000122 // Figure out the size of things and inform the catalog of file offsets.
123 off_t fileOffset = headerSize();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000124 fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
125 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
126 fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
127 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
128 fileOffset += fCatalog->setFileOffset(fPageResources[i],
129 fileOffset);
130 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000131 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000132 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000133 if (fPages.count() > 1) {
134 // TODO(vandebo) For linearized format, save the start of the
135 // first page xref table and calculate the size.
136 }
137
138 for (int i = 0; i < fPageTree.count(); i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000139 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000140
141 for (int i = 1; i < fPages.count(); i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000142 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000143
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000144 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000145 i < fPageResources.count();
146 i++)
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000147 fileOffset += fCatalog->setFileOffset(fPageResources[i],
148 fileOffset);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000149
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000150 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
151 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000152 fXRefFileOffset = fileOffset;
153 }
154
155 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000156 fDocCatalog->emitObject(stream, fCatalog.get(), true);
157 fPages[0]->emitObject(stream, fCatalog.get(), true);
158 fPages[0]->emitPage(stream, fCatalog.get());
159 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
160 fPageResources[i]->emit(stream, fCatalog.get(), true);
161 }
162 fCatalog->emitSubstituteResources(stream, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000163 // TODO(vandebo) support linearized format
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000164 // if (fPages.size() > 1) {
165 // // TODO(vandebo) save the file offset for the first page xref table.
166 // fCatalog->emitXrefTable(stream, true);
167 // }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000168
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000169 for (int i = 0; i < fPageTree.count(); i++) {
170 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
171 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000172
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000173 for (int i = 1; i < fPages.count(); i++) {
174 fPages[i]->emitPage(stream, fCatalog.get());
175 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000176
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000177 for (int i = fSecondPageFirstResourceIndex;
178 i < fPageResources.count();
179 i++) {
180 fPageResources[i]->emit(stream, fCatalog.get(), true);
181 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000182
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000183 fCatalog->emitSubstituteResources(stream, false);
184 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000185 emitFooter(stream, objCount);
186 return true;
187}
188
reed@google.com1feb3302011-07-20 18:43:19 +0000189bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000190 if (fPageTree.count() != 0) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000191 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000192 }
193
194 pageNumber--;
195 SkASSERT(pageNumber >= 0);
196
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000197 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000198 int oldSize = fPages.count();
199 fPages.setCount(pageNumber + 1);
200 for (int i = oldSize; i <= pageNumber; i++) {
201 fPages[i] = NULL;
202 }
203 }
204
205 SkPDFPage* page = new SkPDFPage(pdfDevice);
206 SkSafeUnref(fPages[pageNumber]);
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000207 fPages[pageNumber] = page; // Reference from new passed to fPages.
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000208 return true;
209}
210
reed@google.com1feb3302011-07-20 18:43:19 +0000211bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000212 if (fPageTree.count() != 0) {
213 return false;
214 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000215
216 SkPDFPage* page = new SkPDFPage(pdfDevice);
217 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000218 return true;
219}
220
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000221const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
222 return fPages;
223}
224
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000225void SkPDFDocument::emitHeader(SkWStream* stream) {
226 stream->writeText("%PDF-1.4\n%");
227 // The PDF spec recommends including a comment with four bytes, all
228 // with their high bits set. This is "Skia" with the high bits set.
229 stream->write32(0xD3EBE9E1);
230 stream->writeText("\n");
231}
232
233size_t SkPDFDocument::headerSize() {
234 SkDynamicMemoryWStream buffer;
235 emitHeader(&buffer);
236 return buffer.getOffset();
237}
238
239void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
240 if (fTrailerDict.get() == NULL) {
241 fTrailerDict = new SkPDFDict();
242 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
243
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000244 // TODO(vandebo) Linearized format will take a Prev entry too.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000245 // TODO(vandebo) PDF/A requires an ID entry.
reed@google.comc789cf12011-07-20 12:14:33 +0000246 fTrailerDict->insertInt("Size", objCount);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000247 fTrailerDict->insert("Root",
248 new SkPDFObjRef(fDocCatalog.get()))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000249 }
250
251 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000252 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000253 stream->writeText("\nstartxref\n");
254 stream->writeBigDecAsText(fXRefFileOffset);
255 stream->writeText("\n%%EOF");
256}