LCOV - code coverage report
Current view: top level - gfx/skia/skia/src/pdf - SkPDFMetadata.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 148 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 12 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright 2015 Google Inc.
       3             :  *
       4             :  * Use of this source code is governed by a BSD-style license that can be
       5             :  * found in the LICENSE file.
       6             :  */
       7             : 
       8             : #include "SkMD5.h"
       9             : #include "SkMilestone.h"
      10             : #include "SkPDFMetadata.h"
      11             : #include "SkPDFTypes.h"
      12             : #include <utility>
      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             : 
      19           0 : static SkString pdf_date(const SkTime::DateTime& dt) {
      20           0 :     int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes);
      21           0 :     char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-';
      22           0 :     int timeZoneHours = SkTAbs(timeZoneMinutes) / 60;
      23           0 :     timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60;
      24             :     return SkStringPrintf(
      25             :             "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'",
      26           0 :             static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth),
      27           0 :             static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour),
      28           0 :             static_cast<unsigned>(dt.fMinute),
      29           0 :             static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours,
      30           0 :             timeZoneMinutes);
      31             : }
      32             : 
      33             : namespace {
      34             : static const struct {
      35             :     const char* const key;
      36             :     SkString SkDocument::PDFMetadata::*const valuePtr;
      37             : } gMetadataKeys[] = {
      38             :         {"Title", &SkDocument::PDFMetadata::fTitle},
      39             :         {"Author", &SkDocument::PDFMetadata::fAuthor},
      40             :         {"Subject", &SkDocument::PDFMetadata::fSubject},
      41             :         {"Keywords", &SkDocument::PDFMetadata::fKeywords},
      42             :         {"Creator", &SkDocument::PDFMetadata::fCreator},
      43             : };
      44             : }  // namespace
      45             : 
      46           0 : sk_sp<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict(
      47             :         const SkDocument::PDFMetadata& metadata) {
      48           0 :     auto dict = sk_make_sp<SkPDFDict>();
      49           0 :     for (const auto keyValuePtr : gMetadataKeys) {
      50           0 :         const SkString& value = metadata.*(keyValuePtr.valuePtr);
      51           0 :         if (value.size() > 0) {
      52           0 :             dict->insertString(keyValuePtr.key, value);
      53             :         }
      54             :     }
      55           0 :     if (metadata.fProducer.isEmpty()) {
      56           0 :         dict->insertString("Producer", SKPDF_PRODUCER);
      57             :     } else {
      58           0 :         dict->insertString("Producer", metadata.fProducer);
      59           0 :         dict->insertString(SKPDF_CUSTOM_PRODUCER_KEY, SKPDF_PRODUCER);
      60             :     }
      61           0 :     if (metadata.fCreation.fEnabled) {
      62           0 :         dict->insertString("CreationDate",
      63           0 :                            pdf_date(metadata.fCreation.fDateTime));
      64             :     }
      65           0 :     if (metadata.fModified.fEnabled) {
      66           0 :         dict->insertString("ModDate", pdf_date(metadata.fModified.fDateTime));
      67             :     }
      68           0 :     return dict;
      69             : }
      70             : 
      71           0 : SkPDFMetadata::UUID SkPDFMetadata::CreateUUID(
      72             :         const SkDocument::PDFMetadata& metadata) {
      73             :     // The main requirement is for the UUID to be unique; the exact
      74             :     // format of the data that will be hashed is not important.
      75           0 :     SkMD5 md5;
      76           0 :     const char uuidNamespace[] = "org.skia.pdf\n";
      77           0 :     md5.write(uuidNamespace, strlen(uuidNamespace));
      78           0 :     double msec = SkTime::GetMSecs();
      79           0 :     md5.write(&msec, sizeof(msec));
      80             :     SkTime::DateTime dateTime;
      81           0 :     SkTime::GetDateTime(&dateTime);
      82           0 :     md5.write(&dateTime, sizeof(dateTime));
      83           0 :     if (metadata.fCreation.fEnabled) {
      84           0 :         md5.write(&metadata.fCreation.fDateTime,
      85           0 :                   sizeof(metadata.fCreation.fDateTime));
      86             :     }
      87           0 :     if (metadata.fModified.fEnabled) {
      88           0 :         md5.write(&metadata.fModified.fDateTime,
      89           0 :                   sizeof(metadata.fModified.fDateTime));
      90             :     }
      91             : 
      92           0 :     for (const auto keyValuePtr : gMetadataKeys) {
      93           0 :         md5.write(keyValuePtr.key, strlen(keyValuePtr.key));
      94           0 :         md5.write("\037", 1);
      95           0 :         const SkString& value = metadata.*(keyValuePtr.valuePtr);
      96           0 :         md5.write(value.c_str(), value.size());
      97           0 :         md5.write("\036", 1);
      98             :     }
      99             :     SkMD5::Digest digest;
     100           0 :     md5.finish(digest);
     101             :     // See RFC 4122, page 6-7.
     102           0 :     digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
     103           0 :     digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
     104             :     static_assert(sizeof(digest) == sizeof(UUID), "uuid_size");
     105             :     SkPDFMetadata::UUID uuid;
     106           0 :     memcpy(&uuid, &digest, sizeof(digest));
     107           0 :     return uuid;
     108             : }
     109             : 
     110           0 : sk_sp<SkPDFObject> SkPDFMetadata::MakePdfId(const UUID& doc,
     111             :                                             const UUID& instance) {
     112             :     // /ID [ <81b14aafa313db63dbd6f981e49f94f4>
     113             :     //       <81b14aafa313db63dbd6f981e49f94f4> ]
     114           0 :     auto array = sk_make_sp<SkPDFArray>();
     115             :     static_assert(sizeof(SkPDFMetadata::UUID) == 16, "uuid_size");
     116           0 :     array->appendString(
     117           0 :             SkString(reinterpret_cast<const char*>(&doc), sizeof(UUID)));
     118           0 :     array->appendString(
     119           0 :             SkString(reinterpret_cast<const char*>(&instance), sizeof(UUID)));
     120           0 :     return array;
     121             : }
     122             : 
     123             : #define HEXIFY(INPUT_PTR, OUTPUT_PTR, HEX_STRING, BYTE_COUNT) \
     124             :     do {                                                      \
     125             :         for (int i = 0; i < (BYTE_COUNT); ++i) {              \
     126             :             uint8_t value = *(INPUT_PTR)++;                   \
     127             :             *(OUTPUT_PTR)++ = (HEX_STRING)[value >> 4];       \
     128             :             *(OUTPUT_PTR)++ = (HEX_STRING)[value & 0xF];      \
     129             :         }                                                     \
     130             :     } while (false)
     131           0 : static SkString uuid_to_string(const SkPDFMetadata::UUID& uuid) {
     132             :     //  8-4-4-4-12
     133             :     char buffer[36];  // [32 + 4]
     134             :     static const char gHex[] = "0123456789abcdef";
     135             :     SkASSERT(strlen(gHex) == 16);
     136           0 :     char* ptr = buffer;
     137           0 :     const uint8_t* data = uuid.fData;
     138           0 :     HEXIFY(data, ptr, gHex, 4);
     139           0 :     *ptr++ = '-';
     140           0 :     HEXIFY(data, ptr, gHex, 2);
     141           0 :     *ptr++ = '-';
     142           0 :     HEXIFY(data, ptr, gHex, 2);
     143           0 :     *ptr++ = '-';
     144           0 :     HEXIFY(data, ptr, gHex, 2);
     145           0 :     *ptr++ = '-';
     146           0 :     HEXIFY(data, ptr, gHex, 6);
     147           0 :     SkASSERT(ptr == buffer + 36);
     148           0 :     SkASSERT(data == uuid.fData + 16);
     149           0 :     return SkString(buffer, 36);
     150             : }
     151             : #undef HEXIFY
     152             : 
     153             : namespace {
     154           0 : class PDFXMLObject final : public SkPDFObject {
     155             : public:
     156           0 :     PDFXMLObject(SkString xml) : fXML(std::move(xml)) {}
     157           0 :     void emitObject(SkWStream* stream,
     158             :                     const SkPDFObjNumMap& omap) const override {
     159           0 :         SkPDFDict dict("Metadata");
     160           0 :         dict.insertName("Subtype", "XML");
     161           0 :         dict.insertInt("Length", fXML.size());
     162           0 :         dict.emitObject(stream, omap);
     163             :         static const char streamBegin[] = " stream\n";
     164           0 :         stream->write(streamBegin, strlen(streamBegin));
     165             :         // Do not compress this.  The standard requires that a
     166             :         // program that does not understand PDF can grep for
     167             :         // "<?xpacket" and extract the entire XML.
     168           0 :         stream->write(fXML.c_str(), fXML.size());
     169             :         static const char streamEnd[] = "\nendstream";
     170           0 :         stream->write(streamEnd, strlen(streamEnd));
     171           0 :     }
     172             : 
     173             : private:
     174             :     const SkString fXML;
     175             : };
     176             : }  // namespace
     177             : 
     178           0 : static int count_xml_escape_size(const SkString& input) {
     179           0 :     int extra = 0;
     180           0 :     for (size_t i = 0; i < input.size(); ++i) {
     181           0 :         if (input[i] == '&') {
     182           0 :             extra += 4;  // strlen("&amp;") - strlen("&")
     183           0 :         } else if (input[i] == '<') {
     184           0 :             extra += 3;  // strlen("&lt;") - strlen("<")
     185             :         }
     186             :     }
     187           0 :     return extra;
     188             : }
     189             : 
     190           0 : const SkString escape_xml(const SkString& input,
     191             :                           const char* before = nullptr,
     192             :                           const char* after = nullptr) {
     193           0 :     if (input.size() == 0) {
     194           0 :         return input;
     195             :     }
     196             :     // "&" --> "&amp;" and  "<" --> "&lt;"
     197             :     // text is assumed to be in UTF-8
     198             :     // all strings are xml content, not attribute values.
     199           0 :     size_t beforeLen = before ? strlen(before) : 0;
     200           0 :     size_t afterLen = after ? strlen(after) : 0;
     201           0 :     int extra = count_xml_escape_size(input);
     202           0 :     SkString output(input.size() + extra + beforeLen + afterLen);
     203           0 :     char* out = output.writable_str();
     204           0 :     if (before) {
     205           0 :         strncpy(out, before, beforeLen);
     206           0 :         out += beforeLen;
     207             :     }
     208             :     static const char kAmp[] = "&amp;";
     209             :     static const char kLt[] = "&lt;";
     210           0 :     for (size_t i = 0; i < input.size(); ++i) {
     211           0 :         if (input[i] == '&') {
     212           0 :             strncpy(out, kAmp, strlen(kAmp));
     213           0 :             out += strlen(kAmp);
     214           0 :         } else if (input[i] == '<') {
     215           0 :             strncpy(out, kLt, strlen(kLt));
     216           0 :             out += strlen(kLt);
     217             :         } else {
     218           0 :             *out++ = input[i];
     219             :         }
     220             :     }
     221           0 :     if (after) {
     222           0 :         strncpy(out, after, afterLen);
     223           0 :         out += afterLen;
     224             :     }
     225             :     // Validate that we haven't written outside of our string.
     226           0 :     SkASSERT(out == &output.writable_str()[output.size()]);
     227           0 :     *out = '\0';
     228           0 :     return output;
     229             : }
     230             : 
     231           0 : sk_sp<SkPDFObject> SkPDFMetadata::MakeXMPObject(
     232             :         const SkDocument::PDFMetadata& metadata,
     233             :         const UUID& doc,
     234             :         const UUID& instance) {
     235             :     static const char templateString[] =
     236             :             "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
     237             :             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n"
     238             :             " x:xmptk=\"Adobe XMP Core 5.4-c005 78.147326, "
     239             :             "2012/08/23-13:03:03\">\n"
     240             :             "<rdf:RDF "
     241             :             "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
     242             :             "<rdf:Description rdf:about=\"\"\n"
     243             :             " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n"
     244             :             " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"
     245             :             " xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"\n"
     246             :             " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\"\n"
     247             :             " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n"
     248             :             "<pdfaid:part>2</pdfaid:part>\n"
     249             :             "<pdfaid:conformance>B</pdfaid:conformance>\n"
     250             :             "%s"  // ModifyDate
     251             :             "%s"  // CreateDate
     252             :             "%s"  // xmp:CreatorTool
     253             :             "<dc:format>application/pdf</dc:format>\n"
     254             :             "%s"  // dc:title
     255             :             "%s"  // dc:description
     256             :             "%s"  // author
     257             :             "%s"  // keywords
     258             :             "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n"
     259             :             "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n"
     260             :             "%s"  // pdf:Producer
     261             :             "%s"  // pdf:Keywords
     262             :             "</rdf:Description>\n"
     263             :             "</rdf:RDF>\n"
     264             :             "</x:xmpmeta>\n"  // Note:  the standard suggests 4k of padding.
     265             :             "<?xpacket end=\"w\"?>\n";
     266             : 
     267           0 :     SkString creationDate;
     268           0 :     SkString modificationDate;
     269           0 :     if (metadata.fCreation.fEnabled) {
     270           0 :         SkString tmp;
     271           0 :         metadata.fCreation.fDateTime.toISO8601(&tmp);
     272           0 :         SkASSERT(0 == count_xml_escape_size(tmp));
     273             :         // YYYY-mm-ddTHH:MM:SS[+|-]ZZ:ZZ; no need to escape
     274           0 :         creationDate = SkStringPrintf("<xmp:CreateDate>%s</xmp:CreateDate>\n",
     275           0 :                                       tmp.c_str());
     276             :     }
     277           0 :     if (metadata.fModified.fEnabled) {
     278           0 :         SkString tmp;
     279           0 :         metadata.fModified.fDateTime.toISO8601(&tmp);
     280           0 :         SkASSERT(0 == count_xml_escape_size(tmp));
     281           0 :         modificationDate = SkStringPrintf(
     282           0 :                 "<xmp:ModifyDate>%s</xmp:ModifyDate>\n", tmp.c_str());
     283             :     }
     284             :     SkString title =
     285             :             escape_xml(metadata.fTitle,
     286             :                        "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">",
     287           0 :                        "</rdf:li></rdf:Alt></dc:title>\n");
     288             :     SkString author =
     289             :             escape_xml(metadata.fAuthor, "<dc:creator><rdf:Bag><rdf:li>",
     290           0 :                        "</rdf:li></rdf:Bag></dc:creator>\n");
     291             :     // TODO: in theory, XMP can support multiple authors.  Split on a delimiter?
     292             :     SkString subject = escape_xml(
     293             :             metadata.fSubject,
     294             :             "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">",
     295           0 :             "</rdf:li></rdf:Alt></dc:description>\n");
     296             :     SkString keywords1 =
     297             :             escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>",
     298           0 :                        "</rdf:li></rdf:Bag></dc:subject>\n");
     299             :     SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>",
     300           0 :                                     "</pdf:Keywords>\n");
     301             :     // TODO: in theory, keywords can be a list too.
     302             : 
     303           0 :     SkString producer("<pdf:Producer>" SKPDF_PRODUCER "</pdf:Producer>\n");
     304           0 :     if (!metadata.fProducer.isEmpty()) {
     305             :         // TODO: register a developer prefix to make
     306             :         // <skia:SKPDF_CUSTOM_PRODUCER_KEY> a real XML tag.
     307           0 :         producer = escape_xml(
     308             :                 metadata.fProducer, "<pdf:Producer>",
     309             :                 "</pdf:Producer>\n<!-- <skia:" SKPDF_CUSTOM_PRODUCER_KEY ">"
     310           0 :                 SKPDF_PRODUCER "</skia:" SKPDF_CUSTOM_PRODUCER_KEY "> -->\n");
     311             :     }
     312             : 
     313             :     SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>",
     314           0 :                                   "</xmp:CreatorTool>\n");
     315           0 :     SkString documentID = uuid_to_string(doc);  // no need to escape
     316           0 :     SkASSERT(0 == count_xml_escape_size(documentID));
     317           0 :     SkString instanceID = uuid_to_string(instance);
     318           0 :     SkASSERT(0 == count_xml_escape_size(instanceID));
     319           0 :     return sk_make_sp<PDFXMLObject>(SkStringPrintf(
     320             :             templateString, modificationDate.c_str(), creationDate.c_str(),
     321             :             creator.c_str(), title.c_str(), subject.c_str(), author.c_str(),
     322             :             keywords1.c_str(), documentID.c_str(), instanceID.c_str(),
     323           0 :             producer.c_str(), keywords2.c_str()));
     324             : }
     325             : 
     326             : #undef SKPDF_CUSTOM_PRODUCER_KEY
     327             : #undef SKPDF_PRODUCER
     328             : #undef SKPDF_STRING
     329             : #undef SKPDF_STRING_IMPL

Generated by: LCOV version 1.13