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 |
/////////////////////////////////////////////////////////////////////////////// |