| // 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 <algorithm> |
| #include <cstring> |
| |
| #if defined(STARBOARD) |
| #include "starboard/client_porting/poem/string_poem.h" |
| #define STRCHR_OTS strchr |
| #else |
| #define STRCHR_OTS std::strchr |
| #endif |
| |
| // name - Naming Table |
| // http://www.microsoft.com/typography/otspec/name.htm |
| |
| namespace { |
| |
| bool ValidInPsName(char c) { |
| return (c > 0x20 && c < 0x7f && !STRCHR_OTS("[](){}<>/%", 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<const char*>(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<unsigned>(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<unsigned>(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); |
| } |
| 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 (std::vector<NameRecord>::iterator name_iter = this->names.begin(); |
| name_iter != this->names.end(); ++name_iter) { |
| const uint16_t id = name_iter->name_id; |
| if (id >= kStdNameCount || kStdNames[id] == NULL) { |
| continue; |
| } |
| if (name_iter->platform_id == 1) { |
| mac_name[id] = true; |
| continue; |
| } |
| if (name_iter->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<uint16_t>(this->names.size()); |
| uint16_t lang_tag_count = static_cast<uint16_t>(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<uint16_t>(string_offset))) { |
| return Error("Failed to write name header"); |
| } |
| |
| std::string string_data; |
| for (std::vector<NameRecord>::const_iterator name_iter = this->names.begin(); |
| name_iter != this->names.end(); ++name_iter) { |
| const NameRecord& rec = *name_iter; |
| if (string_data.size() + rec.text.size() > |
| std::numeric_limits<uint16_t>::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<uint16_t>(rec.text.size())) || |
| !out->WriteU16(static_cast<uint16_t>(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 (std::vector<std::string>::const_iterator tag_iter = |
| this->lang_tags.begin(); |
| tag_iter != this->lang_tags.end(); ++tag_iter) { |
| if (string_data.size() + tag_iter->size() > |
| std::numeric_limits<uint16_t>::max() || |
| !out->WriteU16(static_cast<uint16_t>(tag_iter->size())) || |
| !out->WriteU16(static_cast<uint16_t>(string_data.size()))) { |
| return Error("Failed to write langTagRecord"); |
| } |
| string_data.append(*tag_iter); |
| } |
| } |
| |
| 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 |
| |
| #undef STRCHR_OTS |