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("&") - strlen("&") 243cb93a386Sopenharmony_ci } else if (input[i] == '<') { 244cb93a386Sopenharmony_ci extra += 3; // strlen("<") - 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 // "&" --> "&" and "<" --> "<" 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[] = "&"; 269cb93a386Sopenharmony_ci static const char kLt[] = "<"; 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