OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2011 Google Inc. | 2 * Copyright 2011 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 "SkDocument.h" | 8 #include "SkDocument.h" |
9 #include "SkPDFCanon.h" | 9 #include "SkPDFCanon.h" |
10 #include "SkPDFDevice.h" | 10 #include "SkPDFDevice.h" |
11 #include "SkPDFFont.h" | 11 #include "SkPDFFont.h" |
12 #include "SkPDFStream.h" | 12 #include "SkPDFStream.h" |
13 #include "SkPDFTypes.h" | 13 #include "SkPDFTypes.h" |
14 #include "SkPDFUtils.h" | 14 #include "SkPDFUtils.h" |
15 #include "SkStream.h" | 15 #include "SkStream.h" |
| 16 #include "SkPDFMetadata.h" |
16 | 17 |
17 class SkPDFDict; | 18 class SkPDFDict; |
18 | 19 |
19 static void emit_pdf_header(SkWStream* stream) { | 20 static void emit_pdf_header(SkWStream* stream) { |
20 stream->writeText("%PDF-1.4\n%"); | 21 stream->writeText("%PDF-1.4\n%"); |
21 // The PDF spec recommends including a comment with four bytes, all | 22 // The PDF spec recommends including a comment with four bytes, all |
22 // with their high bits set. This is "Skia" with the high bits set. | 23 // with their high bits set. This is "Skia" with the high bits set. |
23 stream->write32(0xD3EBE9E1); | 24 stream->write32(0xD3EBE9E1); |
24 stream->writeText("\n"); | 25 stream->writeText("\n"); |
25 } | 26 } |
26 | 27 |
27 static void emit_pdf_footer(SkWStream* stream, | 28 static void emit_pdf_footer(SkWStream* stream, |
28 const SkPDFObjNumMap& objNumMap, | 29 const SkPDFObjNumMap& objNumMap, |
29 const SkPDFSubstituteMap& substitutes, | 30 const SkPDFSubstituteMap& substitutes, |
30 SkPDFObject* docCatalog, | 31 SkPDFObject* docCatalog, |
31 int64_t objCount, | 32 int64_t objCount, |
32 int32_t xRefFileOffset, | 33 int32_t xRefFileOffset, |
33 SkPDFDict* info) { | 34 SkPDFObject* info /* take ownership */, |
| 35 SkPDFObject* id /* take ownership */) { |
34 SkPDFDict trailerDict; | 36 SkPDFDict trailerDict; |
35 // TODO(vandebo): Linearized format will take a Prev entry too. | 37 // TODO(http://crbug.com/80908): Linearized format will take a |
36 // TODO(vandebo): PDF/A requires an ID entry. | 38 // Prev entry too. |
37 trailerDict.insertInt("Size", int(objCount)); | 39 trailerDict.insertInt("Size", int(objCount)); |
38 trailerDict.insertObjRef("Root", SkRef(docCatalog)); | 40 trailerDict.insertObjRef("Root", SkRef(docCatalog)); |
39 SkASSERT(info); | 41 SkASSERT(info); |
40 trailerDict.insertObjRef("Info", SkRef(info)); | 42 trailerDict.insertObjRef("Info", info); |
41 | 43 if (id) { |
| 44 trailerDict.insertObject("ID", id); |
| 45 } |
42 stream->writeText("trailer\n"); | 46 stream->writeText("trailer\n"); |
43 trailerDict.emitObject(stream, objNumMap, substitutes); | 47 trailerDict.emitObject(stream, objNumMap, substitutes); |
44 stream->writeText("\nstartxref\n"); | 48 stream->writeText("\nstartxref\n"); |
45 stream->writeBigDecAsText(xRefFileOffset); | 49 stream->writeBigDecAsText(xRefFileOffset); |
46 stream->writeText("\n%%EOF"); | 50 stream->writeText("\n%%EOF"); |
47 } | 51 } |
48 | 52 |
49 static void perform_font_subsetting( | 53 static void perform_font_subsetting( |
50 const SkTDArray<const SkPDFDevice*>& pageDevices, | 54 const SkTDArray<const SkPDFDevice*>& pageDevices, |
51 SkPDFSubstituteMap* substituteMap) { | 55 SkPDFSubstituteMap* substituteMap) { |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 nextRoundNodes.rewind(); | 159 nextRoundNodes.rewind(); |
156 treeCapacity *= kNodeSize; | 160 treeCapacity *= kNodeSize; |
157 } while (curNodes.count() > 1); | 161 } while (curNodes.count() > 1); |
158 | 162 |
159 pageTree->push(curNodes[0]); // Transfer reference. | 163 pageTree->push(curNodes[0]); // Transfer reference. |
160 if (rootNode) { | 164 if (rootNode) { |
161 *rootNode = curNodes[0]; | 165 *rootNode = curNodes[0]; |
162 } | 166 } |
163 } | 167 } |
164 | 168 |
165 struct Metadata { | |
166 SkTArray<SkDocument::Attribute> fInfo; | |
167 SkAutoTDelete<const SkTime::DateTime> fCreation; | |
168 SkAutoTDelete<const SkTime::DateTime> fModified; | |
169 }; | |
170 | |
171 static SkString pdf_date(const SkTime::DateTime& dt) { | |
172 int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); | |
173 char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; | |
174 int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; | |
175 timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; | |
176 return SkStringPrintf( | |
177 "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", | |
178 static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth), | |
179 static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour), | |
180 static_cast<unsigned>(dt.fMinute), | |
181 static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours, | |
182 timeZoneMinutes); | |
183 } | |
184 | |
185 SkPDFDict* create_document_information_dict(const Metadata& metadata) { | |
186 SkAutoTUnref<SkPDFDict> dict(new SkPDFDict); | |
187 static const char* keys[] = { | |
188 "Title", "Author", "Subject", "Keywords", "Creator" }; | |
189 for (const char* key : keys) { | |
190 for (const SkDocument::Attribute& keyValue : metadata.fInfo) { | |
191 if (keyValue.fKey.equals(key)) { | |
192 dict->insertString(key, keyValue.fValue); | |
193 } | |
194 } | |
195 } | |
196 dict->insertString("Producer", "Skia/PDF"); | |
197 if (metadata.fCreation) { | |
198 dict->insertString("CreationDate", pdf_date(*metadata.fCreation.get())); | |
199 } | |
200 if (metadata.fModified) { | |
201 dict->insertString("ModDate", pdf_date(*metadata.fModified.get())); | |
202 } | |
203 return dict.detach(); | |
204 } | |
205 | |
206 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, | 169 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
207 const Metadata& metadata, | 170 const SkPDFMetadata& metadata, |
208 SkWStream* stream) { | 171 SkWStream* stream) { |
209 if (pageDevices.isEmpty()) { | 172 if (pageDevices.isEmpty()) { |
210 return false; | 173 return false; |
211 } | 174 } |
212 | 175 |
213 SkTDArray<SkPDFDict*> pages; | 176 SkTDArray<SkPDFDict*> pages; |
214 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); | 177 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); |
215 | 178 |
216 for (int i = 0; i < pageDevices.count(); i++) { | 179 for (int i = 0; i < pageDevices.count(); i++) { |
217 SkASSERT(pageDevices[i]); | 180 SkASSERT(pageDevices[i]); |
218 SkASSERT(i == 0 || | 181 SkASSERT(i == 0 || |
219 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); | 182 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); |
220 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i])); | 183 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i])); |
221 pageDevices[i]->appendDestinations(dests, page.get()); | 184 pageDevices[i]->appendDestinations(dests, page.get()); |
222 pages.push(page.detach()); | 185 pages.push(page.detach()); |
223 } | 186 } |
224 | 187 |
225 SkTDArray<SkPDFDict*> pageTree; | |
226 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); | 188 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); |
227 | 189 |
| 190 SkAutoTUnref<SkPDFObject> infoDict( |
| 191 metadata.createDocumentInformationDict()); |
| 192 |
| 193 SkAutoTUnref<SkPDFObject> id, xmp; |
| 194 #ifdef SK_PDF_GENERATE_PDFA |
| 195 SkPDFMetadata::UUID uuid = metadata.uuid(); |
| 196 // We use the same UUID for Document ID and Instance ID since this |
| 197 // is the first revision of this document (and Skia does not |
| 198 // support revising existing PDF documents). |
| 199 // If we are not in PDF/A mode, don't use a UUID since testing |
| 200 // works best with reproducible outputs. |
| 201 id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid)); |
| 202 xmp.reset(metadata.createXMPObject(uuid, uuid)); |
| 203 docCatalog->insertObjRef("Metadata", xmp.detach()); |
| 204 |
| 205 // sRGB is specified by HTML, CSS, and SVG. |
| 206 SkAutoTUnref<SkPDFDict> outputIntent(new SkPDFDict("OutputIntent")); |
| 207 outputIntent->insertName("S", "GTS_PDFA1"); |
| 208 outputIntent->insertString("RegistryName", "http://www.color.org"); |
| 209 outputIntent->insertString("OutputConditionIdentifier", |
| 210 "sRGB IEC61966-2.1"); |
| 211 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); |
| 212 intentArray->appendObject(outputIntent.detach()); |
| 213 // Don't specify OutputIntents if we are not in PDF/A mode since |
| 214 // no one has ever asked for this feature. |
| 215 docCatalog->insertObject("OutputIntents", intentArray.detach()); |
| 216 #endif |
| 217 |
| 218 SkTDArray<SkPDFDict*> pageTree; |
228 SkPDFDict* pageTreeRoot; | 219 SkPDFDict* pageTreeRoot; |
229 generate_page_tree(pages, &pageTree, &pageTreeRoot); | 220 generate_page_tree(pages, &pageTree, &pageTreeRoot); |
230 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); | 221 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); |
231 | 222 |
232 if (dests->size() > 0) { | 223 if (dests->size() > 0) { |
233 docCatalog->insertObjRef("Dests", dests.detach()); | 224 docCatalog->insertObjRef("Dests", dests.detach()); |
234 } | 225 } |
235 | 226 |
236 /* TODO(vandebo): output intent | |
237 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); | |
238 outputIntent->insertName("S", "GTS_PDFA1"); | |
239 outputIntent->insertString("OutputConditionIdentifier", "sRGB"); | |
240 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); | |
241 intentArray->appendObject(SkRef(outputIntent.get())); | |
242 docCatalog->insertObject("OutputIntent", intentArray.detach()); | |
243 */ | |
244 | |
245 // Build font subsetting info before proceeding. | 227 // Build font subsetting info before proceeding. |
246 SkPDFSubstituteMap substitutes; | 228 SkPDFSubstituteMap substitutes; |
247 perform_font_subsetting(pageDevices, &substitutes); | 229 perform_font_subsetting(pageDevices, &substitutes); |
248 | 230 |
249 SkAutoTUnref<SkPDFDict> infoDict( | |
250 create_document_information_dict(metadata)); | |
251 SkPDFObjNumMap objNumMap; | 231 SkPDFObjNumMap objNumMap; |
252 if (objNumMap.addObject(infoDict)) { | 232 objNumMap.addObjectRecursively(infoDict, substitutes); |
253 infoDict->addResources(&objNumMap, substitutes); | 233 objNumMap.addObjectRecursively(docCatalog.get(), substitutes); |
254 } | |
255 if (objNumMap.addObject(docCatalog.get())) { | |
256 docCatalog->addResources(&objNumMap, substitutes); | |
257 } | |
258 size_t baseOffset = stream->bytesWritten(); | 234 size_t baseOffset = stream->bytesWritten(); |
259 emit_pdf_header(stream); | 235 emit_pdf_header(stream); |
260 SkTDArray<int32_t> offsets; | 236 SkTDArray<int32_t> offsets; |
261 for (int i = 0; i < objNumMap.objects().count(); ++i) { | 237 for (int i = 0; i < objNumMap.objects().count(); ++i) { |
262 SkPDFObject* object = objNumMap.objects()[i]; | 238 SkPDFObject* object = objNumMap.objects()[i]; |
263 size_t offset = stream->bytesWritten(); | 239 size_t offset = stream->bytesWritten(); |
264 // This assert checks that size(pdf_header) > 0 and that | 240 // This assert checks that size(pdf_header) > 0 and that |
265 // the output stream correctly reports bytesWritten(). | 241 // the output stream correctly reports bytesWritten(). |
266 SkASSERT(offset > baseOffset); | 242 SkASSERT(offset > baseOffset); |
267 offsets.push(SkToS32(offset - baseOffset)); | 243 offsets.push(SkToS32(offset - baseOffset)); |
(...skipping 11 matching lines...) Expand all Loading... |
279 | 255 |
280 stream->writeText("xref\n0 "); | 256 stream->writeText("xref\n0 "); |
281 stream->writeDecAsText(objCount); | 257 stream->writeDecAsText(objCount); |
282 stream->writeText("\n0000000000 65535 f \n"); | 258 stream->writeText("\n0000000000 65535 f \n"); |
283 for (int i = 0; i < offsets.count(); i++) { | 259 for (int i = 0; i < offsets.count(); i++) { |
284 SkASSERT(offsets[i] > 0); | 260 SkASSERT(offsets[i] > 0); |
285 stream->writeBigDecAsText(offsets[i], 10); | 261 stream->writeBigDecAsText(offsets[i], 10); |
286 stream->writeText(" 00000 n \n"); | 262 stream->writeText(" 00000 n \n"); |
287 } | 263 } |
288 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, | 264 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, |
289 xRefFileOffset, infoDict); | 265 xRefFileOffset, infoDict.detach(), id.detach()); |
290 | 266 |
291 // The page tree has both child and parent pointers, so it creates a | 267 // The page tree has both child and parent pointers, so it creates a |
292 // reference cycle. We must clear that cycle to properly reclaim memory. | 268 // reference cycle. We must clear that cycle to properly reclaim memory. |
293 for (int i = 0; i < pageTree.count(); i++) { | 269 for (int i = 0; i < pageTree.count(); i++) { |
294 pageTree[i]->clear(); | 270 pageTree[i]->clear(); |
295 } | 271 } |
296 pageTree.safeUnrefAll(); | 272 pageTree.safeUnrefAll(); |
297 pages.unrefAll(); | 273 pages.unrefAll(); |
298 return true; | 274 return true; |
299 } | 275 } |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 fMetadata.fInfo = info; | 373 fMetadata.fInfo = info; |
398 fMetadata.fCreation.reset(clone(creationDate)); | 374 fMetadata.fCreation.reset(clone(creationDate)); |
399 fMetadata.fModified.reset(clone(modifiedDate)); | 375 fMetadata.fModified.reset(clone(modifiedDate)); |
400 } | 376 } |
401 | 377 |
402 private: | 378 private: |
403 SkPDFCanon fCanon; | 379 SkPDFCanon fCanon; |
404 SkTDArray<const SkPDFDevice*> fPageDevices; | 380 SkTDArray<const SkPDFDevice*> fPageDevices; |
405 SkAutoTUnref<SkCanvas> fCanvas; | 381 SkAutoTUnref<SkCanvas> fCanvas; |
406 SkScalar fRasterDpi; | 382 SkScalar fRasterDpi; |
407 Metadata fMetadata; | 383 SkPDFMetadata fMetadata; |
408 }; | 384 }; |
409 } // namespace | 385 } // namespace |
410 /////////////////////////////////////////////////////////////////////////////// | 386 /////////////////////////////////////////////////////////////////////////////// |
411 | 387 |
412 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { | 388 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { |
413 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; | 389 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; |
414 } | 390 } |
415 | 391 |
416 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { | 392 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { |
417 SkFILEWStream* stream = new SkFILEWStream(path); | 393 SkFILEWStream* stream = new SkFILEWStream(path); |
418 if (!stream->isValid()) { | 394 if (!stream->isValid()) { |
419 delete stream; | 395 delete stream; |
420 return nullptr; | 396 return nullptr; |
421 } | 397 } |
422 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; | 398 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; |
423 return new SkDocument_PDF(stream, delete_wstream, dpi); | 399 return new SkDocument_PDF(stream, delete_wstream, dpi); |
424 } | 400 } |
OLD | NEW |