| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright 2011 Google Inc. | |
| 3 * | |
| 4 * Use of this source code is governed by a BSD-style license that can be | |
| 5 * found in the LICENSE file. | |
| 6 */ | |
| 7 | |
| 8 #include "SkDocument.h" | |
| 9 #include "SkPDFCanon.h" | |
| 10 #include "SkPDFDevice.h" | |
| 11 #include "SkPDFFont.h" | |
| 12 #include "SkPDFStream.h" | |
| 13 #include "SkPDFTypes.h" | |
| 14 #include "SkPDFUtils.h" | |
| 15 #include "SkStream.h" | |
| 16 #include "SkPDFMetadata.h" | |
| 17 | |
| 18 class SkPDFDict; | |
| 19 | |
| 20 static void emit_pdf_header(SkWStream* stream) { | |
| 21 stream->writeText("%PDF-1.4\n%"); | |
| 22 // 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. | |
| 24 stream->write32(0xD3EBE9E1); | |
| 25 stream->writeText("\n"); | |
| 26 } | |
| 27 | |
| 28 static void emit_pdf_footer(SkWStream* stream, | |
| 29 const SkPDFObjNumMap& objNumMap, | |
| 30 const SkPDFSubstituteMap& substitutes, | |
| 31 SkPDFObject* docCatalog, | |
| 32 int64_t objCount, | |
| 33 int32_t xRefFileOffset, | |
| 34 sk_sp<SkPDFObject> info, | |
| 35 sk_sp<SkPDFObject> id) { | |
| 36 SkPDFDict trailerDict; | |
| 37 // TODO(http://crbug.com/80908): Linearized format will take a | |
| 38 // Prev entry too. | |
| 39 trailerDict.insertInt("Size", int(objCount)); | |
| 40 trailerDict.insertObjRef("Root", sk_ref_sp(docCatalog)); | |
| 41 SkASSERT(info); | |
| 42 trailerDict.insertObjRef("Info", std::move(info)); | |
| 43 if (id) { | |
| 44 trailerDict.insertObject("ID", std::move(id)); | |
| 45 } | |
| 46 stream->writeText("trailer\n"); | |
| 47 trailerDict.emitObject(stream, objNumMap, substitutes); | |
| 48 stream->writeText("\nstartxref\n"); | |
| 49 stream->writeBigDecAsText(xRefFileOffset); | |
| 50 stream->writeText("\n%%EOF"); | |
| 51 } | |
| 52 | |
| 53 static void perform_font_subsetting( | |
| 54 const SkTArray<sk_sp<const SkPDFDevice>>& pageDevices, | |
| 55 SkPDFSubstituteMap* substituteMap) { | |
| 56 SkASSERT(substituteMap); | |
| 57 | |
| 58 SkPDFGlyphSetMap usage; | |
| 59 for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) { | |
| 60 usage.merge(pageDevice->getFontGlyphUsage()); | |
| 61 } | |
| 62 SkPDFGlyphSetMap::F2BIter iterator(usage); | |
| 63 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); | |
| 64 while (entry) { | |
| 65 sk_sp<SkPDFFont> subsetFont( | |
| 66 entry->fFont->getFontSubset(entry->fGlyphSet)); | |
| 67 if (subsetFont) { | |
| 68 substituteMap->setSubstitute(entry->fFont, subsetFont.get()); | |
| 69 } | |
| 70 entry = iterator.next(); | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 static sk_sp<SkPDFDict> create_pdf_page(const SkPDFDevice* pageDevice) { | |
| 75 auto page = sk_make_sp<SkPDFDict>("Page"); | |
| 76 page->insertObject("Resources", pageDevice->makeResourceDict()); | |
| 77 page->insertObject("MediaBox", pageDevice->copyMediaBox()); | |
| 78 auto annotations = sk_make_sp<SkPDFArray>(); | |
| 79 pageDevice->appendAnnotations(annotations.get()); | |
| 80 if (annotations->size() > 0) { | |
| 81 page->insertObject("Annots", std::move(annotations)); | |
| 82 } | |
| 83 auto content = pageDevice->content(); | |
| 84 page->insertObjRef("Contents", sk_make_sp<SkPDFStream>(content.get())); | |
| 85 return page; | |
| 86 } | |
| 87 | |
| 88 // return root node. | |
| 89 static sk_sp<SkPDFDict> generate_page_tree( | |
| 90 const SkTDArray<SkPDFDict*>& pages, | |
| 91 SkTDArray<SkPDFDict*>* pageTree) { | |
| 92 // PDF wants a tree describing all the pages in the document. We arbitrary | |
| 93 // choose 8 (kNodeSize) as the number of allowed children. The internal | |
| 94 // nodes have type "Pages" with an array of children, a parent pointer, and | |
| 95 // the number of leaves below the node as "Count." The leaves are passed | |
| 96 // into the method, have type "Page" and need a parent pointer. This method | |
| 97 // builds the tree bottom up, skipping internal nodes that would have only | |
| 98 // one child. | |
| 99 static const int kNodeSize = 8; | |
| 100 | |
| 101 // curNodes takes a reference to its items, which it passes to pageTree. | |
| 102 SkTDArray<SkPDFDict*> curNodes; | |
| 103 curNodes.setReserve(pages.count()); | |
| 104 for (int i = 0; i < pages.count(); i++) { | |
| 105 SkSafeRef(pages[i]); | |
| 106 curNodes.push(pages[i]); | |
| 107 } | |
| 108 | |
| 109 // nextRoundNodes passes its references to nodes on to curNodes. | |
| 110 SkTDArray<SkPDFDict*> nextRoundNodes; | |
| 111 nextRoundNodes.setReserve((pages.count() + kNodeSize - 1)/kNodeSize); | |
| 112 | |
| 113 int treeCapacity = kNodeSize; | |
| 114 do { | |
| 115 for (int i = 0; i < curNodes.count(); ) { | |
| 116 if (i > 0 && i + 1 == curNodes.count()) { | |
| 117 nextRoundNodes.push(curNodes[i]); | |
| 118 break; | |
| 119 } | |
| 120 | |
| 121 auto newNode = sk_make_sp<SkPDFDict>("Pages"); | |
| 122 auto kids = sk_make_sp<SkPDFArray>(); | |
| 123 kids->reserve(kNodeSize); | |
| 124 | |
| 125 int count = 0; | |
| 126 for (; i < curNodes.count() && count < kNodeSize; i++, count++) { | |
| 127 curNodes[i]->insertObjRef("Parent", newNode); | |
| 128 kids->appendObjRef(sk_ref_sp(curNodes[i])); | |
| 129 | |
| 130 // TODO(vandebo): put the objects in strict access order. | |
| 131 // Probably doesn't matter because they are so small. | |
| 132 if (curNodes[i] != pages[0]) { | |
| 133 pageTree->push(curNodes[i]); // Transfer reference. | |
| 134 } else { | |
| 135 SkSafeUnref(curNodes[i]); | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 // treeCapacity is the number of leaf nodes possible for the | |
| 140 // current set of subtrees being generated. (i.e. 8, 64, 512, ...). | |
| 141 // It is hard to count the number of leaf nodes in the current | |
| 142 // subtree. However, by construction, we know that unless it's the | |
| 143 // last subtree for the current depth, the leaf count will be | |
| 144 // treeCapacity, otherwise it's what ever is left over after | |
| 145 // consuming treeCapacity chunks. | |
| 146 int pageCount = treeCapacity; | |
| 147 if (i == curNodes.count()) { | |
| 148 pageCount = ((pages.count() - 1) % treeCapacity) + 1; | |
| 149 } | |
| 150 newNode->insertInt("Count", pageCount); | |
| 151 newNode->insertObject("Kids", std::move(kids)); | |
| 152 nextRoundNodes.push(newNode.release()); // Transfer reference. | |
| 153 } | |
| 154 | |
| 155 curNodes = nextRoundNodes; | |
| 156 nextRoundNodes.rewind(); | |
| 157 treeCapacity *= kNodeSize; | |
| 158 } while (curNodes.count() > 1); | |
| 159 | |
| 160 pageTree->push(curNodes[0]); // Transfer reference. | |
| 161 return sk_ref_sp(curNodes[0]); | |
| 162 } | |
| 163 | |
| 164 static bool emit_pdf_document(const SkTArray<sk_sp<const SkPDFDevice>>& pageDevi
ces, | |
| 165 const SkPDFMetadata& metadata, | |
| 166 SkWStream* stream) { | |
| 167 if (pageDevices.empty()) { | |
| 168 return false; | |
| 169 } | |
| 170 | |
| 171 SkTDArray<SkPDFDict*> pages; // TODO: SkTArray<sk_sp<SkPDFDict>> | |
| 172 auto dests = sk_make_sp<SkPDFDict>(); | |
| 173 | |
| 174 for (const sk_sp<const SkPDFDevice>& pageDevice : pageDevices) { | |
| 175 SkASSERT(pageDevice); | |
| 176 SkASSERT(pageDevices[0]->getCanon() == pageDevice->getCanon()); | |
| 177 sk_sp<SkPDFDict> page(create_pdf_page(pageDevice.get())); | |
| 178 pageDevice->appendDestinations(dests.get(), page.get()); | |
| 179 pages.push(page.release()); | |
| 180 } | |
| 181 | |
| 182 auto docCatalog = sk_make_sp<SkPDFDict>("Catalog"); | |
| 183 | |
| 184 sk_sp<SkPDFObject> infoDict(metadata.createDocumentInformationDict()); | |
| 185 | |
| 186 sk_sp<SkPDFObject> id, xmp; | |
| 187 #ifdef SK_PDF_GENERATE_PDFA | |
| 188 SkPDFMetadata::UUID uuid = metadata.uuid(); | |
| 189 // We use the same UUID for Document ID and Instance ID since this | |
| 190 // is the first revision of this document (and Skia does not | |
| 191 // support revising existing PDF documents). | |
| 192 // If we are not in PDF/A mode, don't use a UUID since testing | |
| 193 // works best with reproducible outputs. | |
| 194 id.reset(SkPDFMetadata::CreatePdfId(uuid, uuid)); | |
| 195 xmp.reset(metadata.createXMPObject(uuid, uuid)); | |
| 196 docCatalog->insertObjRef("Metadata", std::move(xmp)); | |
| 197 | |
| 198 // sRGB is specified by HTML, CSS, and SVG. | |
| 199 auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent"); | |
| 200 outputIntent->insertName("S", "GTS_PDFA1"); | |
| 201 outputIntent->insertString("RegistryName", "http://www.color.org"); | |
| 202 outputIntent->insertString("OutputConditionIdentifier", | |
| 203 "sRGB IEC61966-2.1"); | |
| 204 auto intentArray = sk_make_sp<SkPDFArray>(); | |
| 205 intentArray->appendObject(std::move(outputIntent)); | |
| 206 // Don't specify OutputIntents if we are not in PDF/A mode since | |
| 207 // no one has ever asked for this feature. | |
| 208 docCatalog->insertObject("OutputIntents", std::move(intentArray)); | |
| 209 #endif | |
| 210 | |
| 211 SkTDArray<SkPDFDict*> pageTree; | |
| 212 docCatalog->insertObjRef("Pages", generate_page_tree(pages, &pageTree)); | |
| 213 | |
| 214 if (dests->size() > 0) { | |
| 215 docCatalog->insertObjRef("Dests", std::move(dests)); | |
| 216 } | |
| 217 | |
| 218 // Build font subsetting info before proceeding. | |
| 219 SkPDFSubstituteMap substitutes; | |
| 220 perform_font_subsetting(pageDevices, &substitutes); | |
| 221 | |
| 222 SkPDFObjNumMap objNumMap; | |
| 223 objNumMap.addObjectRecursively(infoDict.get(), substitutes); | |
| 224 objNumMap.addObjectRecursively(docCatalog.get(), substitutes); | |
| 225 size_t baseOffset = stream->bytesWritten(); | |
| 226 emit_pdf_header(stream); | |
| 227 SkTDArray<int32_t> offsets; | |
| 228 for (int i = 0; i < objNumMap.objects().count(); ++i) { | |
| 229 SkPDFObject* object = objNumMap.objects()[i]; | |
| 230 size_t offset = stream->bytesWritten(); | |
| 231 // This assert checks that size(pdf_header) > 0 and that | |
| 232 // the output stream correctly reports bytesWritten(). | |
| 233 SkASSERT(offset > baseOffset); | |
| 234 offsets.push(SkToS32(offset - baseOffset)); | |
| 235 SkASSERT(object == substitutes.getSubstitute(object)); | |
| 236 SkASSERT(objNumMap.getObjectNumber(object) == i + 1); | |
| 237 stream->writeDecAsText(i + 1); | |
| 238 stream->writeText(" 0 obj\n"); // Generation number is always 0. | |
| 239 object->emitObject(stream, objNumMap, substitutes); | |
| 240 stream->writeText("\nendobj\n"); | |
| 241 } | |
| 242 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset); | |
| 243 | |
| 244 // Include the zeroth object in the count. | |
| 245 int32_t objCount = SkToS32(offsets.count() + 1); | |
| 246 | |
| 247 stream->writeText("xref\n0 "); | |
| 248 stream->writeDecAsText(objCount); | |
| 249 stream->writeText("\n0000000000 65535 f \n"); | |
| 250 for (int i = 0; i < offsets.count(); i++) { | |
| 251 SkASSERT(offsets[i] > 0); | |
| 252 stream->writeBigDecAsText(offsets[i], 10); | |
| 253 stream->writeText(" 00000 n \n"); | |
| 254 } | |
| 255 emit_pdf_footer(stream, objNumMap, substitutes, docCatalog.get(), objCount, | |
| 256 xRefFileOffset, std::move(infoDict), std::move(id)); | |
| 257 | |
| 258 // The page tree has both child and parent pointers, so it creates a | |
| 259 // reference cycle. We must clear that cycle to properly reclaim memory. | |
| 260 for (int i = 0; i < pageTree.count(); i++) { | |
| 261 pageTree[i]->clear(); | |
| 262 } | |
| 263 pageTree.safeUnrefAll(); | |
| 264 pages.unrefAll(); | |
| 265 return true; | |
| 266 } | |
| 267 | |
| 268 #if 0 | |
| 269 // TODO(halcanary): expose notEmbeddableCount in SkDocument | |
| 270 void GetCountOfFontTypes( | |
| 271 const SkTDArray<SkPDFDevice*>& pageDevices, | |
| 272 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], | |
| 273 int* notSubsettableCount, | |
| 274 int* notEmbeddableCount) { | |
| 275 sk_bzero(counts, sizeof(int) * | |
| 276 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); | |
| 277 SkTDArray<SkFontID> seenFonts; | |
| 278 int notSubsettable = 0; | |
| 279 int notEmbeddable = 0; | |
| 280 | |
| 281 for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) { | |
| 282 const SkTDArray<SkPDFFont*>& fontResources = | |
| 283 pageDevices[pageNumber]->getFontResources(); | |
| 284 for (int font = 0; font < fontResources.count(); font++) { | |
| 285 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); | |
| 286 if (seenFonts.find(fontID) == -1) { | |
| 287 counts[fontResources[font]->getType()]++; | |
| 288 seenFonts.push(fontID); | |
| 289 if (!fontResources[font]->canSubset()) { | |
| 290 notSubsettable++; | |
| 291 } | |
| 292 if (!fontResources[font]->canEmbed()) { | |
| 293 notEmbeddable++; | |
| 294 } | |
| 295 } | |
| 296 } | |
| 297 } | |
| 298 if (notSubsettableCount) { | |
| 299 *notSubsettableCount = notSubsettable; | |
| 300 | |
| 301 } | |
| 302 if (notEmbeddableCount) { | |
| 303 *notEmbeddableCount = notEmbeddable; | |
| 304 } | |
| 305 } | |
| 306 #endif | |
| 307 | |
| 308 template <typename T> static T* clone(const T* o) { return o ? new T(*o) : nullp
tr; } | |
| 309 //////////////////////////////////////////////////////////////////////////////// | |
| 310 | |
| 311 namespace { | |
| 312 class SkDocument_PDF : public SkDocument { | |
| 313 public: | |
| 314 SkDocument_PDF(SkWStream* stream, | |
| 315 void (*doneProc)(SkWStream*, bool), | |
| 316 SkScalar rasterDpi, | |
| 317 SkPixelSerializer* jpegEncoder) | |
| 318 : SkDocument(stream, doneProc) | |
| 319 , fRasterDpi(rasterDpi) { | |
| 320 fCanon.setPixelSerializer(SkSafeRef(jpegEncoder)); | |
| 321 } | |
| 322 | |
| 323 virtual ~SkDocument_PDF() { | |
| 324 // subclasses must call close() in their destructors | |
| 325 this->close(); | |
| 326 } | |
| 327 | |
| 328 protected: | |
| 329 SkCanvas* onBeginPage(SkScalar width, SkScalar height, | |
| 330 const SkRect& trimBox) override { | |
| 331 SkASSERT(!fCanvas.get()); | |
| 332 | |
| 333 SkISize pageSize = SkISize::Make( | |
| 334 SkScalarRoundToInt(width), SkScalarRoundToInt(height)); | |
| 335 sk_sp<SkPDFDevice> device( | |
| 336 SkPDFDevice::Create(pageSize, fRasterDpi, &fCanon)); | |
| 337 fCanvas.reset(new SkCanvas(device.get())); | |
| 338 fPageDevices.push_back(std::move(device)); | |
| 339 fCanvas->clipRect(trimBox); | |
| 340 fCanvas->translate(trimBox.x(), trimBox.y()); | |
| 341 return fCanvas.get(); | |
| 342 } | |
| 343 | |
| 344 void onEndPage() override { | |
| 345 SkASSERT(fCanvas.get()); | |
| 346 fCanvas->flush(); | |
| 347 fCanvas.reset(nullptr); | |
| 348 } | |
| 349 | |
| 350 bool onClose(SkWStream* stream) override { | |
| 351 SkASSERT(!fCanvas.get()); | |
| 352 | |
| 353 bool success = emit_pdf_document(fPageDevices, fMetadata, stream); | |
| 354 fPageDevices.reset(); | |
| 355 fCanon.reset(); | |
| 356 return success; | |
| 357 } | |
| 358 | |
| 359 void onAbort() override { | |
| 360 fPageDevices.reset(); | |
| 361 fCanon.reset(); | |
| 362 } | |
| 363 | |
| 364 void setMetadata(const SkDocument::Attribute info[], | |
| 365 int infoCount, | |
| 366 const SkTime::DateTime* creationDate, | |
| 367 const SkTime::DateTime* modifiedDate) override { | |
| 368 fMetadata.fInfo.reset(info, infoCount); | |
| 369 fMetadata.fCreation.reset(clone(creationDate)); | |
| 370 fMetadata.fModified.reset(clone(modifiedDate)); | |
| 371 } | |
| 372 | |
| 373 private: | |
| 374 SkPDFCanon fCanon; | |
| 375 SkTArray<sk_sp<const SkPDFDevice>> fPageDevices; | |
| 376 sk_sp<SkCanvas> fCanvas; | |
| 377 SkScalar fRasterDpi; | |
| 378 SkPDFMetadata fMetadata; | |
| 379 }; | |
| 380 } // namespace | |
| 381 /////////////////////////////////////////////////////////////////////////////// | |
| 382 | |
| 383 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { | |
| 384 return stream ? new SkDocument_PDF(stream, nullptr, dpi, nullptr) : nullptr; | |
| 385 } | |
| 386 | |
| 387 SkDocument* SkDocument::CreatePDF(SkWStream* stream, | |
| 388 SkScalar dpi, | |
| 389 SkPixelSerializer* jpegEncoder) { | |
| 390 return stream | |
| 391 ? new SkDocument_PDF(stream, nullptr, dpi, jpegEncoder) | |
| 392 : nullptr; | |
| 393 } | |
| 394 | |
| 395 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { | |
| 396 SkFILEWStream* stream = new SkFILEWStream(path); | |
| 397 if (!stream->isValid()) { | |
| 398 delete stream; | |
| 399 return nullptr; | |
| 400 } | |
| 401 auto delete_wstream = [](SkWStream* stream, bool) { delete stream; }; | |
| 402 return new SkDocument_PDF(stream, delete_wstream, dpi, nullptr); | |
| 403 } | |
| OLD | NEW |