blob: 7ee27783262d160c33cf99909ca1ff016846a592 [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"
17
robertphillips@google.comacef3c42013-03-20 14:42:51 +000018// Add the resources, starting at firstIndex to the catalog, removing any dupes.
19// A hash table would be really nice here.
20static void addResourcesToCatalog(int firstIndex, bool firstPage,
21 SkTDArray<SkPDFObject*>* resourceList,
22 SkPDFCatalog* catalog) {
23 for (int i = firstIndex; i < resourceList->count(); i++) {
24 int index = resourceList->find((*resourceList)[i]);
25 if (index != i) {
26 (*resourceList)[i]->unref();
27 resourceList->removeShuffle(i);
28 i--;
29 } else {
30 catalog->addObject((*resourceList)[i], firstPage);
31 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000032 }
33}
34
vandebo@chromium.org98594282011-07-25 22:34:12 +000035static void perform_font_subsetting(SkPDFCatalog* catalog,
36 const SkTDArray<SkPDFPage*>& pages,
37 SkTDArray<SkPDFObject*>* substitutes) {
38 SkASSERT(catalog);
39 SkASSERT(substitutes);
40
41 SkPDFGlyphSetMap usage;
42 for (int i = 0; i < pages.count(); ++i) {
43 usage.merge(pages[i]->getFontGlyphUsage());
44 }
45 SkPDFGlyphSetMap::F2BIter iterator(usage);
commit-bot@chromium.orgaa537d42013-02-28 19:03:13 +000046 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
vandebo@chromium.org98594282011-07-25 22:34:12 +000047 while (entry) {
48 SkPDFFont* subsetFont =
49 entry->fFont->getFontSubset(entry->fGlyphSet);
50 if (subsetFont) {
51 catalog->setSubstitute(entry->fFont, subsetFont);
52 substitutes->push(subsetFont); // Transfer ownership to substitutes
53 }
54 entry = iterator.next();
55 }
56}
57
vandebo@chromium.org421d6442011-07-20 17:39:01 +000058SkPDFDocument::SkPDFDocument(Flags flags)
vandebo@chromium.org73322072011-06-21 21:19:41 +000059 : fXRefFileOffset(0),
robertphillips@google.comacef3c42013-03-20 14:42:51 +000060 fSecondPageFirstResourceIndex(0),
reed@google.comaf777272012-09-20 18:19:26 +000061 fTrailerDict(NULL) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +000062 fCatalog.reset(new SkPDFCatalog(flags));
reed@google.comaf777272012-09-20 18:19:26 +000063 fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog"));
64 fCatalog->addObject(fDocCatalog, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000065}
66
67SkPDFDocument::~SkPDFDocument() {
68 fPages.safeUnrefAll();
69
70 // The page tree has both child and parent pointers, so it creates a
71 // reference cycle. We must clear that cycle to properly reclaim memory.
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000072 for (int i = 0; i < fPageTree.count(); i++) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000073 fPageTree[i]->clear();
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000074 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000075 fPageTree.safeUnrefAll();
robertphillips@google.comacef3c42013-03-20 14:42:51 +000076 fPageResources.safeUnrefAll();
vandebo@chromium.org98594282011-07-25 22:34:12 +000077 fSubstitutes.safeUnrefAll();
skia.committer@gmail.com4c5ea442012-09-21 02:01:01 +000078
reed@google.comaf777272012-09-20 18:19:26 +000079 fDocCatalog->unref();
80 SkSafeUnref(fTrailerDict);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000081}
82
83bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000084 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000085 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000086 }
87 for (int i = 0; i < fPages.count(); i++) {
88 if (fPages[i] == NULL) {
89 return false;
90 }
91 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000092
93 // We haven't emitted the document before if fPageTree is empty.
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000094 if (fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000095 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +000096 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000097 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000098 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000099
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000100 /* TODO(vandebo): output intent
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +0000101 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000102 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000103 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000104 new SkPDFString("sRGB"))->unref();
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +0000105 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000106 intentArray->append(outputIntent.get());
107 fDocCatalog->insert("OutputIntent", intentArray.get());
108 */
109
epoger@google.comb58772f2013-03-08 09:09:10 +0000110 SkPDFDict* dests = SkNEW(SkPDFDict); // fPageResources owns reference
111 fCatalog->addObject(dests, true /* onFirstPage */);
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000112 fPageResources.push(dests);
epoger@google.comb58772f2013-03-08 09:09:10 +0000113
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++) {
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000116 int resourceCount = fPageResources.count();
117 fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
118 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
119 fCatalog.get());
edisonn@google.com66bedbb2013-03-19 17:19:05 +0000120 fPages[i]->appendDestinations(dests);
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000121 if (i == 0) {
122 firstPage = false;
123 fSecondPageFirstResourceIndex = fPageResources.count();
124 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000125 }
126
epoger@google.comb58772f2013-03-08 09:09:10 +0000127 fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (dests)))->unref();
128
vandebo@chromium.org98594282011-07-25 22:34:12 +0000129 // Build font subsetting info before proceeding.
130 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
131
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000132 // Figure out the size of things and inform the catalog of file offsets.
133 off_t fileOffset = headerSize();
reed@google.comaf777272012-09-20 18:19:26 +0000134 fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000135 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
caryclark@google.com1445a0d2012-06-06 12:04:24 +0000136 fileOffset += fPages[0]->getPageSize(fCatalog.get(),
137 (size_t) fileOffset);
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000138 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
139 fileOffset += fCatalog->setFileOffset(fPageResources[i],
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000140 fileOffset);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000141 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000142 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000143 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000144 if (fPages.count() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000145 // TODO(vandebo): For linearized format, save the start of the
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000146 // first page xref table and calculate the size.
147 }
148
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000149 for (int i = 0; i < fPageTree.count(); i++) {
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000150 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000151 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000152
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000153 for (int i = 1; i < fPages.count(); i++) {
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000154 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000155 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000156
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000157 for (int i = fSecondPageFirstResourceIndex;
158 i < fPageResources.count();
159 i++) {
160 fileOffset += fCatalog->setFileOffset(fPageResources[i],
161 fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000162 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000163
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000164 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
165 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000166 fXRefFileOffset = fileOffset;
167 }
168
169 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000170 fDocCatalog->emitObject(stream, fCatalog.get(), true);
171 fPages[0]->emitObject(stream, fCatalog.get(), true);
172 fPages[0]->emitPage(stream, fCatalog.get());
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000173 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
174 fPageResources[i]->emit(stream, fCatalog.get(), true);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000175 }
176 fCatalog->emitSubstituteResources(stream, true);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000177 // TODO(vandebo): Support linearized format
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000178 // if (fPages.size() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000179 // // TODO(vandebo): Save the file offset for the first page xref table.
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000180 // fCatalog->emitXrefTable(stream, true);
181 // }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000182
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000183 for (int i = 0; i < fPageTree.count(); i++) {
184 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
185 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000186
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000187 for (int i = 1; i < fPages.count(); i++) {
188 fPages[i]->emitPage(stream, fCatalog.get());
189 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000190
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000191 for (int i = fSecondPageFirstResourceIndex;
192 i < fPageResources.count();
193 i++) {
194 fPageResources[i]->emit(stream, fCatalog.get(), true);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000195 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000196
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000197 fCatalog->emitSubstituteResources(stream, false);
198 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000199 emitFooter(stream, objCount);
200 return true;
201}
202
reed@google.com1feb3302011-07-20 18:43:19 +0000203bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000204 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000205 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000206 }
207
208 pageNumber--;
209 SkASSERT(pageNumber >= 0);
210
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000211 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000212 int oldSize = fPages.count();
213 fPages.setCount(pageNumber + 1);
214 for (int i = oldSize; i <= pageNumber; i++) {
215 fPages[i] = NULL;
216 }
217 }
218
219 SkPDFPage* page = new SkPDFPage(pdfDevice);
220 SkSafeUnref(fPages[pageNumber]);
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000221 fPages[pageNumber] = page; // Reference from new passed to fPages.
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000222 return true;
223}
224
reed@google.com1feb3302011-07-20 18:43:19 +0000225bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000226 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000227 return false;
228 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000229
230 SkPDFPage* page = new SkPDFPage(pdfDevice);
231 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000232 return true;
233}
234
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000235void SkPDFDocument::getCountOfFontTypes(
236 int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
vandebo@chromium.org6e08bfe2012-03-26 23:58:04 +0000237 sk_bzero(counts, sizeof(int) *
238 (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1));
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000239 SkTDArray<SkFontID> seenFonts;
240
241 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
242 const SkTDArray<SkPDFFont*>& fontResources =
243 fPages[pageNumber]->getFontResources();
244 for (int font = 0; font < fontResources.count(); font++) {
245 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
246 if (seenFonts.find(fontID) == -1) {
247 counts[fontResources[font]->getType()]++;
248 seenFonts.push(fontID);
249 }
250 }
251 }
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000252}
253
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000254void SkPDFDocument::emitHeader(SkWStream* stream) {
255 stream->writeText("%PDF-1.4\n%");
256 // The PDF spec recommends including a comment with four bytes, all
257 // with their high bits set. This is "Skia" with the high bits set.
258 stream->write32(0xD3EBE9E1);
259 stream->writeText("\n");
260}
261
262size_t SkPDFDocument::headerSize() {
263 SkDynamicMemoryWStream buffer;
264 emitHeader(&buffer);
265 return buffer.getOffset();
266}
267
268void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
reed@google.comaf777272012-09-20 18:19:26 +0000269 if (NULL == fTrailerDict) {
270 fTrailerDict = SkNEW(SkPDFDict);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000271
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000272 // TODO(vandebo): Linearized format will take a Prev entry too.
273 // TODO(vandebo): PDF/A requires an ID entry.
robertphillips@google.com4debcac2012-05-14 16:33:36 +0000274 fTrailerDict->insertInt("Size", int(objCount));
reed@google.comaf777272012-09-20 18:19:26 +0000275 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000276 }
277
278 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000279 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000280 stream->writeText("\nstartxref\n");
281 stream->writeBigDecAsText(fXRefFileOffset);
282 stream->writeText("\n%%EOF");
283}