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