//! A [Naming Table]( //! https://docs.microsoft.com/en-us/typography/opentype/spec/name) implementation. #[cfg(feature = "std")] use std::string::String; #[cfg(feature = "std")] use std::vec::Vec; use crate::parser::{FromData, LazyArray16, Offset, Offset16, Stream}; use crate::Language; /// A list of [name ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids)'s. pub mod name_id { #![allow(missing_docs)] pub const COPYRIGHT_NOTICE: u16 = 0; pub const FAMILY: u16 = 1; pub const SUBFAMILY: u16 = 2; pub const UNIQUE_ID: u16 = 3; pub const FULL_NAME: u16 = 4; pub const VERSION: u16 = 5; pub const POST_SCRIPT_NAME: u16 = 6; pub const TRADEMARK: u16 = 7; pub const MANUFACTURER: u16 = 8; pub const DESIGNER: u16 = 9; pub const DESCRIPTION: u16 = 10; pub const VENDOR_URL: u16 = 11; pub const DESIGNER_URL: u16 = 12; pub const LICENSE: u16 = 13; pub const LICENSE_URL: u16 = 14; // RESERVED = 15 pub const TYPOGRAPHIC_FAMILY: u16 = 16; pub const TYPOGRAPHIC_SUBFAMILY: u16 = 17; pub const COMPATIBLE_FULL: u16 = 18; pub const SAMPLE_TEXT: u16 = 19; pub const POST_SCRIPT_CID: u16 = 20; pub const WWS_FAMILY: u16 = 21; pub const WWS_SUBFAMILY: u16 = 22; pub const LIGHT_BACKGROUND_PALETTE: u16 = 23; pub const DARK_BACKGROUND_PALETTE: u16 = 24; pub const VARIATIONS_POST_SCRIPT_NAME_PREFIX: u16 = 25; } /// A [platform ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-ids). #[allow(missing_docs)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum PlatformId { Unicode, Macintosh, Iso, Windows, Custom, } impl FromData for PlatformId { const SIZE: usize = 2; #[inline] fn parse(data: &[u8]) -> Option { match u16::parse(data)? { 0 => Some(PlatformId::Unicode), 1 => Some(PlatformId::Macintosh), 2 => Some(PlatformId::Iso), 3 => Some(PlatformId::Windows), 4 => Some(PlatformId::Custom), _ => None, } } } #[inline] fn is_unicode_encoding(platform_id: PlatformId, encoding_id: u16) -> bool { // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids const WINDOWS_SYMBOL_ENCODING_ID: u16 = 0; const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1; match platform_id { PlatformId::Unicode => true, PlatformId::Windows => matches!( encoding_id, WINDOWS_SYMBOL_ENCODING_ID | WINDOWS_UNICODE_BMP_ENCODING_ID ), _ => false, } } #[derive(Clone, Copy)] struct NameRecord { platform_id: PlatformId, encoding_id: u16, language_id: u16, name_id: u16, length: u16, offset: Offset16, } impl FromData for NameRecord { const SIZE: usize = 12; #[inline] fn parse(data: &[u8]) -> Option { let mut s = Stream::new(data); Some(NameRecord { platform_id: s.read::()?, encoding_id: s.read::()?, language_id: s.read::()?, name_id: s.read::()?, length: s.read::()?, offset: s.read::()?, }) } } /// A [Name Record](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-records). #[derive(Clone, Copy)] pub struct Name<'a> { /// A platform ID. pub platform_id: PlatformId, /// A platform-specific encoding ID. pub encoding_id: u16, /// A language ID. pub language_id: u16, /// A [Name ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids). /// /// A predefined list of ID's can be found in the [`name_id`](name_id/index.html) module. pub name_id: u16, /// A raw name data. /// /// Can be in any encoding. Can be empty. pub name: &'a [u8], } impl<'a> Name<'a> { /// Returns the Name's data as a UTF-8 string. /// /// Only Unicode names are supported. And since they are stored as UTF-16BE, /// we can't return `&str` and have to allocate a `String`. /// /// Supports: /// - Unicode Platform ID /// - Windows Platform ID + Symbol /// - Windows Platform ID + Unicode BMP #[cfg(feature = "std")] #[inline(never)] pub fn to_string(&self) -> Option { if self.is_unicode() { self.name_from_utf16_be() } else { None } } /// Checks that the current Name data has a Unicode encoding. #[inline] pub fn is_unicode(&self) -> bool { is_unicode_encoding(self.platform_id, self.encoding_id) } #[cfg(feature = "std")] #[inline(never)] fn name_from_utf16_be(&self) -> Option { let mut name: Vec = Vec::new(); for c in LazyArray16::::new(self.name) { name.push(c); } String::from_utf16(&name).ok() } /// Returns a Name language. pub fn language(&self) -> Language { if self.platform_id == PlatformId::Windows { Language::windows_language(self.language_id) } else if self.platform_id == PlatformId::Macintosh && self.encoding_id == 0 && self.language_id == 0 { Language::English_UnitedStates } else { Language::Unknown } } } #[cfg(feature = "std")] impl<'a> core::fmt::Debug for Name<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { let name = self.to_string(); f.debug_struct("Name") .field("name", &name.as_deref().unwrap_or("unsupported encoding")) .field("platform_id", &self.platform_id) .field("encoding_id", &self.encoding_id) .field("language_id", &self.language_id) .field("language", &self.language()) .field("name_id", &self.name_id) .finish() } } #[cfg(not(feature = "std"))] impl<'a> core::fmt::Debug for Name<'a> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("Name") .field("name", &self.name) .field("platform_id", &self.platform_id) .field("encoding_id", &self.encoding_id) .field("language_id", &self.language_id) .field("language", &self.language()) .field("name_id", &self.name_id) .finish() } } /// A list of face names. #[derive(Clone, Copy, Default)] pub struct Names<'a> { records: LazyArray16<'a, NameRecord>, storage: &'a [u8], } impl<'a> Names<'a> { /// Returns a name at index. pub fn get(&self, index: u16) -> Option> { let record = self.records.get(index)?; let name_start = record.offset.to_usize(); let name_end = name_start + usize::from(record.length); let name = self.storage.get(name_start..name_end)?; Some(Name { platform_id: record.platform_id, encoding_id: record.encoding_id, language_id: record.language_id, name_id: record.name_id, name, }) } /// Returns a number of name records. pub fn len(&self) -> u16 { self.records.len() } /// Checks if there are any name records. pub fn is_empty(&self) -> bool { self.records.is_empty() } } impl core::fmt::Debug for Names<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Names {{ ... }}") } } impl<'a> IntoIterator for Names<'a> { type Item = Name<'a>; type IntoIter = NamesIter<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { NamesIter { names: self, index: 0, } } } /// An iterator over face names. #[derive(Clone, Copy)] #[allow(missing_debug_implementations)] pub struct NamesIter<'a> { names: Names<'a>, index: u16, } impl<'a> Iterator for NamesIter<'a> { type Item = Name<'a>; fn next(&mut self) -> Option { if self.index < self.names.len() { self.index += 1; self.names.get(self.index - 1) } else { None } } #[inline] fn count(self) -> usize { usize::from(self.names.len().saturating_sub(self.index)) } } /// A [Naming Table]( /// https://docs.microsoft.com/en-us/typography/opentype/spec/name). #[derive(Clone, Copy, Default, Debug)] pub struct Table<'a> { /// A list of names. pub names: Names<'a>, } impl<'a> Table<'a> { /// Parses a table from raw data. pub fn parse(data: &'a [u8]) -> Option { // https://docs.microsoft.com/en-us/typography/opentype/spec/name#naming-table-format-1 const LANG_TAG_RECORD_SIZE: u16 = 4; let mut s = Stream::new(data); let version = s.read::()?; let count = s.read::()?; let storage_offset = s.read::()?.to_usize(); if version == 0 { // Do nothing. } else if version == 1 { let lang_tag_count = s.read::()?; let lang_tag_len = lang_tag_count.checked_mul(LANG_TAG_RECORD_SIZE)?; s.advance(usize::from(lang_tag_len)); // langTagRecords } else { // Unsupported version. return None; } let records = s.read_array16::(count)?; if s.offset() < storage_offset { s.advance(storage_offset - s.offset()); } let storage = s.tail()?; Some(Table { names: Names { records, storage }, }) } }