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