• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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