134422610ac22adceeabb66023120f27b96cae953halcanary/*
234422610ac22adceeabb66023120f27b96cae953halcanary * Copyright 2015 Google Inc.
334422610ac22adceeabb66023120f27b96cae953halcanary *
434422610ac22adceeabb66023120f27b96cae953halcanary * Use of this source code is governed by a BSD-style license that can be
534422610ac22adceeabb66023120f27b96cae953halcanary * found in the LICENSE file.
634422610ac22adceeabb66023120f27b96cae953halcanary */
734422610ac22adceeabb66023120f27b96cae953halcanary
8488165e689baf0f215d5798c87d0031b58e4bc8dhalcanary#include "SkMD5.h"
9ffe54004b92f84b65ee4569aaccbc178c51b017fhalcanary#include "SkMilestone.h"
1034422610ac22adceeabb66023120f27b96cae953halcanary#include "SkPDFMetadata.h"
1134422610ac22adceeabb66023120f27b96cae953halcanary#include "SkPDFTypes.h"
12d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary#include "SkUtils.h"
13d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary
14221524de3be1fc343ad328c5e99562f32b5cad9cbungeman#include <utility>
1534422610ac22adceeabb66023120f27b96cae953halcanary
169f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary#define SKPDF_STRING(X) SKPDF_STRING_IMPL(X)
179f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary#define SKPDF_STRING_IMPL(X) #X
189f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary#define SKPDF_PRODUCER "Skia/PDF m" SKPDF_STRING(SK_MILESTONE)
199f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary#define SKPDF_CUSTOM_PRODUCER_KEY "ProductionLibrary"
209f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary
2134422610ac22adceeabb66023120f27b96cae953halcanarystatic SkString pdf_date(const SkTime::DateTime& dt) {
2234422610ac22adceeabb66023120f27b96cae953halcanary    int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes);
2334422610ac22adceeabb66023120f27b96cae953halcanary    char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-';
2434422610ac22adceeabb66023120f27b96cae953halcanary    int timeZoneHours = SkTAbs(timeZoneMinutes) / 60;
2534422610ac22adceeabb66023120f27b96cae953halcanary    timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60;
2634422610ac22adceeabb66023120f27b96cae953halcanary    return SkStringPrintf(
2734422610ac22adceeabb66023120f27b96cae953halcanary            "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'",
2834422610ac22adceeabb66023120f27b96cae953halcanary            static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth),
2934422610ac22adceeabb66023120f27b96cae953halcanary            static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour),
3034422610ac22adceeabb66023120f27b96cae953halcanary            static_cast<unsigned>(dt.fMinute),
3134422610ac22adceeabb66023120f27b96cae953halcanary            static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours,
3234422610ac22adceeabb66023120f27b96cae953halcanary            timeZoneMinutes);
3334422610ac22adceeabb66023120f27b96cae953halcanary}
3434422610ac22adceeabb66023120f27b96cae953halcanary
354b6566644f704cf9e30c71fa547c9b5915752792halcanarynamespace {
364b6566644f704cf9e30c71fa547c9b5915752792halcanarystatic const struct {
374b6566644f704cf9e30c71fa547c9b5915752792halcanary    const char* const key;
384b6566644f704cf9e30c71fa547c9b5915752792halcanary    SkString SkDocument::PDFMetadata::*const valuePtr;
394b6566644f704cf9e30c71fa547c9b5915752792halcanary} gMetadataKeys[] = {
404b6566644f704cf9e30c71fa547c9b5915752792halcanary        {"Title", &SkDocument::PDFMetadata::fTitle},
414b6566644f704cf9e30c71fa547c9b5915752792halcanary        {"Author", &SkDocument::PDFMetadata::fAuthor},
424b6566644f704cf9e30c71fa547c9b5915752792halcanary        {"Subject", &SkDocument::PDFMetadata::fSubject},
434b6566644f704cf9e30c71fa547c9b5915752792halcanary        {"Keywords", &SkDocument::PDFMetadata::fKeywords},
444b6566644f704cf9e30c71fa547c9b5915752792halcanary        {"Creator", &SkDocument::PDFMetadata::fCreator},
454b6566644f704cf9e30c71fa547c9b5915752792halcanary};
464b6566644f704cf9e30c71fa547c9b5915752792halcanary}  // namespace
474b6566644f704cf9e30c71fa547c9b5915752792halcanary
484b6566644f704cf9e30c71fa547c9b5915752792halcanarysk_sp<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict(
494b6566644f704cf9e30c71fa547c9b5915752792halcanary        const SkDocument::PDFMetadata& metadata) {
50ece83924384b2e9e8cd422324c44797deb99ec90halcanary    auto dict = sk_make_sp<SkPDFDict>();
514b6566644f704cf9e30c71fa547c9b5915752792halcanary    for (const auto keyValuePtr : gMetadataKeys) {
524b6566644f704cf9e30c71fa547c9b5915752792halcanary        const SkString& value = metadata.*(keyValuePtr.valuePtr);
534b6566644f704cf9e30c71fa547c9b5915752792halcanary        if (value.size() > 0) {
544b6566644f704cf9e30c71fa547c9b5915752792halcanary            dict->insertString(keyValuePtr.key, value);
5534422610ac22adceeabb66023120f27b96cae953halcanary        }
5634422610ac22adceeabb66023120f27b96cae953halcanary    }
579f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary    if (metadata.fProducer.isEmpty()) {
589f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary        dict->insertString("Producer", SKPDF_PRODUCER);
599f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary    } else {
609f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary        dict->insertString("Producer", metadata.fProducer);
619f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary        dict->insertString(SKPDF_CUSTOM_PRODUCER_KEY, SKPDF_PRODUCER);
629f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary    }
634b6566644f704cf9e30c71fa547c9b5915752792halcanary    if (metadata.fCreation.fEnabled) {
644b6566644f704cf9e30c71fa547c9b5915752792halcanary        dict->insertString("CreationDate",
654b6566644f704cf9e30c71fa547c9b5915752792halcanary                           pdf_date(metadata.fCreation.fDateTime));
6634422610ac22adceeabb66023120f27b96cae953halcanary    }
674b6566644f704cf9e30c71fa547c9b5915752792halcanary    if (metadata.fModified.fEnabled) {
684b6566644f704cf9e30c71fa547c9b5915752792halcanary        dict->insertString("ModDate", pdf_date(metadata.fModified.fDateTime));
6934422610ac22adceeabb66023120f27b96cae953halcanary    }
704b6566644f704cf9e30c71fa547c9b5915752792halcanary    return dict;
7134422610ac22adceeabb66023120f27b96cae953halcanary}
7234422610ac22adceeabb66023120f27b96cae953halcanary
734b6566644f704cf9e30c71fa547c9b5915752792halcanarySkPDFMetadata::UUID SkPDFMetadata::CreateUUID(
744b6566644f704cf9e30c71fa547c9b5915752792halcanary        const SkDocument::PDFMetadata& metadata) {
7534422610ac22adceeabb66023120f27b96cae953halcanary    // The main requirement is for the UUID to be unique; the exact
7634422610ac22adceeabb66023120f27b96cae953halcanary    // format of the data that will be hashed is not important.
7734422610ac22adceeabb66023120f27b96cae953halcanary    SkMD5 md5;
7834422610ac22adceeabb66023120f27b96cae953halcanary    const char uuidNamespace[] = "org.skia.pdf\n";
7934422610ac22adceeabb66023120f27b96cae953halcanary    md5.write(uuidNamespace, strlen(uuidNamespace));
80ec4d4d784dbb250e572f8e04d18d0fd2ebeee851benjaminwagner    double msec = SkTime::GetMSecs();
8134422610ac22adceeabb66023120f27b96cae953halcanary    md5.write(&msec, sizeof(msec));
8234422610ac22adceeabb66023120f27b96cae953halcanary    SkTime::DateTime dateTime;
8334422610ac22adceeabb66023120f27b96cae953halcanary    SkTime::GetDateTime(&dateTime);
8434422610ac22adceeabb66023120f27b96cae953halcanary    md5.write(&dateTime, sizeof(dateTime));
854b6566644f704cf9e30c71fa547c9b5915752792halcanary    if (metadata.fCreation.fEnabled) {
864b6566644f704cf9e30c71fa547c9b5915752792halcanary        md5.write(&metadata.fCreation.fDateTime,
874b6566644f704cf9e30c71fa547c9b5915752792halcanary                  sizeof(metadata.fCreation.fDateTime));
8834422610ac22adceeabb66023120f27b96cae953halcanary    }
894b6566644f704cf9e30c71fa547c9b5915752792halcanary    if (metadata.fModified.fEnabled) {
904b6566644f704cf9e30c71fa547c9b5915752792halcanary        md5.write(&metadata.fModified.fDateTime,
914b6566644f704cf9e30c71fa547c9b5915752792halcanary                  sizeof(metadata.fModified.fDateTime));
9234422610ac22adceeabb66023120f27b96cae953halcanary    }
934b6566644f704cf9e30c71fa547c9b5915752792halcanary
944b6566644f704cf9e30c71fa547c9b5915752792halcanary    for (const auto keyValuePtr : gMetadataKeys) {
954b6566644f704cf9e30c71fa547c9b5915752792halcanary        md5.write(keyValuePtr.key, strlen(keyValuePtr.key));
9634422610ac22adceeabb66023120f27b96cae953halcanary        md5.write("\037", 1);
974b6566644f704cf9e30c71fa547c9b5915752792halcanary        const SkString& value = metadata.*(keyValuePtr.valuePtr);
984b6566644f704cf9e30c71fa547c9b5915752792halcanary        md5.write(value.c_str(), value.size());
9934422610ac22adceeabb66023120f27b96cae953halcanary        md5.write("\036", 1);
10034422610ac22adceeabb66023120f27b96cae953halcanary    }
10134422610ac22adceeabb66023120f27b96cae953halcanary    SkMD5::Digest digest;
10234422610ac22adceeabb66023120f27b96cae953halcanary    md5.finish(digest);
10334422610ac22adceeabb66023120f27b96cae953halcanary    // See RFC 4122, page 6-7.
10434422610ac22adceeabb66023120f27b96cae953halcanary    digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
10534422610ac22adceeabb66023120f27b96cae953halcanary    digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
10634422610ac22adceeabb66023120f27b96cae953halcanary    static_assert(sizeof(digest) == sizeof(UUID), "uuid_size");
10734422610ac22adceeabb66023120f27b96cae953halcanary    SkPDFMetadata::UUID uuid;
10834422610ac22adceeabb66023120f27b96cae953halcanary    memcpy(&uuid, &digest, sizeof(digest));
10934422610ac22adceeabb66023120f27b96cae953halcanary    return uuid;
11034422610ac22adceeabb66023120f27b96cae953halcanary}
11134422610ac22adceeabb66023120f27b96cae953halcanary
1124b6566644f704cf9e30c71fa547c9b5915752792halcanarysk_sp<SkPDFObject> SkPDFMetadata::MakePdfId(const UUID& doc,
1134b6566644f704cf9e30c71fa547c9b5915752792halcanary                                            const UUID& instance) {
11434422610ac22adceeabb66023120f27b96cae953halcanary    // /ID [ <81b14aafa313db63dbd6f981e49f94f4>
11534422610ac22adceeabb66023120f27b96cae953halcanary    //       <81b14aafa313db63dbd6f981e49f94f4> ]
116ece83924384b2e9e8cd422324c44797deb99ec90halcanary    auto array = sk_make_sp<SkPDFArray>();
1174b6566644f704cf9e30c71fa547c9b5915752792halcanary    static_assert(sizeof(SkPDFMetadata::UUID) == 16, "uuid_size");
11834422610ac22adceeabb66023120f27b96cae953halcanary    array->appendString(
11934422610ac22adceeabb66023120f27b96cae953halcanary            SkString(reinterpret_cast<const char*>(&doc), sizeof(UUID)));
12034422610ac22adceeabb66023120f27b96cae953halcanary    array->appendString(
12134422610ac22adceeabb66023120f27b96cae953halcanary            SkString(reinterpret_cast<const char*>(&instance), sizeof(UUID)));
1224b6566644f704cf9e30c71fa547c9b5915752792halcanary    return array;
12334422610ac22adceeabb66023120f27b96cae953halcanary}
12434422610ac22adceeabb66023120f27b96cae953halcanary
125d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary// Convert a block of memory to hexadecimal.  Input and output pointers will be
126d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary// moved to end of the range.
127d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canarystatic void hexify(const uint8_t** inputPtr, char** outputPtr, int count) {
128d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    SkASSERT(inputPtr && *inputPtr);
129d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    SkASSERT(outputPtr && *outputPtr);
130d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    while (count-- > 0) {
131d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary        uint8_t value = *(*inputPtr)++;
132d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary        *(*outputPtr)++ = SkHexadecimalDigits::gLower[value >> 4];
133d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary        *(*outputPtr)++ = SkHexadecimalDigits::gLower[value & 0xF];
134d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    }
135d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary}
136d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary
13734422610ac22adceeabb66023120f27b96cae953halcanarystatic SkString uuid_to_string(const SkPDFMetadata::UUID& uuid) {
13834422610ac22adceeabb66023120f27b96cae953halcanary    //  8-4-4-4-12
13934422610ac22adceeabb66023120f27b96cae953halcanary    char buffer[36];  // [32 + 4]
14034422610ac22adceeabb66023120f27b96cae953halcanary    char* ptr = buffer;
14134422610ac22adceeabb66023120f27b96cae953halcanary    const uint8_t* data = uuid.fData;
142d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    hexify(&data, &ptr, 4);
14334422610ac22adceeabb66023120f27b96cae953halcanary    *ptr++ = '-';
144d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    hexify(&data, &ptr, 2);
14534422610ac22adceeabb66023120f27b96cae953halcanary    *ptr++ = '-';
146d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    hexify(&data, &ptr, 2);
14734422610ac22adceeabb66023120f27b96cae953halcanary    *ptr++ = '-';
148d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    hexify(&data, &ptr, 2);
14934422610ac22adceeabb66023120f27b96cae953halcanary    *ptr++ = '-';
150d6e6e6699a85c6ed79e7d3b01e30d19e361ae049Hal Canary    hexify(&data, &ptr, 6);
15134422610ac22adceeabb66023120f27b96cae953halcanary    SkASSERT(ptr == buffer + 36);
15234422610ac22adceeabb66023120f27b96cae953halcanary    SkASSERT(data == uuid.fData + 16);
15334422610ac22adceeabb66023120f27b96cae953halcanary    return SkString(buffer, 36);
15434422610ac22adceeabb66023120f27b96cae953halcanary}
15534422610ac22adceeabb66023120f27b96cae953halcanary
15634422610ac22adceeabb66023120f27b96cae953halcanarynamespace {
15770d1554d8ea08ddb887550f98b9cfd0afd395d65halcanaryclass PDFXMLObject final : public SkPDFObject {
15834422610ac22adceeabb66023120f27b96cae953halcanarypublic:
159221524de3be1fc343ad328c5e99562f32b5cad9cbungeman    PDFXMLObject(SkString xml) : fXML(std::move(xml)) {}
16034422610ac22adceeabb66023120f27b96cae953halcanary    void emitObject(SkWStream* stream,
161530032a18e373ee673ae96fdbfa1fae6292f8f08halcanary                    const SkPDFObjNumMap& omap) const override {
16234422610ac22adceeabb66023120f27b96cae953halcanary        SkPDFDict dict("Metadata");
16334422610ac22adceeabb66023120f27b96cae953halcanary        dict.insertName("Subtype", "XML");
16434422610ac22adceeabb66023120f27b96cae953halcanary        dict.insertInt("Length", fXML.size());
165530032a18e373ee673ae96fdbfa1fae6292f8f08halcanary        dict.emitObject(stream, omap);
16634422610ac22adceeabb66023120f27b96cae953halcanary        static const char streamBegin[] = " stream\n";
16734422610ac22adceeabb66023120f27b96cae953halcanary        stream->write(streamBegin, strlen(streamBegin));
16834422610ac22adceeabb66023120f27b96cae953halcanary        // Do not compress this.  The standard requires that a
16934422610ac22adceeabb66023120f27b96cae953halcanary        // program that does not understand PDF can grep for
17055325b7c59fe5e8fac809adea7bbec4683d26fabHal Canary        // "<?xpacket" and extract the entire XML.
17134422610ac22adceeabb66023120f27b96cae953halcanary        stream->write(fXML.c_str(), fXML.size());
17234422610ac22adceeabb66023120f27b96cae953halcanary        static const char streamEnd[] = "\nendstream";
17334422610ac22adceeabb66023120f27b96cae953halcanary        stream->write(streamEnd, strlen(streamEnd));
17434422610ac22adceeabb66023120f27b96cae953halcanary    }
17534422610ac22adceeabb66023120f27b96cae953halcanary
17634422610ac22adceeabb66023120f27b96cae953halcanaryprivate:
17734422610ac22adceeabb66023120f27b96cae953halcanary    const SkString fXML;
17834422610ac22adceeabb66023120f27b96cae953halcanary};
17934422610ac22adceeabb66023120f27b96cae953halcanary}  // namespace
18034422610ac22adceeabb66023120f27b96cae953halcanary
18134422610ac22adceeabb66023120f27b96cae953halcanarystatic int count_xml_escape_size(const SkString& input) {
18234422610ac22adceeabb66023120f27b96cae953halcanary    int extra = 0;
18334422610ac22adceeabb66023120f27b96cae953halcanary    for (size_t i = 0; i < input.size(); ++i) {
18434422610ac22adceeabb66023120f27b96cae953halcanary        if (input[i] == '&') {
18534422610ac22adceeabb66023120f27b96cae953halcanary            extra += 4;  // strlen("&amp;") - strlen("&")
18634422610ac22adceeabb66023120f27b96cae953halcanary        } else if (input[i] == '<') {
18734422610ac22adceeabb66023120f27b96cae953halcanary            extra += 3;  // strlen("&lt;") - strlen("<")
18834422610ac22adceeabb66023120f27b96cae953halcanary        }
18934422610ac22adceeabb66023120f27b96cae953halcanary    }
19034422610ac22adceeabb66023120f27b96cae953halcanary    return extra;
19134422610ac22adceeabb66023120f27b96cae953halcanary}
19234422610ac22adceeabb66023120f27b96cae953halcanary
19334422610ac22adceeabb66023120f27b96cae953halcanaryconst SkString escape_xml(const SkString& input,
19434422610ac22adceeabb66023120f27b96cae953halcanary                          const char* before = nullptr,
19534422610ac22adceeabb66023120f27b96cae953halcanary                          const char* after = nullptr) {
19634422610ac22adceeabb66023120f27b96cae953halcanary    if (input.size() == 0) {
19734422610ac22adceeabb66023120f27b96cae953halcanary        return input;
19834422610ac22adceeabb66023120f27b96cae953halcanary    }
19934422610ac22adceeabb66023120f27b96cae953halcanary    // "&" --> "&amp;" and  "<" --> "&lt;"
20034422610ac22adceeabb66023120f27b96cae953halcanary    // text is assumed to be in UTF-8
20134422610ac22adceeabb66023120f27b96cae953halcanary    // all strings are xml content, not attribute values.
20234422610ac22adceeabb66023120f27b96cae953halcanary    size_t beforeLen = before ? strlen(before) : 0;
20334422610ac22adceeabb66023120f27b96cae953halcanary    size_t afterLen = after ? strlen(after) : 0;
20434422610ac22adceeabb66023120f27b96cae953halcanary    int extra = count_xml_escape_size(input);
20534422610ac22adceeabb66023120f27b96cae953halcanary    SkString output(input.size() + extra + beforeLen + afterLen);
20634422610ac22adceeabb66023120f27b96cae953halcanary    char* out = output.writable_str();
20734422610ac22adceeabb66023120f27b96cae953halcanary    if (before) {
20834422610ac22adceeabb66023120f27b96cae953halcanary        strncpy(out, before, beforeLen);
20934422610ac22adceeabb66023120f27b96cae953halcanary        out += beforeLen;
21034422610ac22adceeabb66023120f27b96cae953halcanary    }
21134422610ac22adceeabb66023120f27b96cae953halcanary    static const char kAmp[] = "&amp;";
21234422610ac22adceeabb66023120f27b96cae953halcanary    static const char kLt[] = "&lt;";
21334422610ac22adceeabb66023120f27b96cae953halcanary    for (size_t i = 0; i < input.size(); ++i) {
21434422610ac22adceeabb66023120f27b96cae953halcanary        if (input[i] == '&') {
21534422610ac22adceeabb66023120f27b96cae953halcanary            strncpy(out, kAmp, strlen(kAmp));
21634422610ac22adceeabb66023120f27b96cae953halcanary            out += strlen(kAmp);
21734422610ac22adceeabb66023120f27b96cae953halcanary        } else if (input[i] == '<') {
21834422610ac22adceeabb66023120f27b96cae953halcanary            strncpy(out, kLt, strlen(kLt));
21934422610ac22adceeabb66023120f27b96cae953halcanary            out += strlen(kLt);
22034422610ac22adceeabb66023120f27b96cae953halcanary        } else {
22134422610ac22adceeabb66023120f27b96cae953halcanary            *out++ = input[i];
22234422610ac22adceeabb66023120f27b96cae953halcanary        }
22334422610ac22adceeabb66023120f27b96cae953halcanary    }
22434422610ac22adceeabb66023120f27b96cae953halcanary    if (after) {
22534422610ac22adceeabb66023120f27b96cae953halcanary        strncpy(out, after, afterLen);
22634422610ac22adceeabb66023120f27b96cae953halcanary        out += afterLen;
22734422610ac22adceeabb66023120f27b96cae953halcanary    }
22834422610ac22adceeabb66023120f27b96cae953halcanary    // Validate that we haven't written outside of our string.
22934422610ac22adceeabb66023120f27b96cae953halcanary    SkASSERT(out == &output.writable_str()[output.size()]);
23034422610ac22adceeabb66023120f27b96cae953halcanary    *out = '\0';
23178daeff1f9c1639d4921abb7acd9b76b77660802halcanary    return output;
23234422610ac22adceeabb66023120f27b96cae953halcanary}
23334422610ac22adceeabb66023120f27b96cae953halcanary
2344b6566644f704cf9e30c71fa547c9b5915752792halcanarysk_sp<SkPDFObject> SkPDFMetadata::MakeXMPObject(
2354b6566644f704cf9e30c71fa547c9b5915752792halcanary        const SkDocument::PDFMetadata& metadata,
2364b6566644f704cf9e30c71fa547c9b5915752792halcanary        const UUID& doc,
2374b6566644f704cf9e30c71fa547c9b5915752792halcanary        const UUID& instance) {
23834422610ac22adceeabb66023120f27b96cae953halcanary    static const char templateString[] =
23934422610ac22adceeabb66023120f27b96cae953halcanary            "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
24034422610ac22adceeabb66023120f27b96cae953halcanary            "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n"
24134422610ac22adceeabb66023120f27b96cae953halcanary            " x:xmptk=\"Adobe XMP Core 5.4-c005 78.147326, "
24234422610ac22adceeabb66023120f27b96cae953halcanary            "2012/08/23-13:03:03\">\n"
24334422610ac22adceeabb66023120f27b96cae953halcanary            "<rdf:RDF "
24434422610ac22adceeabb66023120f27b96cae953halcanary            "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
24534422610ac22adceeabb66023120f27b96cae953halcanary            "<rdf:Description rdf:about=\"\"\n"
24634422610ac22adceeabb66023120f27b96cae953halcanary            " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n"
24734422610ac22adceeabb66023120f27b96cae953halcanary            " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"
24834422610ac22adceeabb66023120f27b96cae953halcanary            " xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"\n"
24934422610ac22adceeabb66023120f27b96cae953halcanary            " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\"\n"
25034422610ac22adceeabb66023120f27b96cae953halcanary            " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n"
25134422610ac22adceeabb66023120f27b96cae953halcanary            "<pdfaid:part>2</pdfaid:part>\n"
25234422610ac22adceeabb66023120f27b96cae953halcanary            "<pdfaid:conformance>B</pdfaid:conformance>\n"
25334422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // ModifyDate
25434422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // CreateDate
25534422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // xmp:CreatorTool
25634422610ac22adceeabb66023120f27b96cae953halcanary            "<dc:format>application/pdf</dc:format>\n"
25734422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // dc:title
25834422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // dc:description
25934422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // author
26034422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // keywords
26134422610ac22adceeabb66023120f27b96cae953halcanary            "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n"
26234422610ac22adceeabb66023120f27b96cae953halcanary            "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n"
2639f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary            "%s"  // pdf:Producer
26434422610ac22adceeabb66023120f27b96cae953halcanary            "%s"  // pdf:Keywords
26534422610ac22adceeabb66023120f27b96cae953halcanary            "</rdf:Description>\n"
26634422610ac22adceeabb66023120f27b96cae953halcanary            "</rdf:RDF>\n"
26734422610ac22adceeabb66023120f27b96cae953halcanary            "</x:xmpmeta>\n"  // Note:  the standard suggests 4k of padding.
26834422610ac22adceeabb66023120f27b96cae953halcanary            "<?xpacket end=\"w\"?>\n";
26934422610ac22adceeabb66023120f27b96cae953halcanary
27034422610ac22adceeabb66023120f27b96cae953halcanary    SkString creationDate;
27134422610ac22adceeabb66023120f27b96cae953halcanary    SkString modificationDate;
2724b6566644f704cf9e30c71fa547c9b5915752792halcanary    if (metadata.fCreation.fEnabled) {
27334422610ac22adceeabb66023120f27b96cae953halcanary        SkString tmp;
2744b6566644f704cf9e30c71fa547c9b5915752792halcanary        metadata.fCreation.fDateTime.toISO8601(&tmp);
27534422610ac22adceeabb66023120f27b96cae953halcanary        SkASSERT(0 == count_xml_escape_size(tmp));
27634422610ac22adceeabb66023120f27b96cae953halcanary        // YYYY-mm-ddTHH:MM:SS[+|-]ZZ:ZZ; no need to escape
277d51bdae4e145bfede693f97cf0d54a56d33d3c9ehalcanary        creationDate = SkStringPrintf("<xmp:CreateDate>%s</xmp:CreateDate>\n",
278d51bdae4e145bfede693f97cf0d54a56d33d3c9ehalcanary                                      tmp.c_str());
27934422610ac22adceeabb66023120f27b96cae953halcanary    }
2804b6566644f704cf9e30c71fa547c9b5915752792halcanary    if (metadata.fModified.fEnabled) {
28134422610ac22adceeabb66023120f27b96cae953halcanary        SkString tmp;
2824b6566644f704cf9e30c71fa547c9b5915752792halcanary        metadata.fModified.fDateTime.toISO8601(&tmp);
28334422610ac22adceeabb66023120f27b96cae953halcanary        SkASSERT(0 == count_xml_escape_size(tmp));
284d51bdae4e145bfede693f97cf0d54a56d33d3c9ehalcanary        modificationDate = SkStringPrintf(
28534422610ac22adceeabb66023120f27b96cae953halcanary                "<xmp:ModifyDate>%s</xmp:ModifyDate>\n", tmp.c_str());
28634422610ac22adceeabb66023120f27b96cae953halcanary    }
2874b6566644f704cf9e30c71fa547c9b5915752792halcanary    SkString title =
2884b6566644f704cf9e30c71fa547c9b5915752792halcanary            escape_xml(metadata.fTitle,
2894b6566644f704cf9e30c71fa547c9b5915752792halcanary                       "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">",
2904b6566644f704cf9e30c71fa547c9b5915752792halcanary                       "</rdf:li></rdf:Alt></dc:title>\n");
2914b6566644f704cf9e30c71fa547c9b5915752792halcanary    SkString author =
2924b6566644f704cf9e30c71fa547c9b5915752792halcanary            escape_xml(metadata.fAuthor, "<dc:creator><rdf:Bag><rdf:li>",
2934b6566644f704cf9e30c71fa547c9b5915752792halcanary                       "</rdf:li></rdf:Bag></dc:creator>\n");
29434422610ac22adceeabb66023120f27b96cae953halcanary    // TODO: in theory, XMP can support multiple authors.  Split on a delimiter?
29578daeff1f9c1639d4921abb7acd9b76b77660802halcanary    SkString subject = escape_xml(
2964b6566644f704cf9e30c71fa547c9b5915752792halcanary            metadata.fSubject,
29778daeff1f9c1639d4921abb7acd9b76b77660802halcanary            "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">",
29878daeff1f9c1639d4921abb7acd9b76b77660802halcanary            "</rdf:li></rdf:Alt></dc:description>\n");
2994b6566644f704cf9e30c71fa547c9b5915752792halcanary    SkString keywords1 =
3004b6566644f704cf9e30c71fa547c9b5915752792halcanary            escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>",
3014b6566644f704cf9e30c71fa547c9b5915752792halcanary                       "</rdf:li></rdf:Bag></dc:subject>\n");
3024b6566644f704cf9e30c71fa547c9b5915752792halcanary    SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>",
3034b6566644f704cf9e30c71fa547c9b5915752792halcanary                                    "</pdf:Keywords>\n");
30434422610ac22adceeabb66023120f27b96cae953halcanary    // TODO: in theory, keywords can be a list too.
3059f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary
306492d6b5b06ce586a19e779ad7867e8f2e9d254b1halcanary    SkString producer("<pdf:Producer>" SKPDF_PRODUCER "</pdf:Producer>\n");
3079f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary    if (!metadata.fProducer.isEmpty()) {
3089f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary        // TODO: register a developer prefix to make
3099f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary        // <skia:SKPDF_CUSTOM_PRODUCER_KEY> a real XML tag.
3109f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary        producer = escape_xml(
3119f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary                metadata.fProducer, "<pdf:Producer>",
3129f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary                "</pdf:Producer>\n<!-- <skia:" SKPDF_CUSTOM_PRODUCER_KEY ">"
3139f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary                SKPDF_PRODUCER "</skia:" SKPDF_CUSTOM_PRODUCER_KEY "> -->\n");
3149f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary    }
3159f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary
3164b6566644f704cf9e30c71fa547c9b5915752792halcanary    SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>",
31734422610ac22adceeabb66023120f27b96cae953halcanary                                  "</xmp:CreatorTool>\n");
31834422610ac22adceeabb66023120f27b96cae953halcanary    SkString documentID = uuid_to_string(doc);  // no need to escape
31934422610ac22adceeabb66023120f27b96cae953halcanary    SkASSERT(0 == count_xml_escape_size(documentID));
32034422610ac22adceeabb66023120f27b96cae953halcanary    SkString instanceID = uuid_to_string(instance);
32134422610ac22adceeabb66023120f27b96cae953halcanary    SkASSERT(0 == count_xml_escape_size(instanceID));
3224b6566644f704cf9e30c71fa547c9b5915752792halcanary    return sk_make_sp<PDFXMLObject>(SkStringPrintf(
32334422610ac22adceeabb66023120f27b96cae953halcanary            templateString, modificationDate.c_str(), creationDate.c_str(),
3244b6566644f704cf9e30c71fa547c9b5915752792halcanary            creator.c_str(), title.c_str(), subject.c_str(), author.c_str(),
3254b6566644f704cf9e30c71fa547c9b5915752792halcanary            keywords1.c_str(), documentID.c_str(), instanceID.c_str(),
3269f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary            producer.c_str(), keywords2.c_str()));
32734422610ac22adceeabb66023120f27b96cae953halcanary}
32834422610ac22adceeabb66023120f27b96cae953halcanary
3299f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary#undef SKPDF_CUSTOM_PRODUCER_KEY
3309f4b332f59a67658106d9aaedc8e75a93e04481dhalcanary#undef SKPDF_PRODUCER
3318cd4a24236ffc26522d0372c0d0ab0d96e301b3bhalcanary#undef SKPDF_STRING
3328cd4a24236ffc26522d0372c0d0ab0d96e301b3bhalcanary#undef SKPDF_STRING_IMPL
333