| OLD | NEW |
| (Empty) |
| 1 | |
| 2 /* | |
| 3 * Copyright 2011 Google Inc. | |
| 4 * | |
| 5 * Use of this source code is governed by a BSD-style license that can be | |
| 6 * found in the LICENSE file. | |
| 7 */ | |
| 8 | |
| 9 | |
| 10 #include "SkPDFCatalog.h" | |
| 11 #include "SkPDFDevice.h" | |
| 12 #include "SkPDFDocument.h" | |
| 13 #include "SkPDFFont.h" | |
| 14 #include "SkPDFPage.h" | |
| 15 #include "SkPDFTypes.h" | |
| 16 #include "SkStream.h" | |
| 17 | |
| 18 | |
| 19 static void perform_font_subsetting(SkPDFCatalog* catalog, | |
| 20 const SkTDArray<SkPDFPage*>& pages) { | |
| 21 SkASSERT(catalog); | |
| 22 | |
| 23 SkPDFGlyphSetMap usage; | |
| 24 for (int i = 0; i < pages.count(); ++i) { | |
| 25 usage.merge(pages[i]->getFontGlyphUsage()); | |
| 26 } | |
| 27 SkPDFGlyphSetMap::F2BIter iterator(usage); | |
| 28 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); | |
| 29 while (entry) { | |
| 30 SkAutoTUnref<SkPDFFont> subsetFont( | |
| 31 entry->fFont->getFontSubset(entry->fGlyphSet)); | |
| 32 if (subsetFont) { | |
| 33 catalog->setSubstitute(entry->fFont, subsetFont.get()); | |
| 34 } | |
| 35 entry = iterator.next(); | |
| 36 } | |
| 37 } | |
| 38 | |
| 39 static void emit_pdf_header(SkWStream* stream) { | |
| 40 stream->writeText("%PDF-1.4\n%"); | |
| 41 // The PDF spec recommends including a comment with four bytes, all | |
| 42 // with their high bits set. This is "Skia" with the high bits set. | |
| 43 stream->write32(0xD3EBE9E1); | |
| 44 stream->writeText("\n"); | |
| 45 } | |
| 46 | |
| 47 static void emit_pdf_footer(SkWStream* stream, | |
| 48 SkPDFCatalog* catalog, | |
| 49 SkPDFObject* docCatalog, | |
| 50 int64_t objCount, | |
| 51 int32_t xRefFileOffset) { | |
| 52 SkPDFDict trailerDict; | |
| 53 // TODO(vandebo): Linearized format will take a Prev entry too. | |
| 54 // TODO(vandebo): PDF/A requires an ID entry. | |
| 55 trailerDict.insertInt("Size", int(objCount)); | |
| 56 trailerDict.insert("Root", new SkPDFObjRef(docCatalog))->unref(); | |
| 57 | |
| 58 stream->writeText("trailer\n"); | |
| 59 trailerDict.emitObject(stream, catalog); | |
| 60 stream->writeText("\nstartxref\n"); | |
| 61 stream->writeBigDecAsText(xRefFileOffset); | |
| 62 stream->writeText("\n%%EOF"); | |
| 63 } | |
| 64 | |
| 65 bool SkPDFDocument::EmitPDF(const SkTDArray<const SkPDFDevice*>& pageDevices, | |
| 66 SkWStream* stream) { | |
| 67 if (pageDevices.isEmpty()) { | |
| 68 return false; | |
| 69 } | |
| 70 | |
| 71 SkTDArray<SkPDFPage*> pages; | |
| 72 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); | |
| 73 | |
| 74 for (int i = 0; i < pageDevices.count(); i++) { | |
| 75 SkASSERT(pageDevices[i]); | |
| 76 SkASSERT(i == 0 || | |
| 77 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); | |
| 78 // Reference from new passed to pages. | |
| 79 SkAutoTUnref<SkPDFPage> page(SkNEW_ARGS(SkPDFPage, (pageDevices[i]))); | |
| 80 page->finalizePage(); | |
| 81 page->appendDestinations(dests); | |
| 82 pages.push(page.detach()); | |
| 83 } | |
| 84 SkPDFCatalog catalog; | |
| 85 | |
| 86 SkTDArray<SkPDFDict*> pageTree; | |
| 87 SkAutoTUnref<SkPDFDict> docCatalog(SkNEW_ARGS(SkPDFDict, ("Catalog"))); | |
| 88 | |
| 89 SkPDFDict* pageTreeRoot; | |
| 90 SkPDFPage::GeneratePageTree(pages, &pageTree, &pageTreeRoot); | |
| 91 | |
| 92 docCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); | |
| 93 | |
| 94 /* TODO(vandebo): output intent | |
| 95 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); | |
| 96 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); | |
| 97 outputIntent->insert("OutputConditionIdentifier", | |
| 98 new SkPDFString("sRGB"))->unref(); | |
| 99 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; | |
| 100 intentArray->append(outputIntent.get()); | |
| 101 docCatalog->insert("OutputIntent", intentArray.get()); | |
| 102 */ | |
| 103 | |
| 104 if (dests->size() > 0) { | |
| 105 docCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (dests.get()))) | |
| 106 ->unref(); | |
| 107 } | |
| 108 | |
| 109 // Build font subsetting info before proceeding. | |
| 110 perform_font_subsetting(&catalog, pages); | |
| 111 | |
| 112 SkTSet<SkPDFObject*> resourceSet; | |
| 113 if (resourceSet.add(docCatalog.get())) { | |
| 114 docCatalog->addResources(&resourceSet, &catalog); | |
| 115 } | |
| 116 for (int i = 0; i < resourceSet.count(); ++i) { | |
| 117 SkAssertResult(catalog.addObject(resourceSet[i])); | |
| 118 } | |
| 119 | |
| 120 size_t baseOffset = SkToOffT(stream->bytesWritten()); | |
| 121 emit_pdf_header(stream); | |
| 122 SkTDArray<int32_t> offsets; | |
| 123 for (int i = 0; i < resourceSet.count(); ++i) { | |
| 124 SkPDFObject* object = resourceSet[i]; | |
| 125 offsets.push(SkToS32(stream->bytesWritten() - baseOffset)); | |
| 126 SkASSERT(object == catalog.getSubstituteObject(object)); | |
| 127 SkASSERT(catalog.getObjectNumber(object) == i + 1); | |
| 128 stream->writeDecAsText(i + 1); | |
| 129 stream->writeText(" 0 obj\n"); // Generation number is always 0. | |
| 130 object->emitObject(stream, &catalog); | |
| 131 stream->writeText("\nendobj\n"); | |
| 132 } | |
| 133 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset); | |
| 134 | |
| 135 int32_t objCount = SkToS32(offsets.count() + 1); | |
| 136 | |
| 137 stream->writeText("xref\n0 "); | |
| 138 stream->writeDecAsText(objCount + 1); | |
| 139 stream->writeText("\n0000000000 65535 f \n"); | |
| 140 for (int i = 0; i < offsets.count(); i++) { | |
| 141 SkASSERT(offsets[i] > 0); | |
| 142 stream->writeBigDecAsText(offsets[i], 10); | |
| 143 stream->writeText(" 00000 n \n"); | |
| 144 } | |
| 145 emit_pdf_footer(stream, &catalog, docCatalog.get(), objCount, | |
| 146 xRefFileOffset); | |
| 147 | |
| 148 // The page tree has both child and parent pointers, so it creates a | |
| 149 // reference cycle. We must clear that cycle to properly reclaim memory. | |
| 150 for (int i = 0; i < pageTree.count(); i++) { | |
| 151 pageTree[i]->clear(); | |
| 152 } | |
| 153 pageTree.safeUnrefAll(); | |
| 154 pages.unrefAll(); | |
| 155 return true; | |
| 156 } | |
| 157 | |
| 158 // TODO(halcanary): expose notEmbeddableCount in SkDocument | |
| 159 void SkPDFDocument::GetCountOfFontTypes( | |
| 160 const SkTDArray<SkPDFDevice*>& pageDevices, | |
| 161 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], | |
| 162 int* notSubsettableCount, | |
| 163 int* notEmbeddableCount) { | |
| 164 sk_bzero(counts, sizeof(int) * | |
| 165 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); | |
| 166 SkTDArray<SkFontID> seenFonts; | |
| 167 int notSubsettable = 0; | |
| 168 int notEmbeddable = 0; | |
| 169 | |
| 170 for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) { | |
| 171 const SkTDArray<SkPDFFont*>& fontResources = | |
| 172 pageDevices[pageNumber]->getFontResources(); | |
| 173 for (int font = 0; font < fontResources.count(); font++) { | |
| 174 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); | |
| 175 if (seenFonts.find(fontID) == -1) { | |
| 176 counts[fontResources[font]->getType()]++; | |
| 177 seenFonts.push(fontID); | |
| 178 if (!fontResources[font]->canSubset()) { | |
| 179 notSubsettable++; | |
| 180 } | |
| 181 if (!fontResources[font]->canEmbed()) { | |
| 182 notEmbeddable++; | |
| 183 } | |
| 184 } | |
| 185 } | |
| 186 } | |
| 187 if (notSubsettableCount) { | |
| 188 *notSubsettableCount = notSubsettable; | |
| 189 | |
| 190 } | |
| 191 if (notEmbeddableCount) { | |
| 192 *notEmbeddableCount = notEmbeddable; | |
| 193 } | |
| 194 } | |
| OLD | NEW |