| 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 |