aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/doc
diff options
context:
space:
mode:
authorGravatar halcanary <halcanary@google.com>2015-09-23 12:45:49 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-09-23 12:45:49 -0700
commitf12a1673f024d30d30f06b9f88b5cc072b8a2d1e (patch)
tree8b8e41c3737a7041534da2ab78f34f1ae0c35b0d /src/doc
parentab26a9b427ec7c525ccd0025f19f0c91b74d8f6d (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.cpp70
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
///////////////////////////////////////////////////////////////////////////////