blob: 3ec4877b9825b69fee65ac640633838fcc044f18 [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.org008c4982012-03-21 17:55:04 +000013#include "SkPDFFont.h"
vandebo@chromium.orgbd960c72012-03-21 19:59:04 +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.
vandebo@chromium.orga5180862010-10-26 19:48:49 +000020void addResourcesToCatalog(int firstIndex, bool firstPage,
21 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),
60 fSecondPageFirstResourceIndex(0) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +000061 fCatalog.reset(new SkPDFCatalog(flags));
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000062 fDocCatalog = new SkPDFDict("Catalog");
63 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.org421d6442011-07-20 17:39:01 +000064 fCatalog->addObject(fDocCatalog.get(), 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();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000078}
79
80bool SkPDFDocument::emitPDF(SkWStream* stream) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000081 if (fPages.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000082 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +000083 }
84 for (int i = 0; i < fPages.count(); i++) {
85 if (fPages[i] == NULL) {
86 return false;
87 }
88 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000089
90 // We haven't emitted the document before if fPageTree is empty.
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000091 if (fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000092 SkPDFDict* pageTreeRoot;
vandebo@chromium.org421d6442011-07-20 17:39:01 +000093 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +000094 &pageTreeRoot);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +000095 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000096
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +000097 /* TODO(vandebo): output intent
vandebo@chromium.org2a22e102011-01-25 21:01:34 +000098 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
99 outputIntent->unref(); // SkRefPtr and new both took a reference.
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000100 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000101 outputIntent->insert("OutputConditionIdentifier",
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000102 new SkPDFString("sRGB"))->unref();
vandebo@chromium.org2a22e102011-01-25 21:01:34 +0000103 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
104 intentArray->unref(); // SkRefPtr and new both took a reference.
105 intentArray->append(outputIntent.get());
106 fDocCatalog->insert("OutputIntent", intentArray.get());
107 */
108
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000109 bool firstPage = true;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000110 for (int i = 0; i < fPages.count(); i++) {
111 int resourceCount = fPageResources.count();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000112 fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000113 addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000114 fCatalog.get());
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000115 if (i == 0) {
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000116 firstPage = false;
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000117 fSecondPageFirstResourceIndex = fPageResources.count();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000118 }
119 }
120
vandebo@chromium.org98594282011-07-25 22:34:12 +0000121 // Build font subsetting info before proceeding.
122 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
123
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000124 // Figure out the size of things and inform the catalog of file offsets.
125 off_t fileOffset = headerSize();
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000126 fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
127 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
128 fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
129 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
130 fileOffset += fCatalog->setFileOffset(fPageResources[i],
131 fileOffset);
132 }
vandebo@chromium.org2ef12d42011-07-06 23:31:24 +0000133 // Add the size of resources of substitute objects used on page 1.
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000134 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000135 if (fPages.count() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000136 // TODO(vandebo): For linearized format, save the start of the
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000137 // first page xref table and calculate the size.
138 }
139
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000140 for (int i = 0; i < fPageTree.count(); i++) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000141 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000142 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000143
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000144 for (int i = 1; i < fPages.count(); i++) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000145 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000146 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000147
vandebo@chromium.orga5180862010-10-26 19:48:49 +0000148 for (int i = fSecondPageFirstResourceIndex;
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000149 i < fPageResources.count();
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000150 i++) {
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000151 fileOffset += fCatalog->setFileOffset(fPageResources[i],
152 fileOffset);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000153 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000154
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000155 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
156 false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000157 fXRefFileOffset = fileOffset;
158 }
159
160 emitHeader(stream);
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000161 fDocCatalog->emitObject(stream, fCatalog.get(), true);
162 fPages[0]->emitObject(stream, fCatalog.get(), true);
163 fPages[0]->emitPage(stream, fCatalog.get());
164 for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
165 fPageResources[i]->emit(stream, fCatalog.get(), true);
166 }
167 fCatalog->emitSubstituteResources(stream, true);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000168 // TODO(vandebo): Support linearized format
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000169 // if (fPages.size() > 1) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000170 // // TODO(vandebo): Save the file offset for the first page xref table.
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000171 // fCatalog->emitXrefTable(stream, true);
172 // }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000173
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000174 for (int i = 0; i < fPageTree.count(); i++) {
175 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
176 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000177
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000178 for (int i = 1; i < fPages.count(); i++) {
179 fPages[i]->emitPage(stream, fCatalog.get());
180 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000181
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000182 for (int i = fSecondPageFirstResourceIndex;
183 i < fPageResources.count();
184 i++) {
185 fPageResources[i]->emit(stream, fCatalog.get(), true);
186 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000187
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000188 fCatalog->emitSubstituteResources(stream, false);
189 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000190 emitFooter(stream, objCount);
191 return true;
192}
193
reed@google.com1feb3302011-07-20 18:43:19 +0000194bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000195 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000196 return false;
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000197 }
198
199 pageNumber--;
200 SkASSERT(pageNumber >= 0);
201
vandebo@chromium.org00223fa2011-07-22 01:48:55 +0000202 if (pageNumber >= fPages.count()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000203 int oldSize = fPages.count();
204 fPages.setCount(pageNumber + 1);
205 for (int i = oldSize; i <= pageNumber; i++) {
206 fPages[i] = NULL;
207 }
208 }
209
210 SkPDFPage* page = new SkPDFPage(pdfDevice);
211 SkSafeUnref(fPages[pageNumber]);
ctguil@chromium.orga5c72342011-08-15 23:55:03 +0000212 fPages[pageNumber] = page; // Reference from new passed to fPages.
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000213 return true;
214}
215
reed@google.com1feb3302011-07-20 18:43:19 +0000216bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000217 if (!fPageTree.isEmpty()) {
vandebo@chromium.orgfb6a53a2011-07-18 23:13:19 +0000218 return false;
219 }
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000220
221 SkPDFPage* page = new SkPDFPage(pdfDevice);
222 fPages.push(page); // Reference from new passed to fPages.
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000223 return true;
224}
225
vandebo@chromium.orgbd960c72012-03-21 19:59:04 +0000226void SkPDFDocument::getCountOfFontTypes(
227 int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
228 memset(counts, 0,
229 sizeof(int)* SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1);
230 SkTDArray<SkFontID> seenFonts;
231
232 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
233 const SkTDArray<SkPDFFont*>& fontResources =
234 fPages[pageNumber]->getFontResources();
235 for (int font = 0; font < fontResources.count(); font++) {
236 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
237 if (seenFonts.find(fontID) == -1) {
238 counts[fontResources[font]->getType()]++;
239 seenFonts.push(fontID);
240 }
241 }
242 }
vandebo@chromium.orgd897bfb2011-05-31 18:18:21 +0000243}
244
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000245void SkPDFDocument::emitHeader(SkWStream* stream) {
246 stream->writeText("%PDF-1.4\n%");
247 // The PDF spec recommends including a comment with four bytes, all
248 // with their high bits set. This is "Skia" with the high bits set.
249 stream->write32(0xD3EBE9E1);
250 stream->writeText("\n");
251}
252
253size_t SkPDFDocument::headerSize() {
254 SkDynamicMemoryWStream buffer;
255 emitHeader(&buffer);
256 return buffer.getOffset();
257}
258
259void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
260 if (fTrailerDict.get() == NULL) {
261 fTrailerDict = new SkPDFDict();
262 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
263
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000264 // TODO(vandebo): Linearized format will take a Prev entry too.
265 // TODO(vandebo): PDF/A requires an ID entry.
reed@google.comc789cf12011-07-20 12:14:33 +0000266 fTrailerDict->insertInt("Size", objCount);
vandebo@chromium.orgf7c15762011-02-01 22:19:44 +0000267 fTrailerDict->insert("Root",
268 new SkPDFObjRef(fDocCatalog.get()))->unref();
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000269 }
270
271 stream->writeText("trailer\n");
vandebo@chromium.org421d6442011-07-20 17:39:01 +0000272 fTrailerDict->emitObject(stream, fCatalog.get(), false);
vandebo@chromium.orgd877fdb2010-10-12 23:08:13 +0000273 stream->writeText("\nstartxref\n");
274 stream->writeBigDecAsText(fXRefFileOffset);
275 stream->writeText("\n%%EOF");
276}