• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
addResourcesToCatalog(bool firstPage,SkTSet<SkPDFObject * > * resourceSet,SkPDFCatalog * catalog)19 static 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 
perform_font_subsetting(SkPDFCatalog * catalog,const SkTDArray<SkPDFPage * > & pages,SkTDArray<SkPDFObject * > * substitutes)27 static 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 
SkPDFDocument(Flags flags)50 SkPDFDocument::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 
~SkPDFDocument()60 SkPDFDocument::~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 
emitPDF(SkWStream * stream)85 bool 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 
setPage(int pageNumber,SkPDFDevice * pdfDevice)229 bool 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 
appendPage(SkPDFDevice * pdfDevice)251 bool 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 
261 // Deprecated.
getCountOfFontTypes(int counts[SkAdvancedTypefaceMetrics::kOther_Font+2]) const262 void SkPDFDocument::getCountOfFontTypes(
263         int counts[SkAdvancedTypefaceMetrics::kOther_Font + 2]) const {
264     sk_bzero(counts, sizeof(int) *
265                      (SkAdvancedTypefaceMetrics::kOther_Font + 2));
266     SkTDArray<SkFontID> seenFonts;
267     int notEmbeddable = 0;
268 
269     for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
270         const SkTDArray<SkPDFFont*>& fontResources =
271                 fPages[pageNumber]->getFontResources();
272         for (int font = 0; font < fontResources.count(); font++) {
273             SkFontID fontID = fontResources[font]->typeface()->uniqueID();
274             if (seenFonts.find(fontID) == -1) {
275                 counts[fontResources[font]->getType()]++;
276                 seenFonts.push(fontID);
277                 if (!fontResources[font]->canEmbed()) {
278                     notEmbeddable++;
279                 }
280             }
281         }
282     }
283     counts[SkAdvancedTypefaceMetrics::kOther_Font + 1] = notEmbeddable;
284 }
285 
getCountOfFontTypes(int counts[SkAdvancedTypefaceMetrics::kOther_Font+1],int * notSubsettableCount,int * notEmbeddableCount) const286 void SkPDFDocument::getCountOfFontTypes(
287         int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1],
288         int* notSubsettableCount,
289         int* notEmbeddableCount) const {
290     sk_bzero(counts, sizeof(int) *
291                      (SkAdvancedTypefaceMetrics::kOther_Font + 1));
292     SkTDArray<SkFontID> seenFonts;
293     int notSubsettable = 0;
294     int notEmbeddable = 0;
295 
296     for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
297         const SkTDArray<SkPDFFont*>& fontResources =
298                 fPages[pageNumber]->getFontResources();
299         for (int font = 0; font < fontResources.count(); font++) {
300             SkFontID fontID = fontResources[font]->typeface()->uniqueID();
301             if (seenFonts.find(fontID) == -1) {
302                 counts[fontResources[font]->getType()]++;
303                 seenFonts.push(fontID);
304                 if (!fontResources[font]->canSubset()) {
305                     notSubsettable++;
306                 }
307                 if (!fontResources[font]->canEmbed()) {
308                     notEmbeddable++;
309                 }
310             }
311         }
312     }
313     if (notSubsettableCount) {
314         *notSubsettableCount = notSubsettable;
315 
316     }
317     if (notEmbeddableCount) {
318         *notEmbeddableCount = notEmbeddable;
319     }
320 }
321 
emitHeader(SkWStream * stream)322 void SkPDFDocument::emitHeader(SkWStream* stream) {
323     stream->writeText("%PDF-1.4\n%");
324     // The PDF spec recommends including a comment with four bytes, all
325     // with their high bits set.  This is "Skia" with the high bits set.
326     stream->write32(0xD3EBE9E1);
327     stream->writeText("\n");
328 }
329 
headerSize()330 size_t SkPDFDocument::headerSize() {
331     SkDynamicMemoryWStream buffer;
332     emitHeader(&buffer);
333     return buffer.getOffset();
334 }
335 
emitFooter(SkWStream * stream,int64_t objCount)336 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
337     if (NULL == fTrailerDict) {
338         fTrailerDict = SkNEW(SkPDFDict);
339 
340         // TODO(vandebo): Linearized format will take a Prev entry too.
341         // TODO(vandebo): PDF/A requires an ID entry.
342         fTrailerDict->insertInt("Size", int(objCount));
343         fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
344     }
345 
346     stream->writeText("trailer\n");
347     fTrailerDict->emitObject(stream, fCatalog.get(), false);
348     stream->writeText("\nstartxref\n");
349     stream->writeBigDecAsText(fXRefFileOffset);
350     stream->writeText("\n%%EOF");
351 }
352