aboutsummaryrefslogtreecommitdiffhomepage
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
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
-rw-r--r--dm/DMSrcSink.cpp6
-rw-r--r--include/core/SkDocument.h29
-rw-r--r--include/core/SkTArray.h8
-rw-r--r--src/doc/SkDocument_PDF.cpp70
-rw-r--r--tests/PDFMetadataAttributeTest.cpp52
5 files changed, 162 insertions, 3 deletions
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 10cdd0965b..a4e240bbaa 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -930,6 +930,12 @@ Error PDFSink::draw(const Src& src, SkBitmap*, SkWStream* dst, SkString*) const
if (!doc) {
return "SkDocument::CreatePDF() returned nullptr";
}
+ SkTArray<SkDocument::Attribute> info;
+ info.emplace_back(SkString("Title"), src.name());
+ info.emplace_back(SkString("Subject"),
+ SkString("rendering correctness test"));
+ info.emplace_back(SkString("Creator"), SkString("Skia/DM"));
+ doc->setMetadata(info, nullptr, nullptr);
return draw_skdocument(src, doc.get(), dst);
}
diff --git a/include/core/SkDocument.h b/include/core/SkDocument.h
index a35f448208..316d15a253 100644
--- a/include/core/SkDocument.h
+++ b/include/core/SkDocument.h
@@ -12,6 +12,8 @@
#include "SkPicture.h"
#include "SkRect.h"
#include "SkRefCnt.h"
+#include "SkString.h"
+#include "SkTime.h"
class SkCanvas;
class SkWStream;
@@ -104,6 +106,33 @@ public:
*/
void abort();
+ /**
+ * Set the document's metadata, if supported by the document
+ * type. The creationDate and modifiedDate parameters can be
+ * nullptr. For example:
+ *
+ * SkDocument* make_doc(SkWStream* output) {
+ * SkTArray<SkDocument::Attribute> info;
+ * info.emplace_back(SkString("Title"), SkString("..."));
+ * info.emplace_back(SkString("Author"), SkString("..."));
+ * info.emplace_back(SkString("Subject"), SkString("..."));
+ * info.emplace_back(SkString("Keywords"), SkString("..."));
+ * info.emplace_back(SkString("Creator"), SkString("..."));
+ * SkTime::DateTime now;
+ * SkTime::GetDateTime(&now);
+ * SkDocument* doc = SkDocument::CreatePDF(output);
+ * doc->setMetadata(info, &now, &now);
+ * return doc;
+ * }
+ */
+ struct Attribute {
+ SkString fKey, fValue;
+ Attribute(const SkString& k, const SkString& v) : fKey(k), fValue(v) {}
+ };
+ virtual void setMetadata(const SkTArray<SkDocument::Attribute>&,
+ const SkTime::DateTime* /* creationDate */,
+ const SkTime::DateTime* /* modifiedDate */) {}
+
protected:
SkDocument(SkWStream*, void (*)(SkWStream*, bool aborted));
diff --git a/include/core/SkTArray.h b/include/core/SkTArray.h
index d67956be32..401f7084d6 100644
--- a/include/core/SkTArray.h
+++ b/include/core/SkTArray.h
@@ -194,6 +194,14 @@ public:
}
/**
+ * Construct a new T at the back of this array.
+ */
+ template<class... Args> T& emplace_back(Args&&... args) {
+ T* newT = reinterpret_cast<T*>(this->push_back_raw(1));
+ return *new (newT) T(skstd::forward<Args>(args)...);
+ }
+
+ /**
* Allocates n more default-initialized T values, and returns the address of
* the start of that new range. Note: this address is only valid until the
* next API call made on the array that might add or remove elements.
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
///////////////////////////////////////////////////////////////////////////////
diff --git a/tests/PDFMetadataAttributeTest.cpp b/tests/PDFMetadataAttributeTest.cpp
new file mode 100644
index 0000000000..e58146ba2b
--- /dev/null
+++ b/tests/PDFMetadataAttributeTest.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkDocument.h"
+#include "SkStream.h"
+#include "SkData.h"
+#include "Test.h"
+
+DEF_TEST(SkPDF_MetadataAttribute, r) {
+ REQUIRE_PDF_DOCUMENT(SkPDF_MetadataAttribute, r);
+ SkDynamicMemoryWStream pdf;
+ SkAutoTUnref<SkDocument> doc(SkDocument::CreatePDF(&pdf));
+ SkTArray<SkDocument::Attribute> info;
+ info.emplace_back(SkString("Title"), SkString("A1"));
+ info.emplace_back(SkString("Author"), SkString("A2"));
+ info.emplace_back(SkString("Subject"), SkString("A3"));
+ info.emplace_back(SkString("Keywords"), SkString("A4"));
+ info.emplace_back(SkString("Creator"), SkString("A5"));
+ SkTime::DateTime now;
+ SkTime::GetDateTime(&now);
+ doc->setMetadata(info, &now, &now);
+ doc->beginPage(612.0f, 792.0f);
+ doc->close();
+ SkAutoTUnref<SkData> data(pdf.copyToData());
+ static const char* expectations[] = {
+ "/Title (A1)",
+ "/Author (A2)",
+ "/Subject (A3)",
+ "/Keywords (A4)",
+ "/Creator (A5)",
+ "/Producer (Skia/PDF)",
+ "/CreationDate (D:",
+ "/ModDate (D:"
+ };
+ for (const char* expectation : expectations) {
+ bool found = false;
+ size_t N = 1 + data->size() - strlen(expectation);
+ for (size_t i = 0; i < N; ++i) {
+ if (0 == memcmp(data->bytes() + i,
+ expectation, strlen(expectation))) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ERRORF(r, "expectation missing: '%s'.", expectation);
+ }
+ }
+}