SkPDF: SkPDFDocument reorganized so that some objects can be serialized early.

No change in output.

BUG=skia:5087
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1802033003

Review URL: https://codereview.chromium.org/1802033003
This commit is contained in:
halcanary 2016-03-21 13:45:05 -07:00 committed by Commit bot
parent b4bd11e665
commit 50e82e6176
3 changed files with 251 additions and 192 deletions

View File

@ -10,43 +10,87 @@
#include "SkPDFDevice.h"
#include "SkPDFDocument.h"
#include "SkPDFFont.h"
#include "SkPDFMetadata.h"
#include "SkPDFStream.h"
#include "SkPDFTypes.h"
#include "SkPDFUtils.h"
#include "SkStream.h"
static void emit_pdf_header(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");
SkPDFObjectSerializer::SkPDFObjectSerializer() : fBaseOffset(0), fNextToBeSerialized(0) {}
template <class T> static void renew(T* t) { t->~T(); new (t) T; }
void SkPDFObjectSerializer::addObjectRecursively(const sk_sp<SkPDFObject>& object) {
fObjNumMap.addObjectRecursively(object.get(), fSubstituteMap);
}
static void emit_pdf_footer(SkWStream* stream,
const SkPDFObjNumMap& objNumMap,
const SkPDFSubstituteMap& substitutes,
SkPDFObject* docCatalog,
int64_t objCount,
int32_t xRefFileOffset,
sk_sp<SkPDFObject> info,
void SkPDFObjectSerializer::serializeHeader(SkWStream* wStream, const SkPDFMetadata& md) {
fBaseOffset = wStream->bytesWritten();
static const char kHeader[] = "%PDF-1.4\n%\xE1\xE9\xEB\xD3\n";
wStream->write(kHeader, strlen(kHeader));
// The PDF spec recommends including a comment with four
// bytes, all with their high bits set. "\xD3\xEB\xE9\xE1" is
// "Skia" with the high bits set.
fInfoDict.reset(md.createDocumentInformationDict());
this->addObjectRecursively(fInfoDict);
this->serializeObjects(wStream);
}
// Serialize all objects in the fObjNumMap that have not yet been serialized;
void SkPDFObjectSerializer::serializeObjects(SkWStream* wStream) {
const SkTArray<sk_sp<SkPDFObject>>& objects = fObjNumMap.objects();
while (fNextToBeSerialized < objects.count()) {
SkPDFObject* object = objects[fNextToBeSerialized].get();
int32_t index = fNextToBeSerialized + 1; // Skip object 0.
// "The first entry in the [XREF] table (object number 0) is
// always free and has a generation number of 65,535; it is
// the head of the linked list of free objects."
SkASSERT(fOffsets.count() == fNextToBeSerialized);
fOffsets.push(this->offset(wStream));
SkASSERT(object == fSubstituteMap.getSubstitute(object));
wStream->writeDecAsText(index);
wStream->writeText(" 0 obj\n"); // Generation number is always 0.
object->emitObject(wStream, fObjNumMap, fSubstituteMap);
wStream->writeText("\nendobj\n");
object->drop();
++fNextToBeSerialized;
}
}
// Xref table and footer
void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
const sk_sp<SkPDFObject> docCatalog,
sk_sp<SkPDFObject> id) {
this->serializeObjects(wStream);
int32_t xRefFileOffset = this->offset(wStream);
// Include the special zeroth object in the count.
int32_t objCount = SkToS32(fOffsets.count() + 1);
wStream->writeText("xref\n0 ");
wStream->writeDecAsText(objCount);
wStream->writeText("\n0000000000 65535 f \n");
for (int i = 0; i < fOffsets.count(); i++) {
wStream->writeBigDecAsText(fOffsets[i], 10);
wStream->writeText(" 00000 n \n");
}
SkPDFDict trailerDict;
// TODO(http://crbug.com/80908): Linearized format will take a
// Prev entry too.
trailerDict.insertInt("Size", int(objCount));
trailerDict.insertObjRef("Root", sk_ref_sp(docCatalog));
SkASSERT(info);
trailerDict.insertObjRef("Info", std::move(info));
trailerDict.insertInt("Size", objCount);
SkASSERT(docCatalog);
trailerDict.insertObjRef("Root", docCatalog);
SkASSERT(fInfoDict);
trailerDict.insertObjRef("Info", std::move(fInfoDict));
if (id) {
trailerDict.insertObject("ID", std::move(id));
}
stream->writeText("trailer\n");
trailerDict.emitObject(stream, objNumMap, substitutes);
stream->writeText("\nstartxref\n");
stream->writeBigDecAsText(xRefFileOffset);
stream->writeText("\n%%EOF");
wStream->writeText("trailer\n");
trailerDict.emitObject(wStream, fObjNumMap, fSubstituteMap);
wStream->writeText("\nstartxref\n");
wStream->writeBigDecAsText(xRefFileOffset);
wStream->writeText("\n%%EOF");
}
int32_t SkPDFObjectSerializer::offset(SkWStream* wStream) {
size_t offset = wStream->bytesWritten();
SkASSERT(offset > fBaseOffset);
return SkToS32(offset - fBaseOffset);
}
static void perform_font_subsetting(
@ -86,7 +130,7 @@ static sk_sp<SkPDFDict> create_pdf_page(const SkPDFDevice* pageDevice) {
// return root node.
static sk_sp<SkPDFDict> generate_page_tree(
const SkTDArray<SkPDFDict*>& pages,
SkTDArray<SkPDFDict*>& pages,
SkTDArray<SkPDFDict*>* pageTree) {
// PDF wants a tree describing all the pages in the document. We arbitrary
// choose 8 (kNodeSize) as the number of allowed children. The internal
@ -160,111 +204,6 @@ static sk_sp<SkPDFDict> generate_page_tree(
return sk_ref_sp(curNodes[0]);
}
static bool emit_pdf_document(const SkTArray<sk_sp<const SkPDFDevice>>& pageDevices,
const SkPDFMetadata& metadata,
SkWStream* stream) {
if (pageDevices.empty()) {
return false;
}
SkTDArray<SkPDFDict*> pages; // TODO: SkTArray<sk_sp<SkPDFDict>>
auto dests = sk_make_sp<SkPDFDict>();
for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) {
SkASSERT(pageDevice);
SkASSERT(pageDevices[0]->getCanon() == pageDevice->getCanon());
sk_sp<SkPDFDict> page(create_pdf_page(pageDevice.get()));
pageDevice->appendDestinations(dests.get(), page.get());
pages.push(page.release());
}
auto docCatalog = sk_make_sp<SkPDFDict>("Catalog");
sk_sp<SkPDFObject> infoDict(metadata.createDocumentInformationDict());
sk_sp<SkPDFObject> id, xmp;
#ifdef SK_PDF_GENERATE_PDFA
SkPDFMetadata::UUID uuid = metadata.uuid();
// We use the same UUID for Document ID and Instance ID since this
// is the first revision of this document (and Skia does not
// support revising existing PDF documents).
// If we are not in PDF/A mode, don't use a UUID since testing
// works best with reproducible outputs.
id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid));
xmp.reset(metadata.createXMPObject(uuid, uuid));
docCatalog->insertObjRef("Metadata", std::move(xmp));
// sRGB is specified by HTML, CSS, and SVG.
auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent");
outputIntent->insertName("S", "GTS_PDFA1");
outputIntent->insertString("RegistryName", "http://www.color.org");
outputIntent->insertString("OutputConditionIdentifier",
"sRGB IEC61966-2.1");
auto intentArray = sk_make_sp<SkPDFArray>();
intentArray->appendObject(std::move(outputIntent));
// Don't specify OutputIntents if we are not in PDF/A mode since
// no one has ever asked for this feature.
docCatalog->insertObject("OutputIntents", std::move(intentArray));
#endif
SkTDArray<SkPDFDict*> pageTree;
docCatalog->insertObjRef("Pages", generate_page_tree(pages, &pageTree));
if (dests->size() > 0) {
docCatalog->insertObjRef("Dests", std::move(dests));
}
// Build font subsetting info before proceeding.
SkPDFSubstituteMap substitutes;
perform_font_subsetting(pageDevices, &substitutes);
SkPDFObjNumMap objNumMap;
objNumMap.addObjectRecursively(infoDict.get(), substitutes);
objNumMap.addObjectRecursively(docCatalog.get(), substitutes);
size_t baseOffset = stream->bytesWritten();
emit_pdf_header(stream);
SkTDArray<int32_t> offsets;
for (int i = 0; i < objNumMap.objects().count(); ++i) {
SkPDFObject* object = objNumMap.objects()[i].get();
size_t offset = stream->bytesWritten();
// This assert checks that size(pdf_header) > 0 and that
// the output stream correctly reports bytesWritten().
SkASSERT(offset > baseOffset);
offsets.push(SkToS32(offset - baseOffset));
SkASSERT(object == substitutes.getSubstitute(object));
SkASSERT(objNumMap.getObjectNumber(object) == i + 1);
stream->writeDecAsText(i + 1);
stream->writeText(" 0 obj\n"); // Generation number is always 0.
object->emitObject(stream, objNumMap, substitutes);
stream->writeText("\nendobj\n");
object->drop();
}
int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset);
// Include the zeroth object in the count.
int32_t objCount = SkToS32(offsets.count() + 1);
stream->writeText("xref\n0 ");
stream->writeDecAsText(objCount);
stream->writeText("\n0000000000 65535 f \n");
for (int i = 0; i < offsets.count(); i++) {
SkASSERT(offsets[i] > 0);
stream->writeBigDecAsText(offsets[i], 10);
stream->writeText(" 00000 n \n");
}
emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
xRefFileOffset, std::move(infoDict), std::move(id));
// 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 < pageTree.count(); i++) {
pageTree[i]->drop();
}
pageTree.safeUnrefAll();
pages.unrefAll();
return true;
}
#if 0
// TODO(halcanary): expose notEmbeddableCount in SkDocument
void GetCountOfFontTypes(
@ -308,28 +247,32 @@ void GetCountOfFontTypes(
template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullptr; }
////////////////////////////////////////////////////////////////////////////////
namespace {
class SkPDFDocument : public SkDocument {
public:
SkPDFDocument(SkWStream* stream,
SkPDFDocument::SkPDFDocument(SkWStream* stream,
void (*doneProc)(SkWStream*, bool),
SkScalar rasterDpi,
SkPixelSerializer* jpegEncoder)
: SkDocument(stream, doneProc)
, fRasterDpi(rasterDpi) {
fCanon.setPixelSerializer(SkSafeRef(jpegEncoder));
}
}
virtual ~SkPDFDocument() {
// subclasses must call close() in their destructors
SkPDFDocument::~SkPDFDocument() {
// subclasses of SkDocument must call close() in their destructors.
this->close();
}
void SkPDFDocument::serialize(const sk_sp<SkPDFObject>& object) {
fObjectSerializer.addObjectRecursively(object);
fObjectSerializer.serializeObjects(this->getStream());
}
SkCanvas* SkPDFDocument::onBeginPage(SkScalar width, SkScalar height,
const SkRect& trimBox) {
SkASSERT(!fCanvas.get()); // endPage() was called before this.
if (fPageDevices.empty()) {
// if this is the first page if the document.
fObjectSerializer.serializeHeader(this->getStream(), fMetadata);
}
protected:
SkCanvas* onBeginPage(SkScalar width, SkScalar height,
const SkRect& trimBox) override {
SkASSERT(!fCanvas.get());
SkISize pageSize = SkISize::Make(
SkScalarRoundToInt(width), SkScalarRoundToInt(height));
sk_sp<SkPDFDevice> device(
@ -339,45 +282,99 @@ protected:
fCanvas->clipRect(trimBox);
fCanvas->translate(trimBox.x(), trimBox.y());
return fCanvas.get();
}
}
void onEndPage() override {
void SkPDFDocument::onEndPage() {
SkASSERT(fCanvas.get());
fCanvas->flush();
fCanvas.reset(nullptr);
}
}
bool onClose(SkWStream* stream) override {
SkASSERT(!fCanvas.get());
bool success = emit_pdf_document(fPageDevices, fMetadata, stream);
void SkPDFDocument::onAbort() {
fCanvas.reset(nullptr);
fPageDevices.reset();
fCanon.reset();
return success;
}
renew(&fObjectSerializer);
}
void onAbort() override {
fPageDevices.reset();
fCanon.reset();
}
void setMetadata(const SkDocument::Attribute info[],
void SkPDFDocument::setMetadata(const SkDocument::Attribute info[],
int infoCount,
const SkTime::DateTime* creationDate,
const SkTime::DateTime* modifiedDate) override {
const SkTime::DateTime* modifiedDate) {
fMetadata.fInfo.reset(info, infoCount);
fMetadata.fCreation.reset(clone(creationDate));
fMetadata.fModified.reset(clone(modifiedDate));
}
bool SkPDFDocument::onClose(SkWStream* stream) {
SkASSERT(!fCanvas.get());
if (fPageDevices.empty()) {
fPageDevices.reset();
fCanon.reset();
renew(&fObjectSerializer);
return false;
}
SkTDArray<SkPDFDict*> pages; // TODO: SkTArray<sk_sp<SkPDFDict>>
auto dests = sk_make_sp<SkPDFDict>();
for (const sk_sp<const SkPDFDevice>& pageDevice : fPageDevices) {
SkASSERT(pageDevice);
SkASSERT(fPageDevices[0]->getCanon() == pageDevice->getCanon());
sk_sp<SkPDFDict> page(create_pdf_page(pageDevice.get()));
pageDevice->appendDestinations(dests.get(), page.get());
pages.push(page.release());
}
private:
SkPDFCanon fCanon;
SkTArray<sk_sp<const SkPDFDevice>> fPageDevices;
sk_sp<SkCanvas> fCanvas;
SkScalar fRasterDpi;
SkPDFMetadata fMetadata;
};
} // namespace
auto docCatalog = sk_make_sp<SkPDFDict>("Catalog");
sk_sp<SkPDFObject> id, xmp;
#ifdef SK_PDF_GENERATE_PDFA
SkPDFMetadata::UUID uuid = metadata.uuid();
// We use the same UUID for Document ID and Instance ID since this
// is the first revision of this document (and Skia does not
// support revising existing PDF documents).
// If we are not in PDF/A mode, don't use a UUID since testing
// works best with reproducible outputs.
id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid));
xmp.reset(metadata.createXMPObject(uuid, uuid));
docCatalog->insertObjRef("Metadata", std::move(xmp));
// sRGB is specified by HTML, CSS, and SVG.
auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent");
outputIntent->insertName("S", "GTS_PDFA1");
outputIntent->insertString("RegistryName", "http://www.color.org");
outputIntent->insertString("OutputConditionIdentifier",
"sRGB IEC61966-2.1");
auto intentArray = sk_make_sp<SkPDFArray>();
intentArray->appendObject(std::move(outputIntent));
// Don't specify OutputIntents if we are not in PDF/A mode since
// no one has ever asked for this feature.
docCatalog->insertObject("OutputIntents", std::move(intentArray));
#endif
SkTDArray<SkPDFDict*> pageTree;
docCatalog->insertObjRef("Pages", generate_page_tree(pages, &pageTree));
if (dests->size() > 0) {
docCatalog->insertObjRef("Dests", std::move(dests));
}
// Build font subsetting info before calling addObjectRecursively().
perform_font_subsetting(fPageDevices, &fObjectSerializer.fSubstituteMap);
fObjectSerializer.addObjectRecursively(docCatalog);
fObjectSerializer.serializeObjects(this->getStream());
fObjectSerializer.serializeFooter(
this->getStream(), docCatalog, std::move(id));
pageTree.unrefAll(); // TODO(halcanary): make this unnecesary by
// refactoring generate_page_tree().
pages.unrefAll();
fPageDevices.reset();
fCanon.reset();
renew(&fObjectSerializer);
return true;
}
///////////////////////////////////////////////////////////////////////////////
sk_sp<SkDocument> SkPDFMakeDocument(SkWStream* stream,

View File

@ -8,6 +8,8 @@
#define SkPDFDocument_DEFINED
#include "SkDocument.h"
#include "SkPDFMetadata.h"
#include "SkPDFTypes.h"
sk_sp<SkDocument> SkPDFMakeDocument(
SkWStream* stream,
@ -15,4 +17,63 @@ sk_sp<SkDocument> SkPDFMakeDocument(
SkScalar rasterDpi,
SkPixelSerializer* jpegEncoder);
// Logically part of SkPDFDocument (like SkPDFCanon), but separate to
// keep similar functionality together.
struct SkPDFObjectSerializer : SkNoncopyable {
SkPDFObjNumMap fObjNumMap;
SkPDFSubstituteMap fSubstituteMap;
SkTDArray<int32_t> fOffsets;
sk_sp<SkPDFObject> fInfoDict;
size_t fBaseOffset;
int32_t fNextToBeSerialized; // index in fObjNumMap
SkPDFObjectSerializer();
void addObjectRecursively(const sk_sp<SkPDFObject>&);
void serializeHeader(SkWStream*, const SkPDFMetadata&);
void serializeObjects(SkWStream*);
void serializeFooter(SkWStream*, const sk_sp<SkPDFObject>, sk_sp<SkPDFObject>);
int32_t offset(SkWStream*);
};
/** Concrete implementation of SkDocument that creates PDF files. This
class does not produced linearized or optimized PDFs; instead it
it attempts to use a minimum amount of RAM. */
class SkPDFDocument : public SkDocument {
public:
SkPDFDocument(SkWStream*,
void (*)(SkWStream*, bool),
SkScalar,
SkPixelSerializer*);
virtual ~SkPDFDocument();
SkCanvas* onBeginPage(SkScalar, SkScalar, const SkRect&) override;
void onEndPage() override;
bool onClose(SkWStream*) override;
void onAbort() override;
void setMetadata(const SkDocument::Attribute[],
int,
const SkTime::DateTime*,
const SkTime::DateTime*) override;
/**
Serialize the object, as well as any other objects it
indirectly refers to. If any any other objects have been added
to the SkPDFObjNumMap without serializing them, they will be
serialized as well.
It might go without saying that objects should not be changed
after calling serialize, since those changes will be too late.
The same goes for changes to the SkPDFSubstituteMap that effect
the object or its dependencies.
*/
void serialize(const sk_sp<SkPDFObject>&);
SkPDFCanon* canon() { return &fCanon; }
private:
SkPDFObjectSerializer fObjectSerializer;
SkPDFCanon fCanon;
SkTArray<sk_sp<const SkPDFDevice>> fPageDevices;
sk_sp<SkCanvas> fCanvas;
SkScalar fRasterDpi;
SkPDFMetadata fMetadata;
};
#endif // SkPDFDocument_DEFINED

View File

@ -33,7 +33,8 @@ static void test_abort(skiatest::Reporter* reporter) {
doc->abort();
REPORTER_ASSERT(reporter, stream.bytesWritten() == 0);
// Test that only the header is written, not the full document.
REPORTER_ASSERT(reporter, stream.bytesWritten() < 256);
}
static void test_abortWithFile(skiatest::Reporter* reporter) {