| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "SkMD5.h" | 8 #include "SkMD5.h" |
| 9 #include "SkMilestone.h" | 9 #include "SkMilestone.h" |
| 10 #include "SkPDFMetadata.h" | 10 #include "SkPDFMetadata.h" |
| 11 #include "SkPDFTypes.h" | 11 #include "SkPDFTypes.h" |
| 12 #include <utility> | 12 #include <utility> |
| 13 | 13 |
| 14 #define SKPDF_STRING(X) SKPDF_STRING_IMPL(X) |
| 15 #define SKPDF_STRING_IMPL(X) #X |
| 16 #define SKPDF_PRODUCER "Skia/PDF m" SKPDF_STRING(SK_MILESTONE) |
| 17 #define SKPDF_CUSTOM_PRODUCER_KEY "ProductionLibrary" |
| 18 |
| 14 static SkString pdf_date(const SkTime::DateTime& dt) { | 19 static SkString pdf_date(const SkTime::DateTime& dt) { |
| 15 int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); | 20 int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); |
| 16 char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; | 21 char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; |
| 17 int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; | 22 int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; |
| 18 timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; | 23 timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; |
| 19 return SkStringPrintf( | 24 return SkStringPrintf( |
| 20 "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", | 25 "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", |
| 21 static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth), | 26 static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth), |
| 22 static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour), | 27 static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour), |
| 23 static_cast<unsigned>(dt.fMinute), | 28 static_cast<unsigned>(dt.fMinute), |
| 24 static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours, | 29 static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours, |
| 25 timeZoneMinutes); | 30 timeZoneMinutes); |
| 26 } | 31 } |
| 27 | 32 |
| 28 #define SKPDF_STRING(X) SKPDF_STRING_IMPL(X) | |
| 29 #define SKPDF_STRING_IMPL(X) #X | |
| 30 | |
| 31 namespace { | 33 namespace { |
| 32 static const struct { | 34 static const struct { |
| 33 const char* const key; | 35 const char* const key; |
| 34 SkString SkDocument::PDFMetadata::*const valuePtr; | 36 SkString SkDocument::PDFMetadata::*const valuePtr; |
| 35 } gMetadataKeys[] = { | 37 } gMetadataKeys[] = { |
| 36 {"Title", &SkDocument::PDFMetadata::fTitle}, | 38 {"Title", &SkDocument::PDFMetadata::fTitle}, |
| 37 {"Author", &SkDocument::PDFMetadata::fAuthor}, | 39 {"Author", &SkDocument::PDFMetadata::fAuthor}, |
| 38 {"Subject", &SkDocument::PDFMetadata::fSubject}, | 40 {"Subject", &SkDocument::PDFMetadata::fSubject}, |
| 39 {"Keywords", &SkDocument::PDFMetadata::fKeywords}, | 41 {"Keywords", &SkDocument::PDFMetadata::fKeywords}, |
| 40 {"Creator", &SkDocument::PDFMetadata::fCreator}, | 42 {"Creator", &SkDocument::PDFMetadata::fCreator}, |
| (...skipping 15 matching lines...) Expand all Loading... |
| 56 | 58 |
| 57 sk_sp<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict( | 59 sk_sp<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict( |
| 58 const SkDocument::PDFMetadata& metadata) { | 60 const SkDocument::PDFMetadata& metadata) { |
| 59 auto dict = sk_make_sp<SkPDFDict>(); | 61 auto dict = sk_make_sp<SkPDFDict>(); |
| 60 for (const auto keyValuePtr : gMetadataKeys) { | 62 for (const auto keyValuePtr : gMetadataKeys) { |
| 61 const SkString& value = metadata.*(keyValuePtr.valuePtr); | 63 const SkString& value = metadata.*(keyValuePtr.valuePtr); |
| 62 if (value.size() > 0) { | 64 if (value.size() > 0) { |
| 63 dict->insertString(keyValuePtr.key, value); | 65 dict->insertString(keyValuePtr.key, value); |
| 64 } | 66 } |
| 65 } | 67 } |
| 66 dict->insertString("Producer", "Skia/PDF m" SKPDF_STRING(SK_MILESTONE)); | 68 if (metadata.fProducer.isEmpty()) { |
| 69 dict->insertString("Producer", SKPDF_PRODUCER); |
| 70 } else { |
| 71 dict->insertString("Producer", metadata.fProducer); |
| 72 dict->insertString(SKPDF_CUSTOM_PRODUCER_KEY, SKPDF_PRODUCER); |
| 73 } |
| 67 if (metadata.fCreation.fEnabled) { | 74 if (metadata.fCreation.fEnabled) { |
| 68 dict->insertString("CreationDate", | 75 dict->insertString("CreationDate", |
| 69 pdf_date(metadata.fCreation.fDateTime)); | 76 pdf_date(metadata.fCreation.fDateTime)); |
| 70 } | 77 } |
| 71 if (metadata.fModified.fEnabled) { | 78 if (metadata.fModified.fEnabled) { |
| 72 dict->insertString("ModDate", pdf_date(metadata.fModified.fDateTime)); | 79 dict->insertString("ModDate", pdf_date(metadata.fModified.fDateTime)); |
| 73 } | 80 } |
| 74 return dict; | 81 return dict; |
| 75 } | 82 } |
| 76 | 83 |
| (...skipping 180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 257 "%s" // ModifyDate | 264 "%s" // ModifyDate |
| 258 "%s" // CreateDate | 265 "%s" // CreateDate |
| 259 "%s" // xmp:CreatorTool | 266 "%s" // xmp:CreatorTool |
| 260 "<dc:format>application/pdf</dc:format>\n" | 267 "<dc:format>application/pdf</dc:format>\n" |
| 261 "%s" // dc:title | 268 "%s" // dc:title |
| 262 "%s" // dc:description | 269 "%s" // dc:description |
| 263 "%s" // author | 270 "%s" // author |
| 264 "%s" // keywords | 271 "%s" // keywords |
| 265 "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n" | 272 "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n" |
| 266 "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n" | 273 "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n" |
| 267 "<pdf:Producer>Skia/PDF m" SKPDF_STRING(SK_MILESTONE) "</pdf:Produce
r>\n" | 274 "%s" // pdf:Producer |
| 268 "%s" // pdf:Keywords | 275 "%s" // pdf:Keywords |
| 269 "</rdf:Description>\n" | 276 "</rdf:Description>\n" |
| 270 "</rdf:RDF>\n" | 277 "</rdf:RDF>\n" |
| 271 "</x:xmpmeta>\n" // Note: the standard suggests 4k of padding. | 278 "</x:xmpmeta>\n" // Note: the standard suggests 4k of padding. |
| 272 "<?xpacket end=\"w\"?>\n"; | 279 "<?xpacket end=\"w\"?>\n"; |
| 273 | 280 |
| 274 SkString creationDate; | 281 SkString creationDate; |
| 275 SkString modificationDate; | 282 SkString modificationDate; |
| 276 if (metadata.fCreation.fEnabled) { | 283 if (metadata.fCreation.fEnabled) { |
| 277 SkString tmp; | 284 SkString tmp; |
| (...skipping 20 matching lines...) Expand all Loading... |
| 298 // TODO: in theory, XMP can support multiple authors. Split on a delimiter? | 305 // TODO: in theory, XMP can support multiple authors. Split on a delimiter? |
| 299 SkString subject = escape_xml( | 306 SkString subject = escape_xml( |
| 300 metadata.fSubject, | 307 metadata.fSubject, |
| 301 "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">", | 308 "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">", |
| 302 "</rdf:li></rdf:Alt></dc:description>\n"); | 309 "</rdf:li></rdf:Alt></dc:description>\n"); |
| 303 SkString keywords1 = | 310 SkString keywords1 = |
| 304 escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>", | 311 escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>", |
| 305 "</rdf:li></rdf:Bag></dc:subject>\n"); | 312 "</rdf:li></rdf:Bag></dc:subject>\n"); |
| 306 SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>", | 313 SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>", |
| 307 "</pdf:Keywords>\n"); | 314 "</pdf:Keywords>\n"); |
| 315 // TODO: in theory, keywords can be a list too. |
| 308 | 316 |
| 309 // TODO: in theory, keywords can be a list too. | 317 SkString producer("<pdf:Producer>SKPDF_PRODUCER</pdf:Producer>\n"); |
| 318 if (!metadata.fProducer.isEmpty()) { |
| 319 // TODO: register a developer prefix to make |
| 320 // <skia:SKPDF_CUSTOM_PRODUCER_KEY> a real XML tag. |
| 321 producer = escape_xml( |
| 322 metadata.fProducer, "<pdf:Producer>", |
| 323 "</pdf:Producer>\n<!-- <skia:" SKPDF_CUSTOM_PRODUCER_KEY ">" |
| 324 SKPDF_PRODUCER "</skia:" SKPDF_CUSTOM_PRODUCER_KEY "> -->\n"); |
| 325 } |
| 326 |
| 310 SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>", | 327 SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>", |
| 311 "</xmp:CreatorTool>\n"); | 328 "</xmp:CreatorTool>\n"); |
| 312 SkString documentID = uuid_to_string(doc); // no need to escape | 329 SkString documentID = uuid_to_string(doc); // no need to escape |
| 313 SkASSERT(0 == count_xml_escape_size(documentID)); | 330 SkASSERT(0 == count_xml_escape_size(documentID)); |
| 314 SkString instanceID = uuid_to_string(instance); | 331 SkString instanceID = uuid_to_string(instance); |
| 315 SkASSERT(0 == count_xml_escape_size(instanceID)); | 332 SkASSERT(0 == count_xml_escape_size(instanceID)); |
| 316 return sk_make_sp<PDFXMLObject>(SkStringPrintf( | 333 return sk_make_sp<PDFXMLObject>(SkStringPrintf( |
| 317 templateString, modificationDate.c_str(), creationDate.c_str(), | 334 templateString, modificationDate.c_str(), creationDate.c_str(), |
| 318 creator.c_str(), title.c_str(), subject.c_str(), author.c_str(), | 335 creator.c_str(), title.c_str(), subject.c_str(), author.c_str(), |
| 319 keywords1.c_str(), documentID.c_str(), instanceID.c_str(), | 336 keywords1.c_str(), documentID.c_str(), instanceID.c_str(), |
| 320 keywords2.c_str())); | 337 producer.c_str(), keywords2.c_str())); |
| 321 } | 338 } |
| 322 | 339 |
| 340 #undef SKPDF_CUSTOM_PRODUCER_KEY |
| 341 #undef SKPDF_PRODUCER |
| 323 #undef SKPDF_STRING | 342 #undef SKPDF_STRING |
| 324 #undef SKPDF_STRING_IMPL | 343 #undef SKPDF_STRING_IMPL |
| OLD | NEW |