Home | History | Annotate | Download | only in pdf
      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 
     19 static 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 
     27 static 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 
     50 SkPDFDocument::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 
     60 SkPDFDocument::~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 
     85 bool 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 
    229 bool 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 
    251 bool 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.
    262 void 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 
    286 void 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 
    322 void 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 
    330 size_t SkPDFDocument::headerSize() {
    331     SkDynamicMemoryWStream buffer;
    332     emitHeader(&buffer);
    333     return buffer.getOffset();
    334 }
    335 
    336 void 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