Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(665)

Unified Diff: src/pdf/SkPDFDocument.cpp

Issue 1802033003: SkPDF: SkPDFDocument reorganized so that some objects can be serialized early. (Closed) Base URL: https://skia.googlesource.com/skia.git@SkPdfDumpMethod
Patch Set: Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« src/pdf/SkPDFDocument.h ('K') | « src/pdf/SkPDFDocument.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/pdf/SkPDFDocument.cpp
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
index b1a1765301b0335e22d73b6cbcfbd299883872a5..057631edf1a41e69a6135ec043780992bf75b55d 100644
--- a/src/pdf/SkPDFDocument.cpp
+++ b/src/pdf/SkPDFDocument.cpp
@@ -9,43 +9,85 @@
#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,
- sk_sp<SkPDFObject> id) {
+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 auto& objects = fObjNumMap.objects();
tomhudson 2016/03/21 15:55:11 Still trying to stop worrying and love the auto; i
hal.canary 2016/03/21 18:49:18 Done.
+ while (fNextToBeSerialized < objects.count()) {
+ SkPDFObject* object = objects[fNextToBeSerialized].get();
+ int32_t index = fNextToBeSerialized + 1; // Skip object 0.
tomhudson 2016/03/21 15:55:11 I can see equivalent code in the previous version
hal.canary 2016/03/21 18:49:18 This is a PDF thing. Object zero is special. "Th
+ 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->dump();
+ ++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 zeroth object in the count.
tomhudson 2016/03/21 15:55:12 Yeah, confusing after we previously skipped object
hal.canary 2016/03/21 18:49:18 Acknowledged.
+ int32_t objCount = SkToS32(fOffsets.count() + 1);
tomhudson 2016/03/21 15:55:12 Switched from int64_t to int32_t?
hal.canary 2016/03/21 18:49:17 For whatever reason, `writeDecAsText` takes an int
+ 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);
tomhudson 2016/03/21 15:55:12 In the old code we explicitly ref'd this here?
hal.canary 2016/03/21 18:49:18 we switched to using sk_sp<T> no need.
+ 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(
@@ -159,111 +201,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->dump();
- }
- 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]->dump();
- }
- pageTree.safeUnrefAll();
- pages.unrefAll();
- return true;
-}
-
#if 0
// TODO(halcanary): expose notEmbeddableCount in SkDocument
void GetCountOfFontTypes(
@@ -307,76 +244,135 @@ 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,
- void (*doneProc)(SkWStream*, bool),
- SkScalar rasterDpi,
- SkPixelSerializer* jpegEncoder)
- : SkDocument(stream, doneProc)
- , fRasterDpi(rasterDpi) {
- fCanon.setPixelSerializer(SkSafeRef(jpegEncoder));
- }
+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
- this->close();
- }
+SkPDFDocument::~SkPDFDocument() {
+ // subclasses must call close() in their destructors
tomhudson 2016/03/21 15:55:11 subclasses of SkDocument, or subclasses of SkPDFDo
hal.canary 2016/03/21 18:49:18 // subclasses of SkDocument
+ this->close();
+}
-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(
- SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon));
- fCanvas.reset(new SkCanvas(device.get()));
- fPageDevices.push_back(std::move(device));
- fCanvas->clipRect(trimBox);
- fCanvas->translate(trimBox.x(), trimBox.y());
- return fCanvas.get();
- }
+void SkPDFDocument::serialize(const sk_sp<SkPDFObject>& object) {
tomhudson 2016/03/21 15:55:11 Name reads like it's going to serialize a single o
hal.canary 2016/03/21 18:49:18 new comments: /** Serialize the object
+ fObjectSerializer.addObjectRecursively(object);
+ fObjectSerializer.serializeObjects(this->getStream());
+}
- void onEndPage() override {
- SkASSERT(fCanvas.get());
- fCanvas->flush();
- fCanvas.reset(nullptr);
+SkCanvas* SkPDFDocument::onBeginPage(SkScalar width, SkScalar height,
+ const SkRect& trimBox) {
+ SkASSERT(!fCanvas.get()); // end page was called.
tomhudson 2016/03/21 15:55:12 Nit: This comment unclear. Reading through the cod
hal.canary 2016/03/21 18:49:18 Done.
+ if (fPageDevices.empty()) { // if first page
+ fObjectSerializer.serializeHeader(this->getStream(), fMetadata);
}
+ SkISize pageSize = SkISize::Make(
+ SkScalarRoundToInt(width), SkScalarRoundToInt(height));
+ sk_sp<SkPDFDevice> device(
+ SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon));
+ fCanvas.reset(new SkCanvas(device.get()));
+ fPageDevices.push_back(std::move(device));
+ fCanvas->clipRect(trimBox);
+ fCanvas->translate(trimBox.x(), trimBox.y());
+ return fCanvas.get();
+}
- bool onClose(SkWStream* stream) override {
- SkASSERT(!fCanvas.get());
+void SkPDFDocument::onEndPage() {
+ SkASSERT(fCanvas.get());
+ fCanvas->flush();
+ fCanvas.reset(nullptr);
+}
- bool success = emit_pdf_document(fPageDevices, fMetadata, stream);
+void SkPDFDocument::onAbort() {
+ fPageDevices.reset();
+ fCanon.reset();
+ renew(&fObjectSerializer);
tomhudson 2016/03/21 15:55:11 Should we be resetting fCanvas?
hal.canary 2016/03/21 18:49:18 Done.
+}
+
+void SkPDFDocument::setMetadata(const SkDocument::Attribute info[],
+ int infoCount,
+ const SkTime::DateTime* creationDate,
+ 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();
- return success;
+ renew(&fObjectSerializer);
+ return false;
}
+ SkTDArray<SkPDFDict*> pages; // TODO: SkTArray<sk_sp<SkPDFDict>>
+ auto dests = sk_make_sp<SkPDFDict>();
- void onAbort() override {
- fPageDevices.reset();
- fCanon.reset();
+ 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());
}
- void setMetadata(const SkDocument::Attribute info[],
- int infoCount,
- const SkTime::DateTime* creationDate,
- const SkTime::DateTime* modifiedDate) override {
- fMetadata.fInfo.reset(info, infoCount);
- fMetadata.fCreation.reset(clone(creationDate));
- fMetadata.fModified.reset(clone(modifiedDate));
+ 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));
}
-private:
- SkPDFCanon fCanon;
- SkTArray<sk_sp<const SkPDFDevice>> fPageDevices;
- sk_sp<SkCanvas> fCanvas;
- SkScalar fRasterDpi;
- SkPDFMetadata fMetadata;
-};
-} // namespace
+ // Build font subsetting info before proceeding.
tomhudson 2016/03/21 15:55:12 Nit: unnecessary comment?
hal.canary 2016/03/21 18:49:18 // Build font subsetting info before calling addOb
+ perform_font_subsetting(fPageDevices, &fObjectSerializer.fSubstituteMap);
+
+ fObjectSerializer.addObjectRecursively(docCatalog);
+ fObjectSerializer.serializeObjects(this->getStream());
+ fObjectSerializer.serializeFooter(this->getStream(), docCatalog, 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]->dump();
+ // }
tomhudson 2016/03/21 15:55:11 Nit: Delete before committing?
hal.canary 2016/03/21 18:49:18 Done.
+ pageTree.unrefAll(); // TODO: necesary?
+ pages.unrefAll();
+ fPageDevices.reset();
+ fCanon.reset();
+ renew(&fObjectSerializer);
+ return true;
+}
+
///////////////////////////////////////////////////////////////////////////////
sk_sp<SkDocument> SkPDFMakeDocument(SkWStream* stream,
« src/pdf/SkPDFDocument.h ('K') | « src/pdf/SkPDFDocument.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698