• 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 
18 // Add the resources, starting at firstIndex to the catalog, removing any dupes.
19 // A hash table would be really nice here.
addResourcesToCatalog(int firstIndex,bool firstPage,SkTDArray<SkPDFObject * > * resourceList,SkPDFCatalog * catalog)20 static void addResourcesToCatalog(int firstIndex, bool firstPage,
21                           SkTDArray<SkPDFObject*>* resourceList,
22                           SkPDFCatalog* catalog) {
23     for (int i = firstIndex; i < resourceList->count(); i++) {
24         int index = resourceList->find((*resourceList)[i]);
25         if (index != i) {
26             (*resourceList)[i]->unref();
27             resourceList->removeShuffle(i);
28             i--;
29         } else {
30             catalog->addObject((*resourceList)[i], firstPage);
31         }
32     }
33 }
34 
perform_font_subsetting(SkPDFCatalog * catalog,const SkTDArray<SkPDFPage * > & pages,SkTDArray<SkPDFObject * > * substitutes)35 static void perform_font_subsetting(SkPDFCatalog* catalog,
36                                     const SkTDArray<SkPDFPage*>& pages,
37                                     SkTDArray<SkPDFObject*>* substitutes) {
38     SkASSERT(catalog);
39     SkASSERT(substitutes);
40 
41     SkPDFGlyphSetMap usage;
42     for (int i = 0; i < pages.count(); ++i) {
43         usage.merge(pages[i]->getFontGlyphUsage());
44     }
45     SkPDFGlyphSetMap::F2BIter iterator(usage);
46     SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
47     while (entry) {
48         SkPDFFont* subsetFont =
49             entry->fFont->getFontSubset(entry->fGlyphSet);
50         if (subsetFont) {
51             catalog->setSubstitute(entry->fFont, subsetFont);
52             substitutes->push(subsetFont);  // Transfer ownership to substitutes
53         }
54         entry = iterator.next();
55     }
56 }
57 
SkPDFDocument(Flags flags)58 SkPDFDocument::SkPDFDocument(Flags flags)
59         : fXRefFileOffset(0),
60           fSecondPageFirstResourceIndex(0),
61           fTrailerDict(NULL) {
62     fCatalog.reset(new SkPDFCatalog(flags));
63     fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog"));
64     fCatalog->addObject(fDocCatalog, true);
65 }
66 
~SkPDFDocument()67 SkPDFDocument::~SkPDFDocument() {
68     fPages.safeUnrefAll();
69 
70     // The page tree has both child and parent pointers, so it creates a
71     // reference cycle.  We must clear that cycle to properly reclaim memory.
72     for (int i = 0; i < fPageTree.count(); i++) {
73         fPageTree[i]->clear();
74     }
75     fPageTree.safeUnrefAll();
76     fPageResources.safeUnrefAll();
77     fSubstitutes.safeUnrefAll();
78 
79     fDocCatalog->unref();
80     SkSafeUnref(fTrailerDict);
81 }
82 
emitPDF(SkWStream * stream)83 bool SkPDFDocument::emitPDF(SkWStream* stream) {
84     if (fPages.isEmpty()) {
85         return false;
86     }
87     for (int i = 0; i < fPages.count(); i++) {
88         if (fPages[i] == NULL) {
89             return false;
90         }
91     }
92 
93     // We haven't emitted the document before if fPageTree is empty.
94     if (fPageTree.isEmpty()) {
95         SkPDFDict* pageTreeRoot;
96         SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
97                                     &pageTreeRoot);
98         fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
99 
100         /* TODO(vandebo): output intent
101         SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
102         outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
103         outputIntent->insert("OutputConditionIdentifier",
104                              new SkPDFString("sRGB"))->unref();
105         SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray;
106         intentArray->append(outputIntent.get());
107         fDocCatalog->insert("OutputIntent", intentArray.get());
108         */
109 
110         bool firstPage = true;
111         for (int i = 0; i < fPages.count(); i++) {
112             int resourceCount = fPageResources.count();
113             fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
114             addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
115                                   fCatalog.get());
116             if (i == 0) {
117                 firstPage = false;
118                 fSecondPageFirstResourceIndex = fPageResources.count();
119             }
120         }
121 
122         // Build font subsetting info before proceeding.
123         perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
124 
125         // Figure out the size of things and inform the catalog of file offsets.
126         off_t fileOffset = headerSize();
127         fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset);
128         fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
129         fileOffset += fPages[0]->getPageSize(fCatalog.get(),
130                 (size_t) fileOffset);
131         for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
132             fileOffset += fCatalog->setFileOffset(fPageResources[i],
133                                                   fileOffset);
134         }
135         // Add the size of resources of substitute objects used on page 1.
136         fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
137         if (fPages.count() > 1) {
138             // TODO(vandebo): For linearized format, save the start of the
139             // first page xref table and calculate the size.
140         }
141 
142         for (int i = 0; i < fPageTree.count(); i++) {
143             fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
144         }
145 
146         for (int i = 1; i < fPages.count(); i++) {
147             fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
148         }
149 
150         for (int i = fSecondPageFirstResourceIndex;
151                  i < fPageResources.count();
152                  i++) {
153             fileOffset += fCatalog->setFileOffset(fPageResources[i],
154                                                   fileOffset);
155         }
156 
157         fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
158                                                               false);
159         fXRefFileOffset = fileOffset;
160     }
161 
162     emitHeader(stream);
163     fDocCatalog->emitObject(stream, fCatalog.get(), true);
164     fPages[0]->emitObject(stream, fCatalog.get(), true);
165     fPages[0]->emitPage(stream, fCatalog.get());
166     for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
167         fPageResources[i]->emit(stream, fCatalog.get(), true);
168     }
169     fCatalog->emitSubstituteResources(stream, true);
170     // TODO(vandebo): Support linearized format
171     // if (fPages.size() > 1) {
172     //     // TODO(vandebo): Save the file offset for the first page xref table.
173     //     fCatalog->emitXrefTable(stream, true);
174     // }
175 
176     for (int i = 0; i < fPageTree.count(); i++) {
177         fPageTree[i]->emitObject(stream, fCatalog.get(), true);
178     }
179 
180     for (int i = 1; i < fPages.count(); i++) {
181         fPages[i]->emitPage(stream, fCatalog.get());
182     }
183 
184     for (int i = fSecondPageFirstResourceIndex;
185             i < fPageResources.count();
186             i++) {
187         fPageResources[i]->emit(stream, fCatalog.get(), true);
188     }
189 
190     fCatalog->emitSubstituteResources(stream, false);
191     int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
192     emitFooter(stream, objCount);
193     return true;
194 }
195 
setPage(int pageNumber,SkPDFDevice * pdfDevice)196 bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
197     if (!fPageTree.isEmpty()) {
198         return false;
199     }
200 
201     pageNumber--;
202     SkASSERT(pageNumber >= 0);
203 
204     if (pageNumber >= fPages.count()) {
205         int oldSize = fPages.count();
206         fPages.setCount(pageNumber + 1);
207         for (int i = oldSize; i <= pageNumber; i++) {
208             fPages[i] = NULL;
209         }
210     }
211 
212     SkPDFPage* page = new SkPDFPage(pdfDevice);
213     SkSafeUnref(fPages[pageNumber]);
214     fPages[pageNumber] = page;  // Reference from new passed to fPages.
215     return true;
216 }
217 
appendPage(SkPDFDevice * pdfDevice)218 bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
219     if (!fPageTree.isEmpty()) {
220         return false;
221     }
222 
223     SkPDFPage* page = new SkPDFPage(pdfDevice);
224     fPages.push(page);  // Reference from new passed to fPages.
225     return true;
226 }
227 
getCountOfFontTypes(int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font+1]) const228 void SkPDFDocument::getCountOfFontTypes(
229         int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const {
230     sk_bzero(counts, sizeof(int) *
231                      (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1));
232     SkTDArray<SkFontID> seenFonts;
233 
234     for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) {
235         const SkTDArray<SkPDFFont*>& fontResources =
236                 fPages[pageNumber]->getFontResources();
237         for (int font = 0; font < fontResources.count(); font++) {
238             SkFontID fontID = fontResources[font]->typeface()->uniqueID();
239             if (seenFonts.find(fontID) == -1) {
240                 counts[fontResources[font]->getType()]++;
241                 seenFonts.push(fontID);
242             }
243         }
244     }
245 }
246 
emitHeader(SkWStream * stream)247 void SkPDFDocument::emitHeader(SkWStream* stream) {
248     stream->writeText("%PDF-1.4\n%");
249     // The PDF spec recommends including a comment with four bytes, all
250     // with their high bits set.  This is "Skia" with the high bits set.
251     stream->write32(0xD3EBE9E1);
252     stream->writeText("\n");
253 }
254 
headerSize()255 size_t SkPDFDocument::headerSize() {
256     SkDynamicMemoryWStream buffer;
257     emitHeader(&buffer);
258     return buffer.getOffset();
259 }
260 
emitFooter(SkWStream * stream,int64_t objCount)261 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
262     if (NULL == fTrailerDict) {
263         fTrailerDict = SkNEW(SkPDFDict);
264 
265         // TODO(vandebo): Linearized format will take a Prev entry too.
266         // TODO(vandebo): PDF/A requires an ID entry.
267         fTrailerDict->insertInt("Size", int(objCount));
268         fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref();
269     }
270 
271     stream->writeText("trailer\n");
272     fTrailerDict->emitObject(stream, fCatalog.get(), false);
273     stream->writeText("\nstartxref\n");
274     stream->writeBigDecAsText(fXRefFileOffset);
275     stream->writeText("\n%%EOF");
276 }
277