|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 void SkPDFDocument::getCountOfFontTypes( |
|
262 int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const { |
|
263 sk_bzero(counts, sizeof(int) * |
|
264 (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1)); |
|
265 SkTDArray<SkFontID> seenFonts; |
|
266 |
|
267 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { |
|
268 const SkTDArray<SkPDFFont*>& fontResources = |
|
269 fPages[pageNumber]->getFontResources(); |
|
270 for (int font = 0; font < fontResources.count(); font++) { |
|
271 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); |
|
272 if (seenFonts.find(fontID) == -1) { |
|
273 counts[fontResources[font]->getType()]++; |
|
274 seenFonts.push(fontID); |
|
275 } |
|
276 } |
|
277 } |
|
278 } |
|
279 |
|
280 void SkPDFDocument::emitHeader(SkWStream* stream) { |
|
281 stream->writeText("%PDF-1.4\n%"); |
|
282 // The PDF spec recommends including a comment with four bytes, all |
|
283 // with their high bits set. This is "Skia" with the high bits set. |
|
284 stream->write32(0xD3EBE9E1); |
|
285 stream->writeText("\n"); |
|
286 } |
|
287 |
|
288 size_t SkPDFDocument::headerSize() { |
|
289 SkDynamicMemoryWStream buffer; |
|
290 emitHeader(&buffer); |
|
291 return buffer.getOffset(); |
|
292 } |
|
293 |
|
294 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { |
|
295 if (NULL == fTrailerDict) { |
|
296 fTrailerDict = SkNEW(SkPDFDict); |
|
297 |
|
298 // TODO(vandebo): Linearized format will take a Prev entry too. |
|
299 // TODO(vandebo): PDF/A requires an ID entry. |
|
300 fTrailerDict->insertInt("Size", int(objCount)); |
|
301 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); |
|
302 } |
|
303 |
|
304 stream->writeText("trailer\n"); |
|
305 fTrailerDict->emitObject(stream, fCatalog.get(), false); |
|
306 stream->writeText("\nstartxref\n"); |
|
307 stream->writeBigDecAsText(fXRefFileOffset); |
|
308 stream->writeText("\n%%EOF"); |
|
309 } |