1 /*
2 * Copyright (C) 2011 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "SkPDFDevice.h"
18 #include "SkPDFDocument.h"
19 #include "SkPDFPage.h"
20 #include "SkStream.h"
21
22 // Add the resources, starting at firstIndex to the catalog, removing any dupes.
23 // A hash table would be really nice here.
addResourcesToCatalog(int firstIndex,bool firstPage,SkTDArray<SkPDFObject * > * resourceList,SkPDFCatalog * catalog)24 void addResourcesToCatalog(int firstIndex, bool firstPage,
25 SkTDArray<SkPDFObject*>* resourceList,
26 SkPDFCatalog* catalog) {
27 for (int i = firstIndex; i < resourceList->count(); i++) {
28 int index = resourceList->find((*resourceList)[i]);
29 if (index != i) {
30 (*resourceList)[i]->unref();
31 resourceList->removeShuffle(i);
32 i--;
33 } else {
34 catalog->addObject((*resourceList)[i], firstPage);
35 }
36 }
37 }
38
SkPDFDocument()39 SkPDFDocument::SkPDFDocument() : fXRefFileOffset(0) {
40 fDocCatalog = new SkPDFDict("Catalog");
41 fDocCatalog->unref(); // SkRefPtr and new both took a reference.
42 fCatalog.addObject(fDocCatalog.get(), true);
43 }
44
~SkPDFDocument()45 SkPDFDocument::~SkPDFDocument() {
46 fPages.safeUnrefAll();
47
48 // The page tree has both child and parent pointers, so it creates a
49 // reference cycle. We must clear that cycle to properly reclaim memory.
50 for (int i = 0; i < fPageTree.count(); i++)
51 fPageTree[i]->clear();
52 fPageTree.safeUnrefAll();
53 fPageResources.safeUnrefAll();
54 }
55
emitPDF(SkWStream * stream)56 bool SkPDFDocument::emitPDF(SkWStream* stream) {
57 if (fPages.isEmpty())
58 return false;
59
60 // We haven't emitted the document before if fPageTree is empty.
61 if (fPageTree.count() == 0) {
62 SkPDFDict* pageTreeRoot;
63 SkPDFPage::generatePageTree(fPages, &fCatalog, &fPageTree,
64 &pageTreeRoot);
65 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
66
67 /* TODO(vandebo) output intent
68 SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
69 outputIntent->unref(); // SkRefPtr and new both took a reference.
70 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
71 outputIntent->insert("OutputConditionIdentifier",
72 new SkPDFString("sRGB"))->unref();
73 SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
74 intentArray->unref(); // SkRefPtr and new both took a reference.
75 intentArray->append(outputIntent.get());
76 fDocCatalog->insert("OutputIntent", intentArray.get());
77 */
78
79 bool first_page = true;
80 for (int i = 0; i < fPages.count(); i++) {
81 int resourceCount = fPageResources.count();
82 fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources);
83 addResourcesToCatalog(resourceCount, first_page, &fPageResources,
84 &fCatalog);
85 if (i == 0) {
86 first_page = false;
87 fSecondPageFirstResourceIndex = fPageResources.count();
88 }
89 }
90
91 // Figure out the size of things and inform the catalog of file offsets.
92 off_t fileOffset = headerSize();
93 fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset);
94 fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset);
95 fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset);
96 for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
97 fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
98 if (fPages.count() > 1) {
99 // TODO(vandebo) For linearized format, save the start of the
100 // first page xref table and calculate the size.
101 }
102
103 for (int i = 0; i < fPageTree.count(); i++)
104 fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset);
105
106 for (int i = 1; i < fPages.count(); i++)
107 fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);
108
109 for (int i = fSecondPageFirstResourceIndex;
110 i < fPageResources.count();
111 i++)
112 fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
113
114 fXRefFileOffset = fileOffset;
115 }
116
117 emitHeader(stream);
118 fDocCatalog->emitObject(stream, &fCatalog, true);
119 fPages[0]->emitObject(stream, &fCatalog, true);
120 fPages[0]->emitPage(stream, &fCatalog);
121 for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
122 fPageResources[i]->emitObject(stream, &fCatalog, true);
123 // TODO(vandebo) support linearized format
124 //if (fPages.size() > 1) {
125 // // TODO(vandebo) save the file offset for the first page xref table.
126 // fCatalog.emitXrefTable(stream, true);
127 //}
128
129 for (int i = 0; i < fPageTree.count(); i++)
130 fPageTree[i]->emitObject(stream, &fCatalog, true);
131
132 for (int i = 1; i < fPages.count(); i++)
133 fPages[i]->emitPage(stream, &fCatalog);
134
135 for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++)
136 fPageResources[i]->emitObject(stream, &fCatalog, true);
137
138 int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
139 emitFooter(stream, objCount);
140 return true;
141 }
142
appendPage(const SkRefPtr<SkPDFDevice> & pdfDevice)143 bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) {
144 if (fPageTree.count() != 0)
145 return false;
146
147 SkPDFPage* page = new SkPDFPage(pdfDevice);
148 fPages.push(page); // Reference from new passed to fPages.
149 // The rest of the pages will be added to the catalog along with the rest
150 // of the page tree. But the first page has to be marked as such, so we
151 // handle it here.
152 if (fPages.count() == 1)
153 fCatalog.addObject(page, true);
154 return true;
155 }
156
getPages()157 const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
158 return fPages;
159 }
160
emitHeader(SkWStream * stream)161 void SkPDFDocument::emitHeader(SkWStream* stream) {
162 stream->writeText("%PDF-1.4\n%");
163 // The PDF spec recommends including a comment with four bytes, all
164 // with their high bits set. This is "Skia" with the high bits set.
165 stream->write32(0xD3EBE9E1);
166 stream->writeText("\n");
167 }
168
headerSize()169 size_t SkPDFDocument::headerSize() {
170 SkDynamicMemoryWStream buffer;
171 emitHeader(&buffer);
172 return buffer.getOffset();
173 }
174
emitFooter(SkWStream * stream,int64_t objCount)175 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
176 if (fTrailerDict.get() == NULL) {
177 fTrailerDict = new SkPDFDict();
178 fTrailerDict->unref(); // SkRefPtr and new both took a reference.
179
180 // TODO(vandebo) Linearized format will take a Prev entry too.
181 // TODO(vandebo) PDF/A requires an ID entry.
182 fTrailerDict->insert("Size", new SkPDFInt(objCount))->unref();
183 fTrailerDict->insert("Root",
184 new SkPDFObjRef(fDocCatalog.get()))->unref();
185 }
186
187 stream->writeText("trailer\n");
188 fTrailerDict->emitObject(stream, &fCatalog, false);
189 stream->writeText("\nstartxref\n");
190 stream->writeBigDecAsText(fXRefFileOffset);
191 stream->writeText("\n%%EOF");
192 }
193