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.
AllowedInPsName(char c)18 bool AllowedInPsName(char c) {
19 return isalnum(c) || std::strchr("-._~", c);
20 }
21
SanitizePsNameAscii(std::string & name)22 bool SanitizePsNameAscii(std::string& name) {
23 if (name.size() > 63)
24 return false;
25
26 for (unsigned i = 0; i < name.size(); ++i) {
27 if (!AllowedInPsName(name[i])) {
28 name[i] = '_';
29 }
30 }
31 return true;
32 }
33
SanitizePsNameUtf16Be(std::string & name)34 bool SanitizePsNameUtf16Be(std::string& name) {
35 if ((name.size() & 1) != 0)
36 return false;
37 if (name.size() > 2 * 63)
38 return false;
39
40 for (unsigned i = 0; i < name.size(); i += 2) {
41 if (name[i] != 0) {
42 // non-Latin1 char in psname? reject it altogether
43 return false;
44 }
45 if (!AllowedInPsName(name[i+1])) {
46 name[i] = '_';
47 }
48 }
49 return true;
50 }
51
AssignToUtf16BeFromAscii(std::string * target,const std::string & source)52 void AssignToUtf16BeFromAscii(std::string* target,
53 const std::string& source) {
54 target->resize(source.size() * 2);
55 for (unsigned i = 0, j = 0; i < source.size(); i++) {
56 (*target)[j++] = '\0';
57 (*target)[j++] = source[i];
58 }
59 }
60
61 } // namespace
62
63
64 namespace ots {
65
Parse(const uint8_t * data,size_t length)66 bool OpenTypeNAME::Parse(const uint8_t* data, size_t length) {
67 Buffer table(data, length);
68
69 uint16_t format = 0;
70 if (!table.ReadU16(&format) || format > 1) {
71 return Error("Failed to read table format or bad format %d", format);
72 }
73
74 uint16_t count = 0;
75 if (!table.ReadU16(&count)) {
76 return Error("Failed to read name count");
77 }
78
79 uint16_t string_offset = 0;
80 if (!table.ReadU16(&string_offset) || string_offset > length) {
81 return Error("Failed to read or bad stringOffset");
82 }
83 const char* string_base = reinterpret_cast<const char*>(data) +
84 string_offset;
85
86 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 for (unsigned i = 0; i < count; ++i) {
93 NameRecord rec;
94 uint16_t name_length, name_offset = 0;
95 if (!table.ReadU16(&rec.platform_id) ||
96 !table.ReadU16(&rec.encoding_id) ||
97 !table.ReadU16(&rec.language_id) ||
98 !table.ReadU16(&rec.name_id) ||
99 !table.ReadU16(&name_length) ||
100 !table.ReadU16(&name_offset)) {
101 return Error("Failed to read name entry %d", i);
102 }
103 // check platform & encoding, discard names with unknown values
104 switch (rec.platform_id) {
105 case 0: // Unicode
106 if (rec.encoding_id > 6) {
107 continue;
108 }
109 break;
110 case 1: // Macintosh
111 if (rec.encoding_id > 32) {
112 continue;
113 }
114 break;
115 case 2: // ISO
116 if (rec.encoding_id > 2) {
117 continue;
118 }
119 break;
120 case 3: // Windows: IDs 7 to 9 are "reserved"
121 if (rec.encoding_id > 6 && rec.encoding_id != 10) {
122 continue;
123 }
124 break;
125 case 4: // Custom (OTF Windows NT compatibility)
126 if (rec.encoding_id > 255) {
127 continue;
128 }
129 break;
130 default: // unknown platform
131 continue;
132 }
133
134 const unsigned name_end = static_cast<unsigned>(string_offset) +
135 name_offset + name_length;
136 if (name_end > length) {
137 continue;
138 }
139 rec.text.resize(name_length);
140 rec.text.assign(string_base + name_offset, name_length);
141
142 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 if (rec.platform_id == 1) {
147 if (!SanitizePsNameAscii(rec.text)) {
148 continue;
149 }
150 } else if (rec.platform_id == 0 || rec.platform_id == 3) {
151 if (!SanitizePsNameUtf16Be(rec.text)) {
152 continue;
153 }
154 }
155 }
156
157 if (!this->names.empty() && !(this->names.back() < rec)) {
158 Warning("name records are not sorted.");
159 sort_required = true;
160 }
161
162 this->names.push_back(rec);
163 this->name_ids.insert(rec.name_id);
164 }
165
166 if (format == 1) {
167 // extended name table format with language tags
168 uint16_t lang_tag_count;
169 if (!table.ReadU16(&lang_tag_count)) {
170 return Error("Failed to read langTagCount");
171 }
172 for (unsigned i = 0; i < lang_tag_count; ++i) {
173 uint16_t tag_length = 0;
174 uint16_t tag_offset = 0;
175 if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) {
176 return Error("Faile to read length or offset for langTagRecord %d", i);
177 }
178 const unsigned tag_end = static_cast<unsigned>(string_offset) +
179 tag_offset + tag_length;
180 if (tag_end > length) {
181 return Error("bad end of tag %d > %ld for langTagRecord %d", tag_end, length, i);
182 }
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 if (tag_length > 100 * 2) {
189 return Error("Too long language tag for LangTagRecord %d: %d", i, tag_length);
190 }
191 std::string tag(string_base + tag_offset, tag_length);
192 this->lang_tags.push_back(tag);
193 }
194 }
195
196 if (table.offset() > string_offset) {
197 // the string storage apparently overlapped the name/tag records;
198 // consider this font to be badly broken
199 return Error("Bad table offset %ld > %d", table.offset(), string_offset);
200 }
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 static const uint16_t kStdNameCount = 7;
211 static const char* kStdNames[kStdNameCount] = {
212 NULL,
213 "OTS derived font",
214 "Unspecified",
215 NULL,
216 "OTS derived font",
217 "1.000",
218 "OTS-derived-font"
219 };
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 bool mac_name[kStdNameCount] = { 0 };
224 bool win_name[kStdNameCount] = { 0 };
225 for (const auto& name : this->names) {
226 const uint16_t id = name.name_id;
227 if (id >= kStdNameCount || kStdNames[id] == NULL) {
228 continue;
229 }
230 if (name.platform_id == 1) {
231 mac_name[id] = true;
232 continue;
233 }
234 if (name.platform_id == 3) {
235 win_name[id] = true;
236 continue;
237 }
238 }
239
240 for (uint16_t i = 0; i < kStdNameCount; ++i) {
241 if (kStdNames[i] == NULL) {
242 continue;
243 }
244 if (!mac_name[i] && !win_name[i]) {
245 NameRecord mac_rec(1 /* platform_id */, 0 /* encoding_id */,
246 0 /* language_id */ , i /* name_id */);
247 mac_rec.text.assign(kStdNames[i]);
248
249 NameRecord win_rec(3 /* platform_id */, 1 /* encoding_id */,
250 1033 /* language_id */ , i /* name_id */);
251 AssignToUtf16BeFromAscii(&win_rec.text, std::string(kStdNames[i]));
252
253 this->names.push_back(mac_rec);
254 this->names.push_back(win_rec);
255 sort_required = true;
256 }
257 }
258
259 if (sort_required) {
260 std::sort(this->names.begin(), this->names.end());
261 }
262
263 return true;
264 }
265
Serialize(OTSStream * out)266 bool OpenTypeNAME::Serialize(OTSStream* out) {
267 uint16_t name_count = static_cast<uint16_t>(this->names.size());
268 uint16_t lang_tag_count = static_cast<uint16_t>(this->lang_tags.size());
269 uint16_t format = 0;
270 size_t string_offset = 6 + name_count * 12;
271
272 if (this->lang_tags.size() > 0) {
273 // lang tags require a format-1 name table
274 format = 1;
275 string_offset += 2 + lang_tag_count * 4;
276 }
277 if (string_offset > 0xffff) {
278 return Error("Bad stringOffset: %ld", string_offset);
279 }
280 if (!out->WriteU16(format) ||
281 !out->WriteU16(name_count) ||
282 !out->WriteU16(static_cast<uint16_t>(string_offset))) {
283 return Error("Failed to write name header");
284 }
285
286 std::string string_data;
287 for (const auto& rec : this->names) {
288 if (string_data.size() + rec.text.size() >
289 std::numeric_limits<uint16_t>::max() ||
290 !out->WriteU16(rec.platform_id) ||
291 !out->WriteU16(rec.encoding_id) ||
292 !out->WriteU16(rec.language_id) ||
293 !out->WriteU16(rec.name_id) ||
294 !out->WriteU16(static_cast<uint16_t>(rec.text.size())) ||
295 !out->WriteU16(static_cast<uint16_t>(string_data.size())) ) {
296 return Error("Faile to write nameRecord");
297 }
298 string_data.append(rec.text);
299 }
300
301 if (format == 1) {
302 if (!out->WriteU16(lang_tag_count)) {
303 return Error("Faile to write langTagCount");
304 }
305 for (const auto& tag : this->lang_tags) {
306 if (string_data.size() + tag.size() >
307 std::numeric_limits<uint16_t>::max() ||
308 !out->WriteU16(static_cast<uint16_t>(tag.size())) ||
309 !out->WriteU16(static_cast<uint16_t>(string_data.size()))) {
310 return Error("Failed to write langTagRecord");
311 }
312 string_data.append(tag);
313 }
314 }
315
316 if (!out->Write(string_data.data(), string_data.size())) {
317 return Error("Faile to write string data");
318 }
319
320 return true;
321 }
322
IsValidNameId(uint16_t nameID,bool addIfMissing)323 bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) {
324 if (addIfMissing && !this->name_ids.count(nameID)) {
325 bool added_unicode = false;
326 bool added_macintosh = false;
327 bool added_windows = false;
328 const size_t names_size = this->names.size(); // original size
329 for (size_t i = 0; i < names_size; ++i) switch (names[i].platform_id) {
330 case 0:
331 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 this->names.emplace_back(0, 0, 0, nameID);
336 this->names.back().text = "NoName";
337 added_unicode = true;
338 }
339 break;
340 case 1:
341 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 this->names.emplace_back(1, 0, 0, nameID);
346 this->names.back().text = "NoName";
347 added_macintosh = true;
348 }
349 break;
350 case 3:
351 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 this->names.emplace_back(3, 1, 1033, nameID);
356 this->names.back().text = "NoName";
357 added_windows = true;
358 }
359 break;
360 }
361 if (added_unicode || added_macintosh || added_windows) {
362 std::sort(this->names.begin(), this->names.end());
363 this->name_ids.insert(nameID);
364 }
365 }
366 return this->name_ids.count(nameID);
367 }
368
369 } // namespace
370