Skip to main content

read_fonts/model/
name.rs

1//! Names of fonts and glyphs.
2
3mod glyph;
4
5pub(crate) use glyph::{glyph_name, glyph_names, GlyphNameIndex};
6#[doc(inline)]
7pub use glyph::{GlyphName, GlyphNameSource};
8
9use crate::{
10    ps::{
11        cff::{CffFontRef, Metadata as CffMetadata},
12        type1::Type1Font,
13    },
14    tables::name::{language_id_to_bcp47, Encoding, Name as SfntName, NameRecord, NameString},
15    FontData, FontRead, TableProvider,
16};
17use core::cmp::Ordering;
18use core::fmt;
19
20#[doc(inline)]
21pub use crate::types::NameId;
22
23/// Access to names in a font.
24///
25/// SFNT names come from the `name` table. For Type 1 fonts, the available
26/// notice, family, weight, full, version, and PostScript names are exposed as
27/// name IDs 0, 1, 2, 4, 5, and 6, respectively. A Type 1 `Notice` may contain
28/// either copyright or trademark text; it is exposed as ID 0. The axes of a
29/// multiple master font are named by IDs from 256, one per axis, which
30/// [`Axis::name`](super::variation::Axis::name) returns. Standalone CFF
31/// fonts expose family, weight, full, and PostScript names as IDs 1, 2, 4,
32/// and 6, and copyright, version, and notice as IDs 0, 5, and 7.
33#[derive(Clone, Copy)]
34pub struct Names<'a> {
35    source: Source<'a>,
36}
37
38#[derive(Clone, Copy)]
39enum Source<'a> {
40    /// The data of the `name` table, if the font has one.
41    Sfnt(Option<FontData<'a>>),
42    Type1(&'a Type1Font),
43    StandaloneCff(Option<CffMetadata<'a>>),
44}
45
46impl<'a> Names<'a> {
47    /// Creates a name facade from an SFNT table provider.
48    pub fn from_sfnt(tables: &impl TableProvider<'a>) -> Self {
49        Self {
50            source: Source::Sfnt(tables.name().ok().map(|name| name.offset_data())),
51        }
52    }
53
54    /// Creates a name facade from a Type 1 font.
55    pub fn from_type1(font: &'a Type1Font) -> Self {
56        Self {
57            source: Source::Type1(font),
58        }
59    }
60
61    /// Creates a name facade from a standalone CFF font.
62    pub fn from_cff(font: &CffFontRef<'a>) -> Self {
63        Self {
64            source: Source::StandaloneCff(font.metadata()),
65        }
66    }
67
68    /// Returns the name entry for the given identifier.
69    ///
70    /// Returns `None` if the font has no name with this identifier.
71    pub fn get(&self, id: NameId) -> Option<Name<'a>> {
72        // Records are sorted by platform, encoding and language before
73        // identifier, so those for one identifier are spread out. The first
74        // is kept so the strings are found without scanning the records
75        // before it again.
76        let start = match &self.source {
77            Source::Sfnt(data) => sfnt_name(*data)?
78                .name_record()
79                .iter()
80                .position(|record| record.name_id() == id)?,
81            Source::Type1(font) => type1_name(font, id).map(|_| 0)?,
82            Source::StandaloneCff(meta) => standalone_cff_name(meta.as_ref()?, id).map(|_| 0)?,
83        };
84        Some(Name {
85            source: self.source,
86            id,
87            start,
88        })
89    }
90}
91
92/// Reads the `name` table from its data.
93fn sfnt_name(data: Option<FontData<'_>>) -> Option<SfntName<'_>> {
94    SfntName::read(data?).ok()
95}
96
97/// Returns the Type 1 string for a name identifier.
98fn type1_name(font: &Type1Font, id: NameId) -> Option<&str> {
99    match id {
100        NameId::COPYRIGHT_NOTICE => font.notice(),
101        NameId::FAMILY_NAME => font.family_name(),
102        NameId::SUBFAMILY_NAME => font.weight(),
103        NameId::FULL_NAME => font.full_name(),
104        NameId::VERSION_STRING => font.version(),
105        NameId::POSTSCRIPT_NAME => font.name(),
106        id => {
107            let index = id.to_u16().checked_sub(TYPE1_AXIS_NAMES)?;
108            font.multiple_master()?.axes().get(index as usize)?.name()
109        }
110    }
111}
112
113/// The name ID of the first axis of a Type 1 multiple master font. Each
114/// later axis takes the next.
115pub(crate) const TYPE1_AXIS_NAMES: u16 = 256;
116
117/// Returns the standalone CFF string for a name identifier.
118fn standalone_cff_name<'a>(meta: &CffMetadata<'a>, id: NameId) -> Option<&'a str> {
119    match id {
120        NameId::COPYRIGHT_NOTICE => meta.copyright(),
121        NameId::FAMILY_NAME => meta.family_name(),
122        NameId::SUBFAMILY_NAME => meta.weight(),
123        NameId::FULL_NAME => meta.full_name(),
124        NameId::VERSION_STRING => meta.version(),
125        NameId::POSTSCRIPT_NAME => meta.name(),
126        NameId::TRADEMARK => meta.notice(),
127        _ => None,
128    }
129}
130
131/// The localized strings for one name identifier.
132#[derive(Clone, Copy)]
133pub struct Name<'a> {
134    source: Source<'a>,
135    id: NameId,
136    /// The index of the first `name` record with this identifier.
137    start: usize,
138}
139
140impl<'a> Name<'a> {
141    /// Returns the identifier for this name.
142    pub fn id(&self) -> NameId {
143        self.id
144    }
145
146    /// Returns `en-US` or `en` if present, falling back to the first entry.
147    ///
148    /// This prefers `en-US`, then `en`, then a name without a language tag,
149    /// and finally the first name in table order. Records with unsupported
150    /// encodings are skipped. Returns `None` if no decodable name with this
151    /// identifier is available.
152    pub fn english_or_first(&self) -> Option<Localized<'a>> {
153        let mut best_rank = -1;
154        let mut best = None;
155        for (index, name) in self.iter().enumerate() {
156            let rank = match (index, name.language().as_ref()) {
157                (_, Some(language)) if *language == "en-US" => return Some(name),
158                (_, Some(language)) if *language == "en" => 2,
159                (_, None) => 1,
160                (0, _) => 0,
161                _ => continue,
162            };
163            if rank > best_rank {
164                best_rank = rank;
165                best = Some(name);
166            }
167        }
168        best
169    }
170
171    /// Iterates over the localized strings for this identifier, in table order.
172    ///
173    /// Records with unsupported encodings are skipped. Malformed text in a
174    /// supported encoding may contain replacement characters.
175    pub fn iter(&self) -> impl Iterator<Item = Localized<'a>> + 'a {
176        let (id, start) = (self.id, self.start);
177        let sfnt = match self.source {
178            Source::Sfnt(data) => sfnt_name(data),
179            _ => None,
180        };
181        let sfnt = sfnt.into_iter().flat_map(move |table| {
182            table
183                .name_record()
184                .get(start..)
185                .unwrap_or_default()
186                .iter()
187                .filter(move |record| record.name_id() == id)
188                .filter_map(move |record| Localized::from_record(&table, record))
189        });
190        let type1 = match &self.source {
191            Source::Type1(font) => type1_name(font, id),
192            _ => None,
193        }
194        .map(|name| Localized {
195            name: Encoded::from_str(name),
196            language: None,
197            sfnt_record: None,
198        });
199        let standalone_cff = match &self.source {
200            Source::StandaloneCff(Some(meta)) => standalone_cff_name(meta, id),
201            _ => None,
202        }
203        .map(|name| Localized {
204            name: Encoded::from_str(name),
205            language: None,
206            sfnt_record: None,
207        });
208        sfnt.chain(type1).chain(standalone_cff)
209    }
210}
211
212/// A font name and its optional BCP 47 language tag.
213///
214/// The language is `None` when the source supplies no recognized language or
215/// when the name comes from a Type 1 or standalone CFF font.
216#[derive(Clone, Copy, Debug)]
217pub struct Localized<'a> {
218    name: Encoded<'a>,
219    language: Option<Encoded<'a>>,
220    sfnt_record: Option<NameRecord>,
221}
222
223impl<'a> Localized<'a> {
224    /// Returns the encoded name text.
225    pub fn name(self) -> Encoded<'a> {
226        self.name
227    }
228
229    /// Returns the encoded language tag, if available.
230    pub fn language(self) -> Option<Encoded<'a>> {
231        self.language
232    }
233
234    /// Returns the source `name` record for an SFNT name, or `None` for Type 1 or CFF.
235    pub fn sfnt_record(self) -> Option<NameRecord> {
236        self.sfnt_record
237    }
238
239    fn from_record(table: &SfntName<'a>, record: &NameRecord) -> Option<Self> {
240        if Encoding::new(record.platform_id(), record.encoding_id()) == Encoding::Unknown {
241            return None;
242        }
243        let name = Encoded::from_name_string(record.string(table.string_data()).ok()?);
244        let language = if table.version() == 1 && record.language_id() >= 0x8000 {
245            let index = (record.language_id() - 0x8000) as usize;
246            table
247                .lang_tag_record()
248                .and_then(|tags| tags.get(index))
249                .and_then(|tag| tag.lang_tag(table.string_data()).ok())
250                .filter(|tag| tag.chars().all(|ch| ch.is_ascii()))
251                .map(Encoded::from_name_string)
252        } else {
253            language_id_to_bcp47(record.platform_id(), record.language_id()).map(Encoded::from_str)
254        };
255        Some(Self {
256            name,
257            language,
258            sfnt_record: Some(*record),
259        })
260    }
261}
262
263/// A name or language tag in its source encoding.
264///
265/// SFNT UTF-16BE and Mac Roman strings are decoded when displayed or compared,
266/// without allocating. Type 1 and standalone CFF names and built-in language
267/// tags borrow UTF-8 text directly.
268#[derive(Clone, Copy)]
269pub struct Encoded<'a>(EncodedRepr<'a>);
270
271#[derive(Clone, Copy)]
272enum EncodedRepr<'a> {
273    Sfnt(NameString<'a>),
274    Utf8(&'a str),
275}
276
277impl<'a> Encoded<'a> {
278    fn from_name_string(name: NameString<'a>) -> Self {
279        Self(EncodedRepr::Sfnt(name))
280    }
281
282    fn from_str(name: &'a str) -> Self {
283        Self(EncodedRepr::Utf8(name))
284    }
285
286    /// Iterates over decoded Unicode characters.
287    pub fn chars(&self) -> impl Iterator<Item = char> + 'a {
288        let sfnt = match self.0 {
289            EncodedRepr::Sfnt(name) => Some(name),
290            _ => None,
291        };
292        let utf8 = match self.0 {
293            EncodedRepr::Utf8(name) => Some(name),
294            _ => None,
295        };
296        sfnt.into_iter()
297            .flat_map(|name| name.chars())
298            .chain(utf8.into_iter().flat_map(str::chars))
299    }
300
301    /// Returns whether the decoded string begins with `prefix`.
302    pub fn starts_with(&self, prefix: &str) -> bool {
303        let mut chars = self.chars();
304        prefix.chars().all(|ch| chars.next() == Some(ch))
305    }
306}
307
308impl fmt::Display for Encoded<'_> {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        for ch in self.chars() {
311            ch.fmt(f)?;
312        }
313        Ok(())
314    }
315}
316
317impl fmt::Debug for Encoded<'_> {
318    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319        write!(f, "\"{self}\"")
320    }
321}
322
323impl<'a, 'b> PartialEq<Encoded<'b>> for Encoded<'a> {
324    fn eq(&self, other: &Encoded<'b>) -> bool {
325        self.chars().eq(other.chars())
326    }
327}
328
329impl Eq for Encoded<'_> {}
330
331impl<'a, 'b> PartialOrd<Encoded<'b>> for Encoded<'a> {
332    fn partial_cmp(&self, other: &Encoded<'b>) -> Option<Ordering> {
333        Some(self.chars().cmp(other.chars()))
334    }
335}
336
337impl PartialEq<&str> for Encoded<'_> {
338    fn eq(&self, other: &&str) -> bool {
339        self.chars().eq(other.chars())
340    }
341}
342
343impl PartialOrd<&str> for Encoded<'_> {
344    fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
345        Some(self.chars().cmp(other.chars()))
346    }
347}
348
349impl PartialEq<Encoded<'_>> for &str {
350    fn eq(&self, other: &Encoded<'_>) -> bool {
351        other == self
352    }
353}
354
355impl PartialOrd<Encoded<'_>> for &str {
356    fn partial_cmp(&self, other: &Encoded<'_>) -> Option<Ordering> {
357        Some(self.chars().cmp(other.chars()))
358    }
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364    use crate::{model::Font, types::Tag, FontData};
365    use alloc::vec::Vec;
366
367    // The iterators borrow the font, not the name or string they came from,
368    // so they outlive both.
369    #[test]
370    fn iterators_outlive_their_names() {
371        let font = Font::new(font_test_data::NAMES_ONLY, 0).unwrap();
372        let localized = font.names().get(NameId::FAMILY_NAME).unwrap().iter();
373        let chars = font
374            .names()
375            .get(NameId::FAMILY_NAME)
376            .unwrap()
377            .english_or_first()
378            .unwrap()
379            .name()
380            .chars();
381        assert!(localized.count() > 0);
382        assert!(chars.count() > 0);
383    }
384
385    struct NameProvider<'a>(&'a [u8]);
386
387    impl<'a> TableProvider<'a> for NameProvider<'a> {
388        fn data_for_tag(&self, tag: Tag) -> Option<FontData<'a>> {
389            (tag == Tag::new(b"name")).then(|| FontData::new(self.0))
390        }
391    }
392
393    fn name_table(records: &[(u16, u16, u16, NameId, &str)], tags: &[&str]) -> Vec<u8> {
394        let mut bytes = Vec::new();
395        let version = if tags.is_empty() { 0_u16 } else { 1 };
396        let storage_offset = 6
397            + records.len() * 12
398            + if tags.is_empty() {
399                0
400            } else {
401                2 + tags.len() * 4
402            };
403        bytes.extend_from_slice(&version.to_be_bytes());
404        bytes.extend_from_slice(&(records.len() as u16).to_be_bytes());
405        bytes.extend_from_slice(&(storage_offset as u16).to_be_bytes());
406        let mut storage = Vec::new();
407        for &(platform, encoding, language, id, text) in records {
408            let encoded = if platform == 1 {
409                text.as_bytes().to_vec()
410            } else {
411                text.encode_utf16().flat_map(u16::to_be_bytes).collect()
412            };
413            for value in [
414                platform,
415                encoding,
416                language,
417                id.to_u16(),
418                encoded.len() as u16,
419                storage.len() as u16,
420            ] {
421                bytes.extend_from_slice(&value.to_be_bytes());
422            }
423            storage.extend_from_slice(&encoded);
424        }
425        if !tags.is_empty() {
426            bytes.extend_from_slice(&(tags.len() as u16).to_be_bytes());
427            for tag in tags {
428                let encoded: Vec<u8> = tag.encode_utf16().flat_map(u16::to_be_bytes).collect();
429                bytes.extend_from_slice(&(encoded.len() as u16).to_be_bytes());
430                bytes.extend_from_slice(&(storage.len() as u16).to_be_bytes());
431                storage.extend_from_slice(&encoded);
432            }
433        }
434        bytes.extend_from_slice(&storage);
435        bytes
436    }
437
438    #[test]
439    fn selects_english_by_skrifa_priority() {
440        let data = name_table(
441            &[
442                (3, 1, 0x040c, NameId::FAMILY_NAME, "Français"),
443                (0, 4, 0, NameId::FAMILY_NAME, "Bare"),
444                (1, 0, 0, NameId::FAMILY_NAME, "English"),
445                (3, 1, 0x0409, NameId::FAMILY_NAME, "American English"),
446            ],
447            &[],
448        );
449        let name = Names::from_sfnt(&NameProvider(&data))
450            .get(NameId::FAMILY_NAME)
451            .unwrap();
452        let entries: Vec<_> = name.iter().collect();
453        assert_eq!(entries.len(), 4);
454        assert_eq!(entries[0].name(), "Français");
455        assert_eq!(entries[0].language().unwrap(), "fr-FR");
456        let record = entries[0].sfnt_record().unwrap();
457        assert_eq!(record.name_id(), NameId::FAMILY_NAME);
458        assert_eq!(record.platform_id(), 3);
459        assert_eq!(record.encoding_id(), 1);
460        assert_eq!(record.language_id(), 0x040c);
461        assert!(entries[1].language().is_none());
462        assert_eq!(entries[2].language().unwrap(), "en");
463        let best = name.english_or_first().unwrap();
464        assert_eq!(best.name(), "American English");
465        assert_eq!(best.language().unwrap(), "en-US");
466        assert_eq!(best.name().to_string(), "American English");
467    }
468
469    // A missing identifier has no entry, and an entry starting partway
470    // through the records still finds every string for its identifier.
471    #[test]
472    fn get_finds_an_identifier_wherever_its_records_start() {
473        let data = name_table(
474            &[
475                (1, 0, 0, NameId::FAMILY_NAME, "Mac Family"),
476                (1, 0, 0, NameId::FULL_NAME, "Mac Full"),
477                (3, 1, 0x0409, NameId::FAMILY_NAME, "Windows Family"),
478                (3, 1, 0x0409, NameId::FULL_NAME, "Windows Full"),
479            ],
480            &[],
481        );
482        let names = Names::from_sfnt(&NameProvider(&data));
483        assert!(names.get(NameId::DESCRIPTION).is_none());
484        let full: Vec<_> = names
485            .get(NameId::FULL_NAME)
486            .unwrap()
487            .iter()
488            .map(|name| name.name().to_string())
489            .collect();
490        assert_eq!(full, ["Mac Full", "Windows Full"]);
491        assert_eq!(names.get(NameId::FAMILY_NAME).unwrap().iter().count(), 2);
492    }
493
494    #[test]
495    fn skips_names_with_unsupported_encodings() {
496        let data = name_table(
497            &[
498                (3, 2, 0x0409, NameId::FAMILY_NAME, "Unsupported English"),
499                (3, 1, 0x040c, NameId::FAMILY_NAME, "Français"),
500            ],
501            &[],
502        );
503        let name = Names::from_sfnt(&NameProvider(&data))
504            .get(NameId::FAMILY_NAME)
505            .unwrap();
506        let entries: Vec<_> = name.iter().collect();
507        assert_eq!(entries.len(), 1);
508        assert_eq!(entries[0].name(), "Français");
509        assert_eq!(name.english_or_first().unwrap().name(), "Français");
510    }
511
512    #[test]
513    fn reads_version_one_language_tags() {
514        let data = name_table(
515            &[(3, 1, 0x8000, NameId::FULL_NAME, "Tagged Name")],
516            &["en-GB"],
517        );
518        let name = Names::from_sfnt(&NameProvider(&data))
519            .get(NameId::FULL_NAME)
520            .unwrap();
521        let value = name.english_or_first().unwrap();
522        assert_eq!(value.name(), "Tagged Name");
523        assert_eq!(value.language().unwrap(), "en-GB");
524
525        let invalid = name_table(&[(3, 1, 0x8000, NameId::FULL_NAME, "Tagged Name")], &["é"]);
526        let value = Names::from_sfnt(&NameProvider(&invalid))
527            .get(NameId::FULL_NAME)
528            .unwrap()
529            .english_or_first()
530            .unwrap();
531        assert_eq!(value.name(), "Tagged Name");
532        assert!(value.language().is_none());
533
534        let long_tag = "en-Latn-US-x-private-custom-segment";
535        assert!(long_tag.len() > 30);
536        let data = name_table(
537            &[(3, 1, 0x8000, NameId::FULL_NAME, "Tagged Name")],
538            &[long_tag],
539        );
540        let value = Names::from_sfnt(&NameProvider(&data))
541            .get(NameId::FULL_NAME)
542            .unwrap()
543            .english_or_first()
544            .unwrap();
545        assert_eq!(value.language().unwrap(), long_tag);
546    }
547
548    #[test]
549    fn exposes_type1_names_by_sfnt_id() {
550        let data = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB;
551        let font = Font::new(data, 0).unwrap();
552        for (id, expected) in [
553            (
554                NameId::COPYRIGHT_NOTICE,
555                "Copyright 2015-2021 Google LLC. All Rights Reserved.",
556            ),
557            (NameId::FAMILY_NAME, "Noto Serif"),
558            (NameId::SUBFAMILY_NAME, "Book"),
559            (NameId::FULL_NAME, "Noto Serif Regular"),
560            (
561                NameId::VERSION_STRING,
562                "2.007; ttfautohint (v1.8) -l 8 -r 50 -G 200 -x 14 -D latn -f none -a qsq -X \"\"",
563            ),
564            (NameId::POSTSCRIPT_NAME, "NotoSerif-Regular"),
565        ] {
566            let name = font.names().get(id).unwrap();
567            let value = name.english_or_first().unwrap();
568            assert_eq!(value.name(), expected);
569            assert!(value.language().is_none());
570            assert!(value.sfnt_record().is_none());
571            assert_eq!(name.iter().count(), 1);
572        }
573        assert!(font.names().get(NameId::DESCRIPTION).is_none());
574
575        let type1 = Type1Font::new(data).unwrap();
576        assert_eq!(
577            Names::from_type1(&type1)
578                .get(NameId::POSTSCRIPT_NAME)
579                .unwrap()
580                .english_or_first()
581                .unwrap()
582                .name(),
583            "NotoSerif-Regular"
584        );
585    }
586
587    #[test]
588    fn encoded_strings_compare_and_match_prefixes_without_decoding_to_string() {
589        let data = name_table(
590            &[
591                (1, 0, 0, NameId::FAMILY_NAME, "English"),
592                (3, 1, 0x0409, NameId::FAMILY_NAME, "English"),
593                (3, 1, 0x040c, NameId::FAMILY_NAME, "Français"),
594            ],
595            &[],
596        );
597        let names = Names::from_sfnt(&NameProvider(&data));
598        let entries: Vec<_> = names.get(NameId::FAMILY_NAME).unwrap().iter().collect();
599        let mac = entries[0].name();
600        let utf16 = entries[1].name();
601        let accented = entries[2].name();
602        assert_eq!(mac, utf16);
603        assert_eq!(mac, "English");
604        assert!(mac <= "English");
605        assert!("English" >= mac);
606        assert!(mac < accented);
607        assert!(accented > "English");
608        assert!(accented.starts_with("Fran"));
609        assert!(accented.starts_with("Français"));
610        assert!(accented.starts_with(""));
611        assert!(!accented.starts_with("France"));
612        assert!(!mac.starts_with("English!"));
613
614        let type1 = Encoded::from_str("Français");
615        assert_eq!(accented, type1);
616        assert!(type1.starts_with("Fran"));
617    }
618}