blob: c4a5d448a54cc1ca6dd873a4a7737b3095aee7ac [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.com6addb192013-04-02 15:33:08 +000017#include "SkTSet.h"
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000018
edisonn@google.com6addb192013-04-02 15:33:08 +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.com6addb192013-04-02 15:33:08 +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.com6addb192013-04-02 15:33:08 +000069
70 if (fFirstPageResources) {
71 fFirstPageResources->safeUnrefAll();
72 }
73 if (fOtherPageResources) {
74 fOtherPageResources->safeUnrefAll();
75 }
76
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);
edisonn@google.com6addb192013-04-02 15:33:08 +000081 SkDELETE(fFirstPageResources);
82 SkDELETE(fOtherPageResources);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000083}
84
halcanaryf361b712015-01-13 07:12:57 -080085namespace {
86class Streamer {
87public:
88 Streamer(SkPDFCatalog* cat, SkWStream* out)
89 : fCat(cat), fOut(out), fBaseOffset(SkToOffT(out->bytesWritten())) {
90 }
91
92 void stream(SkPDFObject* obj) {
93 fCat->setFileOffset(obj, this->offset());
94 obj->emit(fOut, fCat, true);
95 }
96
97 off_t offset() {
98 return SkToOffT(fOut->bytesWritten()) - fBaseOffset;
99 }
100
101private:
102 SkPDFCatalog* const fCat;
103 SkWStream* const fOut;
104 const off_t fBaseOffset;
105};
106} // namespace
107
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000108bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000109 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000110 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000111 }
112 for (int i = 0; i < fPages.count(); i++) {
113 if (fPages[i] == NULL) {
114 return false;
115 }
116 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000117
edisonn@google.com6addb192013-04-02 15:33:08 +0000118 fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>);
119 fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>);
120
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000121 // We haven't emitted the document before if fPageTree is empty.
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000122 if (fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000123 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000124 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000125 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000126 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000127
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000128 /* TODO(vandebo): output intent
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +0000129 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000130 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000131 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000132 new SkPDFString("sRGB"))->unref();
vandebo@chromium.orgd96d17b2013-01-04 19:31:24 +0000133 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000134 intentArray->append(outputIntent.get());
135 fDocCatalog->insert("OutputIntent", intentArray.get());
136 */
137
vandebo@chromium.org1a475232013-05-10 18:29:43 +0000138 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict));
epoger@google.comb58772f2013-03-08 09:09:10 +0000139
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000140 bool firstPage = true;
edisonn@google.com6addb192013-04-02 15:33:08 +0000141 /* The references returned in newResources are transfered to
142 * fFirstPageResources or fOtherPageResources depending on firstPage and
143 * knownResources doesn't have a reference but just relies on the other
144 * two sets to maintain a reference.
145 */
146 SkTSet<SkPDFObject*> knownResources;
147
148 // mergeInto returns the number of duplicates.
149 // If there are duplicates, there is a bug and we mess ref counting.
150 SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources);
151 SkASSERT(duplicates == 0);
152
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000153 for (int i = 0; i < fPages.count(); i++) {
edisonn@google.com6addb192013-04-02 15:33:08 +0000154 if (i == 1) {
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000155 firstPage = false;
edisonn@google.com6addb192013-04-02 15:33:08 +0000156 SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources);
robertphillips@google.comacef3c42013-03-20 14:42:51 +0000157 }
edisonn@google.com6addb192013-04-02 15:33:08 +0000158 SkTSet<SkPDFObject*> newResources;
159 fPages[i]->finalizePage(
160 fCatalog.get(), firstPage, knownResources, &newResources);
161 addResourcesToCatalog(firstPage, &newResources, fCatalog.get());
162 if (firstPage) {
163 SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources);
164 } else {
165 SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources);
166 }
167 SkASSERT(duplicates == 0);
168
169 SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources);
170 SkASSERT(duplicates == 0);
171
172 fPages[i]->appendDestinations(dests);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000173 }
174
vandebo@chromium.org1a475232013-05-10 18:29:43 +0000175 if (dests->size() > 0) {
176 SkPDFDict* raw_dests = dests.get();
177 fFirstPageResources->add(dests.detach()); // Transfer ownership.
178 fCatalog->addObject(raw_dests, true /* onFirstPage */);
179 fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->unref();
180 }
epoger@google.comb58772f2013-03-08 09:09:10 +0000181
vandebo@chromium.org98594282011-07-25 22:34:12 +0000182 // Build font subsetting info before proceeding.
183 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000184 }
185
halcanaryf361b712015-01-13 07:12:57 -0800186 Streamer out(fCatalog, stream);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000187 emitHeader(stream);
halcanaryf361b712015-01-13 07:12:57 -0800188
189 out.stream(fDocCatalog);
190 out.stream(fPages[0]);
191 out.stream(fPages[0]->getContentStream());
192
edisonn@google.com6addb192013-04-02 15:33:08 +0000193 for (int i = 0; i < fFirstPageResources->count(); i++) {
halcanaryf361b712015-01-13 07:12:57 -0800194 out.stream((*fFirstPageResources)[i]);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000195 }
halcanaryf361b712015-01-13 07:12:57 -0800196
197 SkTSet<SkPDFObject*>* firstPageSubstituteResources =
198 fCatalog->getSubstituteList(true);
199 for (int i = 0; i < firstPageSubstituteResources->count(); ++i) {
200 out.stream((*firstPageSubstituteResources)[i]);
201 }
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000202 // TODO(vandebo): Support linearized format
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000203 // if (fPages.size() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000204 // // TODO(vandebo): Save the file offset for the first page xref table.
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000205 // fCatalog->emitXrefTable(stream, true);
206 // }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000207
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000208 for (int i = 0; i < fPageTree.count(); i++) {
halcanaryf361b712015-01-13 07:12:57 -0800209 out.stream(fPageTree[i]);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000210 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000211
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000212 for (int i = 1; i < fPages.count(); i++) {
halcanaryf361b712015-01-13 07:12:57 -0800213 out.stream(fPages[i]->getContentStream());
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000214 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000215
edisonn@google.com6addb192013-04-02 15:33:08 +0000216 for (int i = 0; i < fOtherPageResources->count(); i++) {
halcanaryf361b712015-01-13 07:12:57 -0800217 out.stream((*fOtherPageResources)[i]);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000218 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000219
halcanaryf361b712015-01-13 07:12:57 -0800220 SkTSet<SkPDFObject*>* otherSubstituteResources =
221 fCatalog->getSubstituteList(false);
222 for (int i = 0; i < otherSubstituteResources->count(); ++i) {
223 out.stream((*otherSubstituteResources)[i]);
224 }
225
226 fXRefFileOffset = out.offset();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000227 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000228 emitFooter(stream, objCount);
229 return true;
230}
231
reed@google.com1feb3302011-07-20 18:43:19 +0000232bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000233 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000234 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000235 }
236
237 pageNumber--;
238 SkASSERT(pageNumber >= 0);
239
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000240 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000241 int oldSize = fPages.count();
242 fPages.setCount(pageNumber + 1);
243 for (int i = oldSize; i <= pageNumber; i++) {
244 fPages[i] = NULL;
245 }
246 }
247
248 SkPDFPage* page = new SkPDFPage(pdfDevice);
249 SkSafeUnref(fPages[pageNumber]);
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000250 fPages[pageNumber] = page; // Reference from new passed to fPages.
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000251 return true;
252}
253
reed@google.com1feb3302011-07-20 18:43:19 +0000254bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000255 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000256 return false;
257 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000258
259 SkPDFPage* page = new SkPDFPage(pdfDevice);
260 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000261 return true;
262}
263
vandebo0f9bad02014-06-19 11:05:39 -0700264// Deprecated.
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000265void SkPDFDocument::getCountOfFontTypes(
vandebo0f9bad02014-06-19 11:05:39 -0700266 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 2]) const {
vandebo@chromium.org6e08bfe2012-03-26 23:58:04 +0000267 sk_bzero(counts, sizeof(int) *
vandebo0f9bad02014-06-19 11:05:39 -0700268 (SkAdvancedTypefaceMetrics::kOther_Font + 2));
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000269 SkTDArray<SkFontID> seenFonts;
vandebo0f9bad02014-06-19 11:05:39 -0700270 int notEmbeddable = 0;
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000271
272 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
273 const SkTDArray<SkPDFFont*>& fontResources =
274 fPages[pageNumber]->getFontResources();
275 for (int font = 0; font < fontResources.count(); font++) {
276 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
277 if (seenFonts.find(fontID) == -1) {
278 counts[fontResources[font]->getType()]++;
279 seenFonts.push(fontID);
vandebo0f9bad02014-06-19 11:05:39 -0700280 if (!fontResources[font]->canEmbed()) {
281 notEmbeddable++;
282 }
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000283 }
284 }
285 }
vandebo0f9bad02014-06-19 11:05:39 -0700286 counts[SkAdvancedTypefaceMetrics::kOther_Font + 1] = notEmbeddable;
287}
288
289void SkPDFDocument::getCountOfFontTypes(
290 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
291 int* notSubsettableCount,
292 int* notEmbeddableCount) const {
293 sk_bzero(counts, sizeof(int) *
294 (SkAdvancedTypefaceMetrics::kOther_Font + 1));
295 SkTDArray<SkFontID> seenFonts;
296 int notSubsettable = 0;
297 int notEmbeddable = 0;
298
299 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
300 const SkTDArray<SkPDFFont*>& fontResources =
301 fPages[pageNumber]->getFontResources();
302 for (int font = 0; font < fontResources.count(); font++) {
303 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
304 if (seenFonts.find(fontID) == -1) {
305 counts[fontResources[font]->getType()]++;
306 seenFonts.push(fontID);
307 if (!fontResources[font]->canSubset()) {
308 notSubsettable++;
309 }
310 if (!fontResources[font]->canEmbed()) {
311 notEmbeddable++;
312 }
313 }
314 }
315 }
316 if (notSubsettableCount) {
317 *notSubsettableCount = notSubsettable;
318
319 }
320 if (notEmbeddableCount) {
321 *notEmbeddableCount = notEmbeddable;
322 }
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000323}
324
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000325void SkPDFDocument::emitHeader(SkWStream* stream) {
326 stream->writeText("%PDF-1.4\n%");
327 // The PDF spec recommends including a comment with four bytes, all
328 // with their high bits set. This is "Skia" with the high bits set.
329 stream->write32(0xD3EBE9E1);
330 stream->writeText("\n");
331}
332
333size_t SkPDFDocument::headerSize() {
334 SkDynamicMemoryWStream buffer;
335 emitHeader(&buffer);
336 return buffer.getOffset();
337}
338
339void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
reed@google.comaf777272012-09-20 18:19:26 +0000340 if (NULL == fTrailerDict) {
341 fTrailerDict = SkNEW(SkPDFDict);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000342
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000343 // TODO(vandebo): Linearized format will take a Prev entry too.
344 // TODO(vandebo): PDF/A requires an ID entry.
robertphillips@google.com4debcac2012-05-14 16:33:36 +0000345 fTrailerDict->insertInt("Size", int(objCount));
reed@google.comaf777272012-09-20 18:19:26 +0000346 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000347 }
348
349 stream->writeText("trailer\n");
halcanary4fc48af2015-01-12 10:07:50 -0800350 fTrailerDict->emitObject(stream, fCatalog.get());
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000351 stream->writeText("\nstartxref\n");
352 stream->writeBigDecAsText(fXRefFileOffset);
353 stream->writeText("\n%%EOF");
354}