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. 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 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 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 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 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 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 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 225 const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() { 226 return fPages; 227 } 228 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 237 size_t SkPDFDocument::headerSize() { 238 SkDynamicMemoryWStream buffer; 239 emitHeader(&buffer); 240 return buffer.getOffset(); 241 } 242 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