OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2013 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 "SkPDFDocument.h" | 10 #include "SkPDFCatalog.h" |
11 #include "SkPDFDevice.h" | 11 #include "SkPDFDevice.h" |
| 12 #include "SkPDFFont.h" |
| 13 #include "SkPDFPage.h" |
| 14 #include "SkPDFTypes.h" |
| 15 #include "SkStream.h" |
| 16 |
| 17 static void emit_pdf_header(SkWStream* stream) { |
| 18 stream->writeText("%PDF-1.4\n%"); |
| 19 // The PDF spec recommends including a comment with four bytes, all |
| 20 // with their high bits set. This is "Skia" with the high bits set. |
| 21 stream->write32(0xD3EBE9E1); |
| 22 stream->writeText("\n"); |
| 23 } |
| 24 |
| 25 static void emit_pdf_footer(SkWStream* stream, |
| 26 SkPDFCatalog* catalog, |
| 27 SkPDFObject* docCatalog, |
| 28 int64_t objCount, |
| 29 int32_t xRefFileOffset) { |
| 30 SkPDFDict trailerDict; |
| 31 // TODO(vandebo): Linearized format will take a Prev entry too. |
| 32 // TODO(vandebo): PDF/A requires an ID entry. |
| 33 trailerDict.insertInt("Size", int(objCount)); |
| 34 trailerDict.insert("Root", new SkPDFObjRef(docCatalog))->unref(); |
| 35 |
| 36 stream->writeText("trailer\n"); |
| 37 trailerDict.emitObject(stream, catalog); |
| 38 stream->writeText("\nstartxref\n"); |
| 39 stream->writeBigDecAsText(xRefFileOffset); |
| 40 stream->writeText("\n%%EOF"); |
| 41 } |
| 42 |
| 43 static void perform_font_subsetting(SkPDFCatalog* catalog, |
| 44 const SkTDArray<SkPDFPage*>& pages) { |
| 45 SkASSERT(catalog); |
| 46 |
| 47 SkPDFGlyphSetMap usage; |
| 48 for (int i = 0; i < pages.count(); ++i) { |
| 49 usage.merge(pages[i]->getFontGlyphUsage()); |
| 50 } |
| 51 SkPDFGlyphSetMap::F2BIter iterator(usage); |
| 52 const SkPDFGlyphSetMap::FontGlyphSetPair* entry = iterator.next(); |
| 53 while (entry) { |
| 54 SkAutoTUnref<SkPDFFont> subsetFont( |
| 55 entry->fFont->getFontSubset(entry->fGlyphSet)); |
| 56 if (subsetFont) { |
| 57 catalog->setSubstitute(entry->fFont, subsetFont.get()); |
| 58 } |
| 59 entry = iterator.next(); |
| 60 } |
| 61 } |
| 62 |
| 63 static bool emit_pdf_document(const SkTDArray<const SkPDFDevice*>& pageDevices, |
| 64 SkWStream* stream) { |
| 65 if (pageDevices.isEmpty()) { |
| 66 return false; |
| 67 } |
| 68 |
| 69 SkTDArray<SkPDFPage*> pages; |
| 70 SkAutoTUnref<SkPDFDict> dests(SkNEW(SkPDFDict)); |
| 71 |
| 72 for (int i = 0; i < pageDevices.count(); i++) { |
| 73 SkASSERT(pageDevices[i]); |
| 74 SkASSERT(i == 0 || |
| 75 pageDevices[i - 1]->getCanon() == pageDevices[i]->getCanon()); |
| 76 // Reference from new passed to pages. |
| 77 SkAutoTUnref<SkPDFPage> page(SkNEW_ARGS(SkPDFPage, (pageDevices[i]))); |
| 78 page->finalizePage(); |
| 79 page->appendDestinations(dests); |
| 80 pages.push(page.detach()); |
| 81 } |
| 82 SkPDFCatalog catalog; |
| 83 |
| 84 SkTDArray<SkPDFDict*> pageTree; |
| 85 SkAutoTUnref<SkPDFDict> docCatalog(SkNEW_ARGS(SkPDFDict, ("Catalog"))); |
| 86 |
| 87 SkPDFDict* pageTreeRoot; |
| 88 SkPDFPage::GeneratePageTree(pages, &pageTree, &pageTreeRoot); |
| 89 |
| 90 docCatalog->insert("Pages", new SkPDFObjRef(pageTreeRoot))->unref(); |
| 91 |
| 92 /* TODO(vandebo): output intent |
| 93 SkAutoTUnref<SkPDFDict> outputIntent = new SkPDFDict("OutputIntent"); |
| 94 outputIntent->insert("S", new SkPDFName("GTS_PDFA1"))->unref(); |
| 95 outputIntent->insert("OutputConditionIdentifier", |
| 96 new SkPDFString("sRGB"))->unref(); |
| 97 SkAutoTUnref<SkPDFArray> intentArray = new SkPDFArray; |
| 98 intentArray->append(outputIntent.get()); |
| 99 docCatalog->insert("OutputIntent", intentArray.get()); |
| 100 */ |
| 101 |
| 102 if (dests->size() > 0) { |
| 103 docCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (dests.get()))) |
| 104 ->unref(); |
| 105 } |
| 106 |
| 107 // Build font subsetting info before proceeding. |
| 108 perform_font_subsetting(&catalog, pages); |
| 109 |
| 110 SkTSet<SkPDFObject*> resourceSet; |
| 111 if (resourceSet.add(docCatalog.get())) { |
| 112 docCatalog->addResources(&resourceSet, &catalog); |
| 113 } |
| 114 for (int i = 0; i < resourceSet.count(); ++i) { |
| 115 SkAssertResult(catalog.addObject(resourceSet[i])); |
| 116 } |
| 117 |
| 118 size_t baseOffset = SkToOffT(stream->bytesWritten()); |
| 119 emit_pdf_header(stream); |
| 120 SkTDArray<int32_t> offsets; |
| 121 for (int i = 0; i < resourceSet.count(); ++i) { |
| 122 SkPDFObject* object = resourceSet[i]; |
| 123 offsets.push(SkToS32(stream->bytesWritten() - baseOffset)); |
| 124 SkASSERT(object == catalog.getSubstituteObject(object)); |
| 125 SkASSERT(catalog.getObjectNumber(object) == i + 1); |
| 126 stream->writeDecAsText(i + 1); |
| 127 stream->writeText(" 0 obj\n"); // Generation number is always 0. |
| 128 object->emitObject(stream, &catalog); |
| 129 stream->writeText("\nendobj\n"); |
| 130 } |
| 131 int32_t xRefFileOffset = SkToS32(stream->bytesWritten() - baseOffset); |
| 132 |
| 133 int32_t objCount = SkToS32(offsets.count() + 1); |
| 134 |
| 135 stream->writeText("xref\n0 "); |
| 136 stream->writeDecAsText(objCount + 1); |
| 137 stream->writeText("\n0000000000 65535 f \n"); |
| 138 for (int i = 0; i < offsets.count(); i++) { |
| 139 SkASSERT(offsets[i] > 0); |
| 140 stream->writeBigDecAsText(offsets[i], 10); |
| 141 stream->writeText(" 00000 n \n"); |
| 142 } |
| 143 emit_pdf_footer(stream, &catalog, docCatalog.get(), objCount, |
| 144 xRefFileOffset); |
| 145 |
| 146 // The page tree has both child and parent pointers, so it creates a |
| 147 // reference cycle. We must clear that cycle to properly reclaim memory. |
| 148 for (int i = 0; i < pageTree.count(); i++) { |
| 149 pageTree[i]->clear(); |
| 150 } |
| 151 pageTree.safeUnrefAll(); |
| 152 pages.unrefAll(); |
| 153 return true; |
| 154 } |
| 155 |
| 156 #if 0 |
| 157 // TODO(halcanary): expose notEmbeddableCount in SkDocument |
| 158 void GetCountOfFontTypes( |
| 159 const SkTDArray<SkPDFDevice*>& pageDevices, |
| 160 int counts[SkAdvancedTypefaceMetrics::kOther_Font + 1], |
| 161 int* notSubsettableCount, |
| 162 int* notEmbeddableCount) { |
| 163 sk_bzero(counts, sizeof(int) * |
| 164 (SkAdvancedTypefaceMetrics::kOther_Font + 1)); |
| 165 SkTDArray<SkFontID> seenFonts; |
| 166 int notSubsettable = 0; |
| 167 int notEmbeddable = 0; |
| 168 |
| 169 for (int pageNumber = 0; pageNumber < pageDevices.count(); pageNumber++) { |
| 170 const SkTDArray<SkPDFFont*>& fontResources = |
| 171 pageDevices[pageNumber]->getFontResources(); |
| 172 for (int font = 0; font < fontResources.count(); font++) { |
| 173 SkFontID fontID = fontResources[font]->typeface()->uniqueID(); |
| 174 if (seenFonts.find(fontID) == -1) { |
| 175 counts[fontResources[font]->getType()]++; |
| 176 seenFonts.push(fontID); |
| 177 if (!fontResources[font]->canSubset()) { |
| 178 notSubsettable++; |
| 179 } |
| 180 if (!fontResources[font]->canEmbed()) { |
| 181 notEmbeddable++; |
| 182 } |
| 183 } |
| 184 } |
| 185 } |
| 186 if (notSubsettableCount) { |
| 187 *notSubsettableCount = notSubsettable; |
| 188 |
| 189 } |
| 190 if (notEmbeddableCount) { |
| 191 *notEmbeddableCount = notEmbeddable; |
| 192 } |
| 193 } |
| 194 #endif |
| 195 //////////////////////////////////////////////////////////////////////////////// |
12 | 196 |
13 namespace { | 197 namespace { |
14 class SkDocument_PDF : public SkDocument { | 198 class SkDocument_PDF : public SkDocument { |
15 public: | 199 public: |
16 SkDocument_PDF(SkWStream* stream, | 200 SkDocument_PDF(SkWStream* stream, |
17 void (*doneProc)(SkWStream*, bool), | 201 void (*doneProc)(SkWStream*, bool), |
18 SkScalar rasterDpi) | 202 SkScalar rasterDpi) |
19 : SkDocument(stream, doneProc) | 203 : SkDocument(stream, doneProc) |
20 , fRasterDpi(rasterDpi) {} | 204 , fRasterDpi(rasterDpi) {} |
21 | 205 |
(...skipping 20 matching lines...) Expand all Loading... |
42 | 226 |
43 void onEndPage() SK_OVERRIDE { | 227 void onEndPage() SK_OVERRIDE { |
44 SkASSERT(fCanvas.get()); | 228 SkASSERT(fCanvas.get()); |
45 fCanvas->flush(); | 229 fCanvas->flush(); |
46 fCanvas.reset(NULL); | 230 fCanvas.reset(NULL); |
47 } | 231 } |
48 | 232 |
49 bool onClose(SkWStream* stream) SK_OVERRIDE { | 233 bool onClose(SkWStream* stream) SK_OVERRIDE { |
50 SkASSERT(!fCanvas.get()); | 234 SkASSERT(!fCanvas.get()); |
51 | 235 |
52 bool success = SkPDFDocument::EmitPDF(fPageDevices, stream); | 236 bool success = emit_pdf_document(fPageDevices, stream); |
53 fPageDevices.unrefAll(); | 237 fPageDevices.unrefAll(); |
54 fCanon.reset(); | 238 fCanon.reset(); |
55 return success; | 239 return success; |
56 } | 240 } |
57 | 241 |
58 void onAbort() SK_OVERRIDE { | 242 void onAbort() SK_OVERRIDE { |
59 fPageDevices.unrefAll(); | 243 fPageDevices.unrefAll(); |
60 fCanon.reset(); | 244 fCanon.reset(); |
61 } | 245 } |
62 | 246 |
63 private: | 247 private: |
64 SkPDFCanon fCanon; | 248 SkPDFCanon fCanon; |
65 SkTDArray<const SkPDFDevice*> fPageDevices; | 249 SkTDArray<const SkPDFDevice*> fPageDevices; |
66 SkAutoTUnref<SkCanvas> fCanvas; | 250 SkAutoTUnref<SkCanvas> fCanvas; |
67 SkScalar fRasterDpi; | 251 SkScalar fRasterDpi; |
68 }; | 252 }; |
69 } // namespace | 253 } // namespace |
70 /////////////////////////////////////////////////////////////////////////////// | 254 /////////////////////////////////////////////////////////////////////////////// |
71 | 255 |
72 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { | 256 SkDocument* SkDocument::CreatePDF(SkWStream* stream, SkScalar dpi) { |
73 return stream ? SkNEW_ARGS(SkDocument_PDF, (stream, NULL, dpi)) : NULL; | 257 return stream ? SkNEW_ARGS(SkDocument_PDF, (stream, NULL, dpi)) : NULL; |
74 } | 258 } |
75 | 259 |
76 static void delete_wstream(SkWStream* stream, bool aborted) { | |
77 SkDELETE(stream); | |
78 } | |
79 | |
80 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { | 260 SkDocument* SkDocument::CreatePDF(const char path[], SkScalar dpi) { |
81 SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (path)); | 261 SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (path)); |
82 if (!stream->isValid()) { | 262 if (!stream->isValid()) { |
83 SkDELETE(stream); | 263 SkDELETE(stream); |
84 return NULL; | 264 return NULL; |
85 } | 265 } |
| 266 auto delete_wstream = [](SkWStream* stream, bool) { SkDELETE(stream); }; |
86 return SkNEW_ARGS(SkDocument_PDF, (stream, delete_wstream, dpi)); | 267 return SkNEW_ARGS(SkDocument_PDF, (stream, delete_wstream, dpi)); |
87 } | 268 } |
OLD | NEW |