blob: a22211b8344086f69235cdb1b4d2a500d0380869 [file] [log] [blame]
bashi@chromium.org92519592011-09-16 00:38:15 +00001// Copyright (c) 2011 The Chromium Authors. All rights reserved.
yusukes@chromium.orgd257d182009-11-04 04:56:32 +00002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
bashi@chromium.org92519592011-09-16 00:38:15 +00005#include "name.h"
6
7#include <algorithm>
yusukes@chromium.orgd257d182009-11-04 04:56:32 +00008#include <cstring>
9
yusukes@chromium.orga4099a32009-11-12 01:43:51 +000010#include "cff.h"
yusukes@chromium.orgd257d182009-11-04 04:56:32 +000011
12// name - Naming Table
13// http://www.microsoft.com/opentype/otspec/name.htm
14
bashi@chromium.org92519592011-09-16 00:38:15 +000015namespace {
16
17bool ValidInPsName(char c) {
18 return (c > 0x20 && c < 0x7f && !std::strchr("[](){}<>/%", c));
19}
20
21bool CheckPsNameAscii(const std::string& name) {
22 for (unsigned i = 0; i < name.size(); ++i) {
23 if (!ValidInPsName(name[i])) {
24 return false;
25 }
26 }
27 return true;
28}
29
30bool CheckPsNameUtf16Be(const std::string& name) {
31 if ((name.size() & 1) != 0)
32 return false;
33
34 for (unsigned i = 0; i < name.size(); i += 2) {
35 if (name[i] != 0) {
36 return false;
37 }
38 if (!ValidInPsName(name[i+1])) {
39 return false;
40 }
41 }
42 return true;
43}
44
45void AssignToUtf16BeFromAscii(std::string* target,
46 const std::string& source) {
47 target->resize(source.size() * 2);
48 for (unsigned i = 0, j = 0; i < source.size(); i++) {
49 (*target)[j++] = '\0';
50 (*target)[j++] = source[i];
51 }
52}
53
54} // namespace
55
56
yusukes@chromium.orgd257d182009-11-04 04:56:32 +000057namespace ots {
58
bashi@chromium.org92519592011-09-16 00:38:15 +000059bool ots_name_parse(OpenTypeFile* file, const uint8_t* data, size_t length) {
60 Buffer table(data, length);
yusukes@chromium.orgd257d182009-11-04 04:56:32 +000061
bashi@chromium.org92519592011-09-16 00:38:15 +000062 OpenTypeNAME* name = new OpenTypeNAME;
63 file->name = name;
yusukes@chromium.orgd257d182009-11-04 04:56:32 +000064
bashi@chromium.org92519592011-09-16 00:38:15 +000065 uint16_t format = 0;
66 if (!table.ReadU16(&format) || format > 1) {
67 return OTS_FAILURE();
68 }
yusukes@chromium.orgd257d182009-11-04 04:56:32 +000069
bashi@chromium.org92519592011-09-16 00:38:15 +000070 uint16_t count = 0;
71 if (!table.ReadU16(&count)) {
72 return OTS_FAILURE();
73 }
74
75 uint16_t string_offset = 0;
76 if (!table.ReadU16(&string_offset) || string_offset > length) {
77 return OTS_FAILURE();
78 }
79 const char* string_base = reinterpret_cast<const char*>(data) +
80 string_offset;
81
82 NameRecord prev_record;
83 bool sort_required = false;
84
85 // Read all the names, discarding any with invalid IDs,
86 // and any where the offset/length would be outside the table.
87 // A stricter alternative would be to reject the font if there
88 // are invalid name records, but it's not clear that is necessary.
89 for (unsigned i = 0; i < count; ++i) {
90 NameRecord rec;
91 uint16_t name_length, name_offset;
92 if (!table.ReadU16(&rec.platform_id) ||
93 !table.ReadU16(&rec.encoding_id) ||
94 !table.ReadU16(&rec.language_id) ||
95 !table.ReadU16(&rec.name_id) ||
96 !table.ReadU16(&name_length) ||
97 !table.ReadU16(&name_offset)) {
98 return OTS_FAILURE();
99 }
100 // check platform & encoding, discard names with unknown values
101 switch (rec.platform_id) {
102 case 0: // Unicode
103 if (rec.encoding_id > 6) {
104 continue;
105 }
106 break;
107 case 1: // Macintosh
108 if (rec.encoding_id > 32) {
109 continue;
110 }
111 break;
112 case 2: // ISO
113 if (rec.encoding_id > 2) {
114 continue;
115 }
116 break;
117 case 3: // Windows: IDs 7 to 9 are "reserved"
118 if (rec.encoding_id > 6 && rec.encoding_id != 10) {
119 continue;
120 }
121 break;
122 case 4: // Custom (OTF Windows NT compatibility)
123 if (rec.encoding_id > 255) {
124 continue;
125 }
126 break;
127 default: // unknown platform
128 continue;
129 }
130
131 const unsigned name_end = static_cast<unsigned>(string_offset) +
132 name_offset + name_length;
133 if (name_end > length) {
134 continue;
135 }
136 rec.text.resize(name_length);
137 rec.text.assign(string_base + name_offset, name_length);
138
139 if (rec.name_id == 6) {
140 // PostScript name: check that it is valid, if not then discard it
141 if (rec.platform_id == 1) {
142 if (file->cff && !file->cff->name.empty()) {
143 rec.text = file->cff->name;
144 } else if (!CheckPsNameAscii(rec.text)) {
145 continue;
146 }
147 } else if (rec.platform_id == 0 || rec.platform_id == 3) {
148 if (file->cff && !file->cff->name.empty()) {
149 AssignToUtf16BeFromAscii(&rec.text, file->cff->name);
150 } else if (!CheckPsNameUtf16Be(rec.text)) {
151 continue;
152 }
153 }
154 }
155
156 if ((i > 0) && !(prev_record < rec)) {
bashi@chromium.org89cec1f2011-09-21 06:29:58 +0000157 OTS_WARNING("name records are not sorted.");
158 sort_required = true;
bashi@chromium.org92519592011-09-16 00:38:15 +0000159 }
160
161 name->names.push_back(rec);
162 prev_record = rec;
163 }
164
165 if (format == 1) {
166 // extended name table format with language tags
167 uint16_t lang_tag_count;
168 if (!table.ReadU16(&lang_tag_count)) {
169 return OTS_FAILURE();
170 }
171 for (unsigned i = 0; i < lang_tag_count; ++i) {
172 uint16_t tag_length = 0;
173 uint16_t tag_offset = 0;
174 if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) {
175 return OTS_FAILURE();
176 }
177 const unsigned tag_end = static_cast<unsigned>(string_offset) +
178 tag_offset + tag_length;
179 if (tag_end > length) {
180 return OTS_FAILURE();
181 }
182 std::string tag(string_base + tag_offset, tag_length);
183 name->lang_tags.push_back(tag);
184 }
185 }
186
187 if (table.offset() > string_offset) {
188 // the string storage apparently overlapped the name/tag records;
189 // consider this font to be badly broken
190 return OTS_FAILURE();
191 }
192
193 // check existence of required name strings (synthesize if necessary)
194 // [0 - copyright - skip]
195 // 1 - family
196 // 2 - subfamily
197 // [3 - unique ID - skip]
198 // 4 - full name
199 // 5 - version
200 // 6 - postscript name
201 static const unsigned kStdNameCount = 7;
202 static const char* kStdNames[kStdNameCount] = {
203 NULL,
204 "OTS derived font",
205 "Unspecified",
206 NULL,
207 "OTS derived font",
208 "1.000",
209 "OTS-derived-font"
yusukes@chromium.orgd257d182009-11-04 04:56:32 +0000210 };
yusukes@chromium.orga4099a32009-11-12 01:43:51 +0000211 // The spec says that "In CFF OpenType fonts, these two name strings, when
212 // translated to ASCII, must also be identical to the font name as stored in
213 // the CFF's Name INDEX." And actually, Mac OS X's font parser requires that.
214 if (file->cff && !file->cff->name.empty()) {
bashi@chromium.org92519592011-09-16 00:38:15 +0000215 kStdNames[6] = file->cff->name.c_str();
yusukes@chromium.orga4099a32009-11-12 01:43:51 +0000216 }
217
bashi@chromium.org92519592011-09-16 00:38:15 +0000218 // scan the names to check whether the required "standard" ones are present;
219 // if not, we'll add our fixed versions here
220 bool mac_name[kStdNameCount] = { 0 };
221 bool win_name[kStdNameCount] = { 0 };
222 for (std::vector<NameRecord>::iterator name_iter = name->names.begin();
223 name_iter != name->names.end(); name_iter++) {
224 const uint16_t id = name_iter->name_id;
225 if (id >= kStdNameCount || kStdNames[id] == NULL) {
226 continue;
yusukes@chromium.orga4099a32009-11-12 01:43:51 +0000227 }
bashi@chromium.org92519592011-09-16 00:38:15 +0000228 if (name_iter->platform_id == 1) {
229 mac_name[id] = true;
230 continue;
yusukes@chromium.orgd257d182009-11-04 04:56:32 +0000231 }
bashi@chromium.org92519592011-09-16 00:38:15 +0000232 if (name_iter->platform_id == 3) {
233 win_name[id] = true;
234 continue;
yusukes@chromium.orga4099a32009-11-12 01:43:51 +0000235 }
236 }
237
bashi@chromium.org92519592011-09-16 00:38:15 +0000238 for (unsigned i = 0; i < kStdNameCount; ++i) {
239 if (kStdNames[i] == NULL) {
240 continue;
yusukes@chromium.orgd257d182009-11-04 04:56:32 +0000241 }
bashi@chromium.org92519592011-09-16 00:38:15 +0000242 if (!mac_name[i]) {
243 NameRecord rec(1 /* platform_id */, 0 /* encoding_id */,
244 0 /* language_id */ , i /* name_id */);
245 rec.text.assign(kStdNames[i]);
246 name->names.push_back(rec);
247 sort_required = true;
248 }
249 if (!win_name[i]) {
250 NameRecord rec(3 /* platform_id */, 1 /* encoding_id */,
251 1033 /* language_id */ , i /* name_id */);
252 AssignToUtf16BeFromAscii(&rec.text, std::string(kStdNames[i]));
253 name->names.push_back(rec);
254 sort_required = true;
255 }
256 }
257
258 if (sort_required) {
259 std::sort(name->names.begin(), name->names.end());
yusukes@chromium.orgd257d182009-11-04 04:56:32 +0000260 }
261
262 return true;
263}
264
bashi@chromium.org92519592011-09-16 00:38:15 +0000265bool ots_name_should_serialise(OpenTypeFile* file) {
266 return file->name != NULL;
267}
268
269bool ots_name_serialise(OTSStream* out, OpenTypeFile* file) {
270 const OpenTypeNAME* name = file->name;
271
272 uint16_t name_count = name->names.size();
273 uint16_t lang_tag_count = name->lang_tags.size();
274 uint16_t format = 0;
275 size_t string_offset = 6 + name_count * 12;
276
277 if (name->lang_tags.size() > 0) {
278 // lang tags require a format-1 name table
279 format = 1;
280 string_offset += 2 + lang_tag_count * 4;
281 }
282 if (string_offset > 0xffff) {
283 return OTS_FAILURE();
284 }
285 if (!out->WriteU16(format) ||
286 !out->WriteU16(name_count) ||
287 !out->WriteU16(string_offset)) {
288 return OTS_FAILURE();
289 }
290
291 std::string string_data;
292 for (std::vector<NameRecord>::const_iterator name_iter = name->names.begin();
293 name_iter != name->names.end(); name_iter++) {
294 const NameRecord& rec = *name_iter;
295 if (!out->WriteU16(rec.platform_id) ||
296 !out->WriteU16(rec.encoding_id) ||
297 !out->WriteU16(rec.language_id) ||
298 !out->WriteU16(rec.name_id) ||
299 !out->WriteU16(rec.text.size()) ||
300 !out->WriteU16(string_data.size()) ) {
301 return OTS_FAILURE();
302 }
303 string_data.append(rec.text);
304 }
305
306 if (format == 1) {
307 if (!out->WriteU16(lang_tag_count)) {
308 return OTS_FAILURE();
309 }
310 for (std::vector<std::string>::const_iterator tag_iter =
311 name->lang_tags.begin();
312 tag_iter != name->lang_tags.end(); tag_iter++) {
313 if (!out->WriteU16(tag_iter->size()) ||
314 !out->WriteU16(string_data.size())) {
315 return OTS_FAILURE();
316 }
317 string_data.append(*tag_iter);
318 }
319 }
320
321 if (!out->Write(string_data.data(), string_data.size())) {
322 return OTS_FAILURE();
323 }
324
325 return true;
326}
327
328void ots_name_free(OpenTypeFile* file) {
329 delete file->name;
yusukes@chromium.orgd257d182009-11-04 04:56:32 +0000330}
331
332} // namespace