1mod 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#[derive(Clone, Copy)]
34pub struct Names<'a> {
35 source: Source<'a>,
36}
37
38#[derive(Clone, Copy)]
39enum Source<'a> {
40 Sfnt(Option<FontData<'a>>),
42 Type1(&'a Type1Font),
43 StandaloneCff(Option<CffMetadata<'a>>),
44}
45
46impl<'a> Names<'a> {
47 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 pub fn from_type1(font: &'a Type1Font) -> Self {
56 Self {
57 source: Source::Type1(font),
58 }
59 }
60
61 pub fn from_cff(font: &CffFontRef<'a>) -> Self {
63 Self {
64 source: Source::StandaloneCff(font.metadata()),
65 }
66 }
67
68 pub fn get(&self, id: NameId) -> Option<Name<'a>> {
72 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
92fn sfnt_name(data: Option<FontData<'_>>) -> Option<SfntName<'_>> {
94 SfntName::read(data?).ok()
95}
96
97fn 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
113pub(crate) const TYPE1_AXIS_NAMES: u16 = 256;
116
117fn 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#[derive(Clone, Copy)]
133pub struct Name<'a> {
134 source: Source<'a>,
135 id: NameId,
136 start: usize,
138}
139
140impl<'a> Name<'a> {
141 pub fn id(&self) -> NameId {
143 self.id
144 }
145
146 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 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#[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 pub fn name(self) -> Encoded<'a> {
226 self.name
227 }
228
229 pub fn language(self) -> Option<Encoded<'a>> {
231 self.language
232 }
233
234 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#[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 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 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 #[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 #[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}