blob: 6c4737995173f00bb7f1545716cb964467bde4d8 [file] [log] [blame]
reed@google.com99ac02b2013-06-07 20:30:16 +00001/*
halcanarya43b4152015-03-25 12:15:04 -07002 * Copyright 2011 Google Inc.
reed@google.com99ac02b2013-06-07 20:30:16 +00003 *
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"
halcanarya1f1ee92015-02-20 06:17:26 -08009#include "SkPDFCanon.h"
reed58677362014-10-09 05:30:10 -070010#include "SkPDFDevice.h"
halcanarya43b4152015-03-25 12:15:04 -070011#include "SkPDFFont.h"
halcanary2f7ebcb2015-03-25 12:45:28 -070012#include "SkPDFStream.h"
halcanarya43b4152015-03-25 12:15:04 -070013#include "SkPDFTypes.h"
14#include "SkStream.h"
15
16static void emit_pdf_header(SkWStream* stream) {
17 stream->writeText("%PDF-1.4\n%");
18 // The PDF spec recommends including a comment with four bytes, all
19 // with their high bits set. This is "Skia" with the high bits set.
20 stream->write32(0xD3EBE9E1);
21 stream->writeText("\n");
22}
23
24static void emit_pdf_footer(SkWStream* stream,
halcanary37c46ca2015-03-31 12:30:20 -070025 const SkPDFObjNumMap& objNumMap,
26 const SkPDFSubstituteMap& substitutes,
halcanarya43b4152015-03-25 12:15:04 -070027 SkPDFObject* docCatalog,
28 int64_t objCount,
29 int32_t xRefFileOffset) {
30 SkPDFDict trailerDict;
31 // TODO(vandebo): Linearized format will take a Prev entry too.
32 // TODO(vandebo): PDF/A requires an ID entry.
33 trailerDict.insertInt("Size", int(objCount));
halcanary72266fd2015-05-05 08:00:24 -070034 trailerDict.insertObjRef("Root", SkRef(docCatalog));
halcanarya43b4152015-03-25 12:15:04 -070035
36 stream->writeText("trailer\n");
halcanary37c46ca2015-03-31 12:30:20 -070037 trailerDict.emitObject(stream, objNumMap, substitutes);
halcanarya43b4152015-03-25 12:15:04 -070038 stream->writeText("\nstartxref\n");
39 stream->writeBigDecAsText(xRefFileOffset);
40 stream->writeText("\n%%EOF");
41}
42
halcanary2f7ebcb2015-03-25 12:45:28 -070043static void perform_font_subsetting(
44 const SkTDArray<const SkPDFDevice*>& pageDevices,
halcanary37c46ca2015-03-31 12:30:20 -070045 SkPDFSubstituteMap* substituteMap) {
46 SkASSERT(substituteMap);
halcanarya43b4152015-03-25 12:15:04 -070047
48 SkPDFGlyphSetMap usage;
halcanary2f7ebcb2015-03-25 12:45:28 -070049 for (int i = 0; i < pageDevices.count(); ++i) {
50 usage.merge(pageDevices[i]->getFontGlyphUsage());
halcanarya43b4152015-03-25 12:15:04 -070051 }
52 SkPDFGlyphSetMap::F2BIter iterator(usage);
53 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
54 while (entry) {
55 SkAutoTUnref<SkPDFFont> subsetFont(
56 entry->fFont->getFontSubset(entry->fGlyphSet));
57 if (subsetFont) {
halcanary37c46ca2015-03-31 12:30:20 -070058 substituteMap->setSubstitute(entry->fFont, subsetFont.get());
halcanarya43b4152015-03-25 12:15:04 -070059 }
60 entry = iterator.next();
61 }
62}
63
halcanary72266fd2015-05-05 08:00:24 -070064static SkPDFObject* create_pdf_page_content(const SkPDFDevice* pageDevice) {
65 SkAutoTDelete<SkStreamAsset> content(pageDevice->content());
halcanary385fe4d2015-08-26 13:07:48 -070066 return new SkPDFStream(content.get());
halcanary72266fd2015-05-05 08:00:24 -070067}
68
halcanary2f7ebcb2015-03-25 12:45:28 -070069static SkPDFDict* create_pdf_page(const SkPDFDevice* pageDevice) {
halcanary385fe4d2015-08-26 13:07:48 -070070 SkAutoTUnref<SkPDFDict> page(new SkPDFDict("Page"));
halcanary72266fd2015-05-05 08:00:24 -070071 page->insertObject("Resources", pageDevice->createResourceDict());
72 page->insertObject("MediaBox", pageDevice->copyMediaBox());
73 if (SkPDFArray* annots = pageDevice->getAnnotations()) {
74 SkASSERT(annots->size() > 0);
75 page->insertObject("Annots", SkRef(annots));
halcanary2f7ebcb2015-03-25 12:45:28 -070076 }
halcanary72266fd2015-05-05 08:00:24 -070077 page->insertObjRef("Contents", create_pdf_page_content(pageDevice));
halcanary2f7ebcb2015-03-25 12:45:28 -070078 return page.detach();
79}
80
81static void generate_page_tree(const SkTDArray<SkPDFDict*>& pages,
82 SkTDArray<SkPDFDict*>* pageTree,
83 SkPDFDict** rootNode) {
84 // PDF wants a tree describing all the pages in the document. We arbitrary
85 // choose 8 (kNodeSize) as the number of allowed children. The internal
86 // nodes have type "Pages" with an array of children, a parent pointer, and
87 // the number of leaves below the node as "Count." The leaves are passed
88 // into the method, have type "Page" and need a parent pointer. This method
89 // builds the tree bottom up, skipping internal nodes that would have only
90 // one child.
91 static const int kNodeSize = 8;
92
halcanary2f7ebcb2015-03-25 12:45:28 -070093 // curNodes takes a reference to its items, which it passes to pageTree.
94 SkTDArray<SkPDFDict*> curNodes;
95 curNodes.setReserve(pages.count());
96 for (int i = 0; i < pages.count(); i++) {
97 SkSafeRef(pages[i]);
98 curNodes.push(pages[i]);
99 }
100
101 // nextRoundNodes passes its references to nodes on to curNodes.
102 SkTDArray<SkPDFDict*> nextRoundNodes;
103 nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize);
104
105 int treeCapacity = kNodeSize;
106 do {
107 for (int i = 0; i < curNodes.count(); ) {
108 if (i > 0 && i + 1 == curNodes.count()) {
109 nextRoundNodes.push(curNodes[i]);
110 break;
111 }
112
halcanary72266fd2015-05-05 08:00:24 -0700113 SkAutoTUnref<SkPDFDict> newNode(new SkPDFDict("Pages"));
halcanary2f7ebcb2015-03-25 12:45:28 -0700114 SkAutoTUnref<SkPDFArray> kids(new SkPDFArray);
115 kids->reserve(kNodeSize);
116
117 int count = 0;
118 for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
halcanary72266fd2015-05-05 08:00:24 -0700119 curNodes[i]->insertObjRef("Parent", SkRef(newNode.get()));
120 kids->appendObjRef(SkRef(curNodes[i]));
halcanary2f7ebcb2015-03-25 12:45:28 -0700121
122 // TODO(vandebo): put the objects in strict access order.
123 // Probably doesn't matter because they are so small.
124 if (curNodes[i] != pages[0]) {
125 pageTree->push(curNodes[i]); // Transfer reference.
126 } else {
127 SkSafeUnref(curNodes[i]);
128 }
129 }
130
131 // treeCapacity is the number of leaf nodes possible for the
132 // current set of subtrees being generated. (i.e. 8, 64, 512, ...).
133 // It is hard to count the number of leaf nodes in the current
134 // subtree. However, by construction, we know that unless it's the
135 // last subtree for the current depth, the leaf count will be
136 // treeCapacity, otherwise it's what ever is left over after
137 // consuming treeCapacity chunks.
138 int pageCount = treeCapacity;
139 if (i == curNodes.count()) {
140 pageCount = ((pages.count() - 1) % treeCapacity) + 1;
141 }
halcanary72266fd2015-05-05 08:00:24 -0700142 newNode->insertInt("Count", pageCount);
143 newNode->insertObject("Kids", kids.detach());
144 nextRoundNodes.push(newNode.detach()); // Transfer reference.
halcanary2f7ebcb2015-03-25 12:45:28 -0700145 }
146
147 curNodes = nextRoundNodes;
148 nextRoundNodes.rewind();
149 treeCapacity *= kNodeSize;
150 } while (curNodes.count() > 1);
151
152 pageTree->push(curNodes[0]); // Transfer reference.
153 if (rootNode) {
154 *rootNode = curNodes[0];
155 }
156}
157
halcanarya43b4152015-03-25 12:15:04 -0700158static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices,
159 SkWStream* stream) {
160 if (pageDevices.isEmpty()) {
161 return false;
162 }
163
halcanary2f7ebcb2015-03-25 12:45:28 -0700164 SkTDArray<SkPDFDict*> pages;
halcanary385fe4d2015-08-26 13:07:48 -0700165 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict);
halcanarya43b4152015-03-25 12:15:04 -0700166
167 for (int i = 0; i < pageDevices.count(); i++) {
168 SkASSERT(pageDevices[i]);
169 SkASSERT(i == 0 ||
170 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon());
halcanary2f7ebcb2015-03-25 12:45:28 -0700171 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i]));
172 pageDevices[i]->appendDestinations(dests, page.get());
halcanarya43b4152015-03-25 12:15:04 -0700173 pages.push(page.detach());
174 }
halcanarya43b4152015-03-25 12:15:04 -0700175
176 SkTDArray<SkPDFDict*> pageTree;
halcanary385fe4d2015-08-26 13:07:48 -0700177 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog"));
halcanarya43b4152015-03-25 12:15:04 -0700178
179 SkPDFDict* pageTreeRoot;
halcanary2f7ebcb2015-03-25 12:45:28 -0700180 generate_page_tree(pages, &pageTree, &pageTreeRoot);
halcanary72266fd2015-05-05 08:00:24 -0700181 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot));
halcanarya43b4152015-03-25 12:15:04 -0700182
halcanary72266fd2015-05-05 08:00:24 -0700183 if (dests->size() > 0) {
184 docCatalog->insertObjRef("Dests", dests.detach());
185 }
halcanarya43b4152015-03-25 12:15:04 -0700186
187 /* TODO(vandebo): output intent
188 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
halcanary72266fd2015-05-05 08:00:24 -0700189 outputIntent->insertName("S", "GTS_PDFA1");
190 outputIntent->insertString("OutputConditionIdentifier", "sRGB");
191 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray);
192 intentArray->appendObject(SkRef(outputIntent.get()));
193 docCatalog->insertObject("OutputIntent", intentArray.detach());
halcanarya43b4152015-03-25 12:15:04 -0700194 */
195
halcanarya43b4152015-03-25 12:15:04 -0700196 // Build font subsetting info before proceeding.
halcanary37c46ca2015-03-31 12:30:20 -0700197 SkPDFSubstituteMap substitutes;
198 perform_font_subsetting(pageDevices, &substitutes);
halcanarya43b4152015-03-25 12:15:04 -0700199
halcanary37c46ca2015-03-31 12:30:20 -0700200 SkPDFObjNumMap objNumMap;
201 if (objNumMap.addObject(docCatalog.get())) {
202 docCatalog->addResources(&objNumMap, substitutes);
halcanarya43b4152015-03-25 12:15:04 -0700203 }
halcanary4f2b3312015-08-10 08:49:03 -0700204 size_t baseOffset = stream->bytesWritten();
halcanarya43b4152015-03-25 12:15:04 -0700205 emit_pdf_header(stream);
206 SkTDArray<int32_t> offsets;
halcanary37c46ca2015-03-31 12:30:20 -0700207 for (int i = 0; i < objNumMap.objects().count(); ++i) {
208 SkPDFObject* object = objNumMap.objects()[i];
halcanary4f2b3312015-08-10 08:49:03 -0700209 size_t offset = stream->bytesWritten();
210 // This assert checks that size(pdf_header) > 0 and that
211 // the output stream correctly reports bytesWritten().
212 SkASSERT(offset > baseOffset);
213 offsets.push(SkToS32(offset - baseOffset));
halcanary37c46ca2015-03-31 12:30:20 -0700214 SkASSERT(object == substitutes.getSubstitute(object));
215 SkASSERT(objNumMap.getObjectNumber(object) == i + 1);
halcanarya43b4152015-03-25 12:15:04 -0700216 stream->writeDecAsText(i + 1);
217 stream->writeText(" 0 obj\n"); // Generation number is always 0.
halcanary37c46ca2015-03-31 12:30:20 -0700218 object->emitObject(stream, objNumMap, substitutes);
halcanarya43b4152015-03-25 12:15:04 -0700219 stream->writeText("\nendobj\n");
220 }
221 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset);
222
halcanary41f88f02015-03-26 15:35:18 -0700223 // Include the zeroth object in the count.
halcanarya43b4152015-03-25 12:15:04 -0700224 int32_t objCount = SkToS32(offsets.count() + 1);
225
226 stream->writeText("xref\n0 ");
halcanary41f88f02015-03-26 15:35:18 -0700227 stream->writeDecAsText(objCount);
halcanarya43b4152015-03-25 12:15:04 -0700228 stream->writeText("\n0000000000 65535 f \n");
229 for (int i = 0; i < offsets.count(); i++) {
230 SkASSERT(offsets[i] > 0);
231 stream->writeBigDecAsText(offsets[i], 10);
232 stream->writeText(" 00000 n \n");
233 }
halcanary37c46ca2015-03-31 12:30:20 -0700234 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
halcanarya43b4152015-03-25 12:15:04 -0700235 xRefFileOffset);
236
237 // The page tree has both child and parent pointers, so it creates a
238 // reference cycle. We must clear that cycle to properly reclaim memory.
239 for (int i = 0; i < pageTree.count(); i++) {
240 pageTree[i]->clear();
241 }
242 pageTree.safeUnrefAll();
243 pages.unrefAll();
244 return true;
245}
246
247#if 0
248// TODO(halcanary): expose notEmbeddableCount in SkDocument
249void GetCountOfFontTypes(
250 const SkTDArray<SkPDFDevice*>& pageDevices,
251 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
252 int* notSubsettableCount,
253 int* notEmbeddableCount) {
254 sk_bzero(counts, sizeof(int) *
255 (SkAdvancedTypefaceMetrics::kOther_Font + 1));
256 SkTDArray<SkFontID> seenFonts;
257 int notSubsettable = 0;
258 int notEmbeddable = 0;
259
260 for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) {
261 const SkTDArray<SkPDFFont*>& fontResources =
262 pageDevices[pageNumber]->getFontResources();
263 for (int font = 0; font < fontResources.count(); font++) {
264 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
265 if (seenFonts.find(fontID) == -1) {
266 counts[fontResources[font]->getType()]++;
267 seenFonts.push(fontID);
268 if (!fontResources[font]->canSubset()) {
269 notSubsettable++;
270 }
271 if (!fontResources[font]->canEmbed()) {
272 notEmbeddable++;
273 }
274 }
275 }
276 }
277 if (notSubsettableCount) {
278 *notSubsettableCount = notSubsettable;
279
280 }
281 if (notEmbeddableCount) {
282 *notEmbeddableCount = notEmbeddable;
283 }
284}
285#endif
286////////////////////////////////////////////////////////////////////////////////
reed@google.com99ac02b2013-06-07 20:30:16 +0000287
halcanary7a011842015-03-25 07:52:56 -0700288namespace {
reed@google.com99ac02b2013-06-07 20:30:16 +0000289class SkDocument_PDF : public SkDocument {
290public:
halcanary8c92dc12015-02-19 18:50:05 -0800291 SkDocument_PDF(SkWStream* stream,
halcanary792c80f2015-02-20 07:21:05 -0800292 void (*doneProc)(SkWStream*, bool),
commit-bot@chromium.org8c294902013-10-21 17:14:37 +0000293 SkScalar rasterDpi)
halcanary8c92dc12015-02-19 18:50:05 -0800294 : SkDocument(stream, doneProc)
halcanary8c92dc12015-02-19 18:50:05 -0800295 , fRasterDpi(rasterDpi) {}
skia.committer@gmail.com63193672013-06-08 07:01:13 +0000296
reed@google.com99ac02b2013-06-07 20:30:16 +0000297 virtual ~SkDocument_PDF() {
298 // subclasses must call close() in their destructors
299 this->close();
300 }
301
302protected:
tfarinaf4219dd2015-04-27 17:18:28 -0700303 SkCanvas* onBeginPage(SkScalar width, SkScalar height,
304 const SkRect& trimBox) override {
halcanarya1f1ee92015-02-20 06:17:26 -0800305 SkASSERT(!fCanvas.get());
reed@google.com99ac02b2013-06-07 20:30:16 +0000306
halcanarya1f1ee92015-02-20 06:17:26 -0800307 SkISize pageSize = SkISize::Make(
308 SkScalarRoundToInt(width), SkScalarRoundToInt(height));
halcanary7a011842015-03-25 07:52:56 -0700309 SkAutoTUnref<SkPDFDevice> device(
310 SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon));
halcanary385fe4d2015-08-26 13:07:48 -0700311 fCanvas.reset(new SkCanvas(device.get()));
halcanary7a011842015-03-25 07:52:56 -0700312 fPageDevices.push(device.detach());
halcanarybe519ad2014-11-10 14:22:14 -0800313 fCanvas->clipRect(trimBox);
halcanary93f81612014-11-10 14:01:57 -0800314 fCanvas->translate(trimBox.x(), trimBox.y());
halcanarya1f1ee92015-02-20 06:17:26 -0800315 return fCanvas.get();
reed@google.com99ac02b2013-06-07 20:30:16 +0000316 }
317
mtklein36352bf2015-03-25 18:17:31 -0700318 void onEndPage() override {
halcanarya1f1ee92015-02-20 06:17:26 -0800319 SkASSERT(fCanvas.get());
reed@google.com99ac02b2013-06-07 20:30:16 +0000320 fCanvas->flush();
halcanary96fcdcc2015-08-27 07:41:13 -0700321 fCanvas.reset(nullptr);
reed@google.com99ac02b2013-06-07 20:30:16 +0000322 }
323
mtklein36352bf2015-03-25 18:17:31 -0700324 bool onClose(SkWStream* stream) override {
halcanarya1f1ee92015-02-20 06:17:26 -0800325 SkASSERT(!fCanvas.get());
reed@google.com99ac02b2013-06-07 20:30:16 +0000326
halcanarya43b4152015-03-25 12:15:04 -0700327 bool success = emit_pdf_document(fPageDevices, stream);
halcanary7a011842015-03-25 07:52:56 -0700328 fPageDevices.unrefAll();
halcanary2e3f9d82015-02-27 12:41:03 -0800329 fCanon.reset();
commit-bot@chromium.orgb5a66512013-10-09 21:09:00 +0000330 return success;
331 }
332
mtklein36352bf2015-03-25 18:17:31 -0700333 void onAbort() override {
halcanary7a011842015-03-25 07:52:56 -0700334 fPageDevices.unrefAll();
halcanary2e3f9d82015-02-27 12:41:03 -0800335 fCanon.reset();
reed@google.com99ac02b2013-06-07 20:30:16 +0000336 }
337
338private:
halcanarya1f1ee92015-02-20 06:17:26 -0800339 SkPDFCanon fCanon;
halcanary6d622702015-03-25 08:45:42 -0700340 SkTDArray<const SkPDFDevice*> fPageDevices;
halcanarya1f1ee92015-02-20 06:17:26 -0800341 SkAutoTUnref<SkCanvas> fCanvas;
342 SkScalar fRasterDpi;
reed@google.com99ac02b2013-06-07 20:30:16 +0000343};
halcanary7a011842015-03-25 07:52:56 -0700344} // namespace
reed@google.com99ac02b2013-06-07 20:30:16 +0000345///////////////////////////////////////////////////////////////////////////////
346
halcanary8c92dc12015-02-19 18:50:05 -0800347SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) {
halcanary96fcdcc2015-08-27 07:41:13 -0700348 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr;
reed@google.com99ac02b2013-06-07 20:30:16 +0000349}
350
halcanary8c92dc12015-02-19 18:50:05 -0800351SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) {
halcanary385fe4d2015-08-26 13:07:48 -0700352 SkFILEWStream* stream = new SkFILEWStream(path);
reed@google.com99ac02b2013-06-07 20:30:16 +0000353 if (!stream->isValid()) {
halcanary385fe4d2015-08-26 13:07:48 -0700354 delete stream;
halcanary96fcdcc2015-08-27 07:41:13 -0700355 return nullptr;
reed@google.com99ac02b2013-06-07 20:30:16 +0000356 }
halcanary385fe4d2015-08-26 13:07:48 -0700357 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; };
358 return new SkDocument_PDF(stream, delete_wstream, dpi);
reed@google.com99ac02b2013-06-07 20:30:16 +0000359}