C++程序  |  193行  |  7.24 KB

/*
 * Copyright (C) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "SkPDFDevice.h"
#include "SkPDFDocument.h"
#include "SkPDFPage.h"
#include "SkStream.h"

// Add the resources, starting at firstIndex to the catalog, removing any dupes.
// A hash table would be really nice here.
void addResourcesToCatalog(int firstIndex, bool firstPage,
                          SkTDArray<SkPDFObject*>* resourceList,
                          SkPDFCatalog* catalog) {
    for (int i = firstIndex; i < resourceList->count(); i++) {
        int index = resourceList->find((*resourceList)[i]);
        if (index != i) {
            (*resourceList)[i]->unref();
            resourceList->removeShuffle(i);
            i--;
        } else {
            catalog->addObject((*resourceList)[i], firstPage);
        }
    }
}

SkPDFDocument::SkPDFDocument() : fXRefFileOffset(0) {
    fDocCatalog = new SkPDFDict("Catalog");
    fDocCatalog->unref();  // SkRefPtr and new both took a reference.
    fCatalog.addObject(fDocCatalog.get(), true);
}

SkPDFDocument::~SkPDFDocument() {
    fPages.safeUnrefAll();

    // The page tree has both child and parent pointers, so it creates a
    // reference cycle.  We must clear that cycle to properly reclaim memory.
    for (int i = 0; i < fPageTree.count(); i++)
        fPageTree[i]->clear();
    fPageTree.safeUnrefAll();
    fPageResources.safeUnrefAll();
}

bool SkPDFDocument::emitPDF(SkWStream* stream) {
    if (fPages.isEmpty())
        return false;

    // We haven't emitted the document before if fPageTree is empty.
    if (fPageTree.count() == 0) {
        SkPDFDict* pageTreeRoot;
        SkPDFPage::generatePageTree(fPages, &fCatalog, &fPageTree,
                                    &pageTreeRoot);
        fDocCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref();

        /* TODO(vandebo) output intent
        SkRefPtr<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent");
        outputIntent->unref();  // SkRefPtr and new both took a reference.
        outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref();
        outputIntent->insert("OutputConditionIdentifier",
                             new SkPDFString("sRGB"))->unref();
        SkRefPtr<SkPDFArray> intentArray = new SkPDFArray;
        intentArray->unref();  // SkRefPtr and new both took a reference.
        intentArray->append(outputIntent.get());
        fDocCatalog->insert("OutputIntent", intentArray.get());
        */

        bool first_page = true;
        for (int i = 0; i < fPages.count(); i++) {
            int resourceCount = fPageResources.count();
            fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources);
            addResourcesToCatalog(resourceCount, first_page, &fPageResources,
                                 &fCatalog);
            if (i == 0) {
                first_page = false;
                fSecondPageFirstResourceIndex = fPageResources.count();
            }
        }

        // Figure out the size of things and inform the catalog of file offsets.
        off_t fileOffset = headerSize();
        fileOffset += fCatalog.setFileOffset(fDocCatalog.get(), fileOffset);
        fileOffset += fCatalog.setFileOffset(fPages[0], fileOffset);
        fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset);
        for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
            fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);
        if (fPages.count() > 1) {
            // TODO(vandebo) For linearized format, save the start of the
            // first page xref table and calculate the size.
        }

        for (int i = 0; i < fPageTree.count(); i++)
            fileOffset += fCatalog.setFileOffset(fPageTree[i], fileOffset);

        for (int i = 1; i < fPages.count(); i++)
            fileOffset += fPages[i]->getPageSize(&fCatalog, fileOffset);

        for (int i = fSecondPageFirstResourceIndex;
                 i < fPageResources.count();
                 i++)
            fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset);

        fXRefFileOffset = fileOffset;
    }

    emitHeader(stream);
    fDocCatalog->emitObject(stream, &fCatalog, true);
    fPages[0]->emitObject(stream, &fCatalog, true);
    fPages[0]->emitPage(stream, &fCatalog);
    for (int i = 0; i < fSecondPageFirstResourceIndex; i++)
        fPageResources[i]->emitObject(stream, &fCatalog, true);
    // TODO(vandebo) support linearized format
    //if (fPages.size() > 1) {
    //    // TODO(vandebo) save the file offset for the first page xref table.
    //    fCatalog.emitXrefTable(stream, true);
    //}

    for (int i = 0; i < fPageTree.count(); i++)
        fPageTree[i]->emitObject(stream, &fCatalog, true);

    for (int i = 1; i < fPages.count(); i++)
        fPages[i]->emitPage(stream, &fCatalog);

    for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++)
        fPageResources[i]->emitObject(stream, &fCatalog, true);

    int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1);
    emitFooter(stream, objCount);
    return true;
}

bool SkPDFDocument::appendPage(const SkRefPtr<SkPDFDevice>& pdfDevice) {
    if (fPageTree.count() != 0)
        return false;

    SkPDFPage* page = new SkPDFPage(pdfDevice);
    fPages.push(page);  // Reference from new passed to fPages.
    // The rest of the pages will be added to the catalog along with the rest
    // of the page tree.  But the first page has to be marked as such, so we
    // handle it here.
    if (fPages.count() == 1)
        fCatalog.addObject(page, true);
    return true;
}

const SkTDArray<SkPDFPage*>& SkPDFDocument::getPages() {
    return fPages;
}

void SkPDFDocument::emitHeader(SkWStream* stream) {
    stream->writeText("%PDF-1.4\n%");
    // The PDF spec recommends including a comment with four bytes, all
    // with their high bits set.  This is "Skia" with the high bits set.
    stream->write32(0xD3EBE9E1);
    stream->writeText("\n");
}

size_t SkPDFDocument::headerSize() {
    SkDynamicMemoryWStream buffer;
    emitHeader(&buffer);
    return buffer.getOffset();
}

void SkPDFDocument::emitFooter(SkWStream* stream, int64_t objCount) {
    if (fTrailerDict.get() == NULL) {
        fTrailerDict = new SkPDFDict();
        fTrailerDict->unref();  // SkRefPtr and new both took a reference.

        // TODO(vandebo) Linearized format will take a Prev entry too.
        // TODO(vandebo) PDF/A requires an ID entry.
        fTrailerDict->insert("Size", new SkPDFInt(objCount))->unref();
        fTrailerDict->insert("Root",
                             new SkPDFObjRef(fDocCatalog.get()))->unref();
    }

    stream->writeText("trailer\n");
    fTrailerDict->emitObject(stream, &fCatalog, false);
    stream->writeText("\nstartxref\n");
    stream->writeBigDecAsText(fXRefFileOffset);
    stream->writeText("\n%%EOF");
}