Flutter Engine
The Flutter Engine
SkPDFMetadata.cpp
Go to the documentation of this file.
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
9
16#include "src/base/SkTime.h"
17#include "src/base/SkUtils.h"
18#include "src/core/SkMD5.h"
19#include "src/pdf/SkPDFTypes.h"
20#include "src/pdf/SkPDFUtils.h"
21
22#include <cstdint>
23#include <cstring>
24#include <utility>
25class SkPDFDocument;
26
27static constexpr SkPDF::DateTime kZeroTime = {0, 0, 0, 0, 0, 0, 0, 0};
28
29static bool operator!=(const SkPDF::DateTime& u, const SkPDF::DateTime& v) {
30 return u.fTimeZoneMinutes != v.fTimeZoneMinutes ||
31 u.fYear != v.fYear ||
32 u.fMonth != v.fMonth ||
33 u.fDayOfWeek != v.fDayOfWeek ||
34 u.fDay != v.fDay ||
35 u.fHour != v.fHour ||
36 u.fMinute != v.fMinute ||
37 u.fSecond != v.fSecond;
38}
39
41 int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes);
42 char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-';
43 int timeZoneHours = SkTAbs(timeZoneMinutes) / 60;
44 timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60;
45 return SkStringPrintf(
46 "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'",
47 static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth),
48 static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour),
49 static_cast<unsigned>(dt.fMinute),
50 static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours,
51 timeZoneMinutes);
52}
53
54namespace {
55static const struct {
56 const char* const key;
57 SkString SkPDF::Metadata::*const valuePtr;
58} gMetadataKeys[] = {
59 {"Title", &SkPDF::Metadata::fTitle},
60 {"Author", &SkPDF::Metadata::fAuthor},
61 {"Subject", &SkPDF::Metadata::fSubject},
62 {"Keywords", &SkPDF::Metadata::fKeywords},
63 {"Creator", &SkPDF::Metadata::fCreator},
64 {"Producer", &SkPDF::Metadata::fProducer},
65};
66} // namespace
67
68std::unique_ptr<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict(
69 const SkPDF::Metadata& metadata) {
70 auto dict = SkPDFMakeDict();
71 for (const auto keyValuePtr : gMetadataKeys) {
72 const SkString& value = metadata.*(keyValuePtr.valuePtr);
73 if (value.size() > 0) {
74 dict->insertTextString(keyValuePtr.key, value);
75 }
76 }
77 if (metadata.fCreation != kZeroTime) {
78 dict->insertTextString("CreationDate", pdf_date(metadata.fCreation));
79 }
80 if (metadata.fModified != kZeroTime) {
81 dict->insertTextString("ModDate", pdf_date(metadata.fModified));
82 }
83 return dict;
84}
85
87 // The main requirement is for the UUID to be unique; the exact
88 // format of the data that will be hashed is not important.
89 SkMD5 md5;
90 const char uuidNamespace[] = "org.skia.pdf\n";
91 md5.writeText(uuidNamespace);
92 double msec = SkTime::GetMSecs();
93 md5.write(&msec, sizeof(msec));
94 SkPDF::DateTime dateTime;
95 SkPDFUtils::GetDateTime(&dateTime);
96 md5.write(&dateTime, sizeof(dateTime));
97 md5.write(&metadata.fCreation, sizeof(metadata.fCreation));
98 md5.write(&metadata.fModified, sizeof(metadata.fModified));
99
100 for (const auto keyValuePtr : gMetadataKeys) {
101 md5.writeText(keyValuePtr.key);
102 md5.write("\037", 1);
103 const SkString& value = metadata.*(keyValuePtr.valuePtr);
104 md5.write(value.c_str(), value.size());
105 md5.write("\036", 1);
106 }
107 SkMD5::Digest digest = md5.finish();
108 // See RFC 4122, page 6-7.
109 digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
110 digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
111 static_assert(sizeof(digest) == sizeof(SkUUID), "uuid_size");
112 SkUUID uuid;
113 memcpy((void*)&uuid, &digest, sizeof(digest));
114 return uuid;
115}
116
117std::unique_ptr<SkPDFObject> SkPDFMetadata::MakePdfId(const SkUUID& doc, const SkUUID& instance) {
118 // /ID [ <81b14aafa313db63dbd6f981e49f94f4>
119 // <81b14aafa313db63dbd6f981e49f94f4> ]
120 auto array = SkPDFMakeArray();
121 static_assert(sizeof(SkUUID) == 16, "uuid_size");
122 array->appendByteString(SkString(reinterpret_cast<const char*>(&doc ), sizeof(SkUUID)));
123 array->appendByteString(SkString(reinterpret_cast<const char*>(&instance), sizeof(SkUUID)));
124 return array;
125}
126
127// Convert a block of memory to hexadecimal. Input and output pointers will be
128// moved to end of the range.
129static void hexify(const uint8_t** inputPtr, char** outputPtr, int count) {
130 SkASSERT(inputPtr && *inputPtr);
131 SkASSERT(outputPtr && *outputPtr);
132 while (count-- > 0) {
133 uint8_t value = *(*inputPtr)++;
134 *(*outputPtr)++ = SkHexadecimalDigits::gLower[value >> 4];
135 *(*outputPtr)++ = SkHexadecimalDigits::gLower[value & 0xF];
136 }
137}
138
139static SkString uuid_to_string(const SkUUID& uuid) {
140 // 8-4-4-4-12
141 char buffer[36]; // [32 + 4]
142 char* ptr = buffer;
143 const uint8_t* data = uuid.fData;
144 hexify(&data, &ptr, 4);
145 *ptr++ = '-';
146 hexify(&data, &ptr, 2);
147 *ptr++ = '-';
148 hexify(&data, &ptr, 2);
149 *ptr++ = '-';
150 hexify(&data, &ptr, 2);
151 *ptr++ = '-';
152 hexify(&data, &ptr, 6);
153 SkASSERT(ptr == buffer + 36);
154 SkASSERT(data == uuid.fData + 16);
155 return SkString(buffer, 36);
156}
157
158namespace {
159class PDFXMLObject final : public SkPDFObject {
160public:
161 PDFXMLObject(SkString xml) : fXML(std::move(xml)) {}
162 void emitObject(SkWStream* stream) const override {
163 SkPDFDict dict("Metadata");
164 dict.insertName("Subtype", "XML");
165 dict.insertInt("Length", fXML.size());
166 dict.emitObject(stream);
167 static const char streamBegin[] = " stream\n";
168 stream->writeText(streamBegin);
169 // Do not compress this. The standard requires that a
170 // program that does not understand PDF can grep for
171 // "<?xpacket" and extract the entire XML.
172 stream->write(fXML.c_str(), fXML.size());
173 static const char streamEnd[] = "\nendstream";
174 stream->writeText(streamEnd);
175 }
176
177private:
178 const SkString fXML;
179};
180} // namespace
181
182static int count_xml_escape_size(const SkString& input) {
183 int extra = 0;
184 for (size_t i = 0; i < input.size(); ++i) {
185 if (input[i] == '&') {
186 extra += 4; // strlen("&amp;") - strlen("&")
187 } else if (input[i] == '<') {
188 extra += 3; // strlen("&lt;") - strlen("<")
189 }
190 }
191 return extra;
192}
193
195 const char* before = nullptr,
196 const char* after = nullptr) {
197 if (input.size() == 0) {
198 return input;
199 }
200 // "&" --> "&amp;" and "<" --> "&lt;"
201 // text is assumed to be in UTF-8
202 // all strings are xml content, not attribute values.
203 size_t beforeLen = before ? strlen(before) : 0;
204 size_t afterLen = after ? strlen(after) : 0;
205 int extra = count_xml_escape_size(input);
206 SkString output(input.size() + extra + beforeLen + afterLen);
207 char* out = output.data();
208 if (before) {
209 strncpy(out, before, beforeLen);
210 out += beforeLen;
211 }
212 static const char kAmp[] = "&amp;";
213 static const char kLt[] = "&lt;";
214 for (size_t i = 0; i < input.size(); ++i) {
215 if (input[i] == '&') {
216 memcpy(out, kAmp, strlen(kAmp));
217 out += strlen(kAmp);
218 } else if (input[i] == '<') {
219 memcpy(out, kLt, strlen(kLt));
220 out += strlen(kLt);
221 } else {
222 *out++ = input[i];
223 }
224 }
225 if (after) {
226 strncpy(out, after, afterLen);
227 out += afterLen;
228 }
229 // Validate that we haven't written outside of our string.
230 SkASSERT(out == &output.data()[output.size()]);
231 *out = '\0';
232 return output;
233}
234
236 const SkPDF::Metadata& metadata,
237 const SkUUID& doc,
238 const SkUUID& instance,
239 SkPDFDocument* docPtr) {
240 static const char templateString[] =
241 "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
242 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n"
243 " x:xmptk=\"Adobe XMP Core 5.4-c005 78.147326, "
244 "2012/08/23-13:03:03\">\n"
245 "<rdf:RDF "
246 "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
247 "<rdf:Description rdf:about=\"\"\n"
248 " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n"
249 " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"
250 " xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"\n"
251 " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\"\n"
252 " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n"
253 "<pdfaid:part>2</pdfaid:part>\n"
254 "<pdfaid:conformance>B</pdfaid:conformance>\n"
255 "%s" // ModifyDate
256 "%s" // CreateDate
257 "%s" // xmp:CreatorTool
258 "<dc:format>application/pdf</dc:format>\n"
259 "%s" // dc:title
260 "%s" // dc:description
261 "%s" // author
262 "%s" // keywords
263 "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n"
264 "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n"
265 "%s" // pdf:Producer
266 "%s" // pdf:Keywords
267 "</rdf:Description>\n"
268 "</rdf:RDF>\n"
269 "</x:xmpmeta>\n" // Note: the standard suggests 4k of padding.
270 "<?xpacket end=\"w\"?>\n";
271
272 SkString creationDate;
273 SkString modificationDate;
274 if (metadata.fCreation != kZeroTime) {
275 SkString tmp;
276 metadata.fCreation.toISO8601(&tmp);
278 // YYYY-mm-ddTHH:MM:SS[+|-]ZZ:ZZ; no need to escape
279 creationDate = SkStringPrintf("<xmp:CreateDate>%s</xmp:CreateDate>\n",
280 tmp.c_str());
281 }
282 if (metadata.fModified != kZeroTime) {
283 SkString tmp;
284 metadata.fModified.toISO8601(&tmp);
286 modificationDate = SkStringPrintf(
287 "<xmp:ModifyDate>%s</xmp:ModifyDate>\n", tmp.c_str());
288 }
289 SkString title =
290 escape_xml(metadata.fTitle,
291 "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">",
292 "</rdf:li></rdf:Alt></dc:title>\n");
293 SkString author =
294 escape_xml(metadata.fAuthor, "<dc:creator><rdf:Seq><rdf:li>",
295 "</rdf:li></rdf:Seq></dc:creator>\n");
296 // TODO: in theory, XMP can support multiple authors. Split on a delimiter?
297 SkString subject = escape_xml(
298 metadata.fSubject,
299 "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">",
300 "</rdf:li></rdf:Alt></dc:description>\n");
301 SkString keywords1 =
302 escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>",
303 "</rdf:li></rdf:Bag></dc:subject>\n");
304 SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>",
305 "</pdf:Keywords>\n");
306 // TODO: in theory, keywords can be a list too.
307
308 SkString producer = escape_xml(metadata.fProducer, "<pdf:Producer>", "</pdf:Producer>\n");
309
310 SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>",
311 "</xmp:CreatorTool>\n");
312 SkString documentID = uuid_to_string(doc); // no need to escape
313 SkASSERT(0 == count_xml_escape_size(documentID));
314 SkString instanceID = uuid_to_string(instance);
315 SkASSERT(0 == count_xml_escape_size(instanceID));
316
317
318 auto value = SkStringPrintf(
319 templateString, modificationDate.c_str(), creationDate.c_str(),
320 creator.c_str(), title.c_str(), subject.c_str(), author.c_str(),
321 keywords1.c_str(), documentID.c_str(), instanceID.c_str(),
322 producer.c_str(), keywords2.c_str());
323
324 std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict("Metadata");
325 dict->insertName("Subtype", "XML");
326 return SkPDFStreamOut(std::move(dict),
327 SkMemoryStream::MakeCopy(value.c_str(), value.size()),
329}
330
331#undef SKPDF_CUSTOM_PRODUCER_KEY
332#undef SKPDF_PRODUCER
333#undef SKPDF_STRING
334#undef SKPDF_STRING_IMPL
static SkMD5::Digest md5(const SkBitmap &bm)
Definition: CodecTest.cpp:77
int count
Definition: FontMgrTest.cpp:50
#define SkASSERT(cond)
Definition: SkAssert.h:116
static bool operator!=(const SkPDF::DateTime &u, const SkPDF::DateTime &v)
static SkString uuid_to_string(const SkUUID &uuid)
static void hexify(const uint8_t **inputPtr, char **outputPtr, int count)
SkString escape_xml(const SkString &input, const char *before=nullptr, const char *after=nullptr)
static int count_xml_escape_size(const SkString &input)
static SkString pdf_date(const SkPDF::DateTime &dt)
static constexpr SkPDF::DateTime kZeroTime
SkPDFIndirectReference SkPDFStreamOut(std::unique_ptr< SkPDFDict > dict, std::unique_ptr< SkStreamAsset > content, SkPDFDocument *doc, SkPDFSteamCompressionEnabled compress)
Definition: SkPDFTypes.cpp:591
static std::unique_ptr< SkPDFDict > SkPDFMakeDict(const char *type=nullptr)
Definition: SkPDFTypes.h:185
static std::unique_ptr< SkPDFArray > SkPDFMakeArray(Args... args)
Definition: SkPDFTypes.h:125
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
static T SkTAbs(T value)
Definition: SkTemplates.h:43
constexpr int SkToInt(S x)
Definition: SkTo.h:29
Definition: SkMD5.h:19
static std::unique_ptr< SkMemoryStream > MakeCopy(const void *data, size_t length)
Definition: SkStream.cpp:306
virtual void emitObject(SkWStream *stream) const =0
size_t size() const
Definition: SkString.h:131
const char * c_str() const
Definition: SkString.h:133
VkInstance instance
Definition: main.cc:48
uint8_t value
const char gLower[16]
Definition: SkUtils.cpp:12
std::unique_ptr< SkPDFObject > MakeDocumentInformationDict(const SkPDF::Metadata &)
SkPDFIndirectReference MakeXMPObject(const SkPDF::Metadata &metadata, const SkUUID &doc, const SkUUID &instance, SkPDFDocument *)
SkUUID CreateUUID(const SkPDF::Metadata &)
std::unique_ptr< SkPDFObject > MakePdfId(const SkUUID &doc, const SkUUID &instance)
void GetDateTime(SkPDF::DateTime *)
Definition: SkPDFUtils.cpp:431
double GetMSecs()
Definition: SkTime.h:17
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
Definition: ref_ptr.h:256
uint8_t data[16]
Definition: SkMD5.h:39
uint8_t fMinute
0..59
Definition: SkPDFDocument.h:77
uint8_t fMonth
1..12
Definition: SkPDFDocument.h:73
uint8_t fDay
1..31
Definition: SkPDFDocument.h:75
uint16_t fYear
e.g. 2005
Definition: SkPDFDocument.h:72
void toISO8601(SkString *dst) const
uint8_t fSecond
0..59
Definition: SkPDFDocument.h:78
int16_t fTimeZoneMinutes
Definition: SkPDFDocument.h:70
uint8_t fHour
0..23
Definition: SkPDFDocument.h:76
uint8_t fDayOfWeek
0..6, 0==Sunday
Definition: SkPDFDocument.h:74
SkString fSubject
Definition: SkPDFDocument.h:96
SkString fAuthor
Definition: SkPDFDocument.h:92
SkString fProducer
SkString fKeywords
DateTime fCreation
DateTime fModified
Definition: SkUUID.h:9
uint8_t fData[16]
Definition: SkUUID.h:10
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63