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