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