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<'_> = 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: Option = self.to_string();
189 f&mut DebugStruct<'_, '_>.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:"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}
336