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 |