1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
/*
* Copyright 2011 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkPDFMakeToUnicodeCmap.h"
#include "SkPDFUtils.h"
#include "SkTo.h"
#include "SkUtils.h"
static void append_tounicode_header(SkDynamicMemoryWStream* cmap,
bool multibyte) {
// 12 dict begin: 12 is an Adobe-suggested value. Shall not change.
// It's there to prevent old version Adobe Readers from malfunctioning.
const char* kHeader =
"/CIDInit /ProcSet findresource begin\n"
"12 dict begin\n"
"begincmap\n";
cmap->writeText(kHeader);
// The /CIDSystemInfo must be consistent to the one in
// SkPDFFont::populateCIDFont().
// We can not pass over the system info object here because the format is
// different. This is not a reference object.
const char* kSysInfo =
"/CIDSystemInfo\n"
"<< /Registry (Adobe)\n"
"/Ordering (UCS)\n"
"/Supplement 0\n"
">> def\n";
cmap->writeText(kSysInfo);
// The CMapName must be consistent to /CIDSystemInfo above.
// /CMapType 2 means ToUnicode.
// Codespace range just tells the PDF processor the valid range.
const char* kTypeInfoHeader =
"/CMapName /Adobe-Identity-UCS def\n"
"/CMapType 2 def\n"
"1 begincodespacerange\n";
cmap->writeText(kTypeInfoHeader);
if (multibyte) {
cmap->writeText("<0000> <FFFF>\n");
} else {
cmap->writeText("<00> <FF>\n");
}
cmap->writeText("endcodespacerange\n");
}
static void append_cmap_footer(SkDynamicMemoryWStream* cmap) {
const char kFooter[] =
"endcmap\n"
"CMapName currentdict /CMap defineresource pop\n"
"end\n"
"end";
cmap->writeText(kFooter);
}
namespace {
struct BFChar {
SkGlyphID fGlyphId;
SkUnichar fUnicode;
};
struct BFRange {
SkGlyphID fStart;
SkGlyphID fEnd;
SkUnichar fUnicode;
};
} // namespace
static void write_glyph(SkDynamicMemoryWStream* cmap,
bool multiByte,
SkGlyphID gid) {
if (multiByte) {
SkPDFUtils::WriteUInt16BE(cmap, gid);
} else {
SkPDFUtils::WriteUInt8(cmap, SkToU8(gid));
}
}
static void append_bfchar_section(const SkTDArray<BFChar>& bfchar,
bool multiByte,
SkDynamicMemoryWStream* cmap) {
// PDF spec defines that every bf* list can have at most 100 entries.
for (int i = 0; i < bfchar.count(); i += 100) {
int count = bfchar.count() - i;
count = SkMin32(count, 100);
cmap->writeDecAsText(count);
cmap->writeText(" beginbfchar\n");
for (int j = 0; j < count; ++j) {
cmap->writeText("<");
write_glyph(cmap, multiByte, bfchar[i + j].fGlyphId);
cmap->writeText("> <");
SkPDFUtils::WriteUTF16beHex(cmap, bfchar[i + j].fUnicode);
cmap->writeText(">\n");
}
cmap->writeText("endbfchar\n");
}
}
static void append_bfrange_section(const SkTDArray<BFRange>& bfrange,
bool multiByte,
SkDynamicMemoryWStream* cmap) {
// PDF spec defines that every bf* list can have at most 100 entries.
for (int i = 0; i < bfrange.count(); i += 100) {
int count = bfrange.count() - i;
count = SkMin32(count, 100);
cmap->writeDecAsText(count);
cmap->writeText(" beginbfrange\n");
for (int j = 0; j < count; ++j) {
cmap->writeText("<");
write_glyph(cmap, multiByte, bfrange[i + j].fStart);
cmap->writeText("> <");
write_glyph(cmap, multiByte, bfrange[i + j].fEnd);
cmap->writeText("> <");
SkPDFUtils::WriteUTF16beHex(cmap, bfrange[i + j].fUnicode);
cmap->writeText(">\n");
}
cmap->writeText("endbfrange\n");
}
}
// Generate <bfchar> and <bfrange> table according to PDF spec 1.4 and Adobe
// Technote 5014.
// The function is not static so we can test it in unit tests.
//
// Current implementation guarantees bfchar and bfrange entries do not overlap.
//
// Current implementation does not attempt aggresive optimizations against
// following case because the specification is not clear.
//
// 4 beginbfchar 1 beginbfchar
// <0003> <0013> <0020> <0014>
// <0005> <0015> to endbfchar
// <0007> <0017> 1 beginbfrange
// <0020> <0014> <0003> <0007> <0013>
// endbfchar endbfrange
//
// Adobe Technote 5014 said: "Code mappings (unlike codespace ranges) may
// overlap, but succeeding maps supersede preceding maps."
//
// In case of searching text in PDF, bfrange will have higher precedence so
// typing char id 0x0014 in search box will get glyph id 0x0004 first. However,
// the spec does not mention how will this kind of conflict being resolved.
//
// For the worst case (having 65536 continuous unicode and we use every other
// one of them), the possible savings by aggressive optimization is 416KB
// pre-compressed and does not provide enough motivation for implementation.
void SkPDFAppendCmapSections(const SkUnichar* glyphToUnicode,
const SkBitSet* subset,
SkDynamicMemoryWStream* cmap,
bool multiByteGlyphs,
SkGlyphID firstGlyphID,
SkGlyphID lastGlyphID) {
int glyphOffset = 0;
if (!multiByteGlyphs) {
glyphOffset = firstGlyphID - 1;
}
SkTDArray<BFChar> bfcharEntries;
SkTDArray<BFRange> bfrangeEntries;
BFRange currentRangeEntry = {0, 0, 0};
bool rangeEmpty = true;
const int limit = (int)lastGlyphID + 1 - glyphOffset;
for (int i = firstGlyphID - glyphOffset; i < limit + 1; ++i) {
bool inSubset = i < limit &&
(subset == nullptr || subset->has(i + glyphOffset));
if (!rangeEmpty) {
// PDF spec requires bfrange not changing the higher byte,
// e.g. <1035> <10FF> <2222> is ok, but
// <1035> <1100> <2222> is no good
bool inRange =
i == currentRangeEntry.fEnd + 1 &&
i >> 8 == currentRangeEntry.fStart >> 8 &&
i < limit &&
glyphToUnicode[i + glyphOffset] ==
currentRangeEntry.fUnicode + i - currentRangeEntry.fStart;
if (!inSubset || !inRange) {
if (currentRangeEntry.fEnd > currentRangeEntry.fStart) {
bfrangeEntries.push(currentRangeEntry);
} else {
BFChar* entry = bfcharEntries.append();
entry->fGlyphId = currentRangeEntry.fStart;
entry->fUnicode = currentRangeEntry.fUnicode;
}
rangeEmpty = true;
}
}
if (inSubset) {
currentRangeEntry.fEnd = i;
if (rangeEmpty) {
currentRangeEntry.fStart = i;
currentRangeEntry.fUnicode = glyphToUnicode[i + glyphOffset];
rangeEmpty = false;
}
}
}
// The spec requires all bfchar entries for a font must come before bfrange
// entries.
append_bfchar_section(bfcharEntries, multiByteGlyphs, cmap);
append_bfrange_section(bfrangeEntries, multiByteGlyphs, cmap);
}
sk_sp<SkPDFStream> SkPDFMakeToUnicodeCmap(
const SkUnichar* glyphToUnicode,
const SkBitSet* subset,
bool multiByteGlyphs,
SkGlyphID firstGlyphID,
SkGlyphID lastGlyphID) {
SkDynamicMemoryWStream cmap;
append_tounicode_header(&cmap, multiByteGlyphs);
SkPDFAppendCmapSections(glyphToUnicode, subset, &cmap, multiByteGlyphs,
firstGlyphID, lastGlyphID);
append_cmap_footer(&cmap);
return sk_make_sp<SkPDFStream>(
std::unique_ptr<SkStreamAsset>(cmap.detachAsStream()));
}
|