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 "SkStream.h" | 15 #include "SkStream.h" |
15 | 16 |
| 17 class SkPDFDict; |
| 18 |
16 static void emit_pdf_header(SkWStream* stream) { | 19 static void emit_pdf_header(SkWStream* stream) { |
17 stream->writeText("%PDF-1.4\n%"); | 20 stream->writeText("%PDF-1.4\n%"); |
18 // The PDF spec recommends including a comment with four bytes, all | 21 // The PDF spec recommends including a comment with four bytes, all |
19 // 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. |
20 stream->write32(0xD3EBE9E1); | 23 stream->write32(0xD3EBE9E1); |
21 stream->writeText("\n"); | 24 stream->writeText("\n"); |
22 } | 25 } |
23 | 26 |
24 static void emit_pdf_footer(SkWStream* stream, | 27 static void emit_pdf_footer(SkWStream* stream, |
25 const SkPDFObjNumMap& objNumMap, | 28 const SkPDFObjNumMap& objNumMap, |
26 const SkPDFSubstituteMap& substitutes, | 29 const SkPDFSubstituteMap& substitutes, |
27 SkPDFObject* docCatalog, | 30 SkPDFObject* docCatalog, |
28 int64_t objCount, | 31 int64_t objCount, |
29 int32_t xRefFileOffset) { | 32 int32_t xRefFileOffset, |
| 33 SkPDFDict* info) { |
30 SkPDFDict trailerDict; | 34 SkPDFDict trailerDict; |
31 // TODO(vandebo): Linearized format will take a Prev entry too. | 35 // TODO(vandebo): Linearized format will take a Prev entry too. |
32 // TODO(vandebo): PDF/A requires an ID entry. | 36 // TODO(vandebo): PDF/A requires an ID entry. |
33 trailerDict.insertInt("Size", int(objCount)); | 37 trailerDict.insertInt("Size", int(objCount)); |
34 trailerDict.insertObjRef("Root", SkRef(docCatalog)); | 38 trailerDict.insertObjRef("Root", SkRef(docCatalog)); |
| 39 SkASSERT(info); |
| 40 trailerDict.insertObjRef("Info", SkRef(info)); |
35 | 41 |
36 stream->writeText("trailer\n"); | 42 stream->writeText("trailer\n"); |
37 trailerDict.emitObject(stream, objNumMap, substitutes); | 43 trailerDict.emitObject(stream, objNumMap, substitutes); |
38 stream->writeText("\nstartxref\n"); | 44 stream->writeText("\nstartxref\n"); |
39 stream->writeBigDecAsText(xRefFileOffset); | 45 stream->writeBigDecAsText(xRefFileOffset); |
40 stream->writeText("\n%%EOF"); | 46 stream->writeText("\n%%EOF"); |
41 } | 47 } |
42 | 48 |
43 static void perform_font_subsetting( | 49 static void perform_font_subsetting( |
44 const SkTDArray<const SkPDFDevice*>& pageDevices, | 50 const SkTDArray<const SkPDFDevice*>& pageDevices, |
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 nextRoundNodes.rewind(); | 155 nextRoundNodes.rewind(); |
150 treeCapacity *= kNodeSize; | 156 treeCapacity *= kNodeSize; |
151 } while (curNodes.count() > 1); | 157 } while (curNodes.count() > 1); |
152 | 158 |
153 pageTree->push(curNodes[0]); // Transfer reference. | 159 pageTree->push(curNodes[0]); // Transfer reference. |
154 if (rootNode) { | 160 if (rootNode) { |
155 *rootNode = curNodes[0]; | 161 *rootNode = curNodes[0]; |
156 } | 162 } |
157 } | 163 } |
158 | 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 |
159 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, | 206 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
| 207 const Metadata& metadata, |
160 SkWStream* stream) { | 208 SkWStream* stream) { |
161 if (pageDevices.isEmpty()) { | 209 if (pageDevices.isEmpty()) { |
162 return false; | 210 return false; |
163 } | 211 } |
164 | 212 |
165 SkTDArray<SkPDFDict*> pages; | 213 SkTDArray<SkPDFDict*> pages; |
166 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); | 214 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); |
167 | 215 |
168 for (int i = 0; i < pageDevices.count(); i++) { | 216 for (int i = 0; i < pageDevices.count(); i++) { |
169 SkASSERT(pageDevices[i]); | 217 SkASSERT(pageDevices[i]); |
(...skipping 21 matching lines...) Expand all Loading... |
191 outputIntent->insertString("OutputConditionIdentifier", "sRGB"); | 239 outputIntent->insertString("OutputConditionIdentifier", "sRGB"); |
192 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); | 240 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); |
193 intentArray->appendObject(SkRef(outputIntent.get())); | 241 intentArray->appendObject(SkRef(outputIntent.get())); |
194 docCatalog->insertObject("OutputIntent", intentArray.detach()); | 242 docCatalog->insertObject("OutputIntent", intentArray.detach()); |
195 */ | 243 */ |
196 | 244 |
197 // Build font subsetting info before proceeding. | 245 // Build font subsetting info before proceeding. |
198 SkPDFSubstituteMap substitutes; | 246 SkPDFSubstituteMap substitutes; |
199 perform_font_subsetting(pageDevices, &substitutes); | 247 perform_font_subsetting(pageDevices, &substitutes); |
200 | 248 |
| 249 SkAutoTUnref<SkPDFDict> infoDict( |
| 250 create_document_information_dict(metadata)); |
201 SkPDFObjNumMap objNumMap; | 251 SkPDFObjNumMap objNumMap; |
| 252 if (objNumMap.addObject(infoDict)) { |
| 253 infoDict->addResources(&objNumMap, substitutes); |
| 254 } |
202 if (objNumMap.addObject(docCatalog.get())) { | 255 if (objNumMap.addObject(docCatalog.get())) { |
203 docCatalog->addResources(&objNumMap, substitutes); | 256 docCatalog->addResources(&objNumMap, substitutes); |
204 } | 257 } |
205 size_t baseOffset = stream->bytesWritten(); | 258 size_t baseOffset = stream->bytesWritten(); |
206 emit_pdf_header(stream); | 259 emit_pdf_header(stream); |
207 SkTDArray<int32_t> offsets; | 260 SkTDArray<int32_t> offsets; |
208 for (int i = 0; i < objNumMap.objects().count(); ++i) { | 261 for (int i = 0; i < objNumMap.objects().count(); ++i) { |
209 SkPDFObject* object = objNumMap.objects()[i]; | 262 SkPDFObject* object = objNumMap.objects()[i]; |
210 size_t offset = stream->bytesWritten(); | 263 size_t offset = stream->bytesWritten(); |
211 // This assert checks that size(pdf_header) > 0 and that | 264 // This assert checks that size(pdf_header) > 0 and that |
(...skipping 14 matching lines...) Expand all Loading... |
226 | 279 |
227 stream->writeText("xref\n0 "); | 280 stream->writeText("xref\n0 "); |
228 stream->writeDecAsText(objCount); | 281 stream->writeDecAsText(objCount); |
229 stream->writeText("\n0000000000 65535 f \n"); | 282 stream->writeText("\n0000000000 65535 f \n"); |
230 for (int i = 0; i < offsets.count(); i++) { | 283 for (int i = 0; i < offsets.count(); i++) { |
231 SkASSERT(offsets[i] > 0); | 284 SkASSERT(offsets[i] > 0); |
232 stream->writeBigDecAsText(offsets[i], 10); | 285 stream->writeBigDecAsText(offsets[i], 10); |
233 stream->writeText(" 00000 n \n"); | 286 stream->writeText(" 00000 n \n"); |
234 } | 287 } |
235 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, | 288 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, |
236 xRefFileOffset); | 289 xRefFileOffset, infoDict); |
237 | 290 |
238 // 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 |
239 // reference cycle. We must clear that cycle to properly reclaim memory. | 292 // reference cycle. We must clear that cycle to properly reclaim memory. |
240 for (int i = 0; i < pageTree.count(); i++) { | 293 for (int i = 0; i < pageTree.count(); i++) { |
241 pageTree[i]->clear(); | 294 pageTree[i]->clear(); |
242 } | 295 } |
243 pageTree.safeUnrefAll(); | 296 pageTree.safeUnrefAll(); |
244 pages.unrefAll(); | 297 pages.unrefAll(); |
245 return true; | 298 return true; |
246 } | 299 } |
(...skipping 30 matching lines...) Expand all Loading... |
277 } | 330 } |
278 if (notSubsettableCount) { | 331 if (notSubsettableCount) { |
279 *notSubsettableCount = notSubsettable; | 332 *notSubsettableCount = notSubsettable; |
280 | 333 |
281 } | 334 } |
282 if (notEmbeddableCount) { | 335 if (notEmbeddableCount) { |
283 *notEmbeddableCount = notEmbeddable; | 336 *notEmbeddableCount = notEmbeddable; |
284 } | 337 } |
285 } | 338 } |
286 #endif | 339 #endif |
| 340 |
| 341 template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullp
tr; } |
287 //////////////////////////////////////////////////////////////////////////////// | 342 //////////////////////////////////////////////////////////////////////////////// |
288 | 343 |
289 namespace { | 344 namespace { |
290 class SkDocument_PDF : public SkDocument { | 345 class SkDocument_PDF : public SkDocument { |
291 public: | 346 public: |
292 SkDocument_PDF(SkWStream* stream, | 347 SkDocument_PDF(SkWStream* stream, |
293 void (*doneProc)(SkWStream*, bool), | 348 void (*doneProc)(SkWStream*, bool), |
294 SkScalar rasterDpi) | 349 SkScalar rasterDpi) |
295 : SkDocument(stream, doneProc) | 350 : SkDocument(stream, doneProc) |
296 , fRasterDpi(rasterDpi) {} | 351 , fRasterDpi(rasterDpi) {} |
(...skipping 21 matching lines...) Expand all Loading... |
318 | 373 |
319 void onEndPage() override { | 374 void onEndPage() override { |
320 SkASSERT(fCanvas.get()); | 375 SkASSERT(fCanvas.get()); |
321 fCanvas->flush(); | 376 fCanvas->flush(); |
322 fCanvas.reset(nullptr); | 377 fCanvas.reset(nullptr); |
323 } | 378 } |
324 | 379 |
325 bool onClose(SkWStream* stream) override { | 380 bool onClose(SkWStream* stream) override { |
326 SkASSERT(!fCanvas.get()); | 381 SkASSERT(!fCanvas.get()); |
327 | 382 |
328 bool success = emit_pdf_document(fPageDevices, stream); | 383 bool success = emit_pdf_document(fPageDevices, fMetadata, stream); |
329 fPageDevices.unrefAll(); | 384 fPageDevices.unrefAll(); |
330 fCanon.reset(); | 385 fCanon.reset(); |
331 return success; | 386 return success; |
332 } | 387 } |
333 | 388 |
334 void onAbort() override { | 389 void onAbort() override { |
335 fPageDevices.unrefAll(); | 390 fPageDevices.unrefAll(); |
336 fCanon.reset(); | 391 fCanon.reset(); |
337 } | 392 } |
338 | 393 |
| 394 void setMetadata(const SkTArray<SkDocument::Attribute>& info, |
| 395 const SkTime::DateTime* creationDate, |
| 396 const SkTime::DateTime* modifiedDate) override { |
| 397 fMetadata.fInfo = info; |
| 398 fMetadata.fCreation.reset(clone(creationDate)); |
| 399 fMetadata.fModified.reset(clone(modifiedDate)); |
| 400 } |
| 401 |
339 private: | 402 private: |
340 SkPDFCanon fCanon; | 403 SkPDFCanon fCanon; |
341 SkTDArray<const SkPDFDevice*> fPageDevices; | 404 SkTDArray<const SkPDFDevice*> fPageDevices; |
342 SkAutoTUnref<SkCanvas> fCanvas; | 405 SkAutoTUnref<SkCanvas> fCanvas; |
343 SkScalar fRasterDpi; | 406 SkScalar fRasterDpi; |
| 407 Metadata fMetadata; |
344 }; | 408 }; |
345 } // namespace | 409 } // namespace |
346 /////////////////////////////////////////////////////////////////////////////// | 410 /////////////////////////////////////////////////////////////////////////////// |
347 | 411 |
348 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { | 412 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { |
349 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; | 413 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; |
350 } | 414 } |
351 | 415 |
352 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { | 416 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { |
353 SkFILEWStream* stream = new SkFILEWStream(path); | 417 SkFILEWStream* stream = new SkFILEWStream(path); |
354 if (!stream->isValid()) { | 418 if (!stream->isValid()) { |
355 delete stream; | 419 delete stream; |
356 return nullptr; | 420 return nullptr; |
357 } | 421 } |
358 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; | 422 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; |
359 return new SkDocument_PDF(stream, delete_wstream, dpi); | 423 return new SkDocument_PDF(stream, delete_wstream, dpi); |
360 } | 424 } |
OLD | NEW |