blob: d47f1f35603fdd32f28a2ebd8063211eb92929da [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
vandebo@chromium.orga5180862010-10-26 19:48:49 +000018// Add the resources, starting at firstIndex to the catalog, removing any dupes.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000019// A hash table would be really nice here.
caryclark@google.com1445a0d2012-06-06 12:04:24 +000020static void addResourcesToCatalog(int firstIndex, bool firstPage,
vandebo@chromium.orga5180862010-10-26 19:48:49 +000021 SkTDArray<SkPDFObject*>* resourceList,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000022 SkPDFCatalog* catalog) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000023 for (int i = firstIndex; i < resourceList->count(); i++) {
24 int index = resourceList->find((*resourceList)[i]);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000025 if (index != i) {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000026 (*resourceList)[i]->unref();
27 resourceList->removeShuffle(i);
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000028 i--;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000029 } else {
vandebo@chromium.orga5180862010-10-26 19:48:49 +000030 catalog->addObject((*resourceList)[i], firstPage);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000031 }
32 }
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);
46 SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
47 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),
reed@google.comaf777272012-09-20 18:19:26 +000060 fSecondPageFirstResourceIndex(0),
61 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();
76 fPageResources.safeUnrefAll();
vandebo@chromium.org98594282011-07-25 22:34:12 +000077 fSubstitutes.safeUnrefAll();
reed@google.comaf777272012-09-20 18:19:26 +000078
79 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.org2a22e102011-01-25 21:01:34 +0000101 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
102 outputIntent->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000103 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000104 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000105 new SkPDFString("sRGB"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000106 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
107 intentArray->unref(); // SkRefPtr and new both took a reference.
108 intentArray->append(outputIntent.get());
109 fDocCatalog->insert("OutputIntent", intentArray.get());
110 */
111
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000112 bool firstPage = true;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000113 for (int i = 0; i < fPages.count(); i++) {
114 int resourceCount = fPageResources.count();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000115 fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000116 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000117 fCatalog.get());
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000118 if (i == 0) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000119 firstPage = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000120 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000121 }
122 }
123
vandebo@chromium.org98594282011-07-25 22:34:12 +0000124 // Build font subsetting info before proceeding.
125 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
126
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000127 // Figure out the size of things and inform the catalog of file offsets.
128 off_t fileOffset = headerSize();
reed@google.comaf777272012-09-20 18:19:26 +0000129 fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000130 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
caryclark@google.com1445a0d2012-06-06 12:04:24 +0000131 fileOffset += fPages[0]->getPageSize(fCatalog.get(),
132 (size_t) fileOffset);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000133 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
134 fileOffset += fCatalog->setFileOffset(fPageResources[i],
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000135 fileOffset);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000136 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000137 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000138 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000139 if (fPages.count() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000140 // TODO(vandebo): For linearized format, save the start of the
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000141 // first page xref table and calculate the size.
142 }
143
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000144 for (int i = 0; i < fPageTree.count(); i++) {
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000145 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000146 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000147
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000148 for (int i = 1; i < fPages.count(); i++) {
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000149 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000150 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000151
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000152 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000153 i < fPageResources.count();
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000154 i++) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000155 fileOffset += fCatalog->setFileOffset(fPageResources[i],
vandebo@chromium.org30580f62012-07-12 20:22:04 +0000156 fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000157 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000158
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000159 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
160 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000161 fXRefFileOffset = fileOffset;
162 }
163
164 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000165 fDocCatalog->emitObject(stream, fCatalog.get(), true);
166 fPages[0]->emitObject(stream, fCatalog.get(), true);
167 fPages[0]->emitPage(stream, fCatalog.get());
168 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
169 fPageResources[i]->emit(stream, fCatalog.get(), true);
170 }
171 fCatalog->emitSubstituteResources(stream, true);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000172 // TODO(vandebo): Support linearized format
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000173 // if (fPages.size() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000174 // // TODO(vandebo): Save the file offset for the first page xref table.
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000175 // fCatalog->emitXrefTable(stream, true);
176 // }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000177
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000178 for (int i = 0; i < fPageTree.count(); i++) {
179 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
180 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000181
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000182 for (int i = 1; i < fPages.count(); i++) {
183 fPages[i]->emitPage(stream, fCatalog.get());
184 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000185
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000186 for (int i = fSecondPageFirstResourceIndex;
187 i < fPageResources.count();
188 i++) {
189 fPageResources[i]->emit(stream, fCatalog.get(), true);
190 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000191
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000192 fCatalog->emitSubstituteResources(stream, false);
193 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000194 emitFooter(stream, objCount);
195 return true;
196}
197
reed@google.com1feb3302011-07-20 18:43:19 +0000198bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000199 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000200 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000201 }
202
203 pageNumber--;
204 SkASSERT(pageNumber >= 0);
205
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000206 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000207 int oldSize = fPages.count();
208 fPages.setCount(pageNumber + 1);
209 for (int i = oldSize; i <= pageNumber; i++) {
210 fPages[i] = NULL;
211 }
212 }
213
214 SkPDFPage* page = new SkPDFPage(pdfDevice);
215 SkSafeUnref(fPages[pageNumber]);
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000216 fPages[pageNumber] = page; // Reference from new passed to fPages.
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000217 return true;
218}
219
reed@google.com1feb3302011-07-20 18:43:19 +0000220bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000221 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000222 return false;
223 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000224
225 SkPDFPage* page = new SkPDFPage(pdfDevice);
226 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000227 return true;
228}
229
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000230void SkPDFDocument::getCountOfFontTypes(
231 int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
vandebo@chromium.org6e08bfe2012-03-26 23:58:04 +0000232 sk_bzero(counts, sizeof(int) *
233 (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1));
vandebo@chromium.org7d6c8f92012-03-22 20:45:15 +0000234 SkTDArray<SkFontID> seenFonts;
235
236 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
237 const SkTDArray<SkPDFFont*>& fontResources =
238 fPages[pageNumber]->getFontResources();
239 for (int font = 0; font < fontResources.count(); font++) {
240 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
241 if (seenFonts.find(fontID) == -1) {
242 counts[fontResources[font]->getType()]++;
243 seenFonts.push(fontID);
244 }
245 }
246 }
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000247}
248
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000249void SkPDFDocument::emitHeader(SkWStream* stream) {
250 stream->writeText("%PDF-1.4\n%");
251 // The PDF spec recommends including a comment with four bytes, all
252 // with their high bits set. This is "Skia" with the high bits set.
253 stream->write32(0xD3EBE9E1);
254 stream->writeText("\n");
255}
256
257size_t SkPDFDocument::headerSize() {
258 SkDynamicMemoryWStream buffer;
259 emitHeader(&buffer);
260 return buffer.getOffset();
261}
262
263void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
reed@google.comaf777272012-09-20 18:19:26 +0000264 if (NULL == fTrailerDict) {
265 fTrailerDict = SkNEW(SkPDFDict);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000266
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000267 // TODO(vandebo): Linearized format will take a Prev entry too.
268 // TODO(vandebo): PDF/A requires an ID entry.
robertphillips@google.com4debcac2012-05-14 16:33:36 +0000269 fTrailerDict->insertInt("Size", int(objCount));
reed@google.comaf777272012-09-20 18:19:26 +0000270 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000271 }
272
273 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000274 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000275 stream->writeText("\nstartxref\n");
276 stream->writeBigDecAsText(fXRefFileOffset);
277 stream->writeText("\n%%EOF");
278}