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 if (info) { |
40 trailerDict.insertObjRef("Info", SkRef(info)); | 42 trailerDict.insertObjRef("Info", info); |
tomhudson
2015/10/09 15:31:37
You made info optional?
hal.canary
2015/10/09 19:13:27
Done.
| |
41 | 43 } |
44 if (id) { | |
45 trailerDict.insertObject("ID", id); | |
tomhudson
2015/10/09 15:31:37
Both info and id are noted "take ownership", but f
hal.canary
2015/10/09 19:13:27
a ref just means "don't inline this object"
| |
46 } | |
42 stream->writeText("trailer\n"); | 47 stream->writeText("trailer\n"); |
43 trailerDict.emitObject(stream, objNumMap, substitutes); | 48 trailerDict.emitObject(stream, objNumMap, substitutes); |
44 stream->writeText("\nstartxref\n"); | 49 stream->writeText("\nstartxref\n"); |
45 stream->writeBigDecAsText(xRefFileOffset); | 50 stream->writeBigDecAsText(xRefFileOffset); |
46 stream->writeText("\n%%EOF"); | 51 stream->writeText("\n%%EOF"); |
47 } | 52 } |
48 | 53 |
49 static void perform_font_subsetting( | 54 static void perform_font_subsetting( |
50 const SkTDArray<const SkPDFDevice*>& pageDevices, | 55 const SkTDArray<const SkPDFDevice*>& pageDevices, |
51 SkPDFSubstituteMap* substituteMap) { | 56 SkPDFSubstituteMap* substituteMap) { |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
155 nextRoundNodes.rewind(); | 160 nextRoundNodes.rewind(); |
156 treeCapacity *= kNodeSize; | 161 treeCapacity *= kNodeSize; |
157 } while (curNodes.count() > 1); | 162 } while (curNodes.count() > 1); |
158 | 163 |
159 pageTree->push(curNodes[0]); // Transfer reference. | 164 pageTree->push(curNodes[0]); // Transfer reference. |
160 if (rootNode) { | 165 if (rootNode) { |
161 *rootNode = curNodes[0]; | 166 *rootNode = curNodes[0]; |
162 } | 167 } |
163 } | 168 } |
164 | 169 |
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, | 170 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
207 const Metadata& metadata, | 171 const SkPDFMetadata& metadata, |
208 SkWStream* stream) { | 172 SkWStream* stream) { |
209 if (pageDevices.isEmpty()) { | 173 if (pageDevices.isEmpty()) { |
210 return false; | 174 return false; |
211 } | 175 } |
212 | 176 |
213 SkTDArray<SkPDFDict*> pages; | 177 SkTDArray<SkPDFDict*> pages; |
214 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); | 178 SkAutoTUnref<SkPDFDict> dests(new SkPDFDict); |
215 | 179 |
216 for (int i = 0; i < pageDevices.count(); i++) { | 180 for (int i = 0; i < pageDevices.count(); i++) { |
217 SkASSERT(pageDevices[i]); | 181 SkASSERT(pageDevices[i]); |
218 SkASSERT(i == 0 || | 182 SkASSERT(i == 0 || |
219 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); | 183 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); |
220 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i])); | 184 SkAutoTUnref<SkPDFDict> page(create_pdf_page(pageDevices[i])); |
221 pageDevices[i]->appendDestinations(dests, page.get()); | 185 pageDevices[i]->appendDestinations(dests, page.get()); |
222 pages.push(page.detach()); | 186 pages.push(page.detach()); |
223 } | 187 } |
224 | 188 |
225 SkTDArray<SkPDFDict*> pageTree; | |
226 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); | 189 SkAutoTUnref<SkPDFDict> docCatalog(new SkPDFDict("Catalog")); |
227 | 190 |
191 SkAutoTUnref<SkPDFObject> infoDict( | |
192 metadata.createDocumentInformationDict()); | |
193 | |
194 SkAutoTUnref<SkPDFObject> id, xmp; | |
195 #ifdef SK_PDF_GENERATE_PDFA | |
196 SkPDFMetadata::UUID uuid = metadata.uuid(); | |
197 id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid)); | |
tomhudson
2015/10/09 15:31:37
CreatePdfId(uuid, uuid) calls the first one "doc"
hal.canary
2015/10/09 15:49:11
// We use the same UUID for Document ID and Instan
tomhudson
2015/10/09 19:21:37
Acknowledged.
| |
198 xmp.reset(metadata.createXMPObject(uuid, uuid)); | |
199 docCatalog->insertObjRef("Metadata", xmp.detach()); | |
200 | |
201 SkAutoTUnref<SkPDFDict> outputIntent(new SkPDFDict("OutputIntent")); | |
202 outputIntent->insertName("S", "GTS_PDFA1"); | |
203 outputIntent->insertString("RegistryName", "http://www.color.org"); | |
204 outputIntent->insertString("OutputConditionIdentifier", | |
205 "sRGB IEC61966-2.1"); | |
tomhudson
2015/10/09 15:31:37
I thought we don't actually support sRGB yet (and
tomhudson
2015/10/09 15:31:37
Creating these intents even when we aren't writing
hal.canary
2015/10/09 15:49:11
HTML, CSS, and SVG all specify that they use sRGB.
hal.canary
2015/10/09 15:49:11
1. I want to save 139 bytes.
2. Nobody cares outsi
tomhudson
2015/10/09 19:21:37
Never mind, my question was wrong - this is inside
| |
206 SkAutoTUnref<SkPDFArray> intentArray(new SkPDFArray); | |
207 intentArray->appendObject(outputIntent.detach()); | |
208 docCatalog->insertObject("OutputIntents", intentArray.detach()); | |
209 #endif | |
210 | |
211 SkTDArray<SkPDFDict*> pageTree; | |
228 SkPDFDict* pageTreeRoot; | 212 SkPDFDict* pageTreeRoot; |
229 generate_page_tree(pages, &pageTree, &pageTreeRoot); | 213 generate_page_tree(pages, &pageTree, &pageTreeRoot); |
230 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); | 214 docCatalog->insertObjRef("Pages", SkRef(pageTreeRoot)); |
231 | 215 |
232 if (dests->size() > 0) { | 216 if (dests->size() > 0) { |
233 docCatalog->insertObjRef("Dests", dests.detach()); | 217 docCatalog->insertObjRef("Dests", dests.detach()); |
234 } | 218 } |
235 | 219 |
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. | 220 // Build font subsetting info before proceeding. |
246 SkPDFSubstituteMap substitutes; | 221 SkPDFSubstituteMap substitutes; |
247 perform_font_subsetting(pageDevices, &substitutes); | 222 perform_font_subsetting(pageDevices, &substitutes); |
248 | 223 |
249 SkAutoTUnref<SkPDFDict> infoDict( | |
250 create_document_information_dict(metadata)); | |
251 SkPDFObjNumMap objNumMap; | 224 SkPDFObjNumMap objNumMap; |
252 if (objNumMap.addObject(infoDict)) { | 225 objNumMap.addObjectRecursively(infoDict, substitutes); |
253 infoDict->addResources(&objNumMap, substitutes); | 226 objNumMap.addObjectRecursively(docCatalog.get(), substitutes); |
254 } | |
255 if (objNumMap.addObject(docCatalog.get())) { | |
256 docCatalog->addResources(&objNumMap, substitutes); | |
257 } | |
258 size_t baseOffset = stream->bytesWritten(); | 227 size_t baseOffset = stream->bytesWritten(); |
259 emit_pdf_header(stream); | 228 emit_pdf_header(stream); |
260 SkTDArray<int32_t> offsets; | 229 SkTDArray<int32_t> offsets; |
261 for (int i = 0; i < objNumMap.objects().count(); ++i) { | 230 for (int i = 0; i < objNumMap.objects().count(); ++i) { |
262 SkPDFObject* object = objNumMap.objects()[i]; | 231 SkPDFObject* object = objNumMap.objects()[i]; |
263 size_t offset = stream->bytesWritten(); | 232 size_t offset = stream->bytesWritten(); |
264 // This assert checks that size(pdf_header) > 0 and that | 233 // This assert checks that size(pdf_header) > 0 and that |
265 // the output stream correctly reports bytesWritten(). | 234 // the output stream correctly reports bytesWritten(). |
266 SkASSERT(offset > baseOffset); | 235 SkASSERT(offset > baseOffset); |
267 offsets.push(SkToS32(offset - baseOffset)); | 236 offsets.push(SkToS32(offset - baseOffset)); |
(...skipping 11 matching lines...) Expand all Loading... | |
279 | 248 |
280 stream->writeText("xref\n0 "); | 249 stream->writeText("xref\n0 "); |
281 stream->writeDecAsText(objCount); | 250 stream->writeDecAsText(objCount); |
282 stream->writeText("\n0000000000 65535 f \n"); | 251 stream->writeText("\n0000000000 65535 f \n"); |
283 for (int i = 0; i < offsets.count(); i++) { | 252 for (int i = 0; i < offsets.count(); i++) { |
284 SkASSERT(offsets[i] > 0); | 253 SkASSERT(offsets[i] > 0); |
285 stream->writeBigDecAsText(offsets[i], 10); | 254 stream->writeBigDecAsText(offsets[i], 10); |
286 stream->writeText(" 00000 n \n"); | 255 stream->writeText(" 00000 n \n"); |
287 } | 256 } |
288 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, | 257 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, |
289 xRefFileOffset, infoDict); | 258 xRefFileOffset, infoDict.detach(), id.detach()); |
290 | 259 |
291 // The page tree has both child and parent pointers, so it creates a | 260 // 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. | 261 // reference cycle. We must clear that cycle to properly reclaim memory. |
293 for (int i = 0; i < pageTree.count(); i++) { | 262 for (int i = 0; i < pageTree.count(); i++) { |
294 pageTree[i]->clear(); | 263 pageTree[i]->clear(); |
295 } | 264 } |
296 pageTree.safeUnrefAll(); | 265 pageTree.safeUnrefAll(); |
297 pages.unrefAll(); | 266 pages.unrefAll(); |
298 return true; | 267 return true; |
299 } | 268 } |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
397 fMetadata.fInfo = info; | 366 fMetadata.fInfo = info; |
398 fMetadata.fCreation.reset(clone(creationDate)); | 367 fMetadata.fCreation.reset(clone(creationDate)); |
399 fMetadata.fModified.reset(clone(modifiedDate)); | 368 fMetadata.fModified.reset(clone(modifiedDate)); |
400 } | 369 } |
401 | 370 |
402 private: | 371 private: |
403 SkPDFCanon fCanon; | 372 SkPDFCanon fCanon; |
404 SkTDArray<const SkPDFDevice*> fPageDevices; | 373 SkTDArray<const SkPDFDevice*> fPageDevices; |
405 SkAutoTUnref<SkCanvas> fCanvas; | 374 SkAutoTUnref<SkCanvas> fCanvas; |
406 SkScalar fRasterDpi; | 375 SkScalar fRasterDpi; |
407 Metadata fMetadata; | 376 SkPDFMetadata fMetadata; |
408 }; | 377 }; |
409 } // namespace | 378 } // namespace |
410 /////////////////////////////////////////////////////////////////////////////// | 379 /////////////////////////////////////////////////////////////////////////////// |
411 | 380 |
412 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { | 381 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { |
413 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; | 382 return stream ? new SkDocument_PDF(stream, nullptr, dpi) : nullptr; |
414 } | 383 } |
415 | 384 |
416 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { | 385 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { |
417 SkFILEWStream* stream = new SkFILEWStream(path); | 386 SkFILEWStream* stream = new SkFILEWStream(path); |
418 if (!stream->isValid()) { | 387 if (!stream->isValid()) { |
419 delete stream; | 388 delete stream; |
420 return nullptr; | 389 return nullptr; |
421 } | 390 } |
422 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; | 391 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; |
423 return new SkDocument_PDF(stream, delete_wstream, dpi); | 392 return new SkDocument_PDF(stream, delete_wstream, dpi); |
424 } | 393 } |
OLD | NEW |