michael@0: michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: michael@0: #include "SkPDFCatalog.h" michael@0: #include "SkPDFDevice.h" michael@0: #include "SkPDFDocument.h" michael@0: #include "SkPDFFont.h" michael@0: #include "SkPDFPage.h" michael@0: #include "SkPDFTypes.h" michael@0: #include "SkStream.h" michael@0: #include "SkTSet.h" michael@0: michael@0: static void addResourcesToCatalog(bool firstPage, michael@0: SkTSet* resourceSet, michael@0: SkPDFCatalog* catalog) { michael@0: for (int i = 0; i < resourceSet->count(); i++) { michael@0: catalog->addObject((*resourceSet)[i], firstPage); michael@0: } michael@0: } michael@0: michael@0: static void perform_font_subsetting(SkPDFCatalog* catalog, michael@0: const SkTDArray& pages, michael@0: SkTDArray* substitutes) { michael@0: SkASSERT(catalog); michael@0: SkASSERT(substitutes); michael@0: michael@0: SkPDFGlyphSetMap usage; michael@0: for (int i = 0; i < pages.count(); ++i) { michael@0: usage.merge(pages[i]->getFontGlyphUsage()); michael@0: } michael@0: SkPDFGlyphSetMap::F2BIter iterator(usage); michael@0: const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); michael@0: while (entry) { michael@0: SkPDFFont* subsetFont = michael@0: entry->fFont->getFontSubset(entry->fGlyphSet); michael@0: if (subsetFont) { michael@0: catalog->setSubstitute(entry->fFont, subsetFont); michael@0: substitutes->push(subsetFont); // Transfer ownership to substitutes michael@0: } michael@0: entry = iterator.next(); michael@0: } michael@0: } michael@0: michael@0: SkPDFDocument::SkPDFDocument(Flags flags) michael@0: : fXRefFileOffset(0), michael@0: fTrailerDict(NULL) { michael@0: fCatalog.reset(new SkPDFCatalog(flags)); michael@0: fDocCatalog = SkNEW_ARGS(SkPDFDict, ("Catalog")); michael@0: fCatalog->addObject(fDocCatalog, true); michael@0: fFirstPageResources = NULL; michael@0: fOtherPageResources = NULL; michael@0: } michael@0: michael@0: SkPDFDocument::~SkPDFDocument() { michael@0: fPages.safeUnrefAll(); michael@0: michael@0: // The page tree has both child and parent pointers, so it creates a michael@0: // reference cycle. We must clear that cycle to properly reclaim memory. michael@0: for (int i = 0; i < fPageTree.count(); i++) { michael@0: fPageTree[i]->clear(); michael@0: } michael@0: fPageTree.safeUnrefAll(); michael@0: michael@0: if (fFirstPageResources) { michael@0: fFirstPageResources->safeUnrefAll(); michael@0: } michael@0: if (fOtherPageResources) { michael@0: fOtherPageResources->safeUnrefAll(); michael@0: } michael@0: michael@0: fSubstitutes.safeUnrefAll(); michael@0: michael@0: fDocCatalog->unref(); michael@0: SkSafeUnref(fTrailerDict); michael@0: SkDELETE(fFirstPageResources); michael@0: SkDELETE(fOtherPageResources); michael@0: } michael@0: michael@0: bool SkPDFDocument::emitPDF(SkWStream* stream) { michael@0: if (fPages.isEmpty()) { michael@0: return false; michael@0: } michael@0: for (int i = 0; i < fPages.count(); i++) { michael@0: if (fPages[i] == NULL) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: fFirstPageResources = SkNEW(SkTSet); michael@0: fOtherPageResources = SkNEW(SkTSet); michael@0: michael@0: // We haven't emitted the document before if fPageTree is empty. michael@0: if (fPageTree.isEmpty()) { michael@0: SkPDFDict* pageTreeRoot; michael@0: SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree, michael@0: &pageTreeRoot); michael@0: fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); michael@0: michael@0: /* TODO(vandebo): output intent michael@0: SkAutoTUnref outputIntent = new SkPDFDict("OutputIntent"); michael@0: outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); michael@0: outputIntent->insert("OutputConditionIdentifier", michael@0: new SkPDFString("sRGB"))->unref(); michael@0: SkAutoTUnref intentArray = new SkPDFArray; michael@0: intentArray->append(outputIntent.get()); michael@0: fDocCatalog->insert("OutputIntent", intentArray.get()); michael@0: */ michael@0: michael@0: SkAutoTUnref dests(SkNEW(SkPDFDict)); michael@0: michael@0: bool firstPage = true; michael@0: /* The references returned in newResources are transfered to michael@0: * fFirstPageResources or fOtherPageResources depending on firstPage and michael@0: * knownResources doesn't have a reference but just relies on the other michael@0: * two sets to maintain a reference. michael@0: */ michael@0: SkTSet knownResources; michael@0: michael@0: // mergeInto returns the number of duplicates. michael@0: // If there are duplicates, there is a bug and we mess ref counting. michael@0: SkDEBUGCODE(int duplicates =) knownResources.mergeInto(*fFirstPageResources); michael@0: SkASSERT(duplicates == 0); michael@0: michael@0: for (int i = 0; i < fPages.count(); i++) { michael@0: if (i == 1) { michael@0: firstPage = false; michael@0: SkDEBUGCODE(duplicates =) knownResources.mergeInto(*fOtherPageResources); michael@0: } michael@0: SkTSet newResources; michael@0: fPages[i]->finalizePage( michael@0: fCatalog.get(), firstPage, knownResources, &newResources); michael@0: addResourcesToCatalog(firstPage, &newResources, fCatalog.get()); michael@0: if (firstPage) { michael@0: SkDEBUGCODE(duplicates =) fFirstPageResources->mergeInto(newResources); michael@0: } else { michael@0: SkDEBUGCODE(duplicates =) fOtherPageResources->mergeInto(newResources); michael@0: } michael@0: SkASSERT(duplicates == 0); michael@0: michael@0: SkDEBUGCODE(duplicates =) knownResources.mergeInto(newResources); michael@0: SkASSERT(duplicates == 0); michael@0: michael@0: fPages[i]->appendDestinations(dests); michael@0: } michael@0: michael@0: if (dests->size() > 0) { michael@0: SkPDFDict* raw_dests = dests.get(); michael@0: fFirstPageResources->add(dests.detach()); // Transfer ownership. michael@0: fCatalog->addObject(raw_dests, true /* onFirstPage */); michael@0: fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (raw_dests)))->unref(); michael@0: } michael@0: michael@0: // Build font subsetting info before proceeding. michael@0: perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes); michael@0: michael@0: // Figure out the size of things and inform the catalog of file offsets. michael@0: off_t fileOffset = headerSize(); michael@0: fileOffset += fCatalog->setFileOffset(fDocCatalog, fileOffset); michael@0: fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset); michael@0: fileOffset += fPages[0]->getPageSize(fCatalog.get(), michael@0: (size_t) fileOffset); michael@0: for (int i = 0; i < fFirstPageResources->count(); i++) { michael@0: fileOffset += fCatalog->setFileOffset((*fFirstPageResources)[i], michael@0: fileOffset); michael@0: } michael@0: // Add the size of resources of substitute objects used on page 1. michael@0: fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true); michael@0: if (fPages.count() > 1) { michael@0: // TODO(vandebo): For linearized format, save the start of the michael@0: // first page xref table and calculate the size. michael@0: } michael@0: michael@0: for (int i = 0; i < fPageTree.count(); i++) { michael@0: fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset); michael@0: } michael@0: michael@0: for (int i = 1; i < fPages.count(); i++) { michael@0: fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset); michael@0: } michael@0: michael@0: for (int i = 0; i < fOtherPageResources->count(); i++) { michael@0: fileOffset += fCatalog->setFileOffset( michael@0: (*fOtherPageResources)[i], fileOffset); michael@0: } michael@0: michael@0: fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, michael@0: false); michael@0: fXRefFileOffset = fileOffset; michael@0: } michael@0: michael@0: emitHeader(stream); michael@0: fDocCatalog->emitObject(stream, fCatalog.get(), true); michael@0: fPages[0]->emitObject(stream, fCatalog.get(), true); michael@0: fPages[0]->emitPage(stream, fCatalog.get()); michael@0: for (int i = 0; i < fFirstPageResources->count(); i++) { michael@0: (*fFirstPageResources)[i]->emit(stream, fCatalog.get(), true); michael@0: } michael@0: fCatalog->emitSubstituteResources(stream, true); michael@0: // TODO(vandebo): Support linearized format michael@0: // if (fPages.size() > 1) { michael@0: // // TODO(vandebo): Save the file offset for the first page xref table. michael@0: // fCatalog->emitXrefTable(stream, true); michael@0: // } michael@0: michael@0: for (int i = 0; i < fPageTree.count(); i++) { michael@0: fPageTree[i]->emitObject(stream, fCatalog.get(), true); michael@0: } michael@0: michael@0: for (int i = 1; i < fPages.count(); i++) { michael@0: fPages[i]->emitPage(stream, fCatalog.get()); michael@0: } michael@0: michael@0: for (int i = 0; i < fOtherPageResources->count(); i++) { michael@0: (*fOtherPageResources)[i]->emit(stream, fCatalog.get(), true); michael@0: } michael@0: michael@0: fCatalog->emitSubstituteResources(stream, false); michael@0: int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1); michael@0: emitFooter(stream, objCount); michael@0: return true; michael@0: } michael@0: michael@0: bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) { michael@0: if (!fPageTree.isEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: pageNumber--; michael@0: SkASSERT(pageNumber >= 0); michael@0: michael@0: if (pageNumber >= fPages.count()) { michael@0: int oldSize = fPages.count(); michael@0: fPages.setCount(pageNumber + 1); michael@0: for (int i = oldSize; i <= pageNumber; i++) { michael@0: fPages[i] = NULL; michael@0: } michael@0: } michael@0: michael@0: SkPDFPage* page = new SkPDFPage(pdfDevice); michael@0: SkSafeUnref(fPages[pageNumber]); michael@0: fPages[pageNumber] = page; // Reference from new passed to fPages. michael@0: return true; michael@0: } michael@0: michael@0: bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) { michael@0: if (!fPageTree.isEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: SkPDFPage* page = new SkPDFPage(pdfDevice); michael@0: fPages.push(page); // Reference from new passed to fPages. michael@0: return true; michael@0: } michael@0: michael@0: void SkPDFDocument::getCountOfFontTypes( michael@0: int counts[SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1]) const { michael@0: sk_bzero(counts, sizeof(int) * michael@0: (SkAdvancedTypefaceMetrics::kNotEmbeddable_Font + 1)); michael@0: SkTDArray seenFonts; michael@0: michael@0: for (int pageNumber = 0; pageNumber < fPages.count(); pageNumber++) { michael@0: const SkTDArray& fontResources = michael@0: fPages[pageNumber]->getFontResources(); michael@0: for (int font = 0; font < fontResources.count(); font++) { michael@0: SkFontID fontID = fontResources[font]->typeface()->uniqueID(); michael@0: if (seenFonts.find(fontID) == -1) { michael@0: counts[fontResources[font]->getType()]++; michael@0: seenFonts.push(fontID); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void SkPDFDocument::emitHeader(SkWStream* stream) { michael@0: stream->writeText("%PDF-1.4\n%"); michael@0: // The PDF spec recommends including a comment with four bytes, all michael@0: // with their high bits set. This is "Skia" with the high bits set. michael@0: stream->write32(0xD3EBE9E1); michael@0: stream->writeText("\n"); michael@0: } michael@0: michael@0: size_t SkPDFDocument::headerSize() { michael@0: SkDynamicMemoryWStream buffer; michael@0: emitHeader(&buffer); michael@0: return buffer.getOffset(); michael@0: } michael@0: michael@0: void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) { michael@0: if (NULL == fTrailerDict) { michael@0: fTrailerDict = SkNEW(SkPDFDict); michael@0: michael@0: // TODO(vandebo): Linearized format will take a Prev entry too. michael@0: // TODO(vandebo): PDF/A requires an ID entry. michael@0: fTrailerDict->insertInt("Size", int(objCount)); michael@0: fTrailerDict->insert("Root", new SkPDFObjRef(fDocCatalog))->unref(); michael@0: } michael@0: michael@0: stream->writeText("trailer\n"); michael@0: fTrailerDict->emitObject(stream, fCatalog.get(), false); michael@0: stream->writeText("\nstartxref\n"); michael@0: stream->writeBigDecAsText(fXRefFileOffset); michael@0: stream->writeText("\n%%EOF"); michael@0: }