diff options
Diffstat (limited to 'src/pdf/SkPDFDocument.cpp')
-rw-r--r-- | src/pdf/SkPDFDocument.cpp | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp new file mode 100644 index 0000000..95370b4 --- /dev/null +++ b/src/pdf/SkPDFDocument.cpp @@ -0,0 +1,192 @@ +/* + * 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"); +} |