OLD | NEW |
1 | 1 |
2 /* | 2 /* |
3 * Copyright 2011 Google Inc. | 3 * Copyright 2011 Google Inc. |
4 * | 4 * |
5 * Use of this source code is governed by a BSD-style license that can be | 5 * Use of this source code is governed by a BSD-style license that can be |
6 * found in the LICENSE file. | 6 * found in the LICENSE file. |
7 */ | 7 */ |
8 | 8 |
9 | 9 |
10 #include "SkPDFCatalog.h" | 10 #include "SkPDFCatalog.h" |
11 #include "SkPDFDevice.h" | 11 #include "SkPDFDevice.h" |
12 #include "SkPDFDocument.h" | 12 #include "SkPDFDocument.h" |
13 #include "SkPDFFont.h" | 13 #include "SkPDFFont.h" |
14 #include "SkPDFPage.h" | 14 #include "SkPDFPage.h" |
15 #include "SkPDFTypes.h" | 15 #include "SkPDFTypes.h" |
16 #include "SkStream.h" | 16 #include "SkStream.h" |
17 #include "SkTSet.h" | 17 #include "SkTSet.h" |
18 | 18 |
19 static void addResourcesToCatalog(bool firstPage, | 19 static void perform_font_subsetting(const SkTDArray<SkPDFPage*>& pages, |
20 SkTSet<SkPDFObject*>* resourceSet, | 20 SkPDFCatalog* catalog) { |
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); | 21 SkASSERT(catalog); |
31 SkASSERT(substitutes); | |
32 | |
33 SkPDFGlyphSetMap usage; | 22 SkPDFGlyphSetMap usage; |
34 for (int i = 0; i < pages.count(); ++i) { | 23 for (int i = 0; i < pages.count(); ++i) { |
35 usage.merge(pages[i]->getFontGlyphUsage()); | 24 usage.merge(pages[i]->getFontGlyphUsage()); |
36 } | 25 } |
37 SkPDFGlyphSetMap::F2BIter iterator(usage); | 26 SkPDFGlyphSetMap::F2BIter iterator(usage); |
38 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); | 27 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); |
39 while (entry) { | 28 while (entry) { |
40 SkPDFFont* subsetFont = | 29 SkAutoTUnref<SkPDFFont> subsetFont( |
41 entry->fFont->getFontSubset(entry->fGlyphSet); | 30 entry->fFont->getFontSubset(entry->fGlyphSet)); |
42 if (subsetFont) { | 31 if (subsetFont) { |
43 catalog->setSubstitute(entry->fFont, subsetFont); | 32 catalog->setSubstitute(entry->fFont, subsetFont); |
44 substitutes->push(subsetFont); // Transfer ownership to substitutes | |
45 } | 33 } |
46 entry = iterator.next(); | 34 entry = iterator.next(); |
47 } | 35 } |
48 } | 36 } |
49 | 37 |
50 SkPDFDocument::SkPDFDocument() | 38 SkPDFDocument::SkPDFDocument() {} |
51 : fXRefFileOffset(0), | |
52 fTrailerDict(NULL) { | |
53 fCatalog.reset(SkNEW(SkPDFCatalog)); | |
54 fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog")); | |
55 fCatalog->addObject(fDocCatalog, true); | |
56 fFirstPageResources = NULL; | |
57 fOtherPageResources = NULL; | |
58 } | |
59 | 39 |
60 SkPDFDocument::~SkPDFDocument() { | 40 SkPDFDocument::~SkPDFDocument() { |
61 fPages.safeUnrefAll(); | 41 fPages.safeUnrefAll(); |
| 42 } |
62 | 43 |
63 // The page tree has both child and parent pointers, so it creates a | 44 static void emit_pdf_header(SkWStream* stream) { |
64 // reference cycle. We must clear that cycle to properly reclaim memory. | 45 stream->writeText("%PDF-1.4\n%"); |
65 for (int i = 0; i < fPageTree.count(); i++) { | 46 // The PDF spec recommends including a comment with four bytes, all |
66 fPageTree[i]->clear(); | 47 // with their high bits set. This is "Skia" with the high bits set. |
67 } | 48 stream->write32(0xD3EBE9E1); |
68 fPageTree.safeUnrefAll(); | 49 stream->writeText("\n"); |
| 50 } |
69 | 51 |
70 if (fFirstPageResources) { | 52 static void emit_pdf_footer(SkWStream* stream, |
71 fFirstPageResources->safeUnrefAll(); | 53 SkPDFCatalog* catalog, |
72 } | 54 SkPDFObject* docCatalog, |
73 if (fOtherPageResources) { | 55 int64_t objCount, |
74 fOtherPageResources->safeUnrefAll(); | 56 int32_t xRefFileOffset) { |
75 } | 57 SkPDFDict trailerDict; |
| 58 // TODO(vandebo): Linearized format will take a Prev entry too. |
| 59 // TODO(vandebo): PDF/A requires an ID entry. |
| 60 trailerDict.insertInt("Size", int(objCount)); |
| 61 trailerDict.insert("Root", new SkPDFObjRef(docCatalog))->unref(); |
76 | 62 |
77 fSubstitutes.safeUnrefAll(); | 63 stream->writeText("trailer\n"); |
78 | 64 trailerDict.emitObject(stream, catalog); |
79 fDocCatalog->unref(); | 65 stream->writeText("\nstartxref\n"); |
80 SkSafeUnref(fTrailerDict); | 66 stream->writeBigDecAsText(xRefFileOffset); |
81 SkDELETE(fFirstPageResources); | 67 stream->writeText("\n%%EOF"); |
82 SkDELETE(fOtherPageResources); | |
83 } | 68 } |
84 | 69 |
85 bool SkPDFDocument::emitPDF(SkWStream* stream) { | 70 bool SkPDFDocument::emitPDF(SkWStream* stream) { |
86 if (fPages.isEmpty()) { | 71 if (fPages.isEmpty()) { |
87 return false; | 72 return false; |
88 } | 73 } |
89 for (int i = 0; i < fPages.count(); i++) { | 74 for (int i = 0; i < fPages.count(); i++) { |
90 if (fPages[i] == NULL) { | 75 if (fPages[i] == NULL) { |
91 return false; | 76 return false; |
92 } | 77 } |
93 } | 78 } |
94 | 79 |
95 fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>); | 80 SkPDFCatalog catalog; |
96 fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>); | 81 SkTDArray<SkPDFDict*> pageTree; |
| 82 SkAutoTUnref<SkPDFDict> docCatalog(SkNEW(SkPDFDict)); |
| 83 SkTDArray<SkPDFObject*> substitutes; |
| 84 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); |
97 | 85 |
98 // We haven't emitted the document before if fPageTree is empty. | 86 SkPDFDict* pageTreeRoot; |
99 if (fPageTree.isEmpty()) { | 87 SkPDFPage::GeneratePageTree(fPages, &pageTree, &pageTreeRoot); |
100 SkPDFDict* pageTreeRoot; | 88 docCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); |
101 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree, | |
102 &pageTreeRoot); | |
103 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); | |
104 | 89 |
105 /* TODO(vandebo): output intent | 90 /* TODO(vandebo): output intent |
106 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); | 91 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); |
107 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); | 92 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); |
108 outputIntent->insert("OutputConditionIdentifier", | 93 outputIntent->insert("OutputConditionIdentifier", |
109 new SkPDFString("sRGB"))->unref(); | 94 new SkPDFString("sRGB"))->unref(); |
110 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; | 95 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; |
111 intentArray->append(outputIntent.get()); | 96 intentArray->append(outputIntent.get()); |
112 fDocCatalog->insert("OutputIntent", intentArray.get()); | 97 fDocCatalog->insert("OutputIntent", intentArray.get()); |
113 */ | 98 */ |
114 | 99 |
115 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); | 100 SkTSet<SkPDFObject*> knownResources; |
| 101 for (int i = 0; i < fPages.count(); i++) { |
| 102 SkTSet<SkPDFObject*> newResources; |
| 103 fPages[i]->finalizePage(); |
| 104 fPages[i]->appendDestinations(dests); |
| 105 } |
116 | 106 |
117 bool firstPage = true; | 107 // Build font subsetting info before proceeding. |
118 /* The references returned in newResources are transfered to | 108 perform_font_subsetting(fPages, &catalog); |
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 | 109 |
125 // mergeInto returns the number of duplicates. | 110 if (dests->size() > 0) { |
126 // If there are duplicates, there is a bug and we mess ref counting. | 111 docCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (dests.get()))) |
127 SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResour
ces); | 112 ->unref(); |
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(*fOtherPageRe
sources); | |
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(newReso
urces); | |
141 } else { | |
142 SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newReso
urces); | |
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 } | 113 } |
162 | 114 |
163 SkTSet<SkPDFObject*> resourceSet; | 115 SkTSet<SkPDFObject*> resourceSet; |
164 if (resourceSet.add(fDocCatalog)) { | 116 if (resourceSet.add(docCatalog)) { |
165 fDocCatalog->addResources(&resourceSet, fCatalog); | 117 docCatalog->addResources(&resourceSet, &catalog); |
166 } | 118 } |
167 off_t baseOffset = SkToOffT(stream->bytesWritten()); | 119 for (int i = 0; i < resourceSet.count(); ++i) { |
168 emitHeader(stream); | 120 catalog.addObject(resourceSet[i]); |
| 121 } |
| 122 |
| 123 size_t baseOffset = stream->bytesWritten(); |
| 124 emit_pdf_header(stream); |
| 125 |
| 126 SkTDArray<int32_t> objectOffsets; |
| 127 |
169 for (int i = 0; i < resourceSet.count(); ++i) { | 128 for (int i = 0; i < resourceSet.count(); ++i) { |
170 SkPDFObject* object = resourceSet[i]; | 129 SkPDFObject* object = resourceSet[i]; |
171 fCatalog->setFileOffset(object, | 130 int32_t offset = SkToS32(stream->bytesWritten() - baseOffset); |
172 SkToOffT(stream->bytesWritten()) - baseOffset); | 131 objectOffsets.push(offset); |
173 SkASSERT(object == fCatalog->getSubstituteObject(object)); | 132 int32_t objectNumber = catalog.getObjectNumber(object); |
174 stream->writeDecAsText(fCatalog->getObjectNumber(object)); | 133 SkASSERT(object == catalog.getSubstituteObject(object)); |
| 134 stream->writeDecAsText(objectNumber); |
175 stream->writeText(" 0 obj\n"); // Generation number is always 0. | 135 stream->writeText(" 0 obj\n"); // Generation number is always 0. |
176 object->emitObject(stream, fCatalog); | 136 object->emitObject(stream, &catalog); |
177 stream->writeText("\nendobj\n"); | 137 stream->writeText("\nendobj\n"); |
178 } | 138 } |
179 fXRefFileOffset = SkToOffT(stream->bytesWritten()) - baseOffset; | 139 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset); |
180 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1); | |
181 emitFooter(stream, objCount); | |
182 return true; | |
183 } | |
184 | 140 |
185 // TODO(halcanary): remove this method, since it is unused. | 141 stream->writeText("xref\n0 "); |
186 bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) { | 142 stream->writeDecAsText(objectOffsets.count() + 1); |
187 if (!fPageTree.isEmpty()) { | 143 stream->writeText("\n0000000000 65535 f \n"); |
188 return false; | 144 for (int i = 0; i < objectOffsets.count(); i++) { |
| 145 SkASSERT(objectOffsets[i] > 0); |
| 146 stream->writeBigDecAsText(objectOffsets[i], 10); |
| 147 stream->writeText(" 00000 n \n"); |
189 } | 148 } |
| 149 int64_t objCount = objectOffsets.count() + 1; |
190 | 150 |
191 pageNumber--; | 151 emit_pdf_footer(stream, &catalog, docCatalog.get(), objCount, |
192 SkASSERT(pageNumber >= 0); | 152 xRefFileOffset); |
193 | 153 |
194 if (pageNumber >= fPages.count()) { | 154 // The page tree has both child and parent pointers, so it creates a |
195 int oldSize = fPages.count(); | 155 // reference cycle. We must clear that cycle to properly reclaim memory. |
196 fPages.setCount(pageNumber + 1); | 156 for (int i = 0; i < pageTree.count(); i++) { |
197 for (int i = oldSize; i <= pageNumber; i++) { | 157 pageTree[i]->clear(); |
198 fPages[i] = NULL; | |
199 } | |
200 } | 158 } |
201 | 159 pageTree.safeUnrefAll(); |
202 SkPDFPage* page = new SkPDFPage(pdfDevice); | |
203 SkSafeUnref(fPages[pageNumber]); | |
204 fPages[pageNumber] = page; // Reference from new passed to fPages. | |
205 return true; | 160 return true; |
206 } | 161 } |
207 | 162 |
208 bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) { | 163 bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) { |
209 if (!fPageTree.isEmpty()) { | 164 fPages.push( |
210 return false; | 165 new SkPDFPage(pdfDevice)); // Reference from new passed to fPages. |
211 } | |
212 | |
213 SkPDFPage* page = new SkPDFPage(pdfDevice); | |
214 fPages.push(page); // Reference from new passed to fPages. | |
215 return true; | 166 return true; |
216 } | 167 } |
217 | 168 |
218 // Deprecated. | |
219 // TODO(halcanary): remove | |
220 void SkPDFDocument::getCountOfFontTypes( | |
221 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 2]) const { | |
222 sk_bzero(counts, sizeof(int) * | |
223 (SkAdvancedTypefaceMetrics::kOther_Font + 2)); | |
224 SkTDArray<SkFontID> seenFonts; | |
225 int notEmbeddable = 0; | |
226 | |
227 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { | |
228 const SkTDArray<SkPDFFont*>& fontResources = | |
229 fPages[pageNumber]->getFontResources(); | |
230 for (int font = 0; font < fontResources.count(); font++) { | |
231 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); | |
232 if (seenFonts.find(fontID) == -1) { | |
233 counts[fontResources[font]->getType()]++; | |
234 seenFonts.push(fontID); | |
235 if (!fontResources[font]->canEmbed()) { | |
236 notEmbeddable++; | |
237 } | |
238 } | |
239 } | |
240 } | |
241 counts[SkAdvancedTypefaceMetrics::kOther_Font + 1] = notEmbeddable; | |
242 } | |
243 | 169 |
244 // TODO(halcanary): expose notEmbeddableCount in SkDocument | 170 // TODO(halcanary): expose notEmbeddableCount in SkDocument |
245 void SkPDFDocument::getCountOfFontTypes( | 171 void SkPDFDocument::getCountOfFontTypes( |
246 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], | 172 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], |
247 int* notSubsettableCount, | 173 int* notSubsettableCount, |
248 int* notEmbeddableCount) const { | 174 int* notEmbeddableCount) const { |
249 sk_bzero(counts, sizeof(int) * | 175 sk_bzero(counts, sizeof(int) * |
250 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); | 176 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); |
251 SkTDArray<SkFontID> seenFonts; | 177 SkTDArray<SkFontID> seenFonts; |
252 int notSubsettable = 0; | 178 int notSubsettable = 0; |
(...skipping 18 matching lines...) Expand all Loading... |
271 } | 197 } |
272 if (notSubsettableCount) { | 198 if (notSubsettableCount) { |
273 *notSubsettableCount = notSubsettable; | 199 *notSubsettableCount = notSubsettable; |
274 | 200 |
275 } | 201 } |
276 if (notEmbeddableCount) { | 202 if (notEmbeddableCount) { |
277 *notEmbeddableCount = notEmbeddable; | 203 *notEmbeddableCount = notEmbeddable; |
278 } | 204 } |
279 } | 205 } |
280 | 206 |
281 void SkPDFDocument::emitHeader(SkWStream* stream) { | |
282 stream->writeText("%PDF-1.4\n%"); | |
283 // The PDF spec recommends including a comment with four bytes, all | |
284 // with their high bits set. This is "Skia" with the high bits set. | |
285 stream->write32(0xD3EBE9E1); | |
286 stream->writeText("\n"); | |
287 } | |
288 | |
289 //TODO(halcanary): remove this function | |
290 size_t SkPDFDocument::headerSize() { | |
291 SkDynamicMemoryWStream buffer; | |
292 emitHeader(&buffer); | |
293 return buffer.getOffset(); | |
294 } | |
295 | |
296 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { | |
297 if (NULL == fTrailerDict) { | |
298 fTrailerDict = SkNEW(SkPDFDict); | |
299 | |
300 // TODO(vandebo): Linearized format will take a Prev entry too. | |
301 // TODO(vandebo): PDF/A requires an ID entry. | |
302 fTrailerDict->insertInt("Size", int(objCount)); | |
303 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); | |
304 } | |
305 | |
306 stream->writeText("trailer\n"); | |
307 fTrailerDict->emitObject(stream, fCatalog.get()); | |
308 stream->writeText("\nstartxref\n"); | |
309 stream->writeBigDecAsText(fXRefFileOffset); | |
310 stream->writeText("\n%%EOF"); | |
311 } | |
OLD | NEW |