1cb93a386Sopenharmony_ci/*
2cb93a386Sopenharmony_ci * Copyright 2015 Google Inc.
3cb93a386Sopenharmony_ci *
4cb93a386Sopenharmony_ci * Use of this source code is governed by a BSD-style license that can be
5cb93a386Sopenharmony_ci * found in the LICENSE file.
6cb93a386Sopenharmony_ci */
7cb93a386Sopenharmony_ci
8cb93a386Sopenharmony_ci#include "src/pdf/SkPDFMetadata.h"
9cb93a386Sopenharmony_ci
10cb93a386Sopenharmony_ci#include "include/private/SkTo.h"
11cb93a386Sopenharmony_ci#include "src/core/SkMD5.h"
12cb93a386Sopenharmony_ci#include "src/core/SkUtils.h"
13cb93a386Sopenharmony_ci#include "src/pdf/SkPDFTypes.h"
14cb93a386Sopenharmony_ci#include "src/utils/SkUTF.h"
15cb93a386Sopenharmony_ci
16cb93a386Sopenharmony_ci#include <utility>
17cb93a386Sopenharmony_ci
18cb93a386Sopenharmony_cistatic constexpr SkTime::DateTime kZeroTime = {0, 0, 0, 0, 0, 0, 0, 0};
19cb93a386Sopenharmony_ci
20cb93a386Sopenharmony_cistatic bool operator!=(const SkTime::DateTime& u, const SkTime::DateTime& v) {
21cb93a386Sopenharmony_ci    return u.fTimeZoneMinutes != v.fTimeZoneMinutes ||
22cb93a386Sopenharmony_ci           u.fYear != v.fYear ||
23cb93a386Sopenharmony_ci           u.fMonth != v.fMonth ||
24cb93a386Sopenharmony_ci           u.fDayOfWeek != v.fDayOfWeek ||
25cb93a386Sopenharmony_ci           u.fDay != v.fDay ||
26cb93a386Sopenharmony_ci           u.fHour != v.fHour ||
27cb93a386Sopenharmony_ci           u.fMinute != v.fMinute ||
28cb93a386Sopenharmony_ci           u.fSecond != v.fSecond;
29cb93a386Sopenharmony_ci}
30cb93a386Sopenharmony_ci
31cb93a386Sopenharmony_cistatic SkString pdf_date(const SkTime::DateTime& dt) {
32cb93a386Sopenharmony_ci    int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes);
33cb93a386Sopenharmony_ci    char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-';
34cb93a386Sopenharmony_ci    int timeZoneHours = SkTAbs(timeZoneMinutes) / 60;
35cb93a386Sopenharmony_ci    timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60;
36cb93a386Sopenharmony_ci    return SkStringPrintf(
37cb93a386Sopenharmony_ci            "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'",
38cb93a386Sopenharmony_ci            static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth),
39cb93a386Sopenharmony_ci            static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour),
40cb93a386Sopenharmony_ci            static_cast<unsigned>(dt.fMinute),
41cb93a386Sopenharmony_ci            static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours,
42cb93a386Sopenharmony_ci            timeZoneMinutes);
43cb93a386Sopenharmony_ci}
44cb93a386Sopenharmony_ci
45cb93a386Sopenharmony_cistatic bool utf8_is_pdfdocencoding(const char* src, size_t len) {
46cb93a386Sopenharmony_ci    const uint8_t* end = (const uint8_t*)src + len;
47cb93a386Sopenharmony_ci    for (const uint8_t* ptr = (const uint8_t*)src; ptr < end; ++ptr) {
48cb93a386Sopenharmony_ci        uint8_t v = *ptr;
49cb93a386Sopenharmony_ci        // See Table D.2 (PDFDocEncoding Character Set) in the PDF3200_2008 spec.
50cb93a386Sopenharmony_ci        if ((v > 23 && v < 32) || v > 126) {
51cb93a386Sopenharmony_ci            return false;
52cb93a386Sopenharmony_ci        }
53cb93a386Sopenharmony_ci    }
54cb93a386Sopenharmony_ci    return true;
55cb93a386Sopenharmony_ci}
56cb93a386Sopenharmony_ci
57cb93a386Sopenharmony_civoid write_utf16be(char** ptr, uint16_t value) {
58cb93a386Sopenharmony_ci    *(*ptr)++ = (value >> 8);
59cb93a386Sopenharmony_ci    *(*ptr)++ = (value & 0xFF);
60cb93a386Sopenharmony_ci}
61cb93a386Sopenharmony_ci
62cb93a386Sopenharmony_ci// Please Note:  This "abuses" the SkString, which "should" only hold UTF8.
63cb93a386Sopenharmony_ci// But the SkString is written as if it is really just a ref-counted array of
64cb93a386Sopenharmony_ci// chars, so this works, as long as we handle endiness and conversions ourselves.
65cb93a386Sopenharmony_ci//
66cb93a386Sopenharmony_ci// Input:  UTF-8
67cb93a386Sopenharmony_ci// Output  UTF-16-BE
68cb93a386Sopenharmony_cistatic SkString to_utf16be(const char* src, size_t len) {
69cb93a386Sopenharmony_ci    SkString ret;
70cb93a386Sopenharmony_ci    const char* const end = src + len;
71cb93a386Sopenharmony_ci    size_t n = 1;  // BOM
72cb93a386Sopenharmony_ci    for (const char* ptr = src; ptr < end;) {
73cb93a386Sopenharmony_ci        SkUnichar u = SkUTF::NextUTF8(&ptr, end);
74cb93a386Sopenharmony_ci        if (u < 0) {
75cb93a386Sopenharmony_ci            break;
76cb93a386Sopenharmony_ci        }
77cb93a386Sopenharmony_ci        n += SkUTF::ToUTF16(u);
78cb93a386Sopenharmony_ci    }
79cb93a386Sopenharmony_ci    ret.resize(2 * n);
80cb93a386Sopenharmony_ci    char* out = ret.writable_str();
81cb93a386Sopenharmony_ci    write_utf16be(&out, 0xFEFF);  // BOM
82cb93a386Sopenharmony_ci    for (const char* ptr = src; ptr < end;) {
83cb93a386Sopenharmony_ci        SkUnichar u = SkUTF::NextUTF8(&ptr, end);
84cb93a386Sopenharmony_ci        if (u < 0) {
85cb93a386Sopenharmony_ci            break;
86cb93a386Sopenharmony_ci        }
87cb93a386Sopenharmony_ci        uint16_t utf16[2];
88cb93a386Sopenharmony_ci        size_t l = SkUTF::ToUTF16(u, utf16);
89cb93a386Sopenharmony_ci        write_utf16be(&out, utf16[0]);
90cb93a386Sopenharmony_ci        if (l == 2) {
91cb93a386Sopenharmony_ci            write_utf16be(&out, utf16[1]);
92cb93a386Sopenharmony_ci        }
93cb93a386Sopenharmony_ci    }
94cb93a386Sopenharmony_ci    SkASSERT(out == ret.writable_str() + 2 * n);
95cb93a386Sopenharmony_ci    return ret;
96cb93a386Sopenharmony_ci}
97cb93a386Sopenharmony_ci
98cb93a386Sopenharmony_ci// Input:  UTF-8
99cb93a386Sopenharmony_ci// Output  UTF-16-BE OR PDFDocEncoding (if that encoding is identical to ASCII encoding).
100cb93a386Sopenharmony_ci//
101cb93a386Sopenharmony_ci// See sections 14.3.3 (Document Information Dictionary) and 7.9.2.2 (Text String Type)
102cb93a386Sopenharmony_ci// of the PDF32000_2008 spec.
103cb93a386Sopenharmony_cistatic SkString convert(const SkString& s) {
104cb93a386Sopenharmony_ci    return utf8_is_pdfdocencoding(s.c_str(), s.size()) ? s : to_utf16be(s.c_str(), s.size());
105cb93a386Sopenharmony_ci}
106cb93a386Sopenharmony_ci
107cb93a386Sopenharmony_cinamespace {
108cb93a386Sopenharmony_cistatic const struct {
109cb93a386Sopenharmony_ci    const char* const key;
110cb93a386Sopenharmony_ci    SkString SkPDF::Metadata::*const valuePtr;
111cb93a386Sopenharmony_ci} gMetadataKeys[] = {
112cb93a386Sopenharmony_ci        {"Title", &SkPDF::Metadata::fTitle},
113cb93a386Sopenharmony_ci        {"Author", &SkPDF::Metadata::fAuthor},
114cb93a386Sopenharmony_ci        {"Subject", &SkPDF::Metadata::fSubject},
115cb93a386Sopenharmony_ci        {"Keywords", &SkPDF::Metadata::fKeywords},
116cb93a386Sopenharmony_ci        {"Creator", &SkPDF::Metadata::fCreator},
117cb93a386Sopenharmony_ci        {"Producer", &SkPDF::Metadata::fProducer},
118cb93a386Sopenharmony_ci};
119cb93a386Sopenharmony_ci}  // namespace
120cb93a386Sopenharmony_ci
121cb93a386Sopenharmony_cistd::unique_ptr<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict(
122cb93a386Sopenharmony_ci        const SkPDF::Metadata& metadata) {
123cb93a386Sopenharmony_ci    auto dict = SkPDFMakeDict();
124cb93a386Sopenharmony_ci    for (const auto keyValuePtr : gMetadataKeys) {
125cb93a386Sopenharmony_ci        const SkString& value = metadata.*(keyValuePtr.valuePtr);
126cb93a386Sopenharmony_ci        if (value.size() > 0) {
127cb93a386Sopenharmony_ci            dict->insertString(keyValuePtr.key, convert(value));
128cb93a386Sopenharmony_ci        }
129cb93a386Sopenharmony_ci    }
130cb93a386Sopenharmony_ci    if (metadata.fCreation != kZeroTime) {
131cb93a386Sopenharmony_ci        dict->insertString("CreationDate", pdf_date(metadata.fCreation));
132cb93a386Sopenharmony_ci    }
133cb93a386Sopenharmony_ci    if (metadata.fModified != kZeroTime) {
134cb93a386Sopenharmony_ci        dict->insertString("ModDate", pdf_date(metadata.fModified));
135cb93a386Sopenharmony_ci    }
136cb93a386Sopenharmony_ci    return std::move(dict);
137cb93a386Sopenharmony_ci}
138cb93a386Sopenharmony_ci
139cb93a386Sopenharmony_ciSkUUID SkPDFMetadata::CreateUUID(const SkPDF::Metadata& metadata) {
140cb93a386Sopenharmony_ci    // The main requirement is for the UUID to be unique; the exact
141cb93a386Sopenharmony_ci    // format of the data that will be hashed is not important.
142cb93a386Sopenharmony_ci    SkMD5 md5;
143cb93a386Sopenharmony_ci    const char uuidNamespace[] = "org.skia.pdf\n";
144cb93a386Sopenharmony_ci    md5.writeText(uuidNamespace);
145cb93a386Sopenharmony_ci    double msec = SkTime::GetMSecs();
146cb93a386Sopenharmony_ci    md5.write(&msec, sizeof(msec));
147cb93a386Sopenharmony_ci    SkTime::DateTime dateTime;
148cb93a386Sopenharmony_ci    SkTime::GetDateTime(&dateTime);
149cb93a386Sopenharmony_ci    md5.write(&dateTime, sizeof(dateTime));
150cb93a386Sopenharmony_ci    md5.write(&metadata.fCreation, sizeof(metadata.fCreation));
151cb93a386Sopenharmony_ci    md5.write(&metadata.fModified, sizeof(metadata.fModified));
152cb93a386Sopenharmony_ci
153cb93a386Sopenharmony_ci    for (const auto keyValuePtr : gMetadataKeys) {
154cb93a386Sopenharmony_ci        md5.writeText(keyValuePtr.key);
155cb93a386Sopenharmony_ci        md5.write("\037", 1);
156cb93a386Sopenharmony_ci        const SkString& value = metadata.*(keyValuePtr.valuePtr);
157cb93a386Sopenharmony_ci        md5.write(value.c_str(), value.size());
158cb93a386Sopenharmony_ci        md5.write("\036", 1);
159cb93a386Sopenharmony_ci    }
160cb93a386Sopenharmony_ci    SkMD5::Digest digest = md5.finish();
161cb93a386Sopenharmony_ci    // See RFC 4122, page 6-7.
162cb93a386Sopenharmony_ci    digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
163cb93a386Sopenharmony_ci    digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
164cb93a386Sopenharmony_ci    static_assert(sizeof(digest) == sizeof(SkUUID), "uuid_size");
165cb93a386Sopenharmony_ci    SkUUID uuid;
166cb93a386Sopenharmony_ci    memcpy((void*)&uuid, &digest, sizeof(digest));
167cb93a386Sopenharmony_ci    return uuid;
168cb93a386Sopenharmony_ci}
169cb93a386Sopenharmony_ci
170cb93a386Sopenharmony_cistd::unique_ptr<SkPDFObject> SkPDFMetadata::MakePdfId(const SkUUID& doc,
171cb93a386Sopenharmony_ci                                            const SkUUID& instance) {
172cb93a386Sopenharmony_ci    // /ID [ <81b14aafa313db63dbd6f981e49f94f4>
173cb93a386Sopenharmony_ci    //       <81b14aafa313db63dbd6f981e49f94f4> ]
174cb93a386Sopenharmony_ci    auto array = SkPDFMakeArray();
175cb93a386Sopenharmony_ci    static_assert(sizeof(SkUUID) == 16, "uuid_size");
176cb93a386Sopenharmony_ci    array->appendString(
177cb93a386Sopenharmony_ci            SkString(reinterpret_cast<const char*>(&doc), sizeof(SkUUID)));
178cb93a386Sopenharmony_ci    array->appendString(
179cb93a386Sopenharmony_ci            SkString(reinterpret_cast<const char*>(&instance), sizeof(SkUUID)));
180cb93a386Sopenharmony_ci    return std::move(array);
181cb93a386Sopenharmony_ci}
182cb93a386Sopenharmony_ci
183cb93a386Sopenharmony_ci// Convert a block of memory to hexadecimal.  Input and output pointers will be
184cb93a386Sopenharmony_ci// moved to end of the range.
185cb93a386Sopenharmony_cistatic void hexify(const uint8_t** inputPtr, char** outputPtr, int count) {
186cb93a386Sopenharmony_ci    SkASSERT(inputPtr && *inputPtr);
187cb93a386Sopenharmony_ci    SkASSERT(outputPtr && *outputPtr);
188cb93a386Sopenharmony_ci    while (count-- > 0) {
189cb93a386Sopenharmony_ci        uint8_t value = *(*inputPtr)++;
190cb93a386Sopenharmony_ci        *(*outputPtr)++ = SkHexadecimalDigits::gLower[value >> 4];
191cb93a386Sopenharmony_ci        *(*outputPtr)++ = SkHexadecimalDigits::gLower[value & 0xF];
192cb93a386Sopenharmony_ci    }
193cb93a386Sopenharmony_ci}
194cb93a386Sopenharmony_ci
195cb93a386Sopenharmony_cistatic SkString uuid_to_string(const SkUUID& uuid) {
196cb93a386Sopenharmony_ci    //  8-4-4-4-12
197cb93a386Sopenharmony_ci    char buffer[36];  // [32 + 4]
198cb93a386Sopenharmony_ci    char* ptr = buffer;
199cb93a386Sopenharmony_ci    const uint8_t* data = uuid.fData;
200cb93a386Sopenharmony_ci    hexify(&data, &ptr, 4);
201cb93a386Sopenharmony_ci    *ptr++ = '-';
202cb93a386Sopenharmony_ci    hexify(&data, &ptr, 2);
203cb93a386Sopenharmony_ci    *ptr++ = '-';
204cb93a386Sopenharmony_ci    hexify(&data, &ptr, 2);
205cb93a386Sopenharmony_ci    *ptr++ = '-';
206cb93a386Sopenharmony_ci    hexify(&data, &ptr, 2);
207cb93a386Sopenharmony_ci    *ptr++ = '-';
208cb93a386Sopenharmony_ci    hexify(&data, &ptr, 6);
209cb93a386Sopenharmony_ci    SkASSERT(ptr == buffer + 36);
210cb93a386Sopenharmony_ci    SkASSERT(data == uuid.fData + 16);
211cb93a386Sopenharmony_ci    return SkString(buffer, 36);
212cb93a386Sopenharmony_ci}
213cb93a386Sopenharmony_ci
214cb93a386Sopenharmony_cinamespace {
215cb93a386Sopenharmony_ciclass PDFXMLObject final : public SkPDFObject {
216cb93a386Sopenharmony_cipublic:
217cb93a386Sopenharmony_ci    PDFXMLObject(SkString xml) : fXML(std::move(xml)) {}
218cb93a386Sopenharmony_ci    void emitObject(SkWStream* stream) const override {
219cb93a386Sopenharmony_ci        SkPDFDict dict("Metadata");
220cb93a386Sopenharmony_ci        dict.insertName("Subtype", "XML");
221cb93a386Sopenharmony_ci        dict.insertInt("Length", fXML.size());
222cb93a386Sopenharmony_ci        dict.emitObject(stream);
223cb93a386Sopenharmony_ci        static const char streamBegin[] = " stream\n";
224cb93a386Sopenharmony_ci        stream->writeText(streamBegin);
225cb93a386Sopenharmony_ci        // Do not compress this.  The standard requires that a
226cb93a386Sopenharmony_ci        // program that does not understand PDF can grep for
227cb93a386Sopenharmony_ci        // "<?xpacket" and extract the entire XML.
228cb93a386Sopenharmony_ci        stream->write(fXML.c_str(), fXML.size());
229cb93a386Sopenharmony_ci        static const char streamEnd[] = "\nendstream";
230cb93a386Sopenharmony_ci        stream->writeText(streamEnd);
231cb93a386Sopenharmony_ci    }
232cb93a386Sopenharmony_ci
233cb93a386Sopenharmony_ciprivate:
234cb93a386Sopenharmony_ci    const SkString fXML;
235cb93a386Sopenharmony_ci};
236cb93a386Sopenharmony_ci}  // namespace
237cb93a386Sopenharmony_ci
238cb93a386Sopenharmony_cistatic int count_xml_escape_size(const SkString& input) {
239cb93a386Sopenharmony_ci    int extra = 0;
240cb93a386Sopenharmony_ci    for (size_t i = 0; i < input.size(); ++i) {
241cb93a386Sopenharmony_ci        if (input[i] == '&') {
242cb93a386Sopenharmony_ci            extra += 4;  // strlen("&amp;") - strlen("&")
243cb93a386Sopenharmony_ci        } else if (input[i] == '<') {
244cb93a386Sopenharmony_ci            extra += 3;  // strlen("&lt;") - strlen("<")
245cb93a386Sopenharmony_ci        }
246cb93a386Sopenharmony_ci    }
247cb93a386Sopenharmony_ci    return extra;
248cb93a386Sopenharmony_ci}
249cb93a386Sopenharmony_ci
250cb93a386Sopenharmony_ciSkString escape_xml(const SkString& input,
251cb93a386Sopenharmony_ci                    const char* before = nullptr,
252cb93a386Sopenharmony_ci                    const char* after = nullptr) {
253cb93a386Sopenharmony_ci    if (input.size() == 0) {
254cb93a386Sopenharmony_ci        return input;
255cb93a386Sopenharmony_ci    }
256cb93a386Sopenharmony_ci    // "&" --> "&amp;" and  "<" --> "&lt;"
257cb93a386Sopenharmony_ci    // text is assumed to be in UTF-8
258cb93a386Sopenharmony_ci    // all strings are xml content, not attribute values.
259cb93a386Sopenharmony_ci    size_t beforeLen = before ? strlen(before) : 0;
260cb93a386Sopenharmony_ci    size_t afterLen = after ? strlen(after) : 0;
261cb93a386Sopenharmony_ci    int extra = count_xml_escape_size(input);
262cb93a386Sopenharmony_ci    SkString output(input.size() + extra + beforeLen + afterLen);
263cb93a386Sopenharmony_ci    char* out = output.writable_str();
264cb93a386Sopenharmony_ci    if (before) {
265cb93a386Sopenharmony_ci        strncpy(out, before, beforeLen);
266cb93a386Sopenharmony_ci        out += beforeLen;
267cb93a386Sopenharmony_ci    }
268cb93a386Sopenharmony_ci    static const char kAmp[] = "&amp;";
269cb93a386Sopenharmony_ci    static const char kLt[] = "&lt;";
270cb93a386Sopenharmony_ci    for (size_t i = 0; i < input.size(); ++i) {
271cb93a386Sopenharmony_ci        if (input[i] == '&') {
272cb93a386Sopenharmony_ci            memcpy(out, kAmp, strlen(kAmp));
273cb93a386Sopenharmony_ci            out += strlen(kAmp);
274cb93a386Sopenharmony_ci        } else if (input[i] == '<') {
275cb93a386Sopenharmony_ci            memcpy(out, kLt, strlen(kLt));
276cb93a386Sopenharmony_ci            out += strlen(kLt);
277cb93a386Sopenharmony_ci        } else {
278cb93a386Sopenharmony_ci            *out++ = input[i];
279cb93a386Sopenharmony_ci        }
280cb93a386Sopenharmony_ci    }
281cb93a386Sopenharmony_ci    if (after) {
282cb93a386Sopenharmony_ci        strncpy(out, after, afterLen);
283cb93a386Sopenharmony_ci        out += afterLen;
284cb93a386Sopenharmony_ci    }
285cb93a386Sopenharmony_ci    // Validate that we haven't written outside of our string.
286cb93a386Sopenharmony_ci    SkASSERT(out == &output.writable_str()[output.size()]);
287cb93a386Sopenharmony_ci    *out = '\0';
288cb93a386Sopenharmony_ci    return output;
289cb93a386Sopenharmony_ci}
290cb93a386Sopenharmony_ci
291cb93a386Sopenharmony_ciSkPDFIndirectReference SkPDFMetadata::MakeXMPObject(
292cb93a386Sopenharmony_ci        const SkPDF::Metadata& metadata,
293cb93a386Sopenharmony_ci        const SkUUID& doc,
294cb93a386Sopenharmony_ci        const SkUUID& instance,
295cb93a386Sopenharmony_ci        SkPDFDocument* docPtr) {
296cb93a386Sopenharmony_ci    static const char templateString[] =
297cb93a386Sopenharmony_ci            "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
298cb93a386Sopenharmony_ci            "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n"
299cb93a386Sopenharmony_ci            " x:xmptk=\"Adobe XMP Core 5.4-c005 78.147326, "
300cb93a386Sopenharmony_ci            "2012/08/23-13:03:03\">\n"
301cb93a386Sopenharmony_ci            "<rdf:RDF "
302cb93a386Sopenharmony_ci            "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
303cb93a386Sopenharmony_ci            "<rdf:Description rdf:about=\"\"\n"
304cb93a386Sopenharmony_ci            " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n"
305cb93a386Sopenharmony_ci            " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"
306cb93a386Sopenharmony_ci            " xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"\n"
307cb93a386Sopenharmony_ci            " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\"\n"
308cb93a386Sopenharmony_ci            " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n"
309cb93a386Sopenharmony_ci            "<pdfaid:part>2</pdfaid:part>\n"
310cb93a386Sopenharmony_ci            "<pdfaid:conformance>B</pdfaid:conformance>\n"
311cb93a386Sopenharmony_ci            "%s"  // ModifyDate
312cb93a386Sopenharmony_ci            "%s"  // CreateDate
313cb93a386Sopenharmony_ci            "%s"  // xmp:CreatorTool
314cb93a386Sopenharmony_ci            "<dc:format>application/pdf</dc:format>\n"
315cb93a386Sopenharmony_ci            "%s"  // dc:title
316cb93a386Sopenharmony_ci            "%s"  // dc:description
317cb93a386Sopenharmony_ci            "%s"  // author
318cb93a386Sopenharmony_ci            "%s"  // keywords
319cb93a386Sopenharmony_ci            "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n"
320cb93a386Sopenharmony_ci            "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n"
321cb93a386Sopenharmony_ci            "%s"  // pdf:Producer
322cb93a386Sopenharmony_ci            "%s"  // pdf:Keywords
323cb93a386Sopenharmony_ci            "</rdf:Description>\n"
324cb93a386Sopenharmony_ci            "</rdf:RDF>\n"
325cb93a386Sopenharmony_ci            "</x:xmpmeta>\n"  // Note:  the standard suggests 4k of padding.
326cb93a386Sopenharmony_ci            "<?xpacket end=\"w\"?>\n";
327cb93a386Sopenharmony_ci
328cb93a386Sopenharmony_ci    SkString creationDate;
329cb93a386Sopenharmony_ci    SkString modificationDate;
330cb93a386Sopenharmony_ci    if (metadata.fCreation != kZeroTime) {
331cb93a386Sopenharmony_ci        SkString tmp;
332cb93a386Sopenharmony_ci        metadata.fCreation.toISO8601(&tmp);
333cb93a386Sopenharmony_ci        SkASSERT(0 == count_xml_escape_size(tmp));
334cb93a386Sopenharmony_ci        // YYYY-mm-ddTHH:MM:SS[+|-]ZZ:ZZ; no need to escape
335cb93a386Sopenharmony_ci        creationDate = SkStringPrintf("<xmp:CreateDate>%s</xmp:CreateDate>\n",
336cb93a386Sopenharmony_ci                                      tmp.c_str());
337cb93a386Sopenharmony_ci    }
338cb93a386Sopenharmony_ci    if (metadata.fModified != kZeroTime) {
339cb93a386Sopenharmony_ci        SkString tmp;
340cb93a386Sopenharmony_ci        metadata.fModified.toISO8601(&tmp);
341cb93a386Sopenharmony_ci        SkASSERT(0 == count_xml_escape_size(tmp));
342cb93a386Sopenharmony_ci        modificationDate = SkStringPrintf(
343cb93a386Sopenharmony_ci                "<xmp:ModifyDate>%s</xmp:ModifyDate>\n", tmp.c_str());
344cb93a386Sopenharmony_ci    }
345cb93a386Sopenharmony_ci    SkString title =
346cb93a386Sopenharmony_ci            escape_xml(metadata.fTitle,
347cb93a386Sopenharmony_ci                       "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">",
348cb93a386Sopenharmony_ci                       "</rdf:li></rdf:Alt></dc:title>\n");
349cb93a386Sopenharmony_ci    SkString author =
350cb93a386Sopenharmony_ci            escape_xml(metadata.fAuthor, "<dc:creator><rdf:Seq><rdf:li>",
351cb93a386Sopenharmony_ci                       "</rdf:li></rdf:Seq></dc:creator>\n");
352cb93a386Sopenharmony_ci    // TODO: in theory, XMP can support multiple authors.  Split on a delimiter?
353cb93a386Sopenharmony_ci    SkString subject = escape_xml(
354cb93a386Sopenharmony_ci            metadata.fSubject,
355cb93a386Sopenharmony_ci            "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">",
356cb93a386Sopenharmony_ci            "</rdf:li></rdf:Alt></dc:description>\n");
357cb93a386Sopenharmony_ci    SkString keywords1 =
358cb93a386Sopenharmony_ci            escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>",
359cb93a386Sopenharmony_ci                       "</rdf:li></rdf:Bag></dc:subject>\n");
360cb93a386Sopenharmony_ci    SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>",
361cb93a386Sopenharmony_ci                                    "</pdf:Keywords>\n");
362cb93a386Sopenharmony_ci    // TODO: in theory, keywords can be a list too.
363cb93a386Sopenharmony_ci
364cb93a386Sopenharmony_ci    SkString producer = escape_xml(metadata.fProducer, "<pdf:Producer>", "</pdf:Producer>\n");
365cb93a386Sopenharmony_ci
366cb93a386Sopenharmony_ci    SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>",
367cb93a386Sopenharmony_ci                                  "</xmp:CreatorTool>\n");
368cb93a386Sopenharmony_ci    SkString documentID = uuid_to_string(doc);  // no need to escape
369cb93a386Sopenharmony_ci    SkASSERT(0 == count_xml_escape_size(documentID));
370cb93a386Sopenharmony_ci    SkString instanceID = uuid_to_string(instance);
371cb93a386Sopenharmony_ci    SkASSERT(0 == count_xml_escape_size(instanceID));
372cb93a386Sopenharmony_ci
373cb93a386Sopenharmony_ci
374cb93a386Sopenharmony_ci    auto value = SkStringPrintf(
375cb93a386Sopenharmony_ci            templateString, modificationDate.c_str(), creationDate.c_str(),
376cb93a386Sopenharmony_ci            creator.c_str(), title.c_str(), subject.c_str(), author.c_str(),
377cb93a386Sopenharmony_ci            keywords1.c_str(), documentID.c_str(), instanceID.c_str(),
378cb93a386Sopenharmony_ci            producer.c_str(), keywords2.c_str());
379cb93a386Sopenharmony_ci
380cb93a386Sopenharmony_ci    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict("Metadata");
381cb93a386Sopenharmony_ci    dict->insertName("Subtype", "XML");
382cb93a386Sopenharmony_ci    return SkPDFStreamOut(std::move(dict),
383cb93a386Sopenharmony_ci                          SkMemoryStream::MakeCopy(value.c_str(), value.size()),
384cb93a386Sopenharmony_ci                          docPtr, false);
385cb93a386Sopenharmony_ci}
386cb93a386Sopenharmony_ci
387cb93a386Sopenharmony_ci#undef SKPDF_CUSTOM_PRODUCER_KEY
388cb93a386Sopenharmony_ci#undef SKPDF_PRODUCER
389cb93a386Sopenharmony_ci#undef SKPDF_STRING
390cb93a386Sopenharmony_ci#undef SKPDF_STRING_IMPL
391