1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/skia/trunk/src/pdf/SkPDFDocument.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,309 @@ 1.4 + 1.5 +/* 1.6 + * Copyright 2011 Google Inc. 1.7 + * 1.8 + * Use of this source code is governed by a BSD-style license that can be 1.9 + * found in the LICENSE file. 1.10 + */ 1.11 + 1.12 + 1.13 +#include "SkPDFCatalog.h" 1.14 +#include "SkPDFDevice.h" 1.15 +#include "SkPDFDocument.h" 1.16 +#include "SkPDFFont.h" 1.17 +#include "SkPDFPage.h" 1.18 +#include "SkPDFTypes.h" 1.19 +#include "SkStream.h" 1.20 +#include "SkTSet.h" 1.21 + 1.22 +static void addResourcesToCatalog(bool firstPage, 1.23 + SkTSet<SkPDFObject*>* resourceSet, 1.24 + SkPDFCatalog* catalog) { 1.25 + for (int i = 0; i < resourceSet->count(); i++) { 1.26 + catalog->addObject((*resourceSet)[i], firstPage); 1.27 + } 1.28 +} 1.29 + 1.30 +static void perform_font_subsetting(SkPDFCatalog* catalog, 1.31 + const SkTDArray<SkPDFPage*>& pages, 1.32 + SkTDArray<SkPDFObject*>* substitutes) { 1.33 + SkASSERT(catalog); 1.34 + SkASSERT(substitutes); 1.35 + 1.36 + SkPDFGlyphSetMap usage; 1.37 + for (int i = 0; i < pages.count(); ++i) { 1.38 + usage.merge(pages[i]->getFontGlyphUsage()); 1.39 + } 1.40 + SkPDFGlyphSetMap::F2BIter iterator(usage); 1.41 + const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); 1.42 + while (entry) { 1.43 + SkPDFFont* subsetFont = 1.44 + entry->fFont->getFontSubset(entry->fGlyphSet); 1.45 + if (subsetFont) { 1.46 + catalog->setSubstitute(entry->fFont, subsetFont); 1.47 + substitutes->push(subsetFont); // Transfer ownership to substitutes 1.48 + } 1.49 + entry = iterator.next(); 1.50 + } 1.51 +} 1.52 + 1.53 +SkPDFDocument::SkPDFDocument(Flags flags) 1.54 + : fXRefFileOffset(0), 1.55 + fTrailerDict(NULL) { 1.56 + fCatalog.reset(new SkPDFCatalog(flags)); 1.57 + fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog")); 1.58 + fCatalog->addObject(fDocCatalog, true); 1.59 + fFirstPageResources = NULL; 1.60 + fOtherPageResources = NULL; 1.61 +} 1.62 + 1.63 +SkPDFDocument::~SkPDFDocument() { 1.64 + fPages.safeUnrefAll(); 1.65 + 1.66 + // The page tree has both child and parent pointers, so it creates a 1.67 + // reference cycle. We must clear that cycle to properly reclaim memory. 1.68 + for (int i = 0; i < fPageTree.count(); i++) { 1.69 + fPageTree[i]->clear(); 1.70 + } 1.71 + fPageTree.safeUnrefAll(); 1.72 + 1.73 + if (fFirstPageResources) { 1.74 + fFirstPageResources->safeUnrefAll(); 1.75 + } 1.76 + if (fOtherPageResources) { 1.77 + fOtherPageResources->safeUnrefAll(); 1.78 + } 1.79 + 1.80 + fSubstitutes.safeUnrefAll(); 1.81 + 1.82 + fDocCatalog->unref(); 1.83 + SkSafeUnref(fTrailerDict); 1.84 + SkDELETE(fFirstPageResources); 1.85 + SkDELETE(fOtherPageResources); 1.86 +} 1.87 + 1.88 +bool SkPDFDocument::emitPDF(SkWStream* stream) { 1.89 + if (fPages.isEmpty()) { 1.90 + return false; 1.91 + } 1.92 + for (int i = 0; i < fPages.count(); i++) { 1.93 + if (fPages[i] == NULL) { 1.94 + return false; 1.95 + } 1.96 + } 1.97 + 1.98 + fFirstPageResources = SkNEW(SkTSet<SkPDFObject*>); 1.99 + fOtherPageResources = SkNEW(SkTSet<SkPDFObject*>); 1.100 + 1.101 + // We haven't emitted the document before if fPageTree is empty. 1.102 + if (fPageTree.isEmpty()) { 1.103 + SkPDFDict* pageTreeRoot; 1.104 + SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree, 1.105 + &pageTreeRoot); 1.106 + fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); 1.107 + 1.108 + /* TODO(vandebo): output intent 1.109 + SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); 1.110 + outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); 1.111 + outputIntent->insert("OutputConditionIdentifier", 1.112 + new SkPDFString("sRGB"))->unref(); 1.113 + SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; 1.114 + intentArray->append(outputIntent.get()); 1.115 + fDocCatalog->insert("OutputIntent", intentArray.get()); 1.116 + */ 1.117 + 1.118 + SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); 1.119 + 1.120 + bool firstPage = true; 1.121 + /* The references returned in newResources are transfered to 1.122 + * fFirstPageResources or fOtherPageResources depending on firstPage and 1.123 + * knownResources doesn't have a reference but just relies on the other 1.124 + * two sets to maintain a reference. 1.125 + */ 1.126 + SkTSet<SkPDFObject*> knownResources; 1.127 + 1.128 + // mergeInto returns the number of duplicates. 1.129 + // If there are duplicates, there is a bug and we mess ref counting. 1.130 + SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources); 1.131 + SkASSERT(duplicates == 0); 1.132 + 1.133 + for (int i = 0; i < fPages.count(); i++) { 1.134 + if (i == 1) { 1.135 + firstPage = false; 1.136 + SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources); 1.137 + } 1.138 + SkTSet<SkPDFObject*> newResources; 1.139 + fPages[i]->finalizePage( 1.140 + fCatalog.get(), firstPage, knownResources, &newResources); 1.141 + addResourcesToCatalog(firstPage, &newResources, fCatalog.get()); 1.142 + if (firstPage) { 1.143 + SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources); 1.144 + } else { 1.145 + SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources); 1.146 + } 1.147 + SkASSERT(duplicates == 0); 1.148 + 1.149 + SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources); 1.150 + SkASSERT(duplicates == 0); 1.151 + 1.152 + fPages[i]->appendDestinations(dests); 1.153 + } 1.154 + 1.155 + if (dests->size() > 0) { 1.156 + SkPDFDict* raw_dests = dests.get(); 1.157 + fFirstPageResources->add(dests.detach()); // Transfer ownership. 1.158 + fCatalog->addObject(raw_dests, true /* onFirstPage */); 1.159 + fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->unref(); 1.160 + } 1.161 + 1.162 + // Build font subsetting info before proceeding. 1.163 + perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes); 1.164 + 1.165 + // Figure out the size of things and inform the catalog of file offsets. 1.166 + off_t fileOffset = headerSize(); 1.167 + fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset); 1.168 + fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset); 1.169 + fileOffset += fPages[0]->getPageSize(fCatalog.get(), 1.170 + (size_t) fileOffset); 1.171 + for (int i = 0; i < fFirstPageResources->count(); i++) { 1.172 + fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i], 1.173 + fileOffset); 1.174 + } 1.175 + // Add the size of resources of substitute objects used on page 1. 1.176 + fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true); 1.177 + if (fPages.count() > 1) { 1.178 + // TODO(vandebo): For linearized format, save the start of the 1.179 + // first page xref table and calculate the size. 1.180 + } 1.181 + 1.182 + for (int i = 0; i < fPageTree.count(); i++) { 1.183 + fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset); 1.184 + } 1.185 + 1.186 + for (int i = 1; i < fPages.count(); i++) { 1.187 + fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset); 1.188 + } 1.189 + 1.190 + for (int i = 0; i < fOtherPageResources->count(); i++) { 1.191 + fileOffset += fCatalog->setFileOffset( 1.192 + (*fOtherPageResources)[i], fileOffset); 1.193 + } 1.194 + 1.195 + fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, 1.196 + false); 1.197 + fXRefFileOffset = fileOffset; 1.198 + } 1.199 + 1.200 + emitHeader(stream); 1.201 + fDocCatalog->emitObject(stream, fCatalog.get(), true); 1.202 + fPages[0]->emitObject(stream, fCatalog.get(), true); 1.203 + fPages[0]->emitPage(stream, fCatalog.get()); 1.204 + for (int i = 0; i < fFirstPageResources->count(); i++) { 1.205 + (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true); 1.206 + } 1.207 + fCatalog->emitSubstituteResources(stream, true); 1.208 + // TODO(vandebo): Support linearized format 1.209 + // if (fPages.size() > 1) { 1.210 + // // TODO(vandebo): Save the file offset for the first page xref table. 1.211 + // fCatalog->emitXrefTable(stream, true); 1.212 + // } 1.213 + 1.214 + for (int i = 0; i < fPageTree.count(); i++) { 1.215 + fPageTree[i]->emitObject(stream, fCatalog.get(), true); 1.216 + } 1.217 + 1.218 + for (int i = 1; i < fPages.count(); i++) { 1.219 + fPages[i]->emitPage(stream, fCatalog.get()); 1.220 + } 1.221 + 1.222 + for (int i = 0; i < fOtherPageResources->count(); i++) { 1.223 + (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true); 1.224 + } 1.225 + 1.226 + fCatalog->emitSubstituteResources(stream, false); 1.227 + int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1); 1.228 + emitFooter(stream, objCount); 1.229 + return true; 1.230 +} 1.231 + 1.232 +bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) { 1.233 + if (!fPageTree.isEmpty()) { 1.234 + return false; 1.235 + } 1.236 + 1.237 + pageNumber--; 1.238 + SkASSERT(pageNumber >= 0); 1.239 + 1.240 + if (pageNumber >= fPages.count()) { 1.241 + int oldSize = fPages.count(); 1.242 + fPages.setCount(pageNumber + 1); 1.243 + for (int i = oldSize; i <= pageNumber; i++) { 1.244 + fPages[i] = NULL; 1.245 + } 1.246 + } 1.247 + 1.248 + SkPDFPage* page = new SkPDFPage(pdfDevice); 1.249 + SkSafeUnref(fPages[pageNumber]); 1.250 + fPages[pageNumber] = page; // Reference from new passed to fPages. 1.251 + return true; 1.252 +} 1.253 + 1.254 +bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) { 1.255 + if (!fPageTree.isEmpty()) { 1.256 + return false; 1.257 + } 1.258 + 1.259 + SkPDFPage* page = new SkPDFPage(pdfDevice); 1.260 + fPages.push(page); // Reference from new passed to fPages. 1.261 + return true; 1.262 +} 1.263 + 1.264 +void SkPDFDocument::getCountOfFontTypes( 1.265 + int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const { 1.266 + sk_bzero(counts, sizeof(int) * 1.267 + (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1)); 1.268 + SkTDArray<SkFontID> seenFonts; 1.269 + 1.270 + for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { 1.271 + const SkTDArray<SkPDFFont*>& fontResources = 1.272 + fPages[pageNumber]->getFontResources(); 1.273 + for (int font = 0; font < fontResources.count(); font++) { 1.274 + SkFontID fontID = fontResources[font]->typeface()->uniqueID(); 1.275 + if (seenFonts.find(fontID) == -1) { 1.276 + counts[fontResources[font]->getType()]++; 1.277 + seenFonts.push(fontID); 1.278 + } 1.279 + } 1.280 + } 1.281 +} 1.282 + 1.283 +void SkPDFDocument::emitHeader(SkWStream* stream) { 1.284 + stream->writeText("%PDF-1.4\n%"); 1.285 + // The PDF spec recommends including a comment with four bytes, all 1.286 + // with their high bits set. This is "Skia" with the high bits set. 1.287 + stream->write32(0xD3EBE9E1); 1.288 + stream->writeText("\n"); 1.289 +} 1.290 + 1.291 +size_t SkPDFDocument::headerSize() { 1.292 + SkDynamicMemoryWStream buffer; 1.293 + emitHeader(&buffer); 1.294 + return buffer.getOffset(); 1.295 +} 1.296 + 1.297 +void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { 1.298 + if (NULL == fTrailerDict) { 1.299 + fTrailerDict = SkNEW(SkPDFDict); 1.300 + 1.301 + // TODO(vandebo): Linearized format will take a Prev entry too. 1.302 + // TODO(vandebo): PDF/A requires an ID entry. 1.303 + fTrailerDict->insertInt("Size", int(objCount)); 1.304 + fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); 1.305 + } 1.306 + 1.307 + stream->writeText("trailer\n"); 1.308 + fTrailerDict->emitObject(stream, fCatalog.get(), false); 1.309 + stream->writeText("\nstartxref\n"); 1.310 + stream->writeBigDecAsText(fXRefFileOffset); 1.311 + stream->writeText("\n%%EOF"); 1.312 +}