//! `name` — Naming table. //! //! Contains human-readable strings like family name, style name, copyright, etc. //! Reference: use crate::font::parse::Reader; use crate::font::FontError; /// Parsed `name` table. #[derive(Debug)] pub struct NameTable { /// All name records extracted from the table. pub records: Vec, } /// A single name record. #[derive(Debug)] pub struct NameRecord { /// Platform ID (0 = Unicode, 1 = Macintosh, 3 = Windows). pub platform_id: u16, /// Encoding ID (platform-specific). pub encoding_id: u16, /// Language ID. pub language_id: u16, /// Name ID (1 = family, 2 = subfamily, 4 = full name, 6 = PostScript name, etc.). pub name_id: u16, /// The decoded string value. pub value: String, } impl NameTable { /// Parse the `name` table from raw bytes. pub fn parse(data: &[u8]) -> Result { let r = Reader::new(data); if r.len() < 6 { return Err(FontError::MalformedTable("name")); } // format(2) + count(2) + stringOffset(2) let count = r.u16(2)? as usize; let string_offset = r.u16(4)? as usize; let mut records = Vec::with_capacity(count); for i in 0..count { let base = 6 + i * 12; if base + 12 > data.len() { break; } let platform_id = r.u16(base)?; let encoding_id = r.u16(base + 2)?; let language_id = r.u16(base + 4)?; let name_id = r.u16(base + 6)?; let length = r.u16(base + 8)? as usize; let offset = r.u16(base + 10)? as usize; let str_start = string_offset + offset; if str_start + length > data.len() { continue; } let raw = r.slice(str_start, length)?; let value = decode_name_string(platform_id, encoding_id, raw); records.push(NameRecord { platform_id, encoding_id, language_id, name_id, value, }); } Ok(NameTable { records }) } /// Get the font family name (name ID 1). /// /// Prefers Windows/Unicode platform, falls back to any platform. pub fn family_name(&self) -> Option<&str> { self.get_name(1) } /// Get the font subfamily/style name (name ID 2, e.g. "Regular", "Bold"). pub fn subfamily_name(&self) -> Option<&str> { self.get_name(2) } /// Get the full font name (name ID 4). pub fn full_name(&self) -> Option<&str> { self.get_name(4) } /// Get a name string by name ID. /// /// Prefers Windows platform (3) with English, then any platform. fn get_name(&self, name_id: u16) -> Option<&str> { // Prefer Windows platform (3), English (language_id 0x0409). let win_en = self .records .iter() .find(|r| r.name_id == name_id && r.platform_id == 3 && r.language_id == 0x0409); if let Some(rec) = win_en { if !rec.value.is_empty() { return Some(&rec.value); } } // Fall back to any Windows platform record. let win = self .records .iter() .find(|r| r.name_id == name_id && r.platform_id == 3); if let Some(rec) = win { if !rec.value.is_empty() { return Some(&rec.value); } } // Fall back to any record. self.records .iter() .find(|r| r.name_id == name_id && !r.value.is_empty()) .map(|r| r.value.as_str()) } } /// Decode a name string based on platform/encoding. fn decode_name_string(platform_id: u16, encoding_id: u16, data: &[u8]) -> String { match platform_id { 0 => { // Unicode platform — always UTF-16BE. decode_utf16be(data) } 1 => { // Macintosh platform. if encoding_id == 0 { // Mac Roman. decode_mac_roman(data) } else { // Other Mac encodings — treat as ASCII fallback. String::from_utf8_lossy(data).into_owned() } } 3 => { // Windows platform — encoding 1 = UTF-16BE, encoding 10 = UTF-16BE. match encoding_id { 1 | 10 => decode_utf16be(data), 0 => { // Symbol encoding — treat as UTF-16BE. decode_utf16be(data) } _ => String::from_utf8_lossy(data).into_owned(), } } _ => String::from_utf8_lossy(data).into_owned(), } } fn decode_utf16be(data: &[u8]) -> String { let mut chars = Vec::with_capacity(data.len() / 2); let mut i = 0; while i + 1 < data.len() { let unit = u16::from_be_bytes([data[i], data[i + 1]]); i += 2; // Handle surrogate pairs. if (0xD800..=0xDBFF).contains(&unit) { if i + 1 < data.len() { let lo = u16::from_be_bytes([data[i], data[i + 1]]); if (0xDC00..=0xDFFF).contains(&lo) { i += 2; let cp = 0x10000 + ((unit as u32 - 0xD800) << 10) + (lo as u32 - 0xDC00); if let Some(ch) = char::from_u32(cp) { chars.push(ch); } continue; } } // Lone surrogate — skip. continue; } if let Some(ch) = char::from_u32(unit as u32) { chars.push(ch); } } chars.into_iter().collect() } fn decode_mac_roman(data: &[u8]) -> String { // Mac Roman: 0x00-0x7F are ASCII, 0x80-0xFF map to specific Unicode code points. static MAC_ROMAN_HIGH: [u16; 128] = [ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7, ]; let mut s = String::with_capacity(data.len()); for &b in data { if b < 0x80 { s.push(b as char); } else { let cp = MAC_ROMAN_HIGH[(b - 0x80) as usize]; if let Some(ch) = char::from_u32(cp as u32) { s.push(ch); } } } s }