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 "SkPDFPage.h"
     14 #include "SkPDFFont.h"
     15 #include "SkStream.h"
     16 
     17 // Add the resources, starting at firstIndex to the catalog, removing any dupes.
     18 // A hash table would be really nice here.
     19 void addResourcesToCatalog(int firstIndex, bool firstPage,
     20                           SkTDArray<SkPDFObject*>* resourceList,
     21                           SkPDFCatalog* catalog) {
     22     for (int i = firstIndex; i < resourceList->count(); i++) {
     23         int index = resourceList->find((*resourceList)[i]);
     24         if (index != i) {
     25             (*resourceList)[i]->unref();
     26             resourceList->removeShuffle(i);
     27             i--;
     28         } else {
     29             catalog->addObject((*resourceList)[i], firstPage);
     30         }
     31     }
     32 }
     33 
     34 static void perform_font_subsetting(SkPDFCatalog* catalog,
     35                                     const SkTDArray<SkPDFPage*>& pages,
     36                                     SkTDArray<SkPDFObject*>* substitutes) {
     37     SkASSERT(catalog);
     38     SkASSERT(substitutes);
     39 
     40     SkPDFGlyphSetMap usage;
     41     for (int i = 0; i < pages.count(); ++i) {
     42         usage.merge(pages[i]->getFontGlyphUsage());
     43     }
     44     SkPDFGlyphSetMap::F2BIter iterator(usage);
     45     SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next();
     46     while (entry) {
     47         SkPDFFont* subsetFont =
     48             entry->fFont->getFontSubset(entry->fGlyphSet);
     49         if (subsetFont) {
     50             catalog->setSubstitute(entry->fFont, subsetFont);
     51             substitutes->push(subsetFont);  // Transfer ownership to substitutes
     52         }
     53         entry = iterator.next();
     54     }
     55 }
     56 
     57 SkPDFDocument::SkPDFDocument(Flags flags)
     58         : fXRefFileOffset(0),
     59           fSecondPageFirstResourceIndex(0) {
     60     fCatalog.reset(new SkPDFCatalog(flags));
     61     fDocCatalog = new SkPDFDict("Catalog");
     62     fDocCatalog->unref();  // SkRefPtr and new both took a reference.
     63     fCatalog->addObject(fDocCatalog.get(), true);
     64 }
     65 
     66 SkPDFDocument::~SkPDFDocument() {
     67     fPages.safeUnrefAll();
     68 
     69     // The page tree has both child and parent pointers, so it creates a
     70     // reference cycle.  We must clear that cycle to properly reclaim memory.
     71     for (int i = 0; i < fPageTree.count(); i++) {
     72         fPageTree[i]->clear();
     73     }
     74     fPageTree.safeUnrefAll();
     75     fPageResources.safeUnrefAll();
     76     fSubstitutes.safeUnrefAll();
     77 }
     78 
     79 bool SkPDFDocument::emitPDF(SkWStream* stream) {
     80     if (fPages.isEmpty()) {
     81         return false;
     82     }
     83     for (int i = 0; i < fPages.count(); i++) {
     84         if (fPages[i] == NULL) {
     85             return false;
     86         }
     87     }
     88 
     89     // We haven't emitted the document before if fPageTree is empty.
     90     if (fPageTree.isEmpty()) {
     91         SkPDFDict* pageTreeRoot;
     92         SkPDFPage::GeneratePageTree(fPages, fCatalog.get(), &fPageTree,
     93                                     &pageTreeRoot);
     94         fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();
     95 
     96         /* TODO(vandebo): output intent
     97         SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
     98         outputIntent->unref();  // SkRefPtr and new both took a reference.
     99         outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
    100         outputIntent->insert("OutputConditionIdentifier",
    101                              new SkPDFString("sRGB"))->unref();
    102         SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
    103         intentArray->unref();  // SkRefPtr and new both took a reference.
    104         intentArray->append(outputIntent.get());
    105         fDocCatalog->insert("OutputIntent", intentArray.get());
    106         */
    107 
    108         bool firstPage = true;
    109         for (int i = 0; i < fPages.count(); i++) {
    110             int resourceCount = fPageResources.count();
    111             fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
    112             addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
    113                                   fCatalog.get());
    114             if (i == 0) {
    115                 firstPage = false;
    116                 fSecondPageFirstResourceIndex = fPageResources.count();
    117             }
    118         }
    119 
    120         // Build font subsetting info before proceeding.
    121         perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
    122 
    123         // Figure out the size of things and inform the catalog of file offsets.
    124         off_t fileOffset = headerSize();
    125         fileOffset += fCatalog->setFileOffset(fDocCatalog.get(), fileOffset);
    126         fileOffset += fCatalog->setFileOffset(fPages[0], fileOffset);
    127         fileOffset += fPages[0]->getPageSize(fCatalog.get(), fileOffset);
    128         for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
    129             fileOffset += fCatalog->setFileOffset(fPageResources[i],
    130                                                   fileOffset);
    131         }
    132         // Add the size of resources of substitute objects used on page 1.
    133         fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset, true);
    134         if (fPages.count() > 1) {
    135             // TODO(vandebo): For linearized format, save the start of the
    136             // first page xref table and calculate the size.
    137         }
    138 
    139         for (int i = 0; i < fPageTree.count(); i++) {
    140             fileOffset += fCatalog->setFileOffset(fPageTree[i], fileOffset);
    141         }
    142 
    143         for (int i = 1; i < fPages.count(); i++) {
    144             fileOffset += fPages[i]->getPageSize(fCatalog.get(), fileOffset);
    145         }
    146 
    147         for (int i = fSecondPageFirstResourceIndex;
    148                  i < fPageResources.count();
    149                  i++) {
    150             fileOffset += fCatalog->setFileOffset(fPageResources[i],
    151                                                   fileOffset);
    152         }
    153 
    154         fileOffset += fCatalog->setSubstituteResourcesOffsets(fileOffset,
    155                                                               false);
    156         fXRefFileOffset = fileOffset;
    157     }
    158 
    159     emitHeader(stream);
    160     fDocCatalog->emitObject(stream, fCatalog.get(), true);
    161     fPages[0]->emitObject(stream, fCatalog.get(), true);
    162     fPages[0]->emitPage(stream, fCatalog.get());
    163     for (int i = 0; i < fSecondPageFirstResourceIndex; i++) {
    164         fPageResources[i]->emit(stream, fCatalog.get(), true);
    165     }
    166     fCatalog->emitSubstituteResources(stream, true);
    167     // TODO(vandebo): Support linearized format
    168     // if (fPages.size() > 1) {
    169     //     // TODO(vandebo): Save the file offset for the first page xref table.
    170     //     fCatalog->emitXrefTable(stream, true);
    171     // }
    172 
    173     for (int i = 0; i < fPageTree.count(); i++) {
    174         fPageTree[i]->emitObject(stream, fCatalog.get(), true);
    175     }
    176 
    177     for (int i = 1; i < fPages.count(); i++) {
    178         fPages[i]->emitPage(stream, fCatalog.get());
    179     }
    180 
    181     for (int i = fSecondPageFirstResourceIndex;
    182             i < fPageResources.count();
    183             i++) {
    184         fPageResources[i]->emit(stream, fCatalog.get(), true);
    185     }
    186 
    187     fCatalog->emitSubstituteResources(stream, false);
    188     int64_t objCount = fCatalog->emitXrefTable(stream, fPages.count() > 1);
    189     emitFooter(stream, objCount);
    190     return true;
    191 }
    192 
    193 bool SkPDFDocument::setPage(int pageNumber, SkPDFDevice* pdfDevice) {
    194     if (!fPageTree.isEmpty()) {
    195         return false;
    196     }
    197 
    198     pageNumber--;
    199     SkASSERT(pageNumber >= 0);
    200 
    201     if (pageNumber >= fPages.count()) {
    202         int oldSize = fPages.count();
    203         fPages.setCount(pageNumber + 1);
    204         for (int i = oldSize; i <= pageNumber; i++) {
    205             fPages[i] = NULL;
    206         }
    207     }
    208 
    209     SkPDFPage* page = new SkPDFPage(pdfDevice);
    210     SkSafeUnref(fPages[pageNumber]);
    211     fPages[pageNumber] = page;  // Reference from new passed to fPages.
    212     return true;
    213 }
    214 
    215 bool SkPDFDocument::appendPage(SkPDFDevice* pdfDevice) {
    216     if (!fPageTree.isEmpty()) {
    217         return false;
    218     }
    219 
    220     SkPDFPage* page = new SkPDFPage(pdfDevice);
    221     fPages.push(page);  // Reference from new passed to fPages.
    222     return true;
    223 }
    224 
    225 const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
    226     return fPages;
    227 }
    228 
    229 void SkPDFDocument::emitHeader(SkWStream* stream) {
    230     stream->writeText("%PDF-1.4\n%");
    231     // The PDF spec recommends including a comment with four bytes, all
    232     // with their high bits set.  This is "Skia" with the high bits set.
    233     stream->write32(0xD3EBE9E1);
    234     stream->writeText("\n");
    235 }
    236 
    237 size_t SkPDFDocument::headerSize() {
    238     SkDynamicMemoryWStream buffer;
    239     emitHeader(&buffer);
    240     return buffer.getOffset();
    241 }
    242 
    243 void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
    244     if (fTrailerDict.get() == NULL) {
    245         fTrailerDict = new SkPDFDict();
    246         fTrailerDict->unref();  // SkRefPtr and new both took a reference.
    247 
    248         // TODO(vandebo): Linearized format will take a Prev entry too.
    249         // TODO(vandebo): PDF/A requires an ID entry.
    250         fTrailerDict->insertInt("Size", objCount);
    251         fTrailerDict->insert("Root",
    252                              new SkPDFObjRef(fDocCatalog.get()))->unref();
    253     }
    254 
    255     stream->writeText("trailer\n");
    256     fTrailerDict->emitObject(stream, fCatalog.get(), false);
    257     stream->writeText("\nstartxref\n");
    258     stream->writeBigDecAsText(fXRefFileOffset);
    259     stream->writeText("\n%%EOF");
    260 }
    261