ttf_parser/tables/
name.rs

1//! A [Naming Table](
2//! https://docs.microsoft.com/en-us/typography/opentype/spec/name) implementation.
3
4#[cfg(feature = "std")]
5use std::string::String;
6#[cfg(feature = "std")]
7use std::vec::Vec;
8
9use crate::parser::{FromData, LazyArray16, Offset, Offset16, Stream};
10use crate::Language;
11
12/// A list of [name ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids)'s.
13pub mod name_id {
14    #![allow(missing_docs)]
15
16    pub const COPYRIGHT_NOTICE: u16 = 0;
17    pub const FAMILY: u16 = 1;
18    pub const SUBFAMILY: u16 = 2;
19    pub const UNIQUE_ID: u16 = 3;
20    pub const FULL_NAME: u16 = 4;
21    pub const VERSION: u16 = 5;
22    pub const POST_SCRIPT_NAME: u16 = 6;
23    pub const TRADEMARK: u16 = 7;
24    pub const MANUFACTURER: u16 = 8;
25    pub const DESIGNER: u16 = 9;
26    pub const DESCRIPTION: u16 = 10;
27    pub const VENDOR_URL: u16 = 11;
28    pub const DESIGNER_URL: u16 = 12;
29    pub const LICENSE: u16 = 13;
30    pub const LICENSE_URL: u16 = 14;
31    //        RESERVED                                  = 15
32    pub const TYPOGRAPHIC_FAMILY: u16 = 16;
33    pub const TYPOGRAPHIC_SUBFAMILY: u16 = 17;
34    pub const COMPATIBLE_FULL: u16 = 18;
35    pub const SAMPLE_TEXT: u16 = 19;
36    pub const POST_SCRIPT_CID: u16 = 20;
37    pub const WWS_FAMILY: u16 = 21;
38    pub const WWS_SUBFAMILY: u16 = 22;
39    pub const LIGHT_BACKGROUND_PALETTE: u16 = 23;
40    pub const DARK_BACKGROUND_PALETTE: u16 = 24;
41    pub const VARIATIONS_POST_SCRIPT_NAME_PREFIX: u16 = 25;
42}
43
44/// A [platform ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#platform-ids).
45#[allow(missing_docs)]
46#[derive(Clone, Copy, PartialEq, Eq, Debug)]
47pub enum PlatformId {
48    Unicode,
49    Macintosh,
50    Iso,
51    Windows,
52    Custom,
53}
54
55impl FromData for PlatformId {
56    const SIZE: usize = 2;
57
58    #[inline]
59    fn parse(data: &[u8]) -> Option<Self> {
60        match u16::parse(data)? {
61            0 => Some(PlatformId::Unicode),
62            1 => Some(PlatformId::Macintosh),
63            2 => Some(PlatformId::Iso),
64            3 => Some(PlatformId::Windows),
65            4 => Some(PlatformId::Custom),
66            _ => None,
67        }
68    }
69}
70
71#[inline]
72fn is_unicode_encoding(platform_id: PlatformId, encoding_id: u16) -> bool {
73    // https://docs.microsoft.com/en-us/typography/opentype/spec/name#windows-encoding-ids
74    const WINDOWS_SYMBOL_ENCODING_ID: u16 = 0;
75    const WINDOWS_UNICODE_BMP_ENCODING_ID: u16 = 1;
76
77    match platform_id {
78        PlatformId::Unicode => true,
79        PlatformId::Windows => matches!(
80            encoding_id,
81            WINDOWS_SYMBOL_ENCODING_ID | WINDOWS_UNICODE_BMP_ENCODING_ID
82        ),
83        _ => false,
84    }
85}
86
87#[derive(Clone, Copy)]
88struct NameRecord {
89    platform_id: PlatformId,
90    encoding_id: u16,
91    language_id: u16,
92    name_id: u16,
93    length: u16,
94    offset: Offset16,
95}
96
97impl FromData for NameRecord {
98    const SIZE: usize = 12;
99
100    #[inline]
101    fn parse(data: &[u8]) -> Option<Self> {
102        let mut s = Stream::new(data);
103        Some(NameRecord {
104            platform_id: s.read::<PlatformId>()?,
105            encoding_id: s.read::<u16>()?,
106            language_id: s.read::<u16>()?,
107            name_id: s.read::<u16>()?,
108            length: s.read::<u16>()?,
109            offset: s.read::<Offset16>()?,
110        })
111    }
112}
113
114/// A [Name Record](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-records).
115#[derive(Clone, Copy)]
116pub struct Name<'a> {
117    /// A platform ID.
118    pub platform_id: PlatformId,
119    /// A platform-specific encoding ID.
120    pub encoding_id: u16,
121    /// A language ID.
122    pub language_id: u16,
123    /// A [Name ID](https://docs.microsoft.com/en-us/typography/opentype/spec/name#name-ids).
124    ///
125    /// A predefined list of ID's can be found in the [`name_id`](name_id/index.html) module.
126    pub name_id: u16,
127    /// A raw name data.
128    ///
129    /// Can be in any encoding. Can be empty.
130    pub name: &'a [u8],
131}
132
133impl<'a> Name<'a> {
134    /// Returns the Name's data as a UTF-8 string.
135    ///
136    /// Only Unicode names are supported. And since they are stored as UTF-16BE,
137    /// we can't return `&str` and have to allocate a `String`.
138    ///
139    /// Supports:
140    /// - Unicode Platform ID
141    /// - Windows Platform ID + Symbol
142    /// - Windows Platform ID + Unicode BMP
143    #[cfg(feature = "std")]
144    #[inline(never)]
145    pub fn to_string(&self) -> Option<String> {
146        if self.is_unicode() {
147            self.name_from_utf16_be()
148        } else {
149            None
150        }
151    }
152
153    /// Checks that the current Name data has a Unicode encoding.
154    #[inline]
155    pub fn is_unicode(&self) -> bool {
156        is_unicode_encoding(self.platform_id, self.encoding_id)
157    }
158
159    #[cfg(feature = "std")]
160    #[inline(never)]
161    fn name_from_utf16_be(&self) -> Option<String> {
162        let mut name: Vec<u16> = Vec::new();
163        for c in LazyArray16::<u16>::new(self.name) {
164            name.push(c);
165        }
166
167        String::from_utf16(&name).ok()
168    }
169
170    /// Returns a Name language.
171    pub fn language(&self) -> Language {
172        if self.platform_id == PlatformId::Windows {
173            Language::windows_language(self.language_id)
174        } else if self.platform_id == PlatformId::Macintosh
175            && self.encoding_id == 0
176            && self.language_id == 0
177        {
178            Language::English_UnitedStates
179        } else {
180            Language::Unknown
181        }
182    }
183}
184
185#[cfg(feature = "std")]
186impl<'a> core::fmt::Debug for Name<'a> {
187    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
188        let name = self.to_string();
189        f.debug_struct("Name")
190            .field("name", &name.as_deref().unwrap_or("unsupported encoding"))
191            .field("platform_id", &self.platform_id)
192            .field("encoding_id", &self.encoding_id)
193            .field("language_id", &self.language_id)
194            .field("language", &self.language())
195            .field("name_id", &self.name_id)
196            .finish()
197    }
198}
199
200#[cfg(not(feature = "std"))]
201impl<'a> core::fmt::Debug for Name<'a> {
202    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
203        f.debug_struct("Name")
204            .field("name", &self.name)
205            .field("platform_id", &self.platform_id)
206            .field("encoding_id", &self.encoding_id)
207            .field("language_id", &self.language_id)
208            .field("language", &self.language())
209            .field("name_id", &self.name_id)
210            .finish()
211    }
212}
213
214/// A list of face names.
215#[derive(Clone, Copy, Default)]
216pub struct Names<'a> {
217    records: LazyArray16<'a, NameRecord>,
218    storage: &'a [u8],
219}
220
221impl<'a> Names<'a> {
222    /// Returns a name at index.
223    pub fn get(&self, index: u16) -> Option<Name<'a>> {
224        let record = self.records.get(index)?;
225        let name_start = record.offset.to_usize();
226        let name_end = name_start + usize::from(record.length);
227        let name = self.storage.get(name_start..name_end)?;
228        Some(Name {
229            platform_id: record.platform_id,
230            encoding_id: record.encoding_id,
231            language_id: record.language_id,
232            name_id: record.name_id,
233            name,
234        })
235    }
236
237    /// Returns a number of name records.
238    pub fn len(&self) -> u16 {
239        self.records.len()
240    }
241
242    /// Checks if there are any name records.
243    pub fn is_empty(&self) -> bool {
244        self.records.is_empty()
245    }
246}
247
248impl core::fmt::Debug for Names<'_> {
249    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
250        write!(f, "Names {{ ... }}")
251    }
252}
253
254impl<'a> IntoIterator for Names<'a> {
255    type Item = Name<'a>;
256    type IntoIter = NamesIter<'a>;
257
258    #[inline]
259    fn into_iter(self) -> Self::IntoIter {
260        NamesIter {
261            names: self,
262            index: 0,
263        }
264    }
265}
266
267/// An iterator over face names.
268#[derive(Clone, Copy)]
269#[allow(missing_debug_implementations)]
270pub struct NamesIter<'a> {
271    names: Names<'a>,
272    index: u16,
273}
274
275impl<'a> Iterator for NamesIter<'a> {
276    type Item = Name<'a>;
277
278    fn next(&mut self) -> Option<Self::Item> {
279        if self.index < self.names.len() {
280            self.index += 1;
281            self.names.get(self.index - 1)
282        } else {
283            None
284        }
285    }
286
287    #[inline]
288    fn count(self) -> usize {
289        usize::from(self.names.len().saturating_sub(self.index))
290    }
291}
292
293/// A [Naming Table](
294/// https://docs.microsoft.com/en-us/typography/opentype/spec/name).
295#[derive(Clone, Copy, Default, Debug)]
296pub struct Table<'a> {
297    /// A list of names.
298    pub names: Names<'a>,
299}
300
301impl<'a> Table<'a> {
302    /// Parses a table from raw data.
303    pub fn parse(data: &'a [u8]) -> Option<Self> {
304        // https://docs.microsoft.com/en-us/typography/opentype/spec/name#naming-table-format-1
305        const LANG_TAG_RECORD_SIZE: u16 = 4;
306
307        let mut s = Stream::new(data);
308        let version = s.read::<u16>()?;
309        let count = s.read::<u16>()?;
310        let storage_offset = s.read::<Offset16>()?.to_usize();
311
312        if version == 0 {
313            // Do nothing.
314        } else if version == 1 {
315            let lang_tag_count = s.read::<u16>()?;
316            let lang_tag_len = lang_tag_count.checked_mul(LANG_TAG_RECORD_SIZE)?;
317            s.advance(usize::from(lang_tag_len)); // langTagRecords
318        } else {
319            // Unsupported version.
320            return None;
321        }
322
323        let records = s.read_array16::<NameRecord>(count)?;
324
325        if s.offset() < storage_offset {
326            s.advance(storage_offset - s.offset());
327        }
328
329        let storage = s.tail()?;
330
331        Some(Table {
332            names: Names { records, storage },
333        })
334    }
335}