Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: src/pdf/SkPDFDocument.cpp

Issue 1033543002: SKPDF: refactor pdfcatalog and pdfdocument (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: 2015-03-23 (Monday) 19:02:27 EDT Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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 }
OLDNEW
« src/pdf/SkPDFCatalog.cpp ('K') | « src/pdf/SkPDFDocument.h ('k') | src/pdf/SkPDFPage.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698