From 2ef12d4bb54312091d644f0ada3639c51c9f6e5a Mon Sep 17 00:00:00 2001 From: "vandebo@chromium.org" Date: Wed, 6 Jul 2011 23:31:24 +0000 Subject: [PATCH] [PDF] Add a concept of a substitute object to the SkPDFCatalog class. Code by Arthur Hsu, original code review: http://codereview.appspot.com/4650060/ Review URL: http://codereview.appspot.com/4639102 git-svn-id: http://skia.googlecode.com/svn/trunk@1812 2bbb7eff-a529-9590-31e7-b0007b416f81 --- include/pdf/SkPDFCatalog.h | 35 +++++++++++++++ include/pdf/SkPDFTypes.h | 25 +++++++---- src/pdf/SkPDFCatalog.cpp | 85 ++++++++++++++++++++++++++++++++++++- src/pdf/SkPDFDocument.cpp | 19 ++++++--- src/pdf/SkPDFTypes.cpp | 14 ++++-- tests/PDFPrimitivesTest.cpp | 59 +++++++++++++++++++++++-- 6 files changed, 211 insertions(+), 26 deletions(-) diff --git a/include/pdf/SkPDFCatalog.h b/include/pdf/SkPDFCatalog.h index e02ffa1a47..794bbb126c 100644 --- a/include/pdf/SkPDFCatalog.h +++ b/include/pdf/SkPDFCatalog.h @@ -70,6 +70,26 @@ public: */ int32_t emitXrefTable(SkWStream* stream, bool firstPage); + /** Set substitute object for the passed object. + */ + void setSubstitute(SkPDFObject* original, SkPDFObject* substitute); + + /** Find and return any substitute object set for the passed object. If + * there is none, return the passed object. + */ + SkPDFObject* getSubstituteObject(SkPDFObject* object); + + /** Set file offsets for the resources of substitute objects. + * @param fileOffset Accumulated offset of current document. + * @param firstPage Indicate whether this is for the first page only. + * @return Accumulated offset of resources of substitute objects. + */ + off_t setSubstituteResourcesOffsets(off_t fileOffset, bool firstPage); + + /** Emit the resources of substitute objects. + */ + void emitSubstituteResources(SkWStream* stream, bool firstPage); + private: struct Rec { Rec(SkPDFObject* object, bool onFirstPage) @@ -84,9 +104,22 @@ private: bool fOnFirstPage; }; + struct SubstituteMapping { + SubstituteMapping(SkPDFObject* original, SkPDFObject* substitute) + : fOriginal(original), fSubstitute(substitute) { + } + SkPDFObject* fOriginal; + SkPDFObject* fSubstitute; + }; + // TODO(vandebo) Make this a hash if it's a performance problem. SkTDArray fCatalog; + // TODO(arthurhsu) Make this a hash if it's a performance problem. + SkTDArray fSubstituteMap; + SkTDArray fSubstituteResourcesFirstPage; + SkTDArray fSubstituteResourcesRemaining; + // Number of objects on the first page. uint32_t fFirstPageCount; // Next object number to assign (on page > 1). @@ -97,6 +130,8 @@ private: int findObjectIndex(SkPDFObject* obj) const; int assignObjNum(SkPDFObject* obj); + + SkTDArray* getSubstituteList(bool firstPage); }; #endif diff --git a/include/pdf/SkPDFTypes.h b/include/pdf/SkPDFTypes.h index 6b5146a2b7..a115802266 100644 --- a/include/pdf/SkPDFTypes.h +++ b/include/pdf/SkPDFTypes.h @@ -39,15 +39,6 @@ public: SkPDFObject(); virtual ~SkPDFObject(); - /** Subclasses must implement this method to print the object to the - * PDF file. - * @param catalog The object catalog to use. - * @param indirect If true, output an object identifier with the object. - * @param stream The writable output stream to send the output to. - */ - virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, - bool indirect) = 0; - /** Return the size (number of bytes) of this object in the final output * file. Compound objects or objects that are computationally intensive * to output should override this method. @@ -65,6 +56,12 @@ public: */ virtual void getResources(SkTDArray* resourceList); + /** Emit this object unless the catalog has a substitute object, in which + * case emit that. + * @see emitObject + */ + void emit(SkWStream* stream, SkPDFCatalog* catalog, bool indirect); + /** Helper function to output an indirect object. * @param catalog The object catalog to use. * @param stream The writable output stream to send the output to. @@ -75,6 +72,16 @@ public: * @param catalog The object catalog to use. */ size_t getIndirectOutputSize(SkPDFCatalog* catalog); + +protected: + /** Subclasses must implement this method to print the object to the + * PDF file. + * @param catalog The object catalog to use. + * @param indirect If true, output an object identifier with the object. + * @param stream The writable output stream to send the output to. + */ + virtual void emitObject(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) = 0; }; /** \class SkPDFObjRef diff --git a/src/pdf/SkPDFCatalog.cpp b/src/pdf/SkPDFCatalog.cpp index afa9d9a1c9..5e9d018a89 100644 --- a/src/pdf/SkPDFCatalog.cpp +++ b/src/pdf/SkPDFCatalog.cpp @@ -25,10 +25,15 @@ SkPDFCatalog::SkPDFCatalog() fNextFirstPageObjNum(0) { } -SkPDFCatalog::~SkPDFCatalog() {} +SkPDFCatalog::~SkPDFCatalog() { + fSubstituteResourcesRemaining.safeUnrefAll(); + fSubstituteResourcesFirstPage.safeUnrefAll(); +} SkPDFObject* SkPDFCatalog::addObject(SkPDFObject* obj, bool onFirstPage) { - SkASSERT(findObjectIndex(obj) == -1); + if (findObjectIndex(obj) != -1) { // object already added + return obj; + } SkASSERT(fNextFirstPageObjNum == 0); if (onFirstPage) fFirstPageCount++; @@ -63,6 +68,12 @@ int SkPDFCatalog::findObjectIndex(SkPDFObject* obj) const { if (fCatalog[i].fObject == obj) return i; } + // If it's not in the main array, check if it's a substitute object. + for (int i = 0; i < fSubstituteMap.count(); ++i) { + if (fSubstituteMap[i].fSubstitute == obj) { + return findObjectIndex(fSubstituteMap[i].fOriginal); + } + } return -1; } @@ -126,3 +137,73 @@ int32_t SkPDFCatalog::emitXrefTable(SkWStream* stream, bool firstPage) { return fCatalog.count() + 1; } + +void SkPDFCatalog::setSubstitute(SkPDFObject* original, + SkPDFObject* substitute) { +#if defined(SK_DEBUG) + // Sanity check: is the original already in substitute list? + for (int i = 0; i < fSubstituteMap.count(); ++i) { + if (original == fSubstituteMap[i].fSubstitute || + original == fSubstituteMap[i].fOriginal) { + SkASSERT(false); + return; + } + } +#endif + // Check if the original is on first page. + bool onFirstPage = false; + for (int i = 0; i < fCatalog.count(); ++i) { + if (fCatalog[i].fObject == original) { + onFirstPage = fCatalog[i].fOnFirstPage; + break; + } +#if defined(SK_DEBUG) + if (i == fCatalog.count() - 1) { + SkASSERT(false); // original not in catalog + return; + } +#endif + } + + SubstituteMapping newMapping(original, substitute); + fSubstituteMap.append(1, &newMapping); + + // Add resource objects of substitute object to catalog. + SkTDArray* targetList = getSubstituteList(onFirstPage); + int existingSize = targetList->count(); + newMapping.fSubstitute->getResources(targetList); + for (int i = existingSize; i < targetList->count(); ++i) { + addObject((*targetList)[i], onFirstPage); + } +} + +SkPDFObject* SkPDFCatalog::getSubstituteObject(SkPDFObject* object) { + for (int i = 0; i < fSubstituteMap.count(); ++i) { + if (object == fSubstituteMap[i].fOriginal) { + return fSubstituteMap[i].fSubstitute; + } + } + return object; +} + +off_t SkPDFCatalog::setSubstituteResourcesOffsets(off_t fileOffset, + bool firstPage) { + SkTDArray* targetList = getSubstituteList(firstPage); + off_t offsetSum = fileOffset; + for (int i = 0; i < targetList->count(); ++i) { + offsetSum += setFileOffset((*targetList)[i], offsetSum); + } + return offsetSum; +} + +void SkPDFCatalog::emitSubstituteResources(SkWStream *stream, bool firstPage) { + SkTDArray* targetList = getSubstituteList(firstPage); + for (int i = 0; i < targetList->count(); ++i) { + (*targetList)[i]->emit(stream, this, true); + } +} + +SkTDArray* SkPDFCatalog::getSubstituteList(bool firstPage) { + return firstPage ? &fSubstituteResourcesFirstPage : + &fSubstituteResourcesRemaining; +} \ No newline at end of file diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp index fce100ba52..faa93601de 100644 --- a/src/pdf/SkPDFDocument.cpp +++ b/src/pdf/SkPDFDocument.cpp @@ -78,14 +78,14 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) { fDocCatalog->insert("OutputIntent", intentArray.get()); */ - bool first_page = true; + bool firstPage = 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); + fPages[i]->finalizePage(&fCatalog, firstPage, &fPageResources); + addResourcesToCatalog(resourceCount, firstPage, &fPageResources, + &fCatalog); if (i == 0) { - first_page = false; + firstPage = false; fSecondPageFirstResourceIndex = fPageResources.count(); } } @@ -97,6 +97,8 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) { fileOffset += fPages[0]->getPageSize(&fCatalog, fileOffset); for (int i = 0; i < fSecondPageFirstResourceIndex; i++) fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset); + // Add the size of resources of substitute objects used on page 1. + fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, true); if (fPages.count() > 1) { // TODO(vandebo) For linearized format, save the start of the // first page xref table and calculate the size. @@ -113,6 +115,7 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) { i++) fileOffset += fCatalog.setFileOffset(fPageResources[i], fileOffset); + fileOffset += fCatalog.setSubstituteResourcesOffsets(fileOffset, false); fXRefFileOffset = fileOffset; } @@ -121,7 +124,8 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) { fPages[0]->emitObject(stream, &fCatalog, true); fPages[0]->emitPage(stream, &fCatalog); for (int i = 0; i < fSecondPageFirstResourceIndex; i++) - fPageResources[i]->emitObject(stream, &fCatalog, true); + fPageResources[i]->emit(stream, &fCatalog, true); + fCatalog.emitSubstituteResources(stream, true); // TODO(vandebo) support linearized format //if (fPages.size() > 1) { // // TODO(vandebo) save the file offset for the first page xref table. @@ -135,8 +139,9 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) { fPages[i]->emitPage(stream, &fCatalog); for (int i = fSecondPageFirstResourceIndex; i < fPageResources.count(); i++) - fPageResources[i]->emitObject(stream, &fCatalog, true); + fPageResources[i]->emit(stream, &fCatalog, true); + fCatalog.emitSubstituteResources(stream, false); int64_t objCount = fCatalog.emitXrefTable(stream, fPages.count() > 1); emitFooter(stream, objCount); return true; diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp index b9420eba83..600145e08f 100644 --- a/src/pdf/SkPDFTypes.cpp +++ b/src/pdf/SkPDFTypes.cpp @@ -27,9 +27,15 @@ SkPDFObject::SkPDFObject() {} SkPDFObject::~SkPDFObject() {} +void SkPDFObject::emit(SkWStream* stream, SkPDFCatalog* catalog, + bool indirect) { + SkPDFObject* realObject = catalog->getSubstituteObject(this); + return realObject->emitObject(stream, catalog, indirect); +} + size_t SkPDFObject::getOutputSize(SkPDFCatalog* catalog, bool indirect) { SkDynamicMemoryWStream buffer; - emitObject(&buffer, catalog, indirect); + emit(&buffer, catalog, indirect); return buffer.getOffset(); } @@ -38,7 +44,7 @@ void SkPDFObject::getResources(SkTDArray* resourceList) {} void SkPDFObject::emitIndirectObject(SkWStream* stream, SkPDFCatalog* catalog) { catalog->emitObjectNumber(stream, this); stream->writeText(" obj\n"); - emitObject(stream, catalog, false); + emit(stream, catalog, false); stream->writeText("\nendobj\n"); } @@ -292,7 +298,7 @@ void SkPDFArray::emitObject(SkWStream* stream, SkPDFCatalog* catalog, stream->writeText("["); for (int i = 0; i < fValue.count(); i++) { - fValue[i]->emitObject(stream, catalog, false); + fValue[i]->emit(stream, catalog, false); if (i + 1 < fValue.count()) stream->writeText(" "); } @@ -350,7 +356,7 @@ void SkPDFDict::emitObject(SkWStream* stream, SkPDFCatalog* catalog, for (int i = 0; i < fValue.count(); i++) { fValue[i].key->emitObject(stream, catalog, false); stream->writeText(" "); - fValue[i].value->emitObject(stream, catalog, false); + fValue[i].value->emit(stream, catalog, false); stream->writeText("\n"); } stream->writeText(">>"); diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp index a5feba8425..d4d1eea958 100644 --- a/tests/PDFPrimitivesTest.cpp +++ b/tests/PDFPrimitivesTest.cpp @@ -24,6 +24,24 @@ #include "SkScalar.h" #include "SkStream.h" +class SkPDFTestDict : public SkPDFDict { +public: + void getResources(SkTDArray* resourceList) { + resourceList->setReserve(resourceList->count() + fResources.count()); + for (int i = 0; i < fResources.count(); i++) { + resourceList->push(fResources[i]); + fResources[i]->ref(); + } + } + + void addResource(SkPDFObject* object) { + fResources.append(1, &object); + } + +private: + SkTDArray fResources; +}; + static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset, const void* buffer, size_t len) { SkAutoDataUnref data(stream.copyToData()); @@ -36,11 +54,12 @@ static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset, static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj, const std::string& representation, bool indirect) { - size_t directSize = obj->getOutputSize(NULL, false); + SkPDFCatalog catalog; + size_t directSize = obj->getOutputSize(&catalog, false); REPORTER_ASSERT(reporter, directSize == representation.size()); SkDynamicMemoryWStream buffer; - obj->emitObject(&buffer, NULL, false); + obj->emit(&buffer, &catalog, false); REPORTER_ASSERT(reporter, directSize == buffer.getOffset()); REPORTER_ASSERT(reporter, stream_equals(buffer, 0, representation.c_str(), directSize)); @@ -52,7 +71,6 @@ static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj, static char footer[] = "\nendobj\n"; static size_t footerLen = strlen(footer); - SkPDFCatalog catalog; catalog.addObject(obj, false); size_t indirectSize = obj->getOutputSize(&catalog, true); @@ -60,7 +78,7 @@ static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj, indirectSize == directSize + headerLen + footerLen); buffer.reset(); - obj->emitObject(&buffer, &catalog, true); + obj->emit(&buffer, &catalog, true); REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset()); REPORTER_ASSERT(reporter, stream_equals(buffer, 0, header, headerLen)); REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen, @@ -121,6 +139,37 @@ static void TestObjectRef(skiatest::Reporter* reporter) { buffer.getOffset())); } +static void TestSubstitute(skiatest::Reporter* reporter) { + SkRefPtr proxy = new SkPDFTestDict(); + proxy->unref(); // SkRefPtr and new both took a reference. + SkRefPtr stub = new SkPDFTestDict(); + stub->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int33 = new SkPDFInt(33); + int33->unref(); // SkRefPtr and new both took a reference. + SkRefPtr stubResource = new SkPDFDict(); + stubResource->unref(); // SkRefPtr and new both took a reference. + SkRefPtr int44 = new SkPDFInt(44); + int44->unref(); // SkRefPtr and new both took a reference. + + stub->insert("Value", int33.get()); + stubResource->insert("InnerValue", int44.get()); + stub->addResource(stubResource.get()); + + SkPDFCatalog catalog; + catalog.addObject(proxy.get(), false); + catalog.setSubstitute(proxy.get(), stub.get()); + + SkDynamicMemoryWStream buffer; + proxy->emit(&buffer, &catalog, false); + catalog.emitSubstituteResources(&buffer, false); + + char expectedResult[] = + "<>1 0 obj\n<>\nendobj\n"; + REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult)); + REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult, + buffer.getOffset())); +} + static void TestPDFPrimitives(skiatest::Reporter* reporter) { SkRefPtr int42 = new SkPDFInt(42); int42->unref(); // SkRefPtr and new both took a reference. @@ -212,6 +261,8 @@ static void TestPDFPrimitives(skiatest::Reporter* reporter) { TestCatalog(reporter); TestObjectRef(reporter); + + TestSubstitute(reporter); } #include "TestClassDef.h"