blob: 4ea9d89dd7d83cc938617e1d892c420f76eead27 [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());
wangxianzhuef6c50a2015-09-17 20:38:02 -070073 SkAutoTUnref<SkPDFArray> annotations(new SkPDFArray);
74 pageDevice->appendAnnotations(annotations);
75 if (annotations->size() > 0) {
76 page->insertObject("Annots", annotations.detach());
halcanary2f7ebcb2015-03-25 12:45:28 -070077 }
halcanary72266fd2015-05-05 08:00:24 -070078 page->insertObjRef("Contents", create_pdf_page_content(pageDevice));
halcanary2f7ebcb2015-03-25 12:45:28 -070079 return page.detach();
80}
81
82static void generate_page_tree(const SkTDArray<SkPDFDict*>& pages,
83 SkTDArray<SkPDFDict*>* pageTree,
84 SkPDFDict** rootNode) {
85 // PDF wants a tree describing all the pages in the document. We arbitrary
86 // choose 8 (kNodeSize) as the number of allowed children. The internal
87 // nodes have type "Pages" with an array of children, a parent pointer, and
88 // the number of leaves below the node as "Count." The leaves are passed
89 // into the method, have type "Page" and need a parent pointer. This method
90 // builds the tree bottom up, skipping internal nodes that would have only
91 // one child.
92 static const int kNodeSize = 8;
93
halcanary2f7ebcb2015-03-25 12:45:28 -070094 // curNodes takes a reference to its items, which it passes to pageTree.
95 SkTDArray<SkPDFDict*> curNodes;
96 curNodes.setReserve(pages.count());
97 for (int i = 0; i < pages.count(); i++) {
98 SkSafeRef(pages[i]);
99 curNodes.push(pages[i]);
100 }
101
102 // nextRoundNodes passes its references to nodes on to curNodes.
103 SkTDArray<SkPDFDict*> nextRoundNodes;
104 nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize);
105
106 int treeCapacity = kNodeSize;
107 do {
108 for (int i = 0; i < curNodes.count(); ) {
109 if (i > 0 && i + 1 == curNodes.count()) {
110 nextRoundNodes.push(curNodes[i]);
111 break;
112 }
113
halcanary72266fd2015-05-05 08:00:24 -0700114 SkAutoTUnref<SkPDFDict> newNode(new SkPDFDict("Pages"));
halcanary2f7ebcb2015-03-25 12:45:28 -0700115 SkAutoTUnref<SkPDFArray> kids(new SkPDFArray);
116 kids->reserve(kNodeSize);
117
118 int count = 0;
119 for (; i < curNodes.count() && count < kNodeSize; i++, count++) {
halcanary72266fd2015-05-05 08:00:24 -0700120 curNodes[i]->insertObjRef("Parent", SkRef(newNode.get()));
121 kids->appendObjRef(SkRef(curNodes[i]));
halcanary2f7ebcb2015-03-25 12:45:28 -0700122
123 // TODO(vandebo): put the objects in strict access order.
124 // Probably doesn't matter because they are so small.
125 if (curNodes[i] != pages[0]) {
126 pageTree->push(curNodes[i]); // Transfer reference.
127 } else {
128 SkSafeUnref(curNodes[i]);
129 }
130 }
131
132 // treeCapacity is the number of leaf nodes possible for the
133 // current set of subtrees being generated. (i.e. 8, 64, 512, ...).
134 // It is hard to count the number of leaf nodes in the current
135 // subtree. However, by construction, we know that unless it's the
136 // last subtree for the current depth, the leaf count will be
137 // treeCapacity, otherwise it's what ever is left over after
138 // consuming treeCapacity chunks.
139 int pageCount = treeCapacity;
140 if (i == curNodes.count()) {
141 pageCount = ((pages.count() - 1) % treeCapacity) + 1;
142 }
halcanary72266fd2015-05-05 08:00:24 -0700143 newNode->insertInt("Count", pageCount);
144 newNode->insertObject("Kids", kids.detach());
145 nextRoundNodes.push(newNode.detach()); // Transfer reference.
halcanary2f7ebcb2015-03-25 12:45:28 -0700146 }
147
148 curNodes = nextRoundNodes;
149 nextRoundNodes.rewind();
150 treeCapacity *= kNodeSize;
151 } while (curNodes.count() > 1);
152
153 pageTree->push(curNodes[0]); // Transfer reference.
154 if (rootNode) {
155 *rootNode = curNodes[0];
156 }
157}
158
halcanarya43b4152015-03-25 12:15:04 -0700159static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices,
160 SkWStream* stream) {
161 if (pageDevices.isEmpty()) {
162 return false;
163 }
164
halcanary2f7ebcb2015-03-25 12:45:28 -0700165 SkTDArray<SkPDFDict*> pages;
halcanary385fe4d2015-08-26 13:07:48 -0700166 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict);
halcanarya43b4152015-03-25 12:15:04 -0700167
168 for (int i = 0; i < pageDevices.count(); i++) {
169 SkASSERT(pageDevices[i]);
170 SkASSERT(i == 0 ||
171 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon());
halcanary2f7ebcb2015-03-25 12:45:28 -0700172 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i]));
173 pageDevices[i]->appendDestinations(dests, page.get());
halcanarya43b4152015-03-25 12:15:04 -0700174 pages.push(page.detach());
175 }
halcanarya43b4152015-03-25 12:15:04 -0700176
177 SkTDArray<SkPDFDict*> pageTree;
halcanary385fe4d2015-08-26 13:07:48 -0700178 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog"));
halcanarya43b4152015-03-25 12:15:04 -0700179
180 SkPDFDict* pageTreeRoot;
halcanary2f7ebcb2015-03-25 12:45:28 -0700181 generate_page_tree(pages, &pageTree, &pageTreeRoot);
halcanary72266fd2015-05-05 08:00:24 -0700182 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot));
halcanarya43b4152015-03-25 12:15:04 -0700183
halcanary72266fd2015-05-05 08:00:24 -0700184 if (dests->size() > 0) {
185 docCatalog->insertObjRef("Dests", dests.detach());
186 }
halcanarya43b4152015-03-25 12:15:04 -0700187
188 /* TODO(vandebo): output intent
189 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
halcanary72266fd2015-05-05 08:00:24 -0700190 outputIntent->insertName("S", "GTS_PDFA1");
191 outputIntent->insertString("OutputConditionIdentifier", "sRGB");
192 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray);
193 intentArray->appendObject(SkRef(outputIntent.get()));
194 docCatalog->insertObject("OutputIntent", intentArray.detach());
halcanarya43b4152015-03-25 12:15:04 -0700195 */
196
halcanarya43b4152015-03-25 12:15:04 -0700197 // Build font subsetting info before proceeding.
halcanary37c46ca2015-03-31 12:30:20 -0700198 SkPDFSubstituteMap substitutes;
199 perform_font_subsetting(pageDevices, &substitutes);
halcanarya43b4152015-03-25 12:15:04 -0700200
halcanary37c46ca2015-03-31 12:30:20 -0700201 SkPDFObjNumMap objNumMap;
202 if (objNumMap.addObject(docCatalog.get())) {
203 docCatalog->addResources(&objNumMap, substitutes);
halcanarya43b4152015-03-25 12:15:04 -0700204 }
halcanary4f2b3312015-08-10 08:49:03 -0700205 size_t baseOffset = stream->bytesWritten();
halcanarya43b4152015-03-25 12:15:04 -0700206 emit_pdf_header(stream);
207 SkTDArray<int32_t> offsets;
halcanary37c46ca2015-03-31 12:30:20 -0700208 for (int i = 0; i < objNumMap.objects().count(); ++i) {
209 SkPDFObject* object = objNumMap.objects()[i];
halcanary4f2b3312015-08-10 08:49:03 -0700210 size_t offset = stream->bytesWritten();
211 // This assert checks that size(pdf_header) > 0 and that
212 // the output stream correctly reports bytesWritten().
213 SkASSERT(offset > baseOffset);
214 offsets.push(SkToS32(offset - baseOffset));
halcanary37c46ca2015-03-31 12:30:20 -0700215 SkASSERT(object == substitutes.getSubstitute(object));
216 SkASSERT(objNumMap.getObjectNumber(object) == i + 1);
halcanarya43b4152015-03-25 12:15:04 -0700217 stream->writeDecAsText(i + 1);
218 stream->writeText(" 0 obj\n"); // Generation number is always 0.
halcanary37c46ca2015-03-31 12:30:20 -0700219 object->emitObject(stream, objNumMap, substitutes);
halcanarya43b4152015-03-25 12:15:04 -0700220 stream->writeText("\nendobj\n");
221 }
222 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset);
223
halcanary41f88f02015-03-26 15:35:18 -0700224 // Include the zeroth object in the count.
halcanarya43b4152015-03-25 12:15:04 -0700225 int32_t objCount = SkToS32(offsets.count() + 1);
226
227 stream->writeText("xref\n0 ");
halcanary41f88f02015-03-26 15:35:18 -0700228 stream->writeDecAsText(objCount);
halcanarya43b4152015-03-25 12:15:04 -0700229 stream->writeText("\n0000000000 65535 f \n");
230 for (int i = 0; i < offsets.count(); i++) {
231 SkASSERT(offsets[i] > 0);
232 stream->writeBigDecAsText(offsets[i], 10);
233 stream->writeText(" 00000 n \n");
234 }
halcanary37c46ca2015-03-31 12:30:20 -0700235 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
halcanarya43b4152015-03-25 12:15:04 -0700236 xRefFileOffset);
237
238 // The page tree has both child and parent pointers, so it creates a
239 // reference cycle. We must clear that cycle to properly reclaim memory.
240 for (int i = 0; i < pageTree.count(); i++) {
241 pageTree[i]->clear();
242 }
243 pageTree.safeUnrefAll();
244 pages.unrefAll();
245 return true;
246}
247
248#if 0
249// TODO(halcanary): expose notEmbeddableCount in SkDocument
250void GetCountOfFontTypes(
251 const SkTDArray<SkPDFDevice*>& pageDevices,
252 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
253 int* notSubsettableCount,
254 int* notEmbeddableCount) {
255 sk_bzero(counts, sizeof(int) *
256 (SkAdvancedTypefaceMetrics::kOther_Font + 1));
257 SkTDArray<SkFontID> seenFonts;
258 int notSubsettable = 0;
259 int notEmbeddable = 0;
260
261 for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) {
262 const SkTDArray<SkPDFFont*>& fontResources =
263 pageDevices[pageNumber]->getFontResources();
264 for (int font = 0; font < fontResources.count(); font++) {
265 SkFontID fontID = fontResources[font]->typeface()->uniqueID();
266 if (seenFonts.find(fontID) == -1) {
267 counts[fontResources[font]->getType()]++;
268 seenFonts.push(fontID);
269 if (!fontResources[font]->canSubset()) {
270 notSubsettable++;
271 }
272 if (!fontResources[font]->canEmbed()) {
273 notEmbeddable++;
274 }
275 }
276 }
277 }
278 if (notSubsettableCount) {
279 *notSubsettableCount = notSubsettable;
280
281 }
282 if (notEmbeddableCount) {
283 *notEmbeddableCount = notEmbeddable;
284 }
285}
286#endif
287////////////////////////////////////////////////////////////////////////////////
reed@google.com99ac02b2013-06-07 20:30:16 +0000288
halcanary7a011842015-03-25 07:52:56 -0700289namespace {
reed@google.com99ac02b2013-06-07 20:30:16 +0000290class SkDocument_PDF : public SkDocument {
291public:
halcanary8c92dc12015-02-19 18:50:05 -0800292 SkDocument_PDF(SkWStream* stream,
halcanary792c80f2015-02-20 07:21:05 -0800293 void (*doneProc)(SkWStream*, bool),
commit-bot@chromium.org8c294902013-10-21 17:14:37 +0000294 SkScalar rasterDpi)
halcanary8c92dc12015-02-19 18:50:05 -0800295 : SkDocument(stream, doneProc)
halcanary8c92dc12015-02-19 18:50:05 -0800296 , fRasterDpi(rasterDpi) {}
skia.committer@gmail.com63193672013-06-08 07:01:13 +0000297
reed@google.com99ac02b2013-06-07 20:30:16 +0000298 virtual ~SkDocument_PDF() {
299 // subclasses must call close() in their destructors
300 this->close();
301 }
302
303protected:
tfarinaf4219dd2015-04-27 17:18:28 -0700304 SkCanvas* onBeginPage(SkScalar width, SkScalar height,
305 const SkRect& trimBox) override {
halcanarya1f1ee92015-02-20 06:17:26 -0800306 SkASSERT(!fCanvas.get());
reed@google.com99ac02b2013-06-07 20:30:16 +0000307
halcanarya1f1ee92015-02-20 06:17:26 -0800308 SkISize pageSize = SkISize::Make(
309 SkScalarRoundToInt(width), SkScalarRoundToInt(height));
halcanary7a011842015-03-25 07:52:56 -0700310 SkAutoTUnref<SkPDFDevice> device(
311 SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon));
halcanary385fe4d2015-08-26 13:07:48 -0700312 fCanvas.reset(new SkCanvas(device.get()));
halcanary7a011842015-03-25 07:52:56 -0700313 fPageDevices.push(device.detach());
halcanarybe519ad2014-11-10 14:22:14 -0800314 fCanvas->clipRect(trimBox);
halcanary93f81612014-11-10 14:01:57 -0800315 fCanvas->translate(trimBox.x(), trimBox.y());
halcanarya1f1ee92015-02-20 06:17:26 -0800316 return fCanvas.get();
reed@google.com99ac02b2013-06-07 20:30:16 +0000317 }
318
mtklein36352bf2015-03-25 18:17:31 -0700319 void onEndPage() override {
halcanarya1f1ee92015-02-20 06:17:26 -0800320 SkASSERT(fCanvas.get());
reed@google.com99ac02b2013-06-07 20:30:16 +0000321 fCanvas->flush();
halcanary96fcdcc2015-08-27 07:41:13 -0700322 fCanvas.reset(nullptr);
reed@google.com99ac02b2013-06-07 20:30:16 +0000323 }
324
mtklein36352bf2015-03-25 18:17:31 -0700325 bool onClose(SkWStream* stream) override {
halcanarya1f1ee92015-02-20 06:17:26 -0800326 SkASSERT(!fCanvas.get());
reed@google.com99ac02b2013-06-07 20:30:16 +0000327
halcanarya43b4152015-03-25 12:15:04 -0700328 bool success = emit_pdf_document(fPageDevices, stream);
halcanary7a011842015-03-25 07:52:56 -0700329 fPageDevices.unrefAll();
halcanary2e3f9d82015-02-27 12:41:03 -0800330 fCanon.reset();
commit-bot@chromium.orgb5a66512013-10-09 21:09:00 +0000331 return success;
332 }
333
mtklein36352bf2015-03-25 18:17:31 -0700334 void onAbort() override {
halcanary7a011842015-03-25 07:52:56 -0700335 fPageDevices.unrefAll();
halcanary2e3f9d82015-02-27 12:41:03 -0800336 fCanon.reset();
reed@google.com99ac02b2013-06-07 20:30:16 +0000337 }
338
339private:
halcanarya1f1ee92015-02-20 06:17:26 -0800340 SkPDFCanon fCanon;
halcanary6d622702015-03-25 08:45:42 -0700341 SkTDArray<const SkPDFDevice*> fPageDevices;
halcanarya1f1ee92015-02-20 06:17:26 -0800342 SkAutoTUnref<SkCanvas> fCanvas;
343 SkScalar fRasterDpi;
reed@google.com99ac02b2013-06-07 20:30:16 +0000344};
halcanary7a011842015-03-25 07:52:56 -0700345} // namespace
reed@google.com99ac02b2013-06-07 20:30:16 +0000346///////////////////////////////////////////////////////////////////////////////
347
halcanary8c92dc12015-02-19 18:50:05 -0800348SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) {
halcanary96fcdcc2015-08-27 07:41:13 -0700349 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr;
reed@google.com99ac02b2013-06-07 20:30:16 +0000350}
351
halcanary8c92dc12015-02-19 18:50:05 -0800352SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) {
halcanary385fe4d2015-08-26 13:07:48 -0700353 SkFILEWStream* stream = new SkFILEWStream(path);
reed@google.com99ac02b2013-06-07 20:30:16 +0000354 if (!stream->isValid()) {
halcanary385fe4d2015-08-26 13:07:48 -0700355 delete stream;
halcanary96fcdcc2015-08-27 07:41:13 -0700356 return nullptr;
reed@google.com99ac02b2013-06-07 20:30:16 +0000357 }
halcanary385fe4d2015-08-26 13:07:48 -0700358 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; };
359 return new SkDocument_PDF(stream, delete_wstream, dpi);
reed@google.com99ac02b2013-06-07 20:30:16 +0000360}