// Copyright (c) 2011-2017 The OTS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "name.h" #include #include // name - Naming Table // http://www.microsoft.com/typography/otspec/name.htm namespace { bool ValidInPsName(char c) { return (c > 0x20 && c < 0x7f && !std::strchr("[](){}<>/%", c)); } bool CheckPsNameAscii(const std::string& name) { for (unsigned i = 0; i < name.size(); ++i) { if (!ValidInPsName(name[i])) { return false; } } return true; } bool CheckPsNameUtf16Be(const std::string& name) { if ((name.size() & 1) != 0) return false; for (unsigned i = 0; i < name.size(); i += 2) { if (name[i] != 0) { return false; } if (!ValidInPsName(name[i+1])) { return false; } } return true; } void AssignToUtf16BeFromAscii(std::string* target, const std::string& source) { target->resize(source.size() * 2); for (unsigned i = 0, j = 0; i < source.size(); i++) { (*target)[j++] = '\0'; (*target)[j++] = source[i]; } } } // namespace namespace ots { bool OpenTypeNAME::Parse(const uint8_t* data, size_t length) { Buffer table(data, length); uint16_t format = 0; if (!table.ReadU16(&format) || format > 1) { return Error("Failed to read table format or bad format %d", format); } uint16_t count = 0; if (!table.ReadU16(&count)) { return Error("Failed to read name count"); } uint16_t string_offset = 0; if (!table.ReadU16(&string_offset) || string_offset > length) { return Error("Failed to read or bad stringOffset"); } const char* string_base = reinterpret_cast(data) + string_offset; bool sort_required = false; // Read all the names, discarding any with invalid IDs, // and any where the offset/length would be outside the table. // A stricter alternative would be to reject the font if there // are invalid name records, but it's not clear that is necessary. for (unsigned i = 0; i < count; ++i) { NameRecord rec; uint16_t name_length, name_offset = 0; if (!table.ReadU16(&rec.platform_id) || !table.ReadU16(&rec.encoding_id) || !table.ReadU16(&rec.language_id) || !table.ReadU16(&rec.name_id) || !table.ReadU16(&name_length) || !table.ReadU16(&name_offset)) { return Error("Failed to read name entry %d", i); } // check platform & encoding, discard names with unknown values switch (rec.platform_id) { case 0: // Unicode if (rec.encoding_id > 6) { continue; } break; case 1: // Macintosh if (rec.encoding_id > 32) { continue; } break; case 2: // ISO if (rec.encoding_id > 2) { continue; } break; case 3: // Windows: IDs 7 to 9 are "reserved" if (rec.encoding_id > 6 && rec.encoding_id != 10) { continue; } break; case 4: // Custom (OTF Windows NT compatibility) if (rec.encoding_id > 255) { continue; } break; default: // unknown platform continue; } const unsigned name_end = static_cast(string_offset) + name_offset + name_length; if (name_end > length) { continue; } rec.text.resize(name_length); rec.text.assign(string_base + name_offset, name_length); if (rec.name_id == 6) { // PostScript name: check that it is valid, if not then discard it if (rec.platform_id == 1) { if (!CheckPsNameAscii(rec.text)) { continue; } } else if (rec.platform_id == 0 || rec.platform_id == 3) { if (!CheckPsNameUtf16Be(rec.text)) { continue; } } } if (!this->names.empty() && !(this->names.back() < rec)) { Warning("name records are not sorted."); sort_required = true; } this->names.push_back(rec); this->name_ids.insert(rec.name_id); } if (format == 1) { // extended name table format with language tags uint16_t lang_tag_count; if (!table.ReadU16(&lang_tag_count)) { return Error("Failed to read langTagCount"); } for (unsigned i = 0; i < lang_tag_count; ++i) { uint16_t tag_length = 0; uint16_t tag_offset = 0; if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) { return Error("Faile to read length or offset for langTagRecord %d", i); } const unsigned tag_end = static_cast(string_offset) + tag_offset + tag_length; if (tag_end > length) { return Error("bad end of tag %d > %ld for langTagRecord %d", tag_end, length, i); } // Lang tag is BCP 47 tag per the spec, the recommonded BCP 47 max tag // length is 35: // https://tools.ietf.org/html/bcp47#section-4.4.1 // We are being too generous and allowing for 100 (multiplied by 2 since // this is UTF-16 string). if (tag_length > 100 * 2) { return Error("Too long language tag for LangTagRecord %d: %d", i, tag_length); } std::string tag(string_base + tag_offset, tag_length); this->lang_tags.push_back(tag); } } if (table.offset() > string_offset) { // the string storage apparently overlapped the name/tag records; // consider this font to be badly broken return Error("Bad table offset %ld > %d", table.offset(), string_offset); } // check existence of required name strings (synthesize if necessary) // [0 - copyright - skip] // 1 - family // 2 - subfamily // [3 - unique ID - skip] // 4 - full name // 5 - version // 6 - postscript name static const uint16_t kStdNameCount = 7; static const char* kStdNames[kStdNameCount] = { NULL, "OTS derived font", "Unspecified", NULL, "OTS derived font", "1.000", "OTS-derived-font" }; // scan the names to check whether the required "standard" ones are present; // if not, we'll add our fixed versions here bool mac_name[kStdNameCount] = { 0 }; bool win_name[kStdNameCount] = { 0 }; for (const auto& name : this->names) { const uint16_t id = name.name_id; if (id >= kStdNameCount || kStdNames[id] == NULL) { continue; } if (name.platform_id == 1) { mac_name[id] = true; continue; } if (name.platform_id == 3) { win_name[id] = true; continue; } } for (uint16_t i = 0; i < kStdNameCount; ++i) { if (kStdNames[i] == NULL) { continue; } if (!mac_name[i] && !win_name[i]) { NameRecord mac_rec(1 /* platform_id */, 0 /* encoding_id */, 0 /* language_id */ , i /* name_id */); mac_rec.text.assign(kStdNames[i]); NameRecord win_rec(3 /* platform_id */, 1 /* encoding_id */, 1033 /* language_id */ , i /* name_id */); AssignToUtf16BeFromAscii(&win_rec.text, std::string(kStdNames[i])); this->names.push_back(mac_rec); this->names.push_back(win_rec); sort_required = true; } } if (sort_required) { std::sort(this->names.begin(), this->names.end()); } return true; } bool OpenTypeNAME::Serialize(OTSStream* out) { uint16_t name_count = static_cast(this->names.size()); uint16_t lang_tag_count = static_cast(this->lang_tags.size()); uint16_t format = 0; size_t string_offset = 6 + name_count * 12; if (this->lang_tags.size() > 0) { // lang tags require a format-1 name table format = 1; string_offset += 2 + lang_tag_count * 4; } if (string_offset > 0xffff) { return Error("Bad stringOffset: %ld", string_offset); } if (!out->WriteU16(format) || !out->WriteU16(name_count) || !out->WriteU16(static_cast(string_offset))) { return Error("Failed to write name header"); } std::string string_data; for (const auto& rec : this->names) { if (string_data.size() + rec.text.size() > std::numeric_limits::max() || !out->WriteU16(rec.platform_id) || !out->WriteU16(rec.encoding_id) || !out->WriteU16(rec.language_id) || !out->WriteU16(rec.name_id) || !out->WriteU16(static_cast(rec.text.size())) || !out->WriteU16(static_cast(string_data.size())) ) { return Error("Faile to write nameRecord"); } string_data.append(rec.text); } if (format == 1) { if (!out->WriteU16(lang_tag_count)) { return Error("Faile to write langTagCount"); } for (const auto& tag : this->lang_tags) { if (string_data.size() + tag.size() > std::numeric_limits::max() || !out->WriteU16(static_cast(tag.size())) || !out->WriteU16(static_cast(string_data.size()))) { return Error("Failed to write langTagRecord"); } string_data.append(tag); } } if (!out->Write(string_data.data(), string_data.size())) { return Error("Faile to write string data"); } return true; } bool OpenTypeNAME::IsValidNameId(uint16_t nameID, bool addIfMissing) { if (addIfMissing && !this->name_ids.count(nameID)) { bool added_unicode = false; bool added_macintosh = false; bool added_windows = false; const size_t names_size = this->names.size(); // original size for (size_t i = 0; i < names_size; ++i) switch (names[i].platform_id) { case 0: if (!added_unicode) { // If there is an existing NameRecord with platform_id == 0 (Unicode), // then add a NameRecord for the the specified nameID with arguments // 0 (Unicode), 0 (v1.0), 0 (unspecified language). this->names.emplace_back(0, 0, 0, nameID); this->names.back().text = "NoName"; added_unicode = true; } break; case 1: if (!added_macintosh) { // If there is an existing NameRecord with platform_id == 1 (Macintosh), // then add a NameRecord for the specified nameID with arguments // 1 (Macintosh), 0 (Roman), 0 (English). this->names.emplace_back(1, 0, 0, nameID); this->names.back().text = "NoName"; added_macintosh = true; } break; case 3: if (!added_windows) { // If there is an existing NameRecord with platform_id == 3 (Windows), // then add a NameRecord for the specified nameID with arguments // 3 (Windows), 1 (UCS), 1033 (US English). this->names.emplace_back(3, 1, 1033, nameID); this->names.back().text = "NoName"; added_windows = true; } break; } if (added_unicode || added_macintosh || added_windows) { std::sort(this->names.begin(), this->names.end()); this->name_ids.insert(nameID); } } return this->name_ids.count(nameID); } } // namespace