359 lines
11 KiB
C++
359 lines
11 KiB
C++
// 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>
|
|
|
|
// 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<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);
|
|
}
|
|
// 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<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 (const auto& rec : this->names) {
|
|
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 (const auto& tag : this->lang_tags) {
|
|
if (string_data.size() + tag.size() >
|
|
std::numeric_limits<uint16_t>::max() ||
|
|
!out->WriteU16(static_cast<uint16_t>(tag.size())) ||
|
|
!out->WriteU16(static_cast<uint16_t>(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
|