michael@0: // Copyright (c) 2011 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "name.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "cff.h" michael@0: michael@0: // name - Naming Table michael@0: // http://www.microsoft.com/typography/otspec/name.htm michael@0: michael@0: #define TABLE_NAME "name" michael@0: michael@0: namespace { michael@0: michael@0: bool ValidInPsName(char c) { michael@0: return (c > 0x20 && c < 0x7f && !std::strchr("[](){}<>/%", c)); michael@0: } michael@0: michael@0: bool CheckPsNameAscii(const std::string& name) { michael@0: for (unsigned i = 0; i < name.size(); ++i) { michael@0: if (!ValidInPsName(name[i])) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool CheckPsNameUtf16Be(const std::string& name) { michael@0: if ((name.size() & 1) != 0) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < name.size(); i += 2) { michael@0: if (name[i] != 0) { michael@0: return false; michael@0: } michael@0: if (!ValidInPsName(name[i+1])) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void AssignToUtf16BeFromAscii(std::string* target, michael@0: const std::string& source) { michael@0: target->resize(source.size() * 2); michael@0: for (unsigned i = 0, j = 0; i < source.size(); i++) { michael@0: (*target)[j++] = '\0'; michael@0: (*target)[j++] = source[i]; michael@0: } michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: michael@0: namespace ots { michael@0: michael@0: bool ots_name_parse(OpenTypeFile* file, const uint8_t* data, size_t length) { michael@0: Buffer table(data, length); michael@0: michael@0: OpenTypeNAME* name = new OpenTypeNAME; michael@0: file->name = name; michael@0: michael@0: uint16_t format = 0; michael@0: if (!table.ReadU16(&format) || format > 1) { michael@0: return OTS_FAILURE_MSG("Failed to read name table format or bad format %d", format); michael@0: } michael@0: michael@0: uint16_t count = 0; michael@0: if (!table.ReadU16(&count)) { michael@0: return OTS_FAILURE_MSG("Failed to read name count"); michael@0: } michael@0: michael@0: uint16_t string_offset = 0; michael@0: if (!table.ReadU16(&string_offset) || string_offset > length) { michael@0: return OTS_FAILURE_MSG("Failed to read strings offset"); michael@0: } michael@0: const char* string_base = reinterpret_cast(data) + michael@0: string_offset; michael@0: michael@0: NameRecord prev_record; michael@0: bool sort_required = false; michael@0: michael@0: // Read all the names, discarding any with invalid IDs, michael@0: // and any where the offset/length would be outside the table. michael@0: // A stricter alternative would be to reject the font if there michael@0: // are invalid name records, but it's not clear that is necessary. michael@0: for (unsigned i = 0; i < count; ++i) { michael@0: NameRecord rec; michael@0: uint16_t name_length, name_offset; michael@0: if (!table.ReadU16(&rec.platform_id) || michael@0: !table.ReadU16(&rec.encoding_id) || michael@0: !table.ReadU16(&rec.language_id) || michael@0: !table.ReadU16(&rec.name_id) || michael@0: !table.ReadU16(&name_length) || michael@0: !table.ReadU16(&name_offset)) { michael@0: return OTS_FAILURE_MSG("Failed to read name entry %d", i); michael@0: } michael@0: // check platform & encoding, discard names with unknown values michael@0: switch (rec.platform_id) { michael@0: case 0: // Unicode michael@0: if (rec.encoding_id > 6) { michael@0: continue; michael@0: } michael@0: break; michael@0: case 1: // Macintosh michael@0: if (rec.encoding_id > 32) { michael@0: continue; michael@0: } michael@0: break; michael@0: case 2: // ISO michael@0: if (rec.encoding_id > 2) { michael@0: continue; michael@0: } michael@0: break; michael@0: case 3: // Windows: IDs 7 to 9 are "reserved" michael@0: if (rec.encoding_id > 6 && rec.encoding_id != 10) { michael@0: continue; michael@0: } michael@0: break; michael@0: case 4: // Custom (OTF Windows NT compatibility) michael@0: if (rec.encoding_id > 255) { michael@0: continue; michael@0: } michael@0: break; michael@0: default: // unknown platform michael@0: continue; michael@0: } michael@0: michael@0: const unsigned name_end = static_cast(string_offset) + michael@0: name_offset + name_length; michael@0: if (name_end > length) { michael@0: continue; michael@0: } michael@0: rec.text.resize(name_length); michael@0: rec.text.assign(string_base + name_offset, name_length); michael@0: michael@0: if (rec.name_id == 6) { michael@0: // PostScript name: check that it is valid, if not then discard it michael@0: if (rec.platform_id == 1) { michael@0: if (file->cff && !file->cff->name.empty()) { michael@0: rec.text = file->cff->name; michael@0: } else if (!CheckPsNameAscii(rec.text)) { michael@0: continue; michael@0: } michael@0: } else if (rec.platform_id == 0 || rec.platform_id == 3) { michael@0: if (file->cff && !file->cff->name.empty()) { michael@0: AssignToUtf16BeFromAscii(&rec.text, file->cff->name); michael@0: } else if (!CheckPsNameUtf16Be(rec.text)) { michael@0: continue; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if ((i > 0) && !(prev_record < rec)) { michael@0: OTS_WARNING("name records are not sorted."); michael@0: sort_required = true; michael@0: } michael@0: michael@0: name->names.push_back(rec); michael@0: prev_record = rec; michael@0: } michael@0: michael@0: if (format == 1) { michael@0: // extended name table format with language tags michael@0: uint16_t lang_tag_count; michael@0: if (!table.ReadU16(&lang_tag_count)) { michael@0: return OTS_FAILURE_MSG("Failed to read language tag count"); michael@0: } michael@0: for (unsigned i = 0; i < lang_tag_count; ++i) { michael@0: uint16_t tag_length = 0; michael@0: uint16_t tag_offset = 0; michael@0: if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) { michael@0: return OTS_FAILURE_MSG("Faile to read tag length or offset"); michael@0: } michael@0: const unsigned tag_end = static_cast(string_offset) + michael@0: tag_offset + tag_length; michael@0: if (tag_end > length) { michael@0: return OTS_FAILURE_MSG("bad end of tag %d > %ld for name entry %d", tag_end, length, i); michael@0: } michael@0: std::string tag(string_base + tag_offset, tag_length); michael@0: name->lang_tags.push_back(tag); michael@0: } michael@0: } michael@0: michael@0: if (table.offset() > string_offset) { michael@0: // the string storage apparently overlapped the name/tag records; michael@0: // consider this font to be badly broken michael@0: return OTS_FAILURE_MSG("Bad table offset %ld > %d", table.offset(), string_offset); michael@0: } michael@0: michael@0: // check existence of required name strings (synthesize if necessary) michael@0: // [0 - copyright - skip] michael@0: // 1 - family michael@0: // 2 - subfamily michael@0: // [3 - unique ID - skip] michael@0: // 4 - full name michael@0: // 5 - version michael@0: // 6 - postscript name michael@0: static const unsigned kStdNameCount = 7; michael@0: static const char* kStdNames[kStdNameCount] = { michael@0: NULL, michael@0: "OTS derived font", michael@0: "Unspecified", michael@0: NULL, michael@0: "OTS derived font", michael@0: "1.000", michael@0: "OTS-derived-font" michael@0: }; michael@0: // The spec says that "In CFF OpenType fonts, these two name strings, when michael@0: // translated to ASCII, must also be identical to the font name as stored in michael@0: // the CFF's Name INDEX." And actually, Mac OS X's font parser requires that. michael@0: if (file->cff && !file->cff->name.empty()) { michael@0: kStdNames[6] = file->cff->name.c_str(); michael@0: } michael@0: michael@0: // scan the names to check whether the required "standard" ones are present; michael@0: // if not, we'll add our fixed versions here michael@0: bool mac_name[kStdNameCount] = { 0 }; michael@0: bool win_name[kStdNameCount] = { 0 }; michael@0: for (std::vector::iterator name_iter = name->names.begin(); michael@0: name_iter != name->names.end(); name_iter++) { michael@0: const uint16_t id = name_iter->name_id; michael@0: if (id >= kStdNameCount || kStdNames[id] == NULL) { michael@0: continue; michael@0: } michael@0: if (name_iter->platform_id == 1) { michael@0: mac_name[id] = true; michael@0: continue; michael@0: } michael@0: if (name_iter->platform_id == 3) { michael@0: win_name[id] = true; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: for (unsigned i = 0; i < kStdNameCount; ++i) { michael@0: if (kStdNames[i] == NULL) { michael@0: continue; michael@0: } michael@0: if (!mac_name[i]) { michael@0: NameRecord rec(1 /* platform_id */, 0 /* encoding_id */, michael@0: 0 /* language_id */ , i /* name_id */); michael@0: rec.text.assign(kStdNames[i]); michael@0: name->names.push_back(rec); michael@0: sort_required = true; michael@0: } michael@0: if (!win_name[i]) { michael@0: NameRecord rec(3 /* platform_id */, 1 /* encoding_id */, michael@0: 1033 /* language_id */ , i /* name_id */); michael@0: AssignToUtf16BeFromAscii(&rec.text, std::string(kStdNames[i])); michael@0: name->names.push_back(rec); michael@0: sort_required = true; michael@0: } michael@0: } michael@0: michael@0: if (sort_required) { michael@0: std::sort(name->names.begin(), name->names.end()); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool ots_name_should_serialise(OpenTypeFile* file) { michael@0: return file->name != NULL; michael@0: } michael@0: michael@0: bool ots_name_serialise(OTSStream* out, OpenTypeFile* file) { michael@0: const OpenTypeNAME* name = file->name; michael@0: michael@0: uint16_t name_count = name->names.size(); michael@0: uint16_t lang_tag_count = name->lang_tags.size(); michael@0: uint16_t format = 0; michael@0: size_t string_offset = 6 + name_count * 12; michael@0: michael@0: if (name->lang_tags.size() > 0) { michael@0: // lang tags require a format-1 name table michael@0: format = 1; michael@0: string_offset += 2 + lang_tag_count * 4; michael@0: } michael@0: if (string_offset > 0xffff) { michael@0: return OTS_FAILURE_MSG("Bad string offset %ld", string_offset); michael@0: } michael@0: if (!out->WriteU16(format) || michael@0: !out->WriteU16(name_count) || michael@0: !out->WriteU16(string_offset)) { michael@0: return OTS_FAILURE_MSG("Failed to write name header"); michael@0: } michael@0: michael@0: std::string string_data; michael@0: for (std::vector::const_iterator name_iter = name->names.begin(); michael@0: name_iter != name->names.end(); name_iter++) { michael@0: const NameRecord& rec = *name_iter; michael@0: if (!out->WriteU16(rec.platform_id) || michael@0: !out->WriteU16(rec.encoding_id) || michael@0: !out->WriteU16(rec.language_id) || michael@0: !out->WriteU16(rec.name_id) || michael@0: !out->WriteU16(rec.text.size()) || michael@0: !out->WriteU16(string_data.size()) ) { michael@0: return OTS_FAILURE_MSG("Faile to write name entry"); michael@0: } michael@0: string_data.append(rec.text); michael@0: } michael@0: michael@0: if (format == 1) { michael@0: if (!out->WriteU16(lang_tag_count)) { michael@0: return OTS_FAILURE_MSG("Faile to write language tag count"); michael@0: } michael@0: for (std::vector::const_iterator tag_iter = michael@0: name->lang_tags.begin(); michael@0: tag_iter != name->lang_tags.end(); tag_iter++) { michael@0: if (!out->WriteU16(tag_iter->size()) || michael@0: !out->WriteU16(string_data.size())) { michael@0: return OTS_FAILURE_MSG("Failed to write string"); michael@0: } michael@0: string_data.append(*tag_iter); michael@0: } michael@0: } michael@0: michael@0: if (!out->Write(string_data.data(), string_data.size())) { michael@0: return OTS_FAILURE_MSG("Faile to write string data"); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void ots_name_free(OpenTypeFile* file) { michael@0: delete file->name; michael@0: } michael@0: michael@0: } // namespace