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 261// Deprecated. 262void SkPDFDocument::getCountOfFontTypes( 263 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 2]) const { 264 sk_bzero(counts, sizeof(int) * 265 (SkAdvancedTypefaceMetrics::kOther_Font + 2)); 266 SkTDArray<SkFontID> seenFonts; 267 int notEmbeddable = 0; 268 269 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { 270 const SkTDArray<SkPDFFont*>& fontResources = 271 fPages[pageNumber]->getFontResources(); 272 for (int font = 0; font < fontResources.count(); font++) { 273 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); 274 if (seenFonts.find(fontID) == -1) { 275 counts[fontResources[font]->getType()]++; 276 seenFonts.push(fontID); 277 if (!fontResources[font]->canEmbed()) { 278 notEmbeddable++; 279 } 280 } 281 } 282 } 283 counts[SkAdvancedTypefaceMetrics::kOther_Font + 1] = notEmbeddable; 284} 285 286void SkPDFDocument::getCountOfFontTypes( 287 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], 288 int* notSubsettableCount, 289 int* notEmbeddableCount) const { 290 sk_bzero(counts, sizeof(int) * 291 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); 292 SkTDArray<SkFontID> seenFonts; 293 int notSubsettable = 0; 294 int notEmbeddable = 0; 295 296 for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { 297 const SkTDArray<SkPDFFont*>& fontResources = 298 fPages[pageNumber]->getFontResources(); 299 for (int font = 0; font < fontResources.count(); font++) { 300 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); 301 if (seenFonts.find(fontID) == -1) { 302 counts[fontResources[font]->getType()]++; 303 seenFonts.push(fontID); 304 if (!fontResources[font]->canSubset()) { 305 notSubsettable++; 306 } 307 if (!fontResources[font]->canEmbed()) { 308 notEmbeddable++; 309 } 310 } 311 } 312 } 313 if (notSubsettableCount) { 314 *notSubsettableCount = notSubsettable; 315 316 } 317 if (notEmbeddableCount) { 318 *notEmbeddableCount = notEmbeddable; 319 } 320} 321 322void SkPDFDocument::emitHeader(SkWStream* stream) { 323 stream->writeText("%PDF-1.4\n%"); 324 // The PDF spec recommends including a comment with four bytes, all 325 // with their high bits set. This is "Skia" with the high bits set. 326 stream->write32(0xD3EBE9E1); 327 stream->writeText("\n"); 328} 329 330size_t SkPDFDocument::headerSize() { 331 SkDynamicMemoryWStream buffer; 332 emitHeader(&buffer); 333 return buffer.getOffset(); 334} 335 336void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { 337 if (NULL == fTrailerDict) { 338 fTrailerDict = SkNEW(SkPDFDict); 339 340 // TODO(vandebo): Linearized format will take a Prev entry too. 341 // TODO(vandebo): PDF/A requires an ID entry. 342 fTrailerDict->insertInt("Size", int(objCount)); 343 fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); 344 } 345 346 stream->writeText("trailer\n"); 347 fTrailerDict->emitObject(stream, fCatalog.get(), false); 348 stream->writeText("\nstartxref\n"); 349 stream->writeBigDecAsText(fXRefFileOffset); 350 stream->writeText("\n%%EOF"); 351} 352