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