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