1
2 /*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10 #include "SkPDFCatalog.h"
11 #include "SkPDFDevice.h"
12 #include "SkPDFDocument.h"
13 #include "SkPDFFont.h"
14 #include "SkPDFPage.h"
15 #include "SkPDFTypes.h"
16 #include "SkStream.h"
17 #include "SkTSet.h"
18
addResourcesToCatalog(bool firstPage,SkTSet<SkPDFObject * > * resourceSet,SkPDFCatalog * catalog)19 static 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);
24 }
25 }
26
perform_font_subsetting(SkPDFCatalog * catalog,const SkTDArray<SkPDFPage * > & pages,SkTDArray<SkPDFObject * > * substitutes)27 static 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);
38 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
39 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
SkPDFDocument(Flags flags)50 SkPDFDocument::SkPDFDocument(Flags flags)
51 : fXRefFileOffset(0),
52 fTrailerDict(NULL) {
53 fCatalog.reset(new SkPDFCatalog(flags));
54 fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog"));
55 fCatalog->addObject(fDocCatalog, true);
56 fFirstPageResources = NULL;
57 fOtherPageResources = NULL;
58 }
59
~SkPDFDocument()60 SkPDFDocument::~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.
65 for (int i = 0; i < fPageTree.count(); i++) {
66 fPageTree[i]->clear();
67 }
68 fPageTree.safeUnrefAll();
69
70 if (fFirstPageResources) {
71 fFirstPageResources->safeUnrefAll();
72 }
73 if (fOtherPageResources) {
74 fOtherPageResources->safeUnrefAll();
75 }
76
77 fSubstitutes.safeUnrefAll();
78
79 fDocCatalog->unref();
80 SkSafeUnref(fTrailerDict);
81 SkDELETE(fFirstPageResources);
82 SkDELETE(fOtherPageResources);
83 }
84
emitPDF(SkWStream * stream)85 bool SkPDFDocument::emitPDF(SkWStream* stream) {
86 if (fPages.isEmpty()) {
87 return false;
88 }
89 for (int i = 0; i < fPages.count(); i++) {
90 if (fPages[i] == NULL) {
91 return false;
92 }
93 }
94
95 fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>);
96 fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>);
97
98 // We haven't emitted the document before if fPageTree is empty.
99 if (fPageTree.isEmpty()) {
100 SkPDFDict* pageTreeRoot;
101 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
102 &pageTreeRoot);
103 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
104
105 /* TODO(vandebo): output intent
106 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
107 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
108 outputIntent->insert("OutputConditionIdentifier",
109 new SkPDFString("sRGB"))->unref();
110 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
111 intentArray->append(outputIntent.get());
112 fDocCatalog->insert("OutputIntent", intentArray.get());
113 */
114
115 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict));
116
117 bool firstPage = true;
118 /* The references returned in newResources are transfered to
119 * fFirstPageResources or fOtherPageResources depending on firstPage and
120 * knownResources doesn't have a reference but just relies on the other
121 * two sets to maintain a reference.
122 */
123 SkTSet<SkPDFObject*> knownResources;
124
125 // mergeInto returns the number of duplicates.
126 // If there are duplicates, there is a bug and we mess ref counting.
127 SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources);
128 SkASSERT(duplicates == 0);
129
130 for (int i = 0; i < fPages.count(); i++) {
131 if (i == 1) {
132 firstPage = false;
133 SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources);
134 }
135 SkTSet<SkPDFObject*> newResources;
136 fPages[i]->finalizePage(
137 fCatalog.get(), firstPage, knownResources, &newResources);
138 addResourcesToCatalog(firstPage, &newResources, fCatalog.get());
139 if (firstPage) {
140 SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources);
141 } else {
142 SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources);
143 }
144 SkASSERT(duplicates == 0);
145
146 SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources);
147 SkASSERT(duplicates == 0);
148
149 fPages[i]->appendDestinations(dests);
150 }
151
152 if (dests->size() > 0) {
153 SkPDFDict* raw_dests = dests.get();
154 fFirstPageResources->add(dests.detach()); // Transfer ownership.
155 fCatalog->addObject(raw_dests, true /* onFirstPage */);
156 fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->unref();
157 }
158
159 // Build font subsetting info before proceeding.
160 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
161
162 // Figure out the size of things and inform the catalog of file offsets.
163 off_t fileOffset = headerSize();
164 fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
165 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
166 fileOffset += fPages[0]->getPageSize(fCatalog.get(),
167 (size_t) fileOffset);
168 for (int i = 0; i < fFirstPageResources->count(); i++) {
169 fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i],
170 fileOffset);
171 }
172 // Add the size of resources of substitute objects used on page 1.
173 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
174 if (fPages.count() > 1) {
175 // TODO(vandebo): For linearized format, save the start of the
176 // first page xref table and calculate the size.
177 }
178
179 for (int i = 0; i < fPageTree.count(); i++) {
180 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
181 }
182
183 for (int i = 1; i < fPages.count(); i++) {
184 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
185 }
186
187 for (int i = 0; i < fOtherPageResources->count(); i++) {
188 fileOffset += fCatalog->setFileOffset(
189 (*fOtherPageResources)[i], fileOffset);
190 }
191
192 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
193 false);
194 fXRefFileOffset = fileOffset;
195 }
196
197 emitHeader(stream);
198 fDocCatalog->emitObject(stream, fCatalog.get(), true);
199 fPages[0]->emitObject(stream, fCatalog.get(), true);
200 fPages[0]->emitPage(stream, fCatalog.get());
201 for (int i = 0; i < fFirstPageResources->count(); i++) {
202 (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true);
203 }
204 fCatalog->emitSubstituteResources(stream, true);
205 // TODO(vandebo): Support linearized format
206 // if (fPages.size() > 1) {
207 // // TODO(vandebo): Save the file offset for the first page xref table.
208 // fCatalog->emitXrefTable(stream, true);
209 // }
210
211 for (int i = 0; i < fPageTree.count(); i++) {
212 fPageTree[i]->emitObject(stream, fCatalog.get(), true);
213 }
214
215 for (int i = 1; i < fPages.count(); i++) {
216 fPages[i]->emitPage(stream, fCatalog.get());
217 }
218
219 for (int i = 0; i < fOtherPageResources->count(); i++) {
220 (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true);
221 }
222
223 fCatalog->emitSubstituteResources(stream, false);
224 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
225 emitFooter(stream, objCount);
226 return true;
227 }
228
setPage(int pageNumber,SkPDFDevice * pdfDevice)229 bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
230 if (!fPageTree.isEmpty()) {
231 return false;
232 }
233
234 pageNumber--;
235 SkASSERT(pageNumber >= 0);
236
237 if (pageNumber >= fPages.count()) {
238 int oldSize = fPages.count();
239 fPages.setCount(pageNumber + 1);
240 for (int i = oldSize; i <= pageNumber; i++) {
241 fPages[i] = NULL;
242 }
243 }
244
245 SkPDFPage* page = new SkPDFPage(pdfDevice);
246 SkSafeUnref(fPages[pageNumber]);
247 fPages[pageNumber] = page; // Reference from new passed to fPages.
248 return true;
249 }
250
appendPage(SkPDFDevice * pdfDevice)251 bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
252 if (!fPageTree.isEmpty()) {
253 return false;
254 }
255
256 SkPDFPage* page = new SkPDFPage(pdfDevice);
257 fPages.push(page); // Reference from new passed to fPages.
258 return true;
259 }
260
261 // Deprecated.
getCountOfFontTypes(int counts[SkAdvancedTypefaceMetrics::kOther_Font+2]) const262 void SkPDFDocument::getCountOfFontTypes(
263 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 2]) const {
264 sk_bzero(counts, sizeof(int) *
265 (SkAdvancedTypefaceMetrics::kOther_Font + 2));
266 SkTDArray<SkFontID> seenFonts;
267 int notEmbeddable = 0;
268
269 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
270 const SkTDArray<SkPDFFont*>& fontResources =
271 fPages[pageNumber]->getFontResources();
272 for (int font = 0; font < fontResources.count(); font++) {
273 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
274 if (seenFonts.find(fontID) == -1) {
275 counts[fontResources[font]->getType()]++;
276 seenFonts.push(fontID);
277 if (!fontResources[font]->canEmbed()) {
278 notEmbeddable++;
279 }
280 }
281 }
282 }
283 counts[SkAdvancedTypefaceMetrics::kOther_Font + 1] = notEmbeddable;
284 }
285
getCountOfFontTypes(int counts[SkAdvancedTypefaceMetrics::kOther_Font+1],int * notSubsettableCount,int * notEmbeddableCount) const286 void SkPDFDocument::getCountOfFontTypes(
287 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
288 int* notSubsettableCount,
289 int* notEmbeddableCount) const {
290 sk_bzero(counts, sizeof(int) *
291 (SkAdvancedTypefaceMetrics::kOther_Font + 1));
292 SkTDArray<SkFontID> seenFonts;
293 int notSubsettable = 0;
294 int notEmbeddable = 0;
295
296 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
297 const SkTDArray<SkPDFFont*>& fontResources =
298 fPages[pageNumber]->getFontResources();
299 for (int font = 0; font < fontResources.count(); font++) {
300 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
301 if (seenFonts.find(fontID) == -1) {
302 counts[fontResources[font]->getType()]++;
303 seenFonts.push(fontID);
304 if (!fontResources[font]->canSubset()) {
305 notSubsettable++;
306 }
307 if (!fontResources[font]->canEmbed()) {
308 notEmbeddable++;
309 }
310 }
311 }
312 }
313 if (notSubsettableCount) {
314 *notSubsettableCount = notSubsettable;
315
316 }
317 if (notEmbeddableCount) {
318 *notEmbeddableCount = notEmbeddable;
319 }
320 }
321
emitHeader(SkWStream * stream)322 void SkPDFDocument::emitHeader(SkWStream* stream) {
323 stream->writeText("%PDF-1.4\n%");
324 // The PDF spec recommends including a comment with four bytes, all
325 // with their high bits set. This is "Skia" with the high bits set.
326 stream->write32(0xD3EBE9E1);
327 stream->writeText("\n");
328 }
329
headerSize()330 size_t SkPDFDocument::headerSize() {
331 SkDynamicMemoryWStream buffer;
332 emitHeader(&buffer);
333 return buffer.getOffset();
334 }
335
emitFooter(SkWStream * stream,int64_t objCount)336 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
337 if (NULL == fTrailerDict) {
338 fTrailerDict = SkNEW(SkPDFDict);
339
340 // TODO(vandebo): Linearized format will take a Prev entry too.
341 // TODO(vandebo): PDF/A requires an ID entry.
342 fTrailerDict->insertInt("Size", int(objCount));
343 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
344 }
345
346 stream->writeText("trailer\n");
347 fTrailerDict->emitObject(stream, fCatalog.get(), false);
348 stream->writeText("\nstartxref\n");
349 stream->writeBigDecAsText(fXRefFileOffset);
350 stream->writeText("\n%%EOF");
351 }
352