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 "SkPDFFont.h" 14#include "SkPDFPage.h" 15#include "SkPDFTypes.h" 16#include "SkStream.h" 17#include "SkTSet.h" 18 19static void addResourcesToCatalog(bool firstPage, 20 SkTSet<SkPDFObject*>* resourceSet, 21 SkPDFCatalog* catalog) { 22 for (int i = 0; i < resourceSet->count(); i++) { 23 catalog->addObject((*resourceSet)[i], firstPage); 24 } 25} 26 27static void perform_font_subsetting(SkPDFCatalog* catalog, 28 const SkTDArray<SkPDFPage*>& pages, 29 SkTDArray<SkPDFObject*>* substitutes) { 30 SkASSERT(catalog); 31 SkASSERT(substitutes); 32 33 SkPDFGlyphSetMap usage; 34 for (int i = 0; i < pages.count(); ++i) { 35 usage.merge(pages[i]->getFontGlyphUsage()); 36 } 37 SkPDFGlyphSetMap::F2BIter iterator(usage); 38 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); 39 while (entry) { 40 SkPDFFont* subsetFont = 41 entry->fFont->getFontSubset(entry->fGlyphSet); 42 if (subsetFont) { 43 catalog->setSubstitute(entry->fFont, subsetFont); 44 substitutes->push(subsetFont); // Transfer ownership to substitutes 45 } 46 entry = iterator.next(); 47 } 48} 49 50SkPDFDocument::SkPDFDocument(Flags flags) 51 : fXRefFileOffset(0), 52 fTrailerDict(NULL) { 53 fCatalog.reset(new SkPDFCatalog(flags)); 54 fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog")); 55 fCatalog->addObject(fDocCatalog, true); 56 fFirstPageResources = NULL; 57 fOtherPageResources = NULL; 58} 59 60SkPDFDocument::~SkPDFDocument() { 61 fPages.safeUnrefAll(); 62 63 // The page tree has both child and parent pointers, so it creates a 64 // reference cycle. We must clear that cycle to properly reclaim memory. 65 for (int i = 0; i < fPageTree.count(); i++) { 66 fPageTree[i]->clear(); 67 } 68 fPageTree.safeUnrefAll(); 69 70 if (fFirstPageResources) { 71 fFirstPageResources->safeUnrefAll(); 72 } 73 if (fOtherPageResources) { 74 fOtherPageResources->safeUnrefAll(); 75 } 76 77 fSubstitutes.safeUnrefAll(); 78 79 fDocCatalog->unref(); 80 SkSafeUnref(fTrailerDict); 81 SkDELETE(fFirstPageResources); 82 SkDELETE(fOtherPageResources); 83} 84 85bool SkPDFDocument::emitPDF(SkWStream* stream) { 86 if (fPages.isEmpty()) { 87 return false; 88 } 89 for (int i = 0; i < fPages.count(); i++) { 90 if (fPages[i] == NULL) { 91 return false; 92 } 93 } 94 95 fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>); 96 fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>); 97 98 // We haven't emitted the document before if fPageTree is empty. 99 if (fPageTree.isEmpty()) { 100 SkPDFDict* pageTreeRoot; 101 SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree, 102 &pageTreeRoot); 103 fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); 104 105 /* TODO(vandebo): output intent 106 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); 107 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); 108 outputIntent->insert("OutputConditionIdentifier", 109 new SkPDFString("sRGB"))->unref(); 110 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; 111 intentArray->append(outputIntent.get()); 112 fDocCatalog->insert("OutputIntent", intentArray.get()); 113 */ 114 115 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); 116 117 bool firstPage = true; 118 /* The references returned in newResources are transfered to 119 * fFirstPageResources or fOtherPageResources depending on firstPage and 120 * knownResources doesn't have a reference but just relies on the other 121 * two sets to maintain a reference. 122 */ 123 SkTSet<SkPDFObject*> knownResources; 124 125 // mergeInto returns the number of duplicates. 126 // If there are duplicates, there is a bug and we mess ref counting. 127 SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources); 128 SkASSERT(duplicates == 0); 129 130 for (int i = 0; i < fPages.count(); i++) { 131 if (i == 1) { 132 firstPage = false; 133 SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources); 134 } 135 SkTSet<SkPDFObject*> newResources; 136 fPages[i]->finalizePage( 137 fCatalog.get(), firstPage, knownResources, &newResources); 138 addResourcesToCatalog(firstPage, &newResources, fCatalog.get()); 139 if (firstPage) { 140 SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources); 141 } else { 142 SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources); 143 } 144 SkASSERT(duplicates == 0); 145 146 SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources); 147 SkASSERT(duplicates == 0); 148 149 fPages[i]->appendDestinations(dests); 150 } 151 152 if (dests->size() > 0) { 153 SkPDFDict* raw_dests = dests.get(); 154 fFirstPageResources->add(dests.detach()); // Transfer ownership. 155 fCatalog->addObject(raw_dests, true /* onFirstPage */); 156 fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->unref(); 157 } 158 159 // Build font subsetting info before proceeding. 160 perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes); 161 162 // Figure out the size of things and inform the catalog of file offsets. 163 off_t fileOffset = headerSize(); 164 fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset); 165 fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset); 166 fileOffset += fPages[0]->getPageSize(fCatalog.get(), 167 (size_t) fileOffset); 168 for (int i = 0; i < fFirstPageResources->count(); i++) { 169 fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i], 170 fileOffset); 171 } 172 // Add the size of resources of substitute objects used on page 1. 173 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true); 174 if (fPages.count() > 1) { 175 // TODO(vandebo): For linearized format, save the start of the 176 // first page xref table and calculate the size. 177 } 178 179 for (int i = 0; i < fPageTree.count(); i++) { 180 fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset); 181 } 182 183 for (int i = 1; i < fPages.count(); i++) { 184 fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset); 185 } 186 187 for (int i = 0; i < fOtherPageResources->count(); i++) { 188 fileOffset += fCatalog->setFileOffset( 189 (*fOtherPageResources)[i], fileOffset); 190 } 191 192 fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, 193 false); 194 fXRefFileOffset = fileOffset; 195 } 196 197 emitHeader(stream); 198 fDocCatalog->emitObject(stream, fCatalog.get(), true); 199 fPages[0]->emitObject(stream, fCatalog.get(), true); 200 fPages[0]->emitPage(stream, fCatalog.get()); 201 for (int i = 0; i < fFirstPageResources->count(); i++) { 202 (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true); 203 } 204 fCatalog->emitSubstituteResources(stream, true); 205 // TODO(vandebo): Support linearized format 206 // if (fPages.size() > 1) { 207 // // TODO(vandebo): Save the file offset for the first page xref table. 208 // fCatalog->emitXrefTable(stream, true); 209 // } 210 211 for (int i = 0; i < fPageTree.count(); i++) { 212 fPageTree[i]->emitObject(stream, fCatalog.get(), true); 213 } 214 215 for (int i = 1; i < fPages.count(); i++) { 216 fPages[i]->emitPage(stream, fCatalog.get()); 217 } 218 219 for (int i = 0; i < fOtherPageResources->count(); i++) { 220 (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true); 221 } 222 223 fCatalog->emitSubstituteResources(stream, false); 224 int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1); 225 emitFooter(stream, objCount); 226 return true; 227} 228 229bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) { 230 if (!fPageTree.isEmpty()) { 231 return false; 232 } 233 234 pageNumber--; 235 SkASSERT(pageNumber >= 0); 236 237 if (pageNumber >= fPages.count()) { 238 int oldSize = fPages.count(); 239 fPages.setCount(pageNumber + 1); 240 for (int i = oldSize; i <= pageNumber; i++) { 241 fPages[i] = NULL; 242 } 243 } 244 245 SkPDFPage* page = new SkPDFPage(pdfDevice); 246 SkSafeUnref(fPages[pageNumber]); 247 fPages[pageNumber] = page; // Reference from new passed to fPages. 248 return true; 249} 250 251bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) { 252 if (!fPageTree.isEmpty()) { 253 return false; 254 } 255 256 SkPDFPage* page = new SkPDFPage(pdfDevice); 257 fPages.push(page); // Reference from new passed to fPages. 258 return true; 259} 260 261void SkPDFDocument::getCountOfFontTypes( 262 int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const { 263 sk_bzero(counts, sizeof(int) * 264 (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1)); 265 SkTDArray<SkFontID> seenFonts; 266 267 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { 268 const SkTDArray<SkPDFFont*>& fontResources = 269 fPages[pageNumber]->getFontResources(); 270 for (int font = 0; font < fontResources.count(); font++) { 271 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); 272 if (seenFonts.find(fontID) == -1) { 273 counts[fontResources[font]->getType()]++; 274 seenFonts.push(fontID); 275 } 276 } 277 } 278} 279 280void SkPDFDocument::emitHeader(SkWStream* stream) { 281 stream->writeText("%PDF-1.4\n%"); 282 // The PDF spec recommends including a comment with four bytes, all 283 // with their high bits set. This is "Skia" with the high bits set. 284 stream->write32(0xD3EBE9E1); 285 stream->writeText("\n"); 286} 287 288size_t SkPDFDocument::headerSize() { 289 SkDynamicMemoryWStream buffer; 290 emitHeader(&buffer); 291 return buffer.getOffset(); 292} 293 294void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { 295 if (NULL == fTrailerDict) { 296 fTrailerDict = SkNEW(SkPDFDict); 297 298 // TODO(vandebo): Linearized format will take a Prev entry too. 299 // TODO(vandebo): PDF/A requires an ID entry. 300 fTrailerDict->insertInt("Size", int(objCount)); 301 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); 302 } 303 304 stream->writeText("trailer\n"); 305 fTrailerDict->emitObject(stream, fCatalog.get(), false); 306 stream->writeText("\nstartxref\n"); 307 stream->writeBigDecAsText(fXRefFileOffset); 308 stream->writeText("\n%%EOF"); 309} 310