bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 1 | // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 5 | #include "name.h" |
| 6 | |
| 7 | #include <algorithm> |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 8 | #include <cstring> |
| 9 | |
yusukes@chromium.org | a4099a3 | 2009-11-12 01:43:51 +0000 | [diff] [blame] | 10 | #include "cff.h" |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 11 | |
| 12 | // name - Naming Table |
| 13 | // http://www.microsoft.com/opentype/otspec/name.htm |
| 14 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 15 | namespace { |
| 16 | |
| 17 | bool ValidInPsName(char c) { |
| 18 | return (c > 0x20 && c < 0x7f && !std::strchr("[](){}<>/%", c)); |
| 19 | } |
| 20 | |
| 21 | bool 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 | |
| 30 | bool 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 | |
| 45 | void 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.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 57 | namespace ots { |
| 58 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 59 | bool ots_name_parse(OpenTypeFile* file, const uint8_t* data, size_t length) { |
| 60 | Buffer table(data, length); |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 61 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 62 | OpenTypeNAME* name = new OpenTypeNAME; |
| 63 | file->name = name; |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 64 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 65 | uint16_t format = 0; |
| 66 | if (!table.ReadU16(&format) || format > 1) { |
| 67 | return OTS_FAILURE(); |
| 68 | } |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 69 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 70 | 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.org | 89cec1f | 2011-09-21 06:29:58 +0000 | [diff] [blame] | 157 | OTS_WARNING("name records are not sorted."); |
| 158 | sort_required = true; |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 159 | } |
| 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.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 210 | }; |
yusukes@chromium.org | a4099a3 | 2009-11-12 01:43:51 +0000 | [diff] [blame] | 211 | // 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.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 215 | kStdNames[6] = file->cff->name.c_str(); |
yusukes@chromium.org | a4099a3 | 2009-11-12 01:43:51 +0000 | [diff] [blame] | 216 | } |
| 217 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 218 | // 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.org | a4099a3 | 2009-11-12 01:43:51 +0000 | [diff] [blame] | 227 | } |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 228 | if (name_iter->platform_id == 1) { |
| 229 | mac_name[id] = true; |
| 230 | continue; |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 231 | } |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 232 | if (name_iter->platform_id == 3) { |
| 233 | win_name[id] = true; |
| 234 | continue; |
yusukes@chromium.org | a4099a3 | 2009-11-12 01:43:51 +0000 | [diff] [blame] | 235 | } |
| 236 | } |
| 237 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 238 | for (unsigned i = 0; i < kStdNameCount; ++i) { |
| 239 | if (kStdNames[i] == NULL) { |
| 240 | continue; |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 241 | } |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 242 | 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.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 260 | } |
| 261 | |
| 262 | return true; |
| 263 | } |
| 264 | |
bashi@chromium.org | 9251959 | 2011-09-16 00:38:15 +0000 | [diff] [blame] | 265 | bool ots_name_should_serialise(OpenTypeFile* file) { |
| 266 | return file->name != NULL; |
| 267 | } |
| 268 | |
| 269 | bool 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 | |
| 328 | void ots_name_free(OpenTypeFile* file) { |
| 329 | delete file->name; |
yusukes@chromium.org | d257d18 | 2009-11-04 04:56:32 +0000 | [diff] [blame] | 330 | } |
| 331 | |
| 332 | } // namespace |