diff options
author | 2015-09-23 12:45:49 -0700 | |
---|---|---|
committer | 2015-09-23 12:45:49 -0700 | |
commit | f12a1673f024d30d30f06b9f88b5cc072b8a2d1e (patch) | |
tree | 8b8e41c3737a7041534da2ab78f34f1ae0c35b0d /src/doc | |
parent | ab26a9b427ec7c525ccd0025f19f0c91b74d8f6d (diff) |
SkPDF: add basic metadata support
Motivation: I want too finalize this API before working on the more
complex problem of adding XMP metadata for PDF/A.
BUG=skia:3110
Review URL: https://codereview.chromium.org/1359943003
Diffstat (limited to 'src/doc')
-rw-r--r-- | src/doc/SkDocument_PDF.cpp | 70 |
1 files changed, 67 insertions, 3 deletions
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp index 4ea9d89dd7..fb22f18ec5 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 /////////////////////////////////////////////////////////////////////////////// |