blob: 1cdea25a2f3b706e957acc82fe0c9d23e91effff [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"
vandebo@chromium.org76d6de02012-03-21 20:31:08 +000013#include "SkPDFFont.h"
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +000014#include "SkPDFPage.h"
15#include "SkPDFTypes.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000016#include "SkStream.h"
edisonn@google.com66bedbb2013-03-19 17:19:05 +000017#include "SkTSet.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000018
edisonn@google.com66bedbb2013-03-19 17:19:05 +000019static void addResourcesToCatalog(bool firstPage,
20 SkTSet<SkPDFObject*>* resourceSet,
21 SkPDFCatalog* catalog) {
22 for (int i = 0; i < resourceSet->count(); i++) {
23 catalog->addObject((*resourceSet)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000024 }
25}
26
vandebo@chromium.org98594282011-07-25 22:34:12 +000027static void perform_font_subsetting(SkPDFCatalog* catalog,
28 const SkTDArray<SkPDFPage*>& pages,
29 SkTDArray<SkPDFObject*>* substitutes) {
30 SkASSERT(catalog);
31 SkASSERT(substitutes);
32
33 SkPDFGlyphSetMap usage;
34 for (int i = 0; i < pages.count(); ++i) {
35 usage.merge(pages[i]->getFontGlyphUsage());
36 }
37 SkPDFGlyphSetMap::F2BIter iterator(usage);
commit-bot@chromium.orgaa537d42013-02-28 19:03:13 +000038 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
vandebo@chromium.org98594282011-07-25 22:34:12 +000039 while (entry) {
40 SkPDFFont* subsetFont =
41 entry->fFont->getFontSubset(entry->fGlyphSet);
42 if (subsetFont) {
43 catalog->setSubstitute(entry->fFont, subsetFont);
44 substitutes->push(subsetFont); // Transfer ownership to substitutes
45 }
46 entry = iterator.next();
47 }
48}
49
vandebo@chromium.org421d6442011-07-20 17:39:01 +000050SkPDFDocument::SkPDFDocument(Flags flags)
vandebo@chromium.org73322072011-06-21 21:19:41 +000051 : fXRefFileOffset(0),
reed@google.comaf777272012-09-20 18:19:26 +000052 fTrailerDict(NULL) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +000053 fCatalog.reset(new SkPDFCatalog(flags));
reed@google.comaf777272012-09-20 18:19:26 +000054 fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog"));
55 fCatalog->addObject(fDocCatalog, true);
edisonn@google.com66bedbb2013-03-19 17:19:05 +000056 fFirstPageResources = NULL;
57 fOtherPageResources = NULL;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000058}
59
60SkPDFDocument::~SkPDFDocument() {
61 fPages.safeUnrefAll();
62
63 // The page tree has both child and parent pointers, so it creates a
64 // reference cycle. We must clear that cycle to properly reclaim memory.
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000065 for (int i = 0; i < fPageTree.count(); i++) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000066 fPageTree[i]->clear();
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000067 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000068 fPageTree.safeUnrefAll();
edisonn@google.com66bedbb2013-03-19 17:19:05 +000069 fFirstPageResources->safeUnrefAll();
70 fOtherPageResources->safeUnrefAll();
vandebo@chromium.org98594282011-07-25 22:34:12 +000071 fSubstitutes.safeUnrefAll();
skia.committer@gmail.com4c5ea442012-09-21 02:01:01 +000072
reed@google.comaf777272012-09-20 18:19:26 +000073 fDocCatalog->unref();
74 SkSafeUnref(fTrailerDict);
edisonn@google.com66bedbb2013-03-19 17:19:05 +000075 SkDELETE(fFirstPageResources);
76 SkDELETE(fOtherPageResources);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000077}
78
79bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000080 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000081 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000082 }
83 for (int i = 0; i < fPages.count(); i++) {
84 if (fPages[i] == NULL) {
85 return false;
86 }
87 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000088
edisonn@google.com66bedbb2013-03-19 17:19:05 +000089 fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>);
90 fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>);
91
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000092 // We haven't emitted the document before if fPageTree is empty.
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000093 if (fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000094 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +000095 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000096 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000097 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000098
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000099 /* TODO(vandebo): output intent
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +0000100 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000101 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000102 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000103 new SkPDFString("sRGB"))->unref();
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +0000104 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000105 intentArray->append(outputIntent.get());
106 fDocCatalog->insert("OutputIntent", intentArray.get());
107 */
108
epoger@google.comb58772f2013-03-08 09:09:10 +0000109 SkPDFDict* dests = SkNEW(SkPDFDict); // fPageResources owns reference
110 fCatalog->addObject(dests, true /* onFirstPage */);
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000111 fFirstPageResources->add(dests);
epoger@google.comb58772f2013-03-08 09:09:10 +0000112
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000113 bool firstPage = true;
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000114 /* The references returned in newResources are transfered to
115 * fFirstPageResources or fOtherPageResources depending on firstPage and
116 * knownResources doesn't have a reference but just relies on the other
117 * two sets to maintain a reference.
118 */
119 SkTSet<SkPDFObject*> knownResources;
120
121 // mergeInto returns the number of duplicates.
122 // If there are duplicates, there is a bug and we mess ref counting.
123 SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources);
124 SkASSERT(duplicates == 0);
125
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000126 for (int i = 0; i < fPages.count(); i++) {
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000127 if (i == 1) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000128 firstPage = false;
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000129 SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000130 }
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000131 SkTSet<SkPDFObject*> newResources;
132 fPages[i]->finalizePage(
133 fCatalog.get(), firstPage, knownResources, &newResources);
134 addResourcesToCatalog(firstPage, &newResources, fCatalog.get());
135 if (firstPage) {
136 SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources);
137 } else {
138 SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources);
139 }
140 SkASSERT(duplicates == 0);
141
142 SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources);
143 SkASSERT(duplicates == 0);
144
145 fPages[i]->appendDestinations(dests);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000146 }
147
epoger@google.comb58772f2013-03-08 09:09:10 +0000148 fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (dests)))->unref();
149
vandebo@chromium.org98594282011-07-25 22:34:12 +0000150 // Build font subsetting info before proceeding.
151 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
152
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000153 // Figure out the size of things and inform the catalog of file offsets.
154 off_t fileOffset = headerSize();
reed@google.comaf777272012-09-20 18:19:26 +0000155 fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000156 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
caryclark@google.com1445a0d2012-06-06 12:04:24 +0000157 fileOffset += fPages[0]->getPageSize(fCatalog.get(),
158 (size_t) fileOffset);
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000159 for (int i = 0; i < fFirstPageResources->count(); i++) {
160 fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i],
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000161 fileOffset);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000162 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000163 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000164 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000165 if (fPages.count() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000166 // TODO(vandebo): For linearized format, save the start of the
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000167 // first page xref table and calculate the size.
168 }
169
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000170 for (int i = 0; i < fPageTree.count(); i++) {
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000171 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000172 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000173
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000174 for (int i = 1; i < fPages.count(); i++) {
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000175 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000176 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000177
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000178 for (int i = 0; i < fOtherPageResources->count(); i++) {
179 fileOffset += fCatalog->setFileOffset(
180 (*fOtherPageResources)[i], fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000181 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000182
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000183 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
184 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000185 fXRefFileOffset = fileOffset;
186 }
187
188 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000189 fDocCatalog->emitObject(stream, fCatalog.get(), true);
190 fPages[0]->emitObject(stream, fCatalog.get(), true);
191 fPages[0]->emitPage(stream, fCatalog.get());
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000192 for (int i = 0; i < fFirstPageResources->count(); i++) {
193 (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000194 }
195 fCatalog->emitSubstituteResources(stream, true);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000196 // TODO(vandebo): Support linearized format
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000197 // if (fPages.size() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000198 // // TODO(vandebo): Save the file offset for the first page xref table.
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000199 // fCatalog->emitXrefTable(stream, true);
200 // }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000201
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000202 for (int i = 0; i < fPageTree.count(); i++) {
203 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
204 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000205
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000206 for (int i = 1; i < fPages.count(); i++) {
207 fPages[i]->emitPage(stream, fCatalog.get());
208 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000209
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000210 for (int i = 0; i < fOtherPageResources->count(); i++) {
211 (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000212 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000213
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000214 fCatalog->emitSubstituteResources(stream, false);
215 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000216 emitFooter(stream, objCount);
217 return true;
218}
219
reed@google.com1feb3302011-07-20 18:43:19 +0000220bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000221 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000222 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000223 }
224
225 pageNumber--;
226 SkASSERT(pageNumber >= 0);
227
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000228 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000229 int oldSize = fPages.count();
230 fPages.setCount(pageNumber + 1);
231 for (int i = oldSize; i <= pageNumber; i++) {
232 fPages[i] = NULL;
233 }
234 }
235
236 SkPDFPage* page = new SkPDFPage(pdfDevice);
237 SkSafeUnref(fPages[pageNumber]);
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000238 fPages[pageNumber] = page; // Reference from new passed to fPages.
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000239 return true;
240}
241
reed@google.com1feb3302011-07-20 18:43:19 +0000242bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000243 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000244 return false;
245 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000246
247 SkPDFPage* page = new SkPDFPage(pdfDevice);
248 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000249 return true;
250}
251
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000252void SkPDFDocument::getCountOfFontTypes(
253 int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
vandebo@chromium.org6e08bfe2012-03-26 23:58:04 +0000254 sk_bzero(counts, sizeof(int) *
255 (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1));
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000256 SkTDArray<SkFontID> seenFonts;
257
258 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
259 const SkTDArray<SkPDFFont*>& fontResources =
260 fPages[pageNumber]->getFontResources();
261 for (int font = 0; font < fontResources.count(); font++) {
262 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
263 if (seenFonts.find(fontID) == -1) {
264 counts[fontResources[font]->getType()]++;
265 seenFonts.push(fontID);
266 }
267 }
268 }
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000269}
270
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000271void SkPDFDocument::emitHeader(SkWStream* stream) {
272 stream->writeText("%PDF-1.4\n%");
273 // The PDF spec recommends including a comment with four bytes, all
274 // with their high bits set. This is "Skia" with the high bits set.
275 stream->write32(0xD3EBE9E1);
276 stream->writeText("\n");
277}
278
279size_t SkPDFDocument::headerSize() {
280 SkDynamicMemoryWStream buffer;
281 emitHeader(&buffer);
282 return buffer.getOffset();
283}
284
285void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
reed@google.comaf777272012-09-20 18:19:26 +0000286 if (NULL == fTrailerDict) {
287 fTrailerDict = SkNEW(SkPDFDict);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000288
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000289 // TODO(vandebo): Linearized format will take a Prev entry too.
290 // TODO(vandebo): PDF/A requires an ID entry.
robertphillips@google.com4debcac2012-05-14 16:33:36 +0000291 fTrailerDict->insertInt("Size", int(objCount));
reed@google.comaf777272012-09-20 18:19:26 +0000292 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000293 }
294
295 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000296 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000297 stream->writeText("\nstartxref\n");
298 stream->writeBigDecAsText(fXRefFileOffset);
299 stream->writeText("\n%%EOF");
300}