1
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9
10#include "SkPDFCatalog.h"
11#include "SkPDFDevice.h"
12#include "SkPDFDocument.h"
13#include "SkPDFFont.h"
14#include "SkPDFPage.h"
15#include "SkPDFTypes.h"
16#include "SkStream.h"
17#include "SkTSet.h"
18
19static void addResourcesToCatalog(bool firstPage,
20                                  SkTSet<SkPDFObject*>* resourceSet,
21                                  SkPDFCatalog* catalog) {
22    for (int i = 0; i < resourceSet->count(); i++) {
23        catalog->addObject((*resourceSet)[i], firstPage);
24    }
25}
26
27static void perform_font_subsetting(SkPDFCatalog* catalog,
28                                    const SkTDArray<SkPDFPage*>& pages,
29                                    SkTDArray<SkPDFObject*>* substitutes) {
30    SkASSERT(catalog);
31    SkASSERT(substitutes);
32
33    SkPDFGlyphSetMap usage;
34    for (int i = 0; i < pages.count(); ++i) {
35        usage.merge(pages[i]->getFontGlyphUsage());
36    }
37    SkPDFGlyphSetMap::F2BIter iterator(usage);
38    const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
39    while (entry) {
40        SkPDFFont* subsetFont =
41            entry->fFont->getFontSubset(entry->fGlyphSet);
42        if (subsetFont) {
43            catalog->setSubstitute(entry->fFont, subsetFont);
44            substitutes->push(subsetFont);  // Transfer ownership to substitutes
45        }
46        entry = iterator.next();
47    }
48}
49
50SkPDFDocument::SkPDFDocument(Flags flags)
51        : fXRefFileOffset(0),
52          fTrailerDict(NULL) {
53    fCatalog.reset(new SkPDFCatalog(flags));
54    fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog"));
55    fCatalog->addObject(fDocCatalog, true);
56    fFirstPageResources = NULL;
57    fOtherPageResources = NULL;
58}
59
60SkPDFDocument::~SkPDFDocument() {
61    fPages.safeUnrefAll();
62
63    // The page tree has both child and parent pointers, so it creates a
64    // reference cycle.  We must clear that cycle to properly reclaim memory.
65    for (int i = 0; i < fPageTree.count(); i++) {
66        fPageTree[i]->clear();
67    }
68    fPageTree.safeUnrefAll();
69
70    if (fFirstPageResources) {
71        fFirstPageResources->safeUnrefAll();
72    }
73    if (fOtherPageResources) {
74        fOtherPageResources->safeUnrefAll();
75    }
76
77    fSubstitutes.safeUnrefAll();
78
79    fDocCatalog->unref();
80    SkSafeUnref(fTrailerDict);
81    SkDELETE(fFirstPageResources);
82    SkDELETE(fOtherPageResources);
83}
84
85bool SkPDFDocument::emitPDF(SkWStream* stream) {
86    if (fPages.isEmpty()) {
87        return false;
88    }
89    for (int i = 0; i < fPages.count(); i++) {
90        if (fPages[i] == NULL) {
91            return false;
92        }
93    }
94
95    fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>);
96    fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>);
97
98    // We haven't emitted the document before if fPageTree is empty.
99    if (fPageTree.isEmpty()) {
100        SkPDFDict* pageTreeRoot;
101        SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
102                                    &pageTreeRoot);
103        fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
104
105        /* TODO(vandebo): output intent
106        SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
107        outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
108        outputIntent->insert("OutputConditionIdentifier",
109                             new SkPDFString("sRGB"))->unref();
110        SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
111        intentArray->append(outputIntent.get());
112        fDocCatalog->insert("OutputIntent", intentArray.get());
113        */
114
115        SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict));
116
117        bool firstPage = true;
118        /* The references returned in newResources are transfered to
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
125        // mergeInto returns the number of duplicates.
126        // If there are duplicates, there is a bug and we mess ref counting.
127        SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources);
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(*fOtherPageResources);
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(newResources);
141            } else {
142                SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources);
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
162        // Figure out the size of things and inform the catalog of file offsets.
163        off_t fileOffset = headerSize();
164        fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
165        fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
166        fileOffset += fPages[0]->getPageSize(fCatalog.get(),
167                (size_t) fileOffset);
168        for (int i = 0; i < fFirstPageResources->count(); i++) {
169            fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i],
170                                                  fileOffset);
171        }
172        // Add the size of resources of substitute objects used on page 1.
173        fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
174        if (fPages.count() > 1) {
175            // TODO(vandebo): For linearized format, save the start of the
176            // first page xref table and calculate the size.
177        }
178
179        for (int i = 0; i < fPageTree.count(); i++) {
180            fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
181        }
182
183        for (int i = 1; i < fPages.count(); i++) {
184            fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
185        }
186
187        for (int i = 0; i < fOtherPageResources->count(); i++) {
188            fileOffset += fCatalog->setFileOffset(
189                (*fOtherPageResources)[i], fileOffset);
190        }
191
192        fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
193                                                              false);
194        fXRefFileOffset = fileOffset;
195    }
196
197    emitHeader(stream);
198    fDocCatalog->emitObject(stream, fCatalog.get(), true);
199    fPages[0]->emitObject(stream, fCatalog.get(), true);
200    fPages[0]->emitPage(stream, fCatalog.get());
201    for (int i = 0; i < fFirstPageResources->count(); i++) {
202        (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true);
203    }
204    fCatalog->emitSubstituteResources(stream, true);
205    // TODO(vandebo): Support linearized format
206    // if (fPages.size() > 1) {
207    //     // TODO(vandebo): Save the file offset for the first page xref table.
208    //     fCatalog->emitXrefTable(stream, true);
209    // }
210
211    for (int i = 0; i < fPageTree.count(); i++) {
212        fPageTree[i]->emitObject(stream, fCatalog.get(), true);
213    }
214
215    for (int i = 1; i < fPages.count(); i++) {
216        fPages[i]->emitPage(stream, fCatalog.get());
217    }
218
219    for (int i = 0; i < fOtherPageResources->count(); i++) {
220        (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true);
221    }
222
223    fCatalog->emitSubstituteResources(stream, false);
224    int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
225    emitFooter(stream, objCount);
226    return true;
227}
228
229bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
230    if (!fPageTree.isEmpty()) {
231        return false;
232    }
233
234    pageNumber--;
235    SkASSERT(pageNumber >= 0);
236
237    if (pageNumber >= fPages.count()) {
238        int oldSize = fPages.count();
239        fPages.setCount(pageNumber + 1);
240        for (int i = oldSize; i <= pageNumber; i++) {
241            fPages[i] = NULL;
242        }
243    }
244
245    SkPDFPage* page = new SkPDFPage(pdfDevice);
246    SkSafeUnref(fPages[pageNumber]);
247    fPages[pageNumber] = page;  // Reference from new passed to fPages.
248    return true;
249}
250
251bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
252    if (!fPageTree.isEmpty()) {
253        return false;
254    }
255
256    SkPDFPage* page = new SkPDFPage(pdfDevice);
257    fPages.push(page);  // Reference from new passed to fPages.
258    return true;
259}
260
261void SkPDFDocument::getCountOfFontTypes(
262        int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
263    sk_bzero(counts, sizeof(int) *
264                     (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1));
265    SkTDArray<SkFontID> seenFonts;
266
267    for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
268        const SkTDArray<SkPDFFont*>& fontResources =
269                fPages[pageNumber]->getFontResources();
270        for (int font = 0; font < fontResources.count(); font++) {
271            SkFontID fontID = fontResources[font]->typeface()->uniqueID();
272            if (seenFonts.find(fontID) == -1) {
273                counts[fontResources[font]->getType()]++;
274                seenFonts.push(fontID);
275            }
276        }
277    }
278}
279
280void SkPDFDocument::emitHeader(SkWStream* stream) {
281    stream->writeText("%PDF-1.4\n%");
282    // The PDF spec recommends including a comment with four bytes, all
283    // with their high bits set.  This is "Skia" with the high bits set.
284    stream->write32(0xD3EBE9E1);
285    stream->writeText("\n");
286}
287
288size_t SkPDFDocument::headerSize() {
289    SkDynamicMemoryWStream buffer;
290    emitHeader(&buffer);
291    return buffer.getOffset();
292}
293
294void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
295    if (NULL == fTrailerDict) {
296        fTrailerDict = SkNEW(SkPDFDict);
297
298        // TODO(vandebo): Linearized format will take a Prev entry too.
299        // TODO(vandebo): PDF/A requires an ID entry.
300        fTrailerDict->insertInt("Size", int(objCount));
301        fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
302    }
303
304    stream->writeText("trailer\n");
305    fTrailerDict->emitObject(stream, fCatalog.get(), false);
306    stream->writeText("\nstartxref\n");
307    stream->writeBigDecAsText(fXRefFileOffset);
308    stream->writeText("\n%%EOF");
309}
310