Line | Count | Source |
1 | | // Copyright (c) 2011-2017 The OTS Authors. All rights reserved. |
2 | | // Use of this source code is governed by a BSD-style license that can be |
3 | | // found in the LICENSE file. |
4 | | |
5 | | #include "name.h" |
6 | | |
7 | | #include <algorithm> |
8 | | #include <cstring> |
9 | | #include <cctype> |
10 | | |
11 | | // name - Naming Table |
12 | | // http://www.microsoft.com/typography/otspec/name.htm |
13 | | |
14 | | namespace { |
15 | | |
16 | | // We disallow characters outside the URI spec "unreserved characters" |
17 | | // set; any chars outside this set will be replaced by underscore. |
18 | 77.9k | bool AllowedInPsName(char c) { |
19 | 77.9k | return isalnum(c) || std::strchr("-._~", c); |
20 | 77.9k | } |
21 | | |
22 | 2.13k | bool SanitizePsNameAscii(std::string& name) { |
23 | 2.13k | if (name.size() > 63) |
24 | 94 | return false; |
25 | | |
26 | 39.9k | for (unsigned i = 0; i < name.size(); ++i) { |
27 | 37.9k | if (!AllowedInPsName(name[i])) { |
28 | 11.3k | name[i] = '_'; |
29 | 11.3k | } |
30 | 37.9k | } |
31 | 2.04k | return true; |
32 | 2.13k | } |
33 | | |
34 | 4.97k | bool SanitizePsNameUtf16Be(std::string& name) { |
35 | 4.97k | if ((name.size() & 1) != 0) |
36 | 843 | return false; |
37 | 4.13k | if (name.size() > 2 * 63) |
38 | 450 | return false; |
39 | | |
40 | 43.7k | for (unsigned i = 0; i < name.size(); i += 2) { |
41 | 40.8k | if (name[i] != 0) { |
42 | | // non-Latin1 char in psname? reject it altogether |
43 | 799 | return false; |
44 | 799 | } |
45 | 40.0k | if (!AllowedInPsName(name[i+1])) { |
46 | 594 | name[i] = '_'; |
47 | 594 | } |
48 | 40.0k | } |
49 | 2.88k | return true; |
50 | 3.68k | } |
51 | | |
52 | | void AssignToUtf16BeFromAscii(std::string* target, |
53 | 88.3k | const std::string& source) { |
54 | 88.3k | target->resize(source.size() * 2); |
55 | 1.20M | for (unsigned i = 0, j = 0; i < source.size(); i++) { |
56 | 1.12M | (*target)[j++] = '\0'; |
57 | 1.12M | (*target)[j++] = source[i]; |
58 | 1.12M | } |
59 | 88.3k | } |
60 | | |
61 | | } // namespace |
62 | | |
63 | | |
64 | | namespace ots { |
65 | | |
66 | 21.7k | bool OpenTypeNAME::Parse(const uint8_t* data, size_t length) { |
67 | 21.7k | Buffer table(data, length); |
68 | | |
69 | 21.7k | uint16_t format = 0; |
70 | 21.7k | if (!table.ReadU16(&format) || format > 1) { |
71 | 67 | return Error("Failed to read table format or bad format %d", format); |
72 | 67 | } |
73 | | |
74 | 21.6k | uint16_t count = 0; |
75 | 21.6k | if (!table.ReadU16(&count)) { |
76 | 19 | return Error("Failed to read name count"); |
77 | 19 | } |
78 | | |
79 | 21.6k | uint16_t string_offset = 0; |
80 | 21.6k | if (!table.ReadU16(&string_offset) || string_offset > length) { |
81 | 21 | return Error("Failed to read or bad stringOffset"); |
82 | 21 | } |
83 | 21.6k | const char* string_base = reinterpret_cast<const char*>(data) + |
84 | 21.6k | string_offset; |
85 | | |
86 | 21.6k | bool sort_required = false; |
87 | | |
88 | | // Read all the names, discarding any with invalid IDs, |
89 | | // and any where the offset/length would be outside the table. |
90 | | // A stricter alternative would be to reject the font if there |
91 | | // are invalid name records, but it's not clear that is necessary. |
92 | 3.33M | for (unsigned i = 0; i < count; ++i) { |
93 | 3.31M | NameRecord rec; |
94 | 3.31M | uint16_t name_length, name_offset = 0; |
95 | 3.31M | if (!table.ReadU16(&rec.platform_id) || |
96 | 3.31M | !table.ReadU16(&rec.encoding_id) || |
97 | 3.31M | !table.ReadU16(&rec.language_id) || |
98 | 3.31M | !table.ReadU16(&rec.name_id) || |
99 | 3.31M | !table.ReadU16(&name_length) || |
100 | 3.31M | !table.ReadU16(&name_offset)) { |
101 | 183 | return Error("Failed to read name entry %d", i); |
102 | 183 | } |
103 | | // check platform & encoding, discard names with unknown values |
104 | 3.31M | switch (rec.platform_id) { |
105 | 888k | case 0: // Unicode |
106 | 888k | if (rec.encoding_id > 6) { |
107 | 263k | continue; |
108 | 263k | } |
109 | 624k | break; |
110 | 624k | case 1: // Macintosh |
111 | 175k | if (rec.encoding_id > 32) { |
112 | 36.0k | continue; |
113 | 36.0k | } |
114 | 139k | break; |
115 | 139k | case 2: // ISO |
116 | 29.0k | if (rec.encoding_id > 2) { |
117 | 14.6k | continue; |
118 | 14.6k | } |
119 | 14.3k | break; |
120 | 93.3k | case 3: // Windows: IDs 7 to 9 are "reserved" |
121 | 93.3k | if (rec.encoding_id > 6 && rec.encoding_id != 10) { |
122 | 13.7k | continue; |
123 | 13.7k | } |
124 | 79.6k | break; |
125 | 79.6k | case 4: // Custom (OTF Windows NT compatibility) |
126 | 35.9k | if (rec.encoding_id > 255) { |
127 | 9.26k | continue; |
128 | 9.26k | } |
129 | 26.6k | break; |
130 | 2.09M | default: // unknown platform |
131 | 2.09M | continue; |
132 | 3.31M | } |
133 | | |
134 | 885k | const unsigned name_end = static_cast<unsigned>(string_offset) + |
135 | 885k | name_offset + name_length; |
136 | 885k | if (name_end > length) { |
137 | 109k | continue; |
138 | 109k | } |
139 | 775k | rec.text.resize(name_length); |
140 | 775k | rec.text.assign(string_base + name_offset, name_length); |
141 | | |
142 | 775k | if (rec.name_id == 6) { |
143 | | // PostScript name: "sanitize" it by replacing any chars outside the |
144 | | // URI spec "unreserved" set by underscore, or reject the name entirely |
145 | | // (and use a fallback) if it looks really broken. |
146 | 7.95k | if (rec.platform_id == 1) { |
147 | 2.13k | if (!SanitizePsNameAscii(rec.text)) { |
148 | 94 | continue; |
149 | 94 | } |
150 | 5.81k | } else if (rec.platform_id == 0 || rec.platform_id == 3) { |
151 | 4.97k | if (!SanitizePsNameUtf16Be(rec.text)) { |
152 | 2.09k | continue; |
153 | 2.09k | } |
154 | 4.97k | } |
155 | 7.95k | } |
156 | | |
157 | 773k | if (!this->names.empty() && !(this->names.back() < rec)) { |
158 | 488k | Warning("name records are not sorted."); |
159 | 488k | sort_required = true; |
160 | 488k | } |
161 | | |
162 | 773k | this->names.push_back(rec); |
163 | 773k | this->name_ids.insert(rec.name_id); |
164 | 773k | } |
165 | | |
166 | 21.4k | if (format == 1) { |
167 | | // extended name table format with language tags |
168 | 294 | uint16_t lang_tag_count; |
169 | 294 | if (!table.ReadU16(&lang_tag_count)) { |
170 | 1 | return Error("Failed to read langTagCount"); |
171 | 1 | } |
172 | 45.6k | for (unsigned i = 0; i < lang_tag_count; ++i) { |
173 | 45.4k | uint16_t tag_length = 0; |
174 | 45.4k | uint16_t tag_offset = 0; |
175 | 45.4k | if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) { |
176 | 29 | return Error("Failed to read length or offset for langTagRecord %d", i); |
177 | 29 | } |
178 | 45.4k | const unsigned tag_end = static_cast<unsigned>(string_offset) + |
179 | 45.4k | tag_offset + tag_length; |
180 | 45.4k | if (tag_end > length) { |
181 | 43 | return Error("bad end of tag %d > %ld for langTagRecord %d", tag_end, length, i); |
182 | 43 | } |
183 | | // Lang tag is BCP 47 tag per the spec, the recommonded BCP 47 max tag |
184 | | // length is 35: |
185 | | // https://tools.ietf.org/html/bcp47#section-4.4.1 |
186 | | // We are being too generous and allowing for 100 (multiplied by 2 since |
187 | | // this is UTF-16 string). |
188 | 45.3k | if (tag_length > 100 * 2) { |
189 | 47 | return Error("Too long language tag for LangTagRecord %d: %d", i, tag_length); |
190 | 47 | } |
191 | 45.3k | std::string tag(string_base + tag_offset, tag_length); |
192 | 45.3k | this->lang_tags.push_back(tag); |
193 | 45.3k | } |
194 | 293 | } |
195 | | |
196 | 21.3k | if (table.offset() > string_offset) { |
197 | | // the string storage apparently overlapped the name/tag records; |
198 | | // consider this font to be badly broken |
199 | 54 | return Error("Bad table offset %ld > %d", table.offset(), string_offset); |
200 | 54 | } |
201 | | |
202 | | // check existence of required name strings (synthesize if necessary) |
203 | | // [0 - copyright - skip] |
204 | | // 1 - family |
205 | | // 2 - subfamily |
206 | | // [3 - unique ID - skip] |
207 | | // 4 - full name |
208 | | // 5 - version |
209 | | // 6 - postscript name |
210 | 21.2k | static const uint16_t kStdNameCount = 7; |
211 | 21.2k | static const char* kStdNames[kStdNameCount] = { |
212 | 21.2k | NULL, |
213 | 21.2k | "OTS derived font", |
214 | 21.2k | "Unspecified", |
215 | 21.2k | NULL, |
216 | 21.2k | "OTS derived font", |
217 | 21.2k | "1.000", |
218 | 21.2k | "OTS-derived-font" |
219 | 21.2k | }; |
220 | | |
221 | | // scan the names to check whether the required "standard" ones are present; |
222 | | // if not, we'll add our fixed versions here |
223 | 21.2k | bool mac_name[kStdNameCount] = { 0 }; |
224 | 21.2k | bool win_name[kStdNameCount] = { 0 }; |
225 | 642k | for (const auto& name : this->names) { |
226 | 642k | const uint16_t id = name.name_id; |
227 | 642k | if (id >= kStdNameCount || kStdNames[id] == NULL) { |
228 | 548k | continue; |
229 | 548k | } |
230 | 94.6k | if (name.platform_id == 1) { |
231 | 23.5k | mac_name[id] = true; |
232 | 23.5k | continue; |
233 | 23.5k | } |
234 | 71.0k | if (name.platform_id == 3) { |
235 | 22.5k | win_name[id] = true; |
236 | 22.5k | continue; |
237 | 22.5k | } |
238 | 71.0k | } |
239 | | |
240 | 170k | for (uint16_t i = 0; i < kStdNameCount; ++i) { |
241 | 149k | if (kStdNames[i] == NULL) { |
242 | 42.5k | continue; |
243 | 42.5k | } |
244 | 106k | if (!mac_name[i] && !win_name[i]) { |
245 | 88.3k | NameRecord mac_rec(1 /* platform_id */, 0 /* encoding_id */, |
246 | 88.3k | 0 /* language_id */ , i /* name_id */); |
247 | 88.3k | mac_rec.text.assign(kStdNames[i]); |
248 | | |
249 | 88.3k | NameRecord win_rec(3 /* platform_id */, 1 /* encoding_id */, |
250 | 88.3k | 1033 /* language_id */ , i /* name_id */); |
251 | 88.3k | AssignToUtf16BeFromAscii(&win_rec.text, std::string(kStdNames[i])); |
252 | | |
253 | 88.3k | this->names.push_back(mac_rec); |
254 | 88.3k | this->names.push_back(win_rec); |
255 | 88.3k | sort_required = true; |
256 | 88.3k | } |
257 | 106k | } |
258 | | |
259 | 21.2k | if (sort_required) { |
260 | 19.8k | std::sort(this->names.begin(), this->names.end()); |
261 | 19.8k | } |
262 | | |
263 | 21.2k | return true; |
264 | 21.3k | } |
265 | | |
266 | 11.3k | bool OpenTypeNAME::Serialize(OTSStream* out) { |
267 | 11.3k | uint16_t name_count = static_cast<uint16_t>(this->names.size()); |
268 | 11.3k | uint16_t lang_tag_count = static_cast<uint16_t>(this->lang_tags.size()); |
269 | 11.3k | uint16_t format = 0; |
270 | 11.3k | size_t string_offset = 6 + name_count * 12; |
271 | | |
272 | 11.3k | if (this->lang_tags.size() > 0) { |
273 | | // lang tags require a format-1 name table |
274 | 35 | format = 1; |
275 | 35 | string_offset += 2 + lang_tag_count * 4; |
276 | 35 | } |
277 | 11.3k | if (string_offset > 0xffff) { |
278 | 0 | return Error("Bad stringOffset: %ld", string_offset); |
279 | 0 | } |
280 | 11.3k | if (!out->WriteU16(format) || |
281 | 11.3k | !out->WriteU16(name_count) || |
282 | 11.3k | !out->WriteU16(static_cast<uint16_t>(string_offset))) { |
283 | 0 | return Error("Failed to write name header"); |
284 | 0 | } |
285 | | |
286 | 11.3k | std::string string_data; |
287 | 457k | for (const auto& rec : this->names) { |
288 | 457k | if (string_data.size() + rec.text.size() > |
289 | 457k | std::numeric_limits<uint16_t>::max() || |
290 | 456k | !out->WriteU16(rec.platform_id) || |
291 | 456k | !out->WriteU16(rec.encoding_id) || |
292 | 456k | !out->WriteU16(rec.language_id) || |
293 | 456k | !out->WriteU16(rec.name_id) || |
294 | 456k | !out->WriteU16(static_cast<uint16_t>(rec.text.size())) || |
295 | 456k | !out->WriteU16(static_cast<uint16_t>(string_data.size())) ) { |
296 | 227 | return Error("Failed to write nameRecord"); |
297 | 227 | } |
298 | 456k | string_data.append(rec.text); |
299 | 456k | } |
300 | | |
301 | 11.1k | if (format == 1) { |
302 | 35 | if (!out->WriteU16(lang_tag_count)) { |
303 | 0 | return Error("Failed to write langTagCount"); |
304 | 0 | } |
305 | 157 | for (const auto& tag : this->lang_tags) { |
306 | 157 | if (string_data.size() + tag.size() > |
307 | 157 | std::numeric_limits<uint16_t>::max() || |
308 | 155 | !out->WriteU16(static_cast<uint16_t>(tag.size())) || |
309 | 155 | !out->WriteU16(static_cast<uint16_t>(string_data.size()))) { |
310 | 2 | return Error("Failed to write langTagRecord"); |
311 | 2 | } |
312 | 155 | string_data.append(tag); |
313 | 155 | } |
314 | 35 | } |
315 | | |
316 | 11.1k | if (!out->Write(string_data.data(), string_data.size())) { |
317 | 0 | return Error("Failed to write string data"); |
318 | 0 | } |
319 | | |
320 | 11.1k | return true; |
321 | 11.1k | } |
322 | | |
323 | 20.4k | bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) { |
324 | 20.4k | if (addIfMissing && !this->name_ids.count(nameID)) { |
325 | 3.56k | bool added_unicode = false; |
326 | 3.56k | bool added_macintosh = false; |
327 | 3.56k | bool added_windows = false; |
328 | 3.56k | const size_t names_size = this->names.size(); // original size |
329 | 773k | for (size_t i = 0; i < names_size; ++i) switch (names[i].platform_id) { |
330 | 410k | case 0: |
331 | 410k | if (!added_unicode) { |
332 | | // If there is an existing NameRecord with platform_id == 0 (Unicode), |
333 | | // then add a NameRecord for the the specified nameID with arguments |
334 | | // 0 (Unicode), 0 (v1.0), 0 (unspecified language). |
335 | 3.36k | this->names.emplace_back(0, 0, 0, nameID); |
336 | 3.36k | this->names.back().text = "NoName"; |
337 | 3.36k | added_unicode = true; |
338 | 3.36k | } |
339 | 410k | break; |
340 | 192k | case 1: |
341 | 192k | if (!added_macintosh) { |
342 | | // If there is an existing NameRecord with platform_id == 1 (Macintosh), |
343 | | // then add a NameRecord for the specified nameID with arguments |
344 | | // 1 (Macintosh), 0 (Roman), 0 (English). |
345 | 3.46k | this->names.emplace_back(1, 0, 0, nameID); |
346 | 3.46k | this->names.back().text = "NoName"; |
347 | 3.46k | added_macintosh = true; |
348 | 3.46k | } |
349 | 192k | break; |
350 | 135k | case 3: |
351 | 135k | if (!added_windows) { |
352 | | // If there is an existing NameRecord with platform_id == 3 (Windows), |
353 | | // then add a NameRecord for the specified nameID with arguments |
354 | | // 3 (Windows), 1 (UCS), 1033 (US English). |
355 | 3.56k | this->names.emplace_back(3, 1, 1033, nameID); |
356 | 3.56k | this->names.back().text = "NoName"; |
357 | 3.56k | added_windows = true; |
358 | 3.56k | } |
359 | 135k | break; |
360 | 769k | } |
361 | 3.56k | if (added_unicode || added_macintosh || added_windows) { |
362 | 3.56k | std::sort(this->names.begin(), this->names.end()); |
363 | 3.56k | this->name_ids.insert(nameID); |
364 | 3.56k | } |
365 | 3.56k | } |
366 | 20.4k | return this->name_ids.count(nameID); |
367 | 20.4k | } |
368 | | |
369 | | // List of font names considered "tricky" (dependent on applying original TrueType instructions) by FreeType, see |
370 | | // https://gitlab.freedesktop.org/freetype/freetype/-/blob/2d9fce53d4ce89f36075168282fcdd7289e082f9/src/truetype/ttobjs.c#L170-241 |
371 | | static const char* tricky_font_names[] = { |
372 | | "cpop", |
373 | | "DFGirl-W6-WIN-BF", |
374 | | "DFGothic-EB", |
375 | | "DFGyoSho-Lt", |
376 | | "DFHei", |
377 | | "DFHSGothic-W5", |
378 | | "DFHSMincho-W3", |
379 | | "DFHSMincho-W7", |
380 | | "DFKaiSho-SB", |
381 | | "DFKaiShu", |
382 | | "DFKai-SB", |
383 | | "DFMing", |
384 | | "DLC", |
385 | | "HuaTianKaiTi?", |
386 | | "HuaTianSongTi?", |
387 | | "Ming(for ISO10646)", |
388 | | "MingLiU", |
389 | | "MingMedium", |
390 | | "PMingLiU", |
391 | | "MingLi43" |
392 | | }; |
393 | | |
394 | 17.8k | bool OpenTypeNAME::IsTrickyFont() const { |
395 | 701k | for (const auto& name : this->names) { |
396 | 701k | const uint16_t id = name.name_id; |
397 | 701k | if (id != 1) { |
398 | 631k | continue; |
399 | 631k | } |
400 | 1.39M | for (const auto* p : tricky_font_names) { |
401 | 1.39M | if (name.text.find(p) != std::string::npos) { |
402 | 188 | return true; |
403 | 188 | } |
404 | 1.39M | } |
405 | 70.0k | } |
406 | 17.6k | return false; |
407 | 17.8k | } |
408 | | |
409 | | } // namespace |