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