| Index: src/doc/SkDocument_PDF.cpp
|
| diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp
|
| index 4ea9d89dd7d83cc938617e1d892c420f76eead27..fb22f18ec5da5af7807935476b07f29783ef4651 100644
|
| --- a/src/doc/SkDocument_PDF.cpp
|
| +++ b/src/doc/SkDocument_PDF.cpp
|
| @@ -11,8 +11,11 @@
|
| #include "SkPDFFont.h"
|
| #include "SkPDFStream.h"
|
| #include "SkPDFTypes.h"
|
| +#include "SkPDFUtils.h"
|
| #include "SkStream.h"
|
|
|
| +class SkPDFDict;
|
| +
|
| static void emit_pdf_header(SkWStream* stream) {
|
| stream->writeText("%PDF-1.4\n%");
|
| // The PDF spec recommends including a comment with four bytes, all
|
| @@ -26,12 +29,15 @@ static void emit_pdf_footer(SkWStream* stream,
|
| const SkPDFSubstituteMap& substitutes,
|
| SkPDFObject* docCatalog,
|
| int64_t objCount,
|
| - int32_t xRefFileOffset) {
|
| + int32_t xRefFileOffset,
|
| + SkPDFDict* info) {
|
| SkPDFDict trailerDict;
|
| // TODO(vandebo): Linearized format will take a Prev entry too.
|
| // TODO(vandebo): PDF/A requires an ID entry.
|
| trailerDict.insertInt("Size", int(objCount));
|
| trailerDict.insertObjRef("Root", SkRef(docCatalog));
|
| + SkASSERT(info);
|
| + trailerDict.insertObjRef("Info", SkRef(info));
|
|
|
| stream->writeText("trailer\n");
|
| trailerDict.emitObject(stream, objNumMap, substitutes);
|
| @@ -156,7 +162,49 @@ static void generate_page_tree(const SkTDArray<SkPDFDict*>& pages,
|
| }
|
| }
|
|
|
| +struct Metadata {
|
| + SkTArray<SkDocument::Attribute> fInfo;
|
| + SkAutoTDelete<const SkTime::DateTime> fCreation;
|
| + SkAutoTDelete<const SkTime::DateTime> fModified;
|
| +};
|
| +
|
| +static SkString pdf_date(const SkTime::DateTime& dt) {
|
| + int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes);
|
| + char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-';
|
| + int timeZoneHours = SkTAbs(timeZoneMinutes) / 60;
|
| + timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60;
|
| + return SkStringPrintf(
|
| + "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'",
|
| + static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth),
|
| + static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour),
|
| + static_cast<unsigned>(dt.fMinute),
|
| + static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours,
|
| + timeZoneMinutes);
|
| +}
|
| +
|
| +SkPDFDict* create_document_information_dict(const Metadata& metadata) {
|
| + SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
|
| + static const char* keys[] = {
|
| + "Title", "Author", "Subject", "Keywords", "Creator" };
|
| + for (const char* key : keys) {
|
| + for (const SkDocument::Attribute& keyValue : metadata.fInfo) {
|
| + if (keyValue.fKey.equals(key)) {
|
| + dict->insertString(key, keyValue.fValue);
|
| + }
|
| + }
|
| + }
|
| + dict->insertString("Producer", "Skia/PDF");
|
| + if (metadata.fCreation) {
|
| + dict->insertString("CreationDate", pdf_date(*metadata.fCreation.get()));
|
| + }
|
| + if (metadata.fModified) {
|
| + dict->insertString("ModDate", pdf_date(*metadata.fModified.get()));
|
| + }
|
| + return dict.detach();
|
| +}
|
| +
|
| static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices,
|
| + const Metadata& metadata,
|
| SkWStream* stream) {
|
| if (pageDevices.isEmpty()) {
|
| return false;
|
| @@ -198,7 +246,12 @@ static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices,
|
| SkPDFSubstituteMap substitutes;
|
| perform_font_subsetting(pageDevices, &substitutes);
|
|
|
| + SkAutoTUnref<SkPDFDict> infoDict(
|
| + create_document_information_dict(metadata));
|
| SkPDFObjNumMap objNumMap;
|
| + if (objNumMap.addObject(infoDict)) {
|
| + infoDict->addResources(&objNumMap, substitutes);
|
| + }
|
| if (objNumMap.addObject(docCatalog.get())) {
|
| docCatalog->addResources(&objNumMap, substitutes);
|
| }
|
| @@ -233,7 +286,7 @@ static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices,
|
| stream->writeText(" 00000 n \n");
|
| }
|
| emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount,
|
| - xRefFileOffset);
|
| + xRefFileOffset, infoDict);
|
|
|
| // The page tree has both child and parent pointers, so it creates a
|
| // reference cycle. We must clear that cycle to properly reclaim memory.
|
| @@ -284,6 +337,8 @@ void GetCountOfFontTypes(
|
| }
|
| }
|
| #endif
|
| +
|
| +template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullptr; }
|
| ////////////////////////////////////////////////////////////////////////////////
|
|
|
| namespace {
|
| @@ -325,7 +380,7 @@ protected:
|
| bool onClose(SkWStream* stream) override {
|
| SkASSERT(!fCanvas.get());
|
|
|
| - bool success = emit_pdf_document(fPageDevices, stream);
|
| + bool success = emit_pdf_document(fPageDevices, fMetadata, stream);
|
| fPageDevices.unrefAll();
|
| fCanon.reset();
|
| return success;
|
| @@ -336,11 +391,20 @@ protected:
|
| fCanon.reset();
|
| }
|
|
|
| + void setMetadata(const SkTArray<SkDocument::Attribute>& info,
|
| + const SkTime::DateTime* creationDate,
|
| + const SkTime::DateTime* modifiedDate) override {
|
| + fMetadata.fInfo = info;
|
| + fMetadata.fCreation.reset(clone(creationDate));
|
| + fMetadata.fModified.reset(clone(modifiedDate));
|
| + }
|
| +
|
| private:
|
| SkPDFCanon fCanon;
|
| SkTDArray<const SkPDFDevice*> fPageDevices;
|
| SkAutoTUnref<SkCanvas> fCanvas;
|
| SkScalar fRasterDpi;
|
| + Metadata fMetadata;
|
| };
|
| } // namespace
|
| ///////////////////////////////////////////////////////////////////////////////
|
|
|