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

Side by Side Diff: src/doc/SkDocument_PDF.cpp

Issue 1781773002: SkPDF: move all pdf sources into src/pdf (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: 2016-03-10 (Thursday) 15:59:02 EST Created 4 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
« no previous file with comments | « src/doc/SkDocument.cpp ('k') | src/doc/SkDocument_PDF_None.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "SkDocument.h"
9 #include "SkPDFCanon.h"
10 #include "SkPDFDevice.h"
11 #include "SkPDFFont.h"
12 #include "SkPDFStream.h"
13 #include "SkPDFTypes.h"
14 #include "SkPDFUtils.h"
15 #include "SkStream.h"
16 #include "SkPDFMetadata.h"
17
18 class SkPDFDict;
19
20 static void emit_pdf_header(SkWStream* stream) {
21 stream->writeText("%PDF-1.4\n%");
22 // The PDF spec recommends including a comment with four bytes, all
23 // with their high bits set. This is "Skia" with the high bits set.
24 stream->write32(0xD3EBE9E1);
25 stream->writeText("\n");
26 }
27
28 static void emit_pdf_footer(SkWStream* stream,
29 const SkPDFObjNumMap& objNumMap,
30 const SkPDFSubstituteMap& substitutes,
31 SkPDFObject* docCatalog,
32 int64_t objCount,
33 int32_t xRefFileOffset,
34 sk_sp<SkPDFObject> info,
35 sk_sp<SkPDFObject> id) {
36 SkPDFDict trailerDict;
37 // TODO(http://crbug.com/80908): Linearized format will take a
38 // Prev entry too.
39 trailerDict.insertInt("Size", int(objCount));
40 trailerDict.insertObjRef("Root", sk_ref_sp(docCatalog));
41 SkASSERT(info);
42 trailerDict.insertObjRef("Info", std::move(info));
43 if (id) {
44 trailerDict.insertObject("ID", std::move(id));
45 }
46 stream->writeText("trailer\n");
47 trailerDict.emitObject(stream, objNumMap, substitutes);
48 stream->writeText("\nstartxref\n");
49 stream->writeBigDecAsText(xRefFileOffset);
50 stream->writeText("\n%%EOF");
51 }
52
53 static void perform_font_subsetting(
54 const SkTArray<sk_sp<const SkPDFDevice>>& pageDevices,
55 SkPDFSubstituteMap* substituteMap) {
56 SkASSERT(substituteMap);
57
58 SkPDFGlyphSetMap usage;
59 for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) {
60 usage.merge(pageDevice->getFontGlyphUsage());
61 }
62 SkPDFGlyphSetMap::F2BIter iterator(usage);
63 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
64 while (entry) {
65 sk_sp<SkPDFFont> subsetFont(
66 entry->fFont->getFontSubset(entry->fGlyphSet));
67 if (subsetFont) {
68 substituteMap->setSubstitute(entry->fFont, subsetFont.get());
69 }
70 entry = iterator.next();
71 }
72 }
73
74 static sk_sp<SkPDFDict> create_pdf_page(const SkPDFDevice* pageDevice) {
75 auto page = sk_make_sp<SkPDFDict>("Page");
76 page->insertObject("Resources", pageDevice->makeResourceDict());
77 page->insertObject("MediaBox", pageDevice->copyMediaBox());
78 auto annotations = sk_make_sp<SkPDFArray>();
79 pageDevice->appendAnnotations(annotations.get());
80 if (annotations->size() > 0) {
81 page->insertObject("Annots", std::move(annotations));
82 }
83 auto content = pageDevice->content();
84 page->insertObjRef("Contents", sk_make_sp<SkPDFStream>(content.get()));
85 return page;
86 }
87
88 // return root node.
89 static sk_sp<SkPDFDict> generate_page_tree(
90 const SkTDArray<SkPDFDict*>& pages,
91 SkTDArray<SkPDFDict*>* pageTree) {
92 // PDF wants a tree describing all the pages in the document. We arbitrary
93 // choose 8 (kNodeSize) as the number of allowed children. The internal
94 // nodes have type "Pages" with an array of children, a parent pointer, and
95 // the number of leaves below the node as "Count." The leaves are passed
96 // into the method, have type "Page" and need a parent pointer. This method
97 // builds the tree bottom up, skipping internal nodes that would have only
98 // one child.
99 static const int kNodeSize = 8;
100
101 // curNodes takes a reference to its items, which it passes to pageTree.
102 SkTDArray<SkPDFDict*> curNodes;
103 curNodes.setReserve(pages.count());
104 for (int i = 0; i < pages.count(); i++) {
105 SkSafeRef(pages[i]);
106 curNodes.push(pages[i]);
107 }
108
109 // nextRoundNodes passes its references to nodes on to curNodes.
110 SkTDArray<SkPDFDict*> nextRoundNodes;
111 nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize);
112
113 int treeCapacity = kNodeSize;
114 do {
115 for (int i = 0; i < curNodes.count(); ) {
116 if (i > 0 && i + 1 == curNodes.count()) {
117 nextRoundNodes.push(curNodes[i]);
118 break;
119 }
120
121 auto newNode = sk_make_sp<SkPDFDict>("Pages");
122 auto kids = sk_make_sp<SkPDFArray>();
123 kids->reserve(kNodeSize);
124
125 int count = 0;
126 for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
127 curNodes[i]->insertObjRef("Parent", newNode);
128 kids->appendObjRef(sk_ref_sp(curNodes[i]));
129
130 // TODO(vandebo): put the objects in strict access order.
131 // Probably doesn't matter because they are so small.
132 if (curNodes[i] != pages[0]) {
133 pageTree->push(curNodes[i]); // Transfer reference.
134 } else {
135 SkSafeUnref(curNodes[i]);
136 }
137 }
138
139 // treeCapacity is the number of leaf nodes possible for the
140 // current set of subtrees being generated. (i.e. 8, 64, 512, ...).
141 // It is hard to count the number of leaf nodes in the current
142 // subtree. However, by construction, we know that unless it's the
143 // last subtree for the current depth, the leaf count will be
144 // treeCapacity, otherwise it's what ever is left over after
145 // consuming treeCapacity chunks.
146 int pageCount = treeCapacity;
147 if (i == curNodes.count()) {
148 pageCount = ((pages.count() - 1) % treeCapacity) + 1;
149 }
150 newNode->insertInt("Count", pageCount);
151 newNode->insertObject("Kids", std::move(kids));
152 nextRoundNodes.push(newNode.release()); // Transfer reference.
153 }
154
155 curNodes = nextRoundNodes;
156 nextRoundNodes.rewind();
157 treeCapacity *= kNodeSize;
158 } while (curNodes.count() > 1);
159
160 pageTree->push(curNodes[0]); // Transfer reference.
161 return sk_ref_sp(curNodes[0]);
162 }
163
164 static bool emit_pdf_document(const SkTArray<sk_sp<const SkPDFDevice>>& pageDevi ces,
165 const SkPDFMetadata& metadata,
166 SkWStream* stream) {
167 if (pageDevices.empty()) {
168 return false;
169 }
170
171 SkTDArray<SkPDFDict*> pages; // TODO: SkTArray<sk_sp<SkPDFDict>>
172 auto dests = sk_make_sp<SkPDFDict>();
173
174 for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) {
175 SkASSERT(pageDevice);
176 SkASSERT(pageDevices[0]->getCanon() == pageDevice->getCanon());
177 sk_sp<SkPDFDict> page(create_pdf_page(pageDevice.get()));
178 pageDevice->appendDestinations(dests.get(), page.get());
179 pages.push(page.release());
180 }
181
182 auto docCatalog = sk_make_sp<SkPDFDict>("Catalog");
183
184 sk_sp<SkPDFObject> infoDict(metadata.createDocumentInformationDict());
185
186 sk_sp<SkPDFObject> id, xmp;
187 #ifdef SK_PDF_GENERATE_PDFA
188 SkPDFMetadata::UUID uuid = metadata.uuid();
189 // We use the same UUID for Document ID and Instance ID since this
190 // is the first revision of this document (and Skia does not
191 // support revising existing PDF documents).
192 // If we are not in PDF/A mode, don't use a UUID since testing
193 // works best with reproducible outputs.
194 id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid));
195 xmp.reset(metadata.createXMPObject(uuid, uuid));
196 docCatalog->insertObjRef("Metadata", std::move(xmp));
197
198 // sRGB is specified by HTML, CSS, and SVG.
199 auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent");
200 outputIntent->insertName("S", "GTS_PDFA1");
201 outputIntent->insertString("RegistryName", "http://www.color.org");
202 outputIntent->insertString("OutputConditionIdentifier",
203 "sRGB IEC61966-2.1");
204 auto intentArray = sk_make_sp<SkPDFArray>();
205 intentArray->appendObject(std::move(outputIntent));
206 // Don't specify OutputIntents if we are not in PDF/A mode since
207 // no one has ever asked for this feature.
208 docCatalog->insertObject("OutputIntents", std::move(intentArray));
209 #endif
210
211 SkTDArray<SkPDFDict*> pageTree;
212 docCatalog->insertObjRef("Pages", generate_page_tree(pages, &pageTree));
213
214 if (dests->size() > 0) {
215 docCatalog->insertObjRef("Dests", std::move(dests));
216 }
217
218 // Build font subsetting info before proceeding.
219 SkPDFSubstituteMap substitutes;
220 perform_font_subsetting(pageDevices, &substitutes);
221
222 SkPDFObjNumMap objNumMap;
223 objNumMap.addObjectRecursively(infoDict.get(), substitutes);
224 objNumMap.addObjectRecursively(docCatalog.get(), substitutes);
225 size_t baseOffset = stream->bytesWritten();
226 emit_pdf_header(stream);
227 SkTDArray<int32_t> offsets;
228 for (int i = 0; i < objNumMap.objects().count(); ++i) {
229 SkPDFObject* object = objNumMap.objects()[i];
230 size_t offset = stream->bytesWritten();
231 // This assert checks that size(pdf_header) > 0 and that
232 // the output stream correctly reports bytesWritten().
233 SkASSERT(offset > baseOffset);
234 offsets.push(SkToS32(offset - baseOffset));
235 SkASSERT(object == substitutes.getSubstitute(object));
236 SkASSERT(objNumMap.getObjectNumber(object) == i + 1);
237 stream->writeDecAsText(i + 1);
238 stream->writeText(" 0 obj\n"); // Generation number is always 0.
239 object->emitObject(stream, objNumMap, substitutes);
240 stream->writeText("\nendobj\n");
241 }
242 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset);
243
244 // Include the zeroth object in the count.
245 int32_t objCount = SkToS32(offsets.count() + 1);
246
247 stream->writeText("xref\n0 ");
248 stream->writeDecAsText(objCount);
249 stream->writeText("\n0000000000 65535 f \n");
250 for (int i = 0; i < offsets.count(); i++) {
251 SkASSERT(offsets[i] > 0);
252 stream->writeBigDecAsText(offsets[i], 10);
253 stream->writeText(" 00000 n \n");
254 }
255 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
256 xRefFileOffset, std::move(infoDict), std::move(id));
257
258 // The page tree has both child and parent pointers, so it creates a
259 // reference cycle. We must clear that cycle to properly reclaim memory.
260 for (int i = 0; i < pageTree.count(); i++) {
261 pageTree[i]->clear();
262 }
263 pageTree.safeUnrefAll();
264 pages.unrefAll();
265 return true;
266 }
267
268 #if 0
269 // TODO(halcanary): expose notEmbeddableCount in SkDocument
270 void GetCountOfFontTypes(
271 const SkTDArray<SkPDFDevice*>& pageDevices,
272 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
273 int* notSubsettableCount,
274 int* notEmbeddableCount) {
275 sk_bzero(counts, sizeof(int) *
276 (SkAdvancedTypefaceMetrics::kOther_Font + 1));
277 SkTDArray<SkFontID> seenFonts;
278 int notSubsettable = 0;
279 int notEmbeddable = 0;
280
281 for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) {
282 const SkTDArray<SkPDFFont*>& fontResources =
283 pageDevices[pageNumber]->getFontResources();
284 for (int font = 0; font < fontResources.count(); font++) {
285 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
286 if (seenFonts.find(fontID) == -1) {
287 counts[fontResources[font]->getType()]++;
288 seenFonts.push(fontID);
289 if (!fontResources[font]->canSubset()) {
290 notSubsettable++;
291 }
292 if (!fontResources[font]->canEmbed()) {
293 notEmbeddable++;
294 }
295 }
296 }
297 }
298 if (notSubsettableCount) {
299 *notSubsettableCount = notSubsettable;
300
301 }
302 if (notEmbeddableCount) {
303 *notEmbeddableCount = notEmbeddable;
304 }
305 }
306 #endif
307
308 template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullp tr; }
309 ////////////////////////////////////////////////////////////////////////////////
310
311 namespace {
312 class SkDocument_PDF : public SkDocument {
313 public:
314 SkDocument_PDF(SkWStream* stream,
315 void (*doneProc)(SkWStream*, bool),
316 SkScalar rasterDpi,
317 SkPixelSerializer* jpegEncoder)
318 : SkDocument(stream, doneProc)
319 , fRasterDpi(rasterDpi) {
320 fCanon.setPixelSerializer(SkSafeRef(jpegEncoder));
321 }
322
323 virtual ~SkDocument_PDF() {
324 // subclasses must call close() in their destructors
325 this->close();
326 }
327
328 protected:
329 SkCanvas* onBeginPage(SkScalar width, SkScalar height,
330 const SkRect& trimBox) override {
331 SkASSERT(!fCanvas.get());
332
333 SkISize pageSize = SkISize::Make(
334 SkScalarRoundToInt(width), SkScalarRoundToInt(height));
335 sk_sp<SkPDFDevice> device(
336 SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon));
337 fCanvas.reset(new SkCanvas(device.get()));
338 fPageDevices.push_back(std::move(device));
339 fCanvas->clipRect(trimBox);
340 fCanvas->translate(trimBox.x(), trimBox.y());
341 return fCanvas.get();
342 }
343
344 void onEndPage() override {
345 SkASSERT(fCanvas.get());
346 fCanvas->flush();
347 fCanvas.reset(nullptr);
348 }
349
350 bool onClose(SkWStream* stream) override {
351 SkASSERT(!fCanvas.get());
352
353 bool success = emit_pdf_document(fPageDevices, fMetadata, stream);
354 fPageDevices.reset();
355 fCanon.reset();
356 return success;
357 }
358
359 void onAbort() override {
360 fPageDevices.reset();
361 fCanon.reset();
362 }
363
364 void setMetadata(const SkDocument::Attribute info[],
365 int infoCount,
366 const SkTime::DateTime* creationDate,
367 const SkTime::DateTime* modifiedDate) override {
368 fMetadata.fInfo.reset(info, infoCount);
369 fMetadata.fCreation.reset(clone(creationDate));
370 fMetadata.fModified.reset(clone(modifiedDate));
371 }
372
373 private:
374 SkPDFCanon fCanon;
375 SkTArray<sk_sp<const SkPDFDevice>> fPageDevices;
376 sk_sp<SkCanvas> fCanvas;
377 SkScalar fRasterDpi;
378 SkPDFMetadata fMetadata;
379 };
380 } // namespace
381 ///////////////////////////////////////////////////////////////////////////////
382
383 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) {
384 return stream ? new SkDocument_PDF(stream, nullptr, dpi, nullptr) : nullptr;
385 }
386
387 SkDocument* SkDocument::CreatePDF(SkWStream* stream,
388 SkScalar dpi,
389 SkPixelSerializer* jpegEncoder) {
390 return stream
391 ? new SkDocument_PDF(stream, nullptr, dpi, jpegEncoder)
392 : nullptr;
393 }
394
395 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) {
396 SkFILEWStream* stream = new SkFILEWStream(path);
397 if (!stream->isValid()) {
398 delete stream;
399 return nullptr;
400 }
401 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; };
402 return new SkDocument_PDF(stream, delete_wstream, dpi, nullptr);
403 }
OLDNEW
« no previous file with comments | « src/doc/SkDocument.cpp ('k') | src/doc/SkDocument_PDF_None.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698