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" | |
17 | 16 |
18 class SkPDFDict; | 17 class SkPDFDict; |
19 | 18 |
20 static void emit_pdf_header(SkWStream* stream) { | 19 static void emit_pdf_header(SkWStream* stream) { |
21 stream->writeText("%PDF-1.4\n%"); | 20 stream->writeText("%PDF-1.4\n%"); |
22 // The PDF spec recommends including a comment with four bytes, all | 21 // The PDF spec recommends including a comment with four bytes, all |
23 // with their high bits set. This is "Skia" with the high bits set. | 22 // with their high bits set. This is "Skia" with the high bits set. |
24 stream->write32(0xD3EBE9E1); | 23 stream->write32(0xD3EBE9E1); |
25 stream->writeText("\n"); | 24 stream->writeText("\n"); |
26 } | 25 } |
27 | 26 |
28 static void emit_pdf_footer(SkWStream* stream, | 27 static void emit_pdf_footer(SkWStream* stream, |
29 const SkPDFObjNumMap& objNumMap, | 28 const SkPDFObjNumMap& objNumMap, |
30 const SkPDFSubstituteMap& substitutes, | 29 const SkPDFSubstituteMap& substitutes, |
31 SkPDFObject* docCatalog, | 30 SkPDFObject* docCatalog, |
32 int64_t objCount, | 31 int64_t objCount, |
33 int32_t xRefFileOffset, | 32 int32_t xRefFileOffset, |
34 SkPDFObject* info /* take ownership */, | 33 SkPDFDict* info) { |
35 SkPDFObject* id /* take ownership */) { | |
36 SkPDFDict trailerDict; | 34 SkPDFDict trailerDict; |
37 // TODO(http://crbug.com/80908): Linearized format will take a | 35 // TODO(vandebo): Linearized format will take a Prev entry too. |
38 // Prev entry too. | 36 // TODO(vandebo): PDF/A requires an ID entry. |
39 trailerDict.insertInt("Size", int(objCount)); | 37 trailerDict.insertInt("Size", int(objCount)); |
40 trailerDict.insertObjRef("Root", SkRef(docCatalog)); | 38 trailerDict.insertObjRef("Root", SkRef(docCatalog)); |
41 SkASSERT(info); | 39 SkASSERT(info); |
42 trailerDict.insertObjRef("Info", info); | 40 trailerDict.insertObjRef("Info", SkRef(info)); |
43 if (id) { | 41 |
44 trailerDict.insertObject("ID", id); | |
45 } | |
46 stream->writeText("trailer\n"); | 42 stream->writeText("trailer\n"); |
47 trailerDict.emitObject(stream, objNumMap, substitutes); | 43 trailerDict.emitObject(stream, objNumMap, substitutes); |
48 stream->writeText("\nstartxref\n"); | 44 stream->writeText("\nstartxref\n"); |
49 stream->writeBigDecAsText(xRefFileOffset); | 45 stream->writeBigDecAsText(xRefFileOffset); |
50 stream->writeText("\n%%EOF"); | 46 stream->writeText("\n%%EOF"); |
51 } | 47 } |
52 | 48 |
53 static void perform_font_subsetting( | 49 static void perform_font_subsetting( |
54 const SkTDArray<const SkPDFDevice*>& pageDevices, | 50 const SkTDArray<const SkPDFDevice*>& pageDevices, |
55 SkPDFSubstituteMap* substituteMap) { | 51 SkPDFSubstituteMap* substituteMap) { |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 nextRoundNodes.rewind(); | 155 nextRoundNodes.rewind(); |
160 treeCapacity *= kNodeSize; | 156 treeCapacity *= kNodeSize; |
161 } while (curNodes.count() > 1); | 157 } while (curNodes.count() > 1); |
162 | 158 |
163 pageTree->push(curNodes[0]); // Transfer reference. | 159 pageTree->push(curNodes[0]); // Transfer reference. |
164 if (rootNode) { | 160 if (rootNode) { |
165 *rootNode = curNodes[0]; | 161 *rootNode = curNodes[0]; |
166 } | 162 } |
167 } | 163 } |
168 | 164 |
| 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 |
169 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, | 206 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
170 const SkPDFMetadata& metadata, | 207 const Metadata& metadata, |
171 SkWStream* stream) { | 208 SkWStream* stream) { |
172 if (pageDevices.isEmpty()) { | 209 if (pageDevices.isEmpty()) { |
173 return false; | 210 return false; |
174 } | 211 } |
175 | 212 |
176 SkTDArray<SkPDFDict*> pages; | 213 SkTDArray<SkPDFDict*> pages; |
177 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); | 214 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); |
178 | 215 |
179 for (int i = 0; i < pageDevices.count(); i++) { | 216 for (int i = 0; i < pageDevices.count(); i++) { |
180 SkASSERT(pageDevices[i]); | 217 SkASSERT(pageDevices[i]); |
181 SkASSERT(i == 0 || | 218 SkASSERT(i == 0 || |
182 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); | 219 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); |
183 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i])); | 220 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i])); |
184 pageDevices[i]->appendDestinations(dests, page.get()); | 221 pageDevices[i]->appendDestinations(dests, page.get()); |
185 pages.push(page.detach()); | 222 pages.push(page.detach()); |
186 } | 223 } |
187 | 224 |
| 225 SkTDArray<SkPDFDict*> pageTree; |
188 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); | 226 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); |
189 | 227 |
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; | |
219 SkPDFDict* pageTreeRoot; | 228 SkPDFDict* pageTreeRoot; |
220 generate_page_tree(pages, &pageTree, &pageTreeRoot); | 229 generate_page_tree(pages, &pageTree, &pageTreeRoot); |
221 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); | 230 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); |
222 | 231 |
223 if (dests->size() > 0) { | 232 if (dests->size() > 0) { |
224 docCatalog->insertObjRef("Dests", dests.detach()); | 233 docCatalog->insertObjRef("Dests", dests.detach()); |
225 } | 234 } |
226 | 235 |
| 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 |
227 // Build font subsetting info before proceeding. | 245 // Build font subsetting info before proceeding. |
228 SkPDFSubstituteMap substitutes; | 246 SkPDFSubstituteMap substitutes; |
229 perform_font_subsetting(pageDevices, &substitutes); | 247 perform_font_subsetting(pageDevices, &substitutes); |
230 | 248 |
| 249 SkAutoTUnref<SkPDFDict> infoDict( |
| 250 create_document_information_dict(metadata)); |
231 SkPDFObjNumMap objNumMap; | 251 SkPDFObjNumMap objNumMap; |
232 objNumMap.addObjectRecursively(infoDict, substitutes); | 252 if (objNumMap.addObject(infoDict)) { |
233 objNumMap.addObjectRecursively(docCatalog.get(), substitutes); | 253 infoDict->addResources(&objNumMap, substitutes); |
| 254 } |
| 255 if (objNumMap.addObject(docCatalog.get())) { |
| 256 docCatalog->addResources(&objNumMap, substitutes); |
| 257 } |
234 size_t baseOffset = stream->bytesWritten(); | 258 size_t baseOffset = stream->bytesWritten(); |
235 emit_pdf_header(stream); | 259 emit_pdf_header(stream); |
236 SkTDArray<int32_t> offsets; | 260 SkTDArray<int32_t> offsets; |
237 for (int i = 0; i < objNumMap.objects().count(); ++i) { | 261 for (int i = 0; i < objNumMap.objects().count(); ++i) { |
238 SkPDFObject* object = objNumMap.objects()[i]; | 262 SkPDFObject* object = objNumMap.objects()[i]; |
239 size_t offset = stream->bytesWritten(); | 263 size_t offset = stream->bytesWritten(); |
240 // This assert checks that size(pdf_header) > 0 and that | 264 // This assert checks that size(pdf_header) > 0 and that |
241 // the output stream correctly reports bytesWritten(). | 265 // the output stream correctly reports bytesWritten(). |
242 SkASSERT(offset > baseOffset); | 266 SkASSERT(offset > baseOffset); |
243 offsets.push(SkToS32(offset - baseOffset)); | 267 offsets.push(SkToS32(offset - baseOffset)); |
(...skipping 11 matching lines...) Expand all Loading... |
255 | 279 |
256 stream->writeText("xref\n0 "); | 280 stream->writeText("xref\n0 "); |
257 stream->writeDecAsText(objCount); | 281 stream->writeDecAsText(objCount); |
258 stream->writeText("\n0000000000 65535 f \n"); | 282 stream->writeText("\n0000000000 65535 f \n"); |
259 for (int i = 0; i < offsets.count(); i++) { | 283 for (int i = 0; i < offsets.count(); i++) { |
260 SkASSERT(offsets[i] > 0); | 284 SkASSERT(offsets[i] > 0); |
261 stream->writeBigDecAsText(offsets[i], 10); | 285 stream->writeBigDecAsText(offsets[i], 10); |
262 stream->writeText(" 00000 n \n"); | 286 stream->writeText(" 00000 n \n"); |
263 } | 287 } |
264 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, | 288 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, |
265 xRefFileOffset, infoDict.detach(), id.detach()); | 289 xRefFileOffset, infoDict); |
266 | 290 |
267 // The page tree has both child and parent pointers, so it creates a | 291 // The page tree has both child and parent pointers, so it creates a |
268 // reference cycle. We must clear that cycle to properly reclaim memory. | 292 // reference cycle. We must clear that cycle to properly reclaim memory. |
269 for (int i = 0; i < pageTree.count(); i++) { | 293 for (int i = 0; i < pageTree.count(); i++) { |
270 pageTree[i]->clear(); | 294 pageTree[i]->clear(); |
271 } | 295 } |
272 pageTree.safeUnrefAll(); | 296 pageTree.safeUnrefAll(); |
273 pages.unrefAll(); | 297 pages.unrefAll(); |
274 return true; | 298 return true; |
275 } | 299 } |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
373 fMetadata.fInfo = info; | 397 fMetadata.fInfo = info; |
374 fMetadata.fCreation.reset(clone(creationDate)); | 398 fMetadata.fCreation.reset(clone(creationDate)); |
375 fMetadata.fModified.reset(clone(modifiedDate)); | 399 fMetadata.fModified.reset(clone(modifiedDate)); |
376 } | 400 } |
377 | 401 |
378 private: | 402 private: |
379 SkPDFCanon fCanon; | 403 SkPDFCanon fCanon; |
380 SkTDArray<const SkPDFDevice*> fPageDevices; | 404 SkTDArray<const SkPDFDevice*> fPageDevices; |
381 SkAutoTUnref<SkCanvas> fCanvas; | 405 SkAutoTUnref<SkCanvas> fCanvas; |
382 SkScalar fRasterDpi; | 406 SkScalar fRasterDpi; |
383 SkPDFMetadata fMetadata; | 407 Metadata fMetadata; |
384 }; | 408 }; |
385 } // namespace | 409 } // namespace |
386 /////////////////////////////////////////////////////////////////////////////// | 410 /////////////////////////////////////////////////////////////////////////////// |
387 | 411 |
388 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { | 412 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { |
389 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; | 413 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; |
390 } | 414 } |
391 | 415 |
392 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { | 416 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { |
393 SkFILEWStream* stream = new SkFILEWStream(path); | 417 SkFILEWStream* stream = new SkFILEWStream(path); |
394 if (!stream->isValid()) { | 418 if (!stream->isValid()) { |
395 delete stream; | 419 delete stream; |
396 return nullptr; | 420 return nullptr; |
397 } | 421 } |
398 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; | 422 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; |
399 return new SkDocument_PDF(stream, delete_wstream, dpi); | 423 return new SkDocument_PDF(stream, delete_wstream, dpi); |
400 } | 424 } |
OLD | NEW |