| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * 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 resouces, starting at firstIndex to the catalog, removing any dupes. |
| // A hash table would be really nice here. |
| void addResoucesToCatalog(int firstIndex, bool firstPage, |
| SkTDArray<SkPDFObject*>* resouceList, |
| SkPDFCatalog* catalog) { |
| for (int i = firstIndex; i < resouceList->count(); i++) { |
| int index = resouceList->find((*resouceList)[i]); |
| if (index != i) { |
| // The resouce lists themselves should already be unique, so the |
| // first page resouces shouldn't have any dups (assuming the first |
| // page resouces are handled first). |
| SkASSERT(!firstPage); |
| (*resouceList)[i]->unref(); |
| resouceList->removeShuffle(i); |
| } else { |
| catalog->addObject((*resouceList)[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); |
| SkRefPtr<SkPDFObjRef> pageTreeRootRef = new SkPDFObjRef(pageTreeRoot); |
| pageTreeRootRef->unref(); // SkRefPtr and new both took a reference. |
| fDocCatalog->insert("Pages", pageTreeRootRef.get()); |
| bool first_page = true; |
| for (int i = 0; i < fPages.count(); i++) { |
| int resourceCount = fPageResources.count(); |
| fPages[i]->finalizePage(&fCatalog, first_page, &fPageResources); |
| addResoucesToCatalog(resourceCount, first_page, &fPageResources, |
| &fCatalog); |
| if (i == 0) { |
| first_page = false; |
| fSecondPageFirstResouceIndex = 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 < fSecondPageFirstResouceIndex; 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 = fSecondPageFirstResouceIndex; |
| 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 < fSecondPageFirstResouceIndex; 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 = fSecondPageFirstResouceIndex; 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; |
| } |
| |
| 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. |
| |
| SkPDFInt* objCountInt = new SkPDFInt(objCount); |
| fTrailerDict->insert("Size", objCountInt); |
| objCountInt->unref(); // insert took a ref and we're done with it. |
| |
| // TODO(vandebo) Linearized format will take a Prev entry too. |
| |
| SkPDFObjRef* docCatalogRef = new SkPDFObjRef(fDocCatalog.get()); |
| fTrailerDict->insert("Root", docCatalogRef); |
| docCatalogRef->unref(); // insert took a ref and we're done with it. |
| |
| // TODO(vandebo) PDF/A requires an ID entry. |
| } |
| |
| stream->writeText("trailer\n"); |
| fTrailerDict->emitObject(stream, &fCatalog, false); |
| stream->writeText("\nstartxref\n"); |
| stream->writeBigDecAsText(fXRefFileOffset); |
| stream->writeText("\n%%EOF"); |
| } |