[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
This commit is contained in:
parent
840e9f3776
commit
2ef12d4bb5
@ -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<struct Rec> fCatalog;
|
||||
|
||||
// TODO(arthurhsu) Make this a hash if it's a performance problem.
|
||||
SkTDArray<SubstituteMapping> fSubstituteMap;
|
||||
SkTDArray<SkPDFObject*> fSubstituteResourcesFirstPage;
|
||||
SkTDArray<SkPDFObject*> 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<SkPDFObject*>* getSubstituteList(bool firstPage);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -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<SkPDFObject*>* 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
|
||||
|
@ -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<SkPDFObject*>* 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<SkPDFObject*>* 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<SkPDFObject*>* targetList = getSubstituteList(firstPage);
|
||||
for (int i = 0; i < targetList->count(); ++i) {
|
||||
(*targetList)[i]->emit(stream, this, true);
|
||||
}
|
||||
}
|
||||
|
||||
SkTDArray<SkPDFObject*>* SkPDFCatalog::getSubstituteList(bool firstPage) {
|
||||
return firstPage ? &fSubstituteResourcesFirstPage :
|
||||
&fSubstituteResourcesRemaining;
|
||||
}
|
@ -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;
|
||||
|
@ -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<SkPDFObject*>* 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(">>");
|
||||
|
@ -24,6 +24,24 @@
|
||||
#include "SkScalar.h"
|
||||
#include "SkStream.h"
|
||||
|
||||
class SkPDFTestDict : public SkPDFDict {
|
||||
public:
|
||||
void getResources(SkTDArray<SkPDFObject*>* 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<SkPDFObject*> 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<SkPDFTestDict> proxy = new SkPDFTestDict();
|
||||
proxy->unref(); // SkRefPtr and new both took a reference.
|
||||
SkRefPtr<SkPDFTestDict> stub = new SkPDFTestDict();
|
||||
stub->unref(); // SkRefPtr and new both took a reference.
|
||||
SkRefPtr<SkPDFInt> int33 = new SkPDFInt(33);
|
||||
int33->unref(); // SkRefPtr and new both took a reference.
|
||||
SkRefPtr<SkPDFDict> stubResource = new SkPDFDict();
|
||||
stubResource->unref(); // SkRefPtr and new both took a reference.
|
||||
SkRefPtr<SkPDFInt> 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[] =
|
||||
"<</Value 33\n>>1 0 obj\n<</InnerValue 44\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<SkPDFInt> 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"
|
||||
|
Loading…
Reference in New Issue
Block a user