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