1use std::borrow::Cow;
8use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
9use std::ffi::OsStr;
10use std::hash::Hash;
11use std::str::FromStr;
12use std::{fs, path};
13
14use crate::glyphdata::{Category, GlyphData, Subcategory};
15use ascii_plist_derive::FromPlist;
16use fontdrasil::types::WidthClass;
17use indexmap::{IndexMap, IndexSet};
18use kurbo::{Affine, Point, Vec2};
19use log::{debug, warn};
20use ordered_float::OrderedFloat;
21use regex::Regex;
22use smol_str::SmolStr;
23
24use crate::error::Error;
25use crate::plist::{FromPlist, Plist, Token, Tokenizer, VecDelimiters};
26
27const V3_METRIC_NAMES: [&str; 6] = [
28 "ascender",
29 "baseline",
30 "descender",
31 "cap height",
32 "x-height",
33 "italic angle",
34];
35
36#[derive(Clone, Debug, Default, PartialEq, Hash)]
37pub struct UserToDesignMapping(BTreeMap<String, AxisUserToDesignMap>);
38
39#[derive(Clone, Debug, Default, PartialEq, Hash)]
40pub struct AxisUserToDesignMap(Vec<(OrderedFloat<f64>, OrderedFloat<f64>)>);
41
42#[derive(Debug, Default, PartialEq, Hash)]
46pub struct Font {
47 pub units_per_em: u16,
48 pub axes: Vec<Axis>,
49 pub masters: Vec<FontMaster>,
50 pub default_master_idx: usize,
51 pub glyphs: BTreeMap<SmolStr, Glyph>,
52 pub glyph_order: Vec<SmolStr>,
53 pub axis_mappings: UserToDesignMapping,
55 pub virtual_masters: Vec<BTreeMap<String, OrderedFloat<f64>>>,
56 pub features: Vec<FeatureSnippet>,
57 pub names: BTreeMap<String, String>,
58 pub instances: Vec<Instance>,
59 pub version_major: i32,
60 pub version_minor: u32,
61 pub date: Option<String>,
62
63 pub kerning_ltr: Kerning,
65 pub kerning_rtl: Kerning,
66
67 pub custom_parameters: CustomParameters,
68}
69
70#[derive(Clone, Debug, PartialEq, Hash, Default)]
72pub struct CustomParameters {
73 pub propagate_anchors: Option<bool>,
74 pub use_typo_metrics: Option<bool>,
75 pub is_fixed_pitch: Option<bool>,
76 pub fs_type: Option<u16>,
77 pub has_wws_names: Option<bool>,
78 pub typo_ascender: Option<i64>,
79 pub typo_descender: Option<i64>,
80 pub typo_line_gap: Option<i64>,
81 pub win_ascent: Option<i64>,
82 pub win_descent: Option<i64>,
83 pub hhea_ascender: Option<i64>,
84 pub hhea_descender: Option<i64>,
85 pub hhea_line_gap: Option<i64>,
86 pub vhea_ascender: Option<i64>,
87 pub vhea_descender: Option<i64>,
88 pub vhea_line_gap: Option<i64>,
89 pub underline_thickness: Option<OrderedFloat<f64>>,
90 pub underline_position: Option<OrderedFloat<f64>>,
91 pub strikeout_position: Option<i64>,
92 pub strikeout_size: Option<i64>,
93 pub subscript_x_offset: Option<i64>,
94 pub subscript_x_size: Option<i64>,
95 pub subscript_y_offset: Option<i64>,
96 pub subscript_y_size: Option<i64>,
97 pub superscript_x_offset: Option<i64>,
98 pub superscript_x_size: Option<i64>,
99 pub superscript_y_offset: Option<i64>,
100 pub superscript_y_size: Option<i64>,
101 pub unicode_range_bits: Option<BTreeSet<u32>>,
102 pub codepage_range_bits: Option<BTreeSet<u32>>,
103 pub panose: Option<Vec<i64>>,
104
105 pub lowest_rec_ppem: Option<i64>,
106 pub hhea_caret_slope_run: Option<i64>,
107 pub hhea_caret_slope_rise: Option<i64>,
108 pub hhea_caret_offset: Option<i64>,
109 pub vhea_caret_slope_run: Option<i64>,
110 pub vhea_caret_slope_rise: Option<i64>,
111 pub vhea_caret_offset: Option<i64>,
112 pub meta_table: Option<MetaTableValues>,
113 pub dont_use_production_names: Option<bool>,
114 pub virtual_masters: Option<Vec<BTreeMap<String, OrderedFloat<f64>>>>,
117 pub glyph_order: Option<Vec<SmolStr>>,
118 pub gasp_table: Option<BTreeMap<i64, i64>>,
119 pub feature_for_feature_variations: Option<SmolStr>,
120}
121
122#[derive(Clone, Debug, PartialEq, Hash, Default)]
124pub struct MetaTableValues {
125 pub dlng: Vec<SmolStr>,
126 pub slng: Vec<SmolStr>,
127}
128
129impl MetaTableValues {
130 fn from_plist(plist: &Plist) -> Option<Self> {
131 let mut ret = MetaTableValues::default();
132 let as_array = plist.as_array()?;
133 for item in as_array {
134 let tag = item.get("tag").and_then(Plist::as_str)?;
135 let data = item.get("data").and_then(Plist::as_str)?;
136 let data = data.split(',').map(SmolStr::new).collect();
137
138 match tag {
139 "dlng" => ret.dlng = data,
140 "slng" => ret.slng = data,
141 _ => log::warn!("Unknown meta table tag '{tag}'"),
142 }
143 }
144
145 if ret.dlng.len() + ret.slng.len() > 0 {
146 Some(ret)
147 } else {
148 None
149 }
150 }
151}
152
153#[derive(Clone, Debug, Default, PartialEq, Hash)]
155pub struct Kerning(BTreeMap<String, BTreeMap<(SmolStr, SmolStr), OrderedFloat<f64>>>);
156
157impl Kerning {
158 pub fn get(&self, master_id: &str) -> Option<&BTreeMap<(SmolStr, SmolStr), OrderedFloat<f64>>> {
159 self.0.get(master_id)
160 }
161
162 pub fn keys(&self) -> impl Iterator<Item = &String> {
163 self.0.keys()
164 }
165
166 pub fn iter(
167 &self,
168 ) -> impl Iterator<Item = (&String, &BTreeMap<(SmolStr, SmolStr), OrderedFloat<f64>>)> {
169 self.0.iter()
170 }
171
172 fn insert(
173 &mut self,
174 master_id: String,
175 lhs_class_or_group: SmolStr,
176 rhs_class_or_group: SmolStr,
177 kern: f64,
178 ) {
179 *self
180 .0
181 .entry(master_id)
182 .or_default()
183 .entry((lhs_class_or_group, rhs_class_or_group))
184 .or_default() = kern.into();
185 }
186}
187
188impl FromPlist for Kerning {
190 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
191 let mut kerning = Kerning::default();
192
193 tokenizer.eat(b'{')?;
194
195 loop {
196 if tokenizer.eat(b'}').is_ok() {
197 break;
198 }
199
200 let master_id: String = tokenizer.parse()?;
202 tokenizer.eat(b'=')?;
203
204 tokenizer.eat(b'{')?;
206 loop {
207 if tokenizer.eat(b'}').is_ok() {
208 break;
209 }
210 let lhs_name_or_class: SmolStr = tokenizer.parse()?;
211 tokenizer.eat(b'=')?;
212 tokenizer.eat(b'{')?;
213
214 loop {
216 if tokenizer.eat(b'}').is_ok() {
217 break;
218 }
219
220 let rhs_name_or_class: SmolStr = tokenizer.parse()?;
221 tokenizer.eat(b'=')?;
222 let value: f64 = tokenizer.parse()?;
223 tokenizer.eat(b';')?;
224
225 kerning.insert(
226 master_id.clone(),
227 lhs_name_or_class.clone(),
228 rhs_name_or_class,
229 value,
230 );
231 }
232 tokenizer.eat(b';')?;
233 }
234
235 tokenizer.eat(b';')?;
236 }
237
238 Ok(kerning)
239 }
240}
241
242#[derive(Debug, PartialEq, Eq, Hash)]
244pub struct FeatureSnippet {
245 pub content: String,
246 pub disabled: bool,
248}
249
250impl FeatureSnippet {
251 pub fn new(content: String, disabled: bool) -> Self {
252 FeatureSnippet { content, disabled }
253 }
254
255 pub fn str_if_enabled(&self) -> Option<&str> {
256 (!self.disabled).then_some(&self.content)
257 }
258}
259
260#[derive(Clone, Default, Debug, PartialEq, Hash)]
261pub struct Glyph {
262 pub name: SmolStr,
263 pub export: bool,
264 pub layers: Vec<Layer>,
265 pub bracket_layers: Vec<Layer>,
266 pub unicode: BTreeSet<u32>,
267 pub left_kern: Option<SmolStr>,
269 pub right_kern: Option<SmolStr>,
271 pub category: Option<Category>,
272 pub sub_category: Option<Subcategory>,
273 pub production_name: Option<SmolStr>,
274}
275
276impl Glyph {
277 pub fn is_nonspacing_mark(&self) -> bool {
278 matches!(
279 (self.category, self.sub_category),
280 (Some(Category::Mark), Some(Subcategory::Nonspacing))
281 )
282 }
283
284 pub(crate) fn has_components(&self) -> bool {
285 self.layers
286 .iter()
287 .chain(self.bracket_layers.iter())
288 .flat_map(Layer::components)
289 .next()
290 .is_some()
291 }
292}
293
294#[derive(Debug, Default, Clone, PartialEq, Hash)]
295pub struct Layer {
296 pub layer_id: String,
297 pub associated_master_id: Option<String>,
298 pub width: OrderedFloat<f64>,
299 pub vert_width: Option<OrderedFloat<f64>>,
300 pub vert_origin: Option<OrderedFloat<f64>>,
301 pub shapes: Vec<Shape>,
302 pub anchors: Vec<Anchor>,
303 pub attributes: LayerAttributes,
304}
305
306impl Layer {
307 pub fn is_master(&self) -> bool {
308 self.associated_master_id.is_none()
309 }
310
311 pub fn master_id(&self) -> &str {
313 self.associated_master_id
314 .as_deref()
315 .unwrap_or(&self.layer_id)
316 }
317
318 pub(crate) fn axis_rules_key(&self) -> Option<String> {
320 (!self.attributes.axis_rules.is_empty())
321 .then(|| format!("{:?} {}", self.attributes.axis_rules, self.master_id()))
322 }
323
324 pub fn is_intermediate(&self) -> bool {
325 self.associated_master_id.is_some() && !self.attributes.coordinates.is_empty()
326 }
327
328 pub(crate) fn components(&self) -> impl Iterator<Item = &Component> + '_ {
329 self.shapes.iter().filter_map(|shape| match shape {
330 Shape::Path(_) => None,
331 Shape::Component(comp) => Some(comp),
332 })
333 }
334
335 fn bracket_info(&self, axes: &[Axis]) -> BTreeMap<String, (Option<i64>, Option<i64>)> {
337 assert!(
338 !self.attributes.axis_rules.is_empty(),
339 "all bracket layers have axis rules"
340 );
341 axes.iter()
342 .zip(&self.attributes.axis_rules)
343 .map(|(axis, rule)| (axis.tag.clone(), (rule.min, rule.max)))
344 .collect()
345 }
346
347 }
349
350#[derive(Clone, Default, Debug, PartialEq, Hash)]
351pub struct LayerAttributes {
352 pub coordinates: Vec<OrderedFloat<f64>>,
353 pub color: bool,
354 pub axis_rules: Vec<AxisRule>,
356}
357
358#[derive(Clone, Default, FromPlist, Debug, PartialEq, Hash)]
359pub struct AxisRule {
360 pub min: Option<i64>,
362 pub max: Option<i64>,
363}
364
365impl AxisRule {
366 fn from_layer_name(name: &str) -> Option<Self> {
371 let idx = name.find([']', '['])?;
375 let reversed = name.as_bytes()[idx] == b']';
376 let tail = name.get(idx + 1..)?;
377 let (value, _) = tail.split_once(']')?;
378 let value = str::parse::<u32>(value.trim()).ok()?;
379 let (min, max) = if reversed {
380 (None, Some(value as _))
381 } else {
382 (Some(value as _), None)
383 };
384 Some(AxisRule { min, max })
385 }
386}
387
388impl FromPlist for LayerAttributes {
390 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
391 let mut coordinates = Vec::new();
392 let mut color = false;
393 let mut axis_rules = Vec::new();
394
395 tokenizer.eat(b'{')?;
396
397 loop {
398 if tokenizer.eat(b'}').is_ok() {
399 break;
400 }
401
402 let key: String = tokenizer.parse()?;
403 tokenizer.eat(b'=')?;
404 match key.as_str() {
405 "coordinates" => coordinates = tokenizer.parse()?,
406 "color" => color = tokenizer.parse()?,
407 "axisRules" => axis_rules = tokenizer.parse()?,
408 _ => tokenizer.skip_rec()?,
411 }
412 tokenizer.eat(b';')?;
413 }
414
415 Ok(LayerAttributes {
416 coordinates,
417 color,
418 axis_rules,
419 })
420 }
421}
422
423#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, FromPlist)]
424pub struct ShapeAttributes {
425 pub gradient: Gradient,
426}
427
428#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, FromPlist)]
429pub struct Gradient {
430 pub start: Vec<OrderedFloat<f64>>,
431 pub end: Vec<OrderedFloat<f64>>,
432 pub colors: Vec<Color>,
433 #[fromplist(key = "type")]
434 pub style: String,
435}
436
437#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
438pub struct Color {
439 pub r: i64,
440 pub g: i64,
441 pub b: i64,
442 pub a: i64,
443 pub stop_offset: OrderedFloat<f64>,
445}
446
447impl FromPlist for Color {
449 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
450 tokenizer.eat(b'(')?;
451
452 let colors = tokenizer.parse::<Vec<i64>>()?;
453 tokenizer.eat(b',')?;
454 let stop_offset = tokenizer.parse::<f64>()?;
455 tokenizer.eat(b')')?;
456
457 match *colors.as_slice() {
459 [black, alpha] => Ok(Color {
461 r: black,
462 g: black,
463 b: black,
464 a: alpha,
465 stop_offset: stop_offset.into(),
466 }),
467 [r, g, b, a] => Ok(Color {
469 r,
470 g,
471 b,
472 a,
473 stop_offset: stop_offset.into(),
474 }),
475 _ => Err(crate::plist::Error::UnexpectedNumberOfValues {
477 value_type: "grayscale (2) or rgba (4)",
478 actual: colors.len(),
479 }),
480 }
481 }
482}
483
484#[derive(Debug, Clone, PartialEq, Hash)]
485pub enum Shape {
486 Path(Path),
487 Component(Component),
488}
489
490impl Shape {
491 pub fn attributes(&self) -> &ShapeAttributes {
492 match self {
493 Shape::Path(p) => &p.attributes,
494 Shape::Component(c) => &c.attributes,
495 }
496 }
497}
498
499#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
500enum FormatVersion {
501 #[default]
502 V2,
503 V3,
504}
505
506#[derive(Default, Debug, PartialEq, FromPlist)]
509#[allow(non_snake_case)]
510struct RawFont {
511 #[fromplist(key = ".formatVersion")]
512 format_version: FormatVersion,
513 units_per_em: Option<i64>,
514 metrics: Vec<RawMetric>,
515 family_name: String,
516 date: Option<String>,
517 copyright: Option<String>,
518 designer: Option<String>,
519 designerURL: Option<String>,
520 manufacturer: Option<String>,
521 manufacturerURL: Option<String>,
522 versionMajor: Option<i64>,
523 versionMinor: Option<i64>,
524 axes: Vec<Axis>,
525 glyphs: Vec<RawGlyph>,
526 font_master: Vec<RawFontMaster>,
527 instances: Vec<RawInstance>,
528 feature_prefixes: Vec<RawFeature>,
529 features: Vec<RawFeature>,
530 classes: Vec<RawFeature>,
531 properties: Vec<RawName>,
532 #[fromplist(alt_name = "kerning")]
533 kerning_LTR: Kerning,
534 kerning_RTL: Kerning,
535 custom_parameters: RawCustomParameters,
536 numbers: Vec<NumberName>,
537}
538
539#[derive(Default, Debug, PartialEq, FromPlist)]
540struct NumberName {
541 name: SmolStr,
542}
543
544#[derive(Clone, Default, Debug, PartialEq, Eq, Hash)]
547pub(crate) struct RawCustomParameters(Vec<RawCustomParameterValue>);
548
549#[derive(Clone, Default, Debug, PartialEq, Eq, Hash, FromPlist)]
550struct RawCustomParameterValue {
551 name: SmolStr,
552 value: Plist,
553 disabled: Option<bool>,
554}
555
556impl FromPlist for FormatVersion {
557 fn parse(tokenizer: &mut Tokenizer) -> Result<Self, crate::plist::Error> {
558 let raw: i64 = FromPlist::parse(tokenizer)?;
559 if raw == 3 {
560 Ok(FormatVersion::V3)
561 } else {
562 Err(crate::plist::Error::Parse(
564 "'3' is the only known format version".into(),
565 ))
566 }
567 }
568}
569
570impl FormatVersion {
571 fn is_v2(self) -> bool {
572 self == FormatVersion::V2
573 }
574
575 fn codepoint_radix(self) -> u32 {
576 match self {
577 FormatVersion::V2 => 16,
578 FormatVersion::V3 => 10,
579 }
580 }
581}
582
583impl FromPlist for RawCustomParameters {
584 fn parse(tokenizer: &mut Tokenizer) -> Result<Self, crate::plist::Error> {
585 Vec::parse(tokenizer).map(RawCustomParameters)
586 }
587}
588
589trait PlistParamsExt {
591 fn as_codepage_bits(&self) -> Option<BTreeSet<u32>>;
592 fn as_unicode_code_ranges(&self) -> Option<BTreeSet<u32>>;
593 fn as_fs_type(&self) -> Option<u16>;
594 fn as_mapping_values(&self) -> Option<Vec<(OrderedFloat<f64>, OrderedFloat<f64>)>>;
595 fn as_axis_location(&self) -> Option<AxisLocation>;
596 fn as_axis(&self) -> Option<Axis>;
597 fn as_bool(&self) -> Option<bool>;
598 fn as_ordered_f64(&self) -> Option<OrderedFloat<f64>>;
599 fn as_vec_of_ints(&self) -> Option<Vec<i64>>;
600 fn as_axis_locations(&self) -> Option<Vec<AxisLocation>>;
601 fn as_vec_of_string(&self) -> Option<Vec<SmolStr>>;
602 fn as_axes(&self) -> Option<Vec<Axis>>;
603 fn as_axis_mappings(&self) -> Option<Vec<AxisMapping>>;
604 fn as_virtual_master(&self) -> Option<BTreeMap<String, OrderedFloat<f64>>>;
605 fn as_gasp_table(&self) -> Option<BTreeMap<i64, i64>>;
606}
607
608impl PlistParamsExt for Plist {
609 fn as_bool(&self) -> Option<bool> {
610 self.as_i64().map(|val| val == 1)
611 }
612
613 fn as_ordered_f64(&self) -> Option<OrderedFloat<f64>> {
614 self.as_f64().map(OrderedFloat)
615 }
616
617 fn as_vec_of_ints(&self) -> Option<Vec<i64>> {
618 if let Some(thing_that_makes_sense) = self.as_array()?.iter().map(Plist::as_i64).collect() {
621 return Some(thing_that_makes_sense);
622 }
623
624 self.as_array()?
625 .iter()
626 .map(|val| val.as_str().and_then(|s| s.parse::<i64>().ok()))
627 .collect()
628 }
629
630 fn as_axis_location(&self) -> Option<AxisLocation> {
631 let plist = self.as_dict()?;
632 let name = plist.get("Axis").and_then(Plist::as_str)?;
633 let location = plist.get("Location").and_then(Plist::as_f64)?;
634 Some(AxisLocation {
635 axis_name: name.into(),
636 location: location.into(),
637 })
638 }
639
640 fn as_axis_locations(&self) -> Option<Vec<AxisLocation>> {
641 let array = self.as_array()?;
642 array.iter().map(Plist::as_axis_location).collect()
643 }
644
645 fn as_vec_of_string(&self) -> Option<Vec<SmolStr>> {
646 self.as_array()?
647 .iter()
648 .map(|plist| plist.as_str().map(SmolStr::from))
649 .collect()
650 }
651
652 fn as_axis(&self) -> Option<Axis> {
653 let plist = self.as_dict()?;
654 let name = plist.get("Name").and_then(Plist::as_str)?;
655 let tag = plist.get("Tag").and_then(Plist::as_str)?;
656 let hidden = plist.get("hidden").and_then(Plist::as_bool);
657 Some(Axis {
658 name: name.into(),
659 tag: tag.into(),
660 hidden,
661 })
662 }
663
664 fn as_axes(&self) -> Option<Vec<Axis>> {
665 self.as_array()?.iter().map(Plist::as_axis).collect()
666 }
667
668 fn as_mapping_values(&self) -> Option<Vec<(OrderedFloat<f64>, OrderedFloat<f64>)>> {
669 let plist = self.as_dict()?;
670 plist
671 .iter()
672 .map(|(key, value)| {
673 let key = key
675 .parse::<f64>()
676 .or_else(|_| key.parse::<i64>().map(|i| i as f64))
677 .ok()?;
678 let val = value.as_f64()?;
679 Some((key.into(), val.into()))
680 })
681 .collect()
682 }
683
684 fn as_axis_mappings(&self) -> Option<Vec<AxisMapping>> {
685 self.as_dict()?
686 .iter()
687 .map(|(tag, mappings)| {
688 let user_to_design = mappings.as_mapping_values()?;
689 Some(AxisMapping {
690 tag: tag.to_string(),
691 user_to_design,
692 })
693 })
694 .collect()
695 }
696
697 fn as_virtual_master(&self) -> Option<BTreeMap<String, OrderedFloat<f64>>> {
698 self.as_array()?
699 .iter()
700 .map(Plist::as_axis_location)
701 .map(|loc| (loc.map(|loc| (loc.axis_name, loc.location))))
702 .collect()
703 }
704
705 fn as_fs_type(&self) -> Option<u16> {
706 Some(self.as_vec_of_ints()?.iter().map(|bit| 1 << bit).sum())
707 }
708
709 fn as_unicode_code_ranges(&self) -> Option<BTreeSet<u32>> {
710 let bits = self.as_vec_of_ints()?;
711 Some(bits.iter().map(|bit| *bit as u32).collect())
712 }
713
714 fn as_codepage_bits(&self) -> Option<BTreeSet<u32>> {
715 let bits = self.as_vec_of_ints()?;
716 bits.iter()
717 .map(|b| codepage_range_bit(*b as _))
718 .collect::<Result<_, _>>()
719 .ok()
720 }
721
722 fn as_gasp_table(&self) -> Option<BTreeMap<i64, i64>> {
723 Some(
724 self.as_dict()?
725 .iter()
726 .filter_map(|(k, v)| k.parse::<i64>().ok().zip(v.as_i64()))
727 .collect(),
728 )
729 }
730}
731
732impl RawCustomParameters {
733 fn to_custom_params(&self) -> Result<CustomParameters, Error> {
735 let mut params = CustomParameters::default();
736 let mut virtual_masters = Vec::<BTreeMap<String, OrderedFloat<f64>>>::new();
737 let mut panose = None;
742 let mut panose_old = None;
743
744 for RawCustomParameterValue {
745 name,
746 value,
747 disabled,
748 } in &self.0
749 {
750 macro_rules! add_and_report_issues {
753 ($field:ident, $converter:path) => {{
754 add_and_report_issues!($field, $converter(value))
755 }};
756
757 ($field:ident, $converter:path, into) => {{
758 add_and_report_issues!($field, $converter(value).map(Into::into))
759 }};
760
761 ($field:ident, $value_expr:expr) => {{
762 let value = $value_expr;
763
764 if value.is_none() {
765 log::warn!("failed to parse param for '{}'", stringify!($field));
766 }
767 if params.$field.is_some() {
768 log::warn!("duplicate param value for field '{}'", stringify!($field));
769 }
770 params.$field = value;
771 }};
772 }
773
774 if *disabled == Some(true) {
775 log::debug!("skipping disabled custom param '{name}'");
776 continue;
777 }
778 match name.as_str() {
779 "Propagate Anchors" => add_and_report_issues!(propagate_anchors, Plist::as_bool),
780 "Use Typo Metrics" => add_and_report_issues!(use_typo_metrics, Plist::as_bool),
781 "isFixedPitch" => add_and_report_issues!(is_fixed_pitch, Plist::as_bool),
782 "Has WWS Names" => add_and_report_issues!(has_wws_names, Plist::as_bool),
783 "typoAscender" => add_and_report_issues!(typo_ascender, Plist::as_i64),
784 "typoDescender" => add_and_report_issues!(typo_descender, Plist::as_i64),
785 "typoLineGap" => add_and_report_issues!(typo_line_gap, Plist::as_i64),
786 "winAscent" => add_and_report_issues!(win_ascent, Plist::as_i64),
787 "winDescent" => add_and_report_issues!(win_descent, Plist::as_i64),
788 "hheaAscender" => add_and_report_issues!(hhea_ascender, Plist::as_i64),
789 "hheaDescender" => add_and_report_issues!(hhea_descender, Plist::as_i64),
790 "hheaLineGap" => add_and_report_issues!(hhea_line_gap, Plist::as_i64),
791 "vheaVertAscender" => add_and_report_issues!(vhea_ascender, Plist::as_i64),
792 "vheaVertDescender" => add_and_report_issues!(vhea_descender, Plist::as_i64),
793 "vheaVertLineGap" => add_and_report_issues!(vhea_line_gap, Plist::as_i64),
794 "underlineThickness" => {
795 add_and_report_issues!(underline_thickness, Plist::as_ordered_f64)
796 }
797 "underlinePosition" => {
798 add_and_report_issues!(underline_position, Plist::as_ordered_f64)
799 }
800 "strikeoutPosition" => add_and_report_issues!(strikeout_position, Plist::as_i64),
801 "strikeoutSize" => add_and_report_issues!(strikeout_size, Plist::as_i64),
802 "subscriptXOffset" => add_and_report_issues!(subscript_x_offset, Plist::as_i64),
803 "subscriptXSize" => add_and_report_issues!(subscript_x_size, Plist::as_i64),
804 "subscriptYOffset" => add_and_report_issues!(subscript_y_offset, Plist::as_i64),
805 "subscriptYSize" => add_and_report_issues!(subscript_y_size, Plist::as_i64),
806 "superscriptXOffset" => add_and_report_issues!(superscript_x_offset, Plist::as_i64),
807 "superscriptXSize" => add_and_report_issues!(superscript_x_size, Plist::as_i64),
808 "superscriptYOffset" => add_and_report_issues!(superscript_y_offset, Plist::as_i64),
809 "superscriptYSize" => add_and_report_issues!(superscript_y_size, Plist::as_i64),
810 "fsType" => add_and_report_issues!(fs_type, Plist::as_fs_type),
811 "unicodeRanges" => {
812 add_and_report_issues!(unicode_range_bits, Plist::as_unicode_code_ranges)
813 }
814 "codePageRanges" => {
815 add_and_report_issues!(codepage_range_bits, Plist::as_codepage_bits)
816 }
817 "openTypeHeadLowestRecPPEM" => {
821 add_and_report_issues!(lowest_rec_ppem, Plist::as_i64)
822 }
823 "openTypeHheaCaretSlopeRun" => {
824 add_and_report_issues!(hhea_caret_slope_run, Plist::as_i64)
825 }
826 "openTypeHheaCaretSlopeRise" => {
827 add_and_report_issues!(hhea_caret_slope_rise, Plist::as_i64)
828 }
829 "openTypeHheaCaretOffset" => {
830 add_and_report_issues!(hhea_caret_offset, Plist::as_i64)
831 }
832 "openTypeVheaCaretSlopeRun" => {
833 add_and_report_issues!(vhea_caret_slope_run, Plist::as_i64)
834 }
835 "openTypeVheaCaretSlopeRise" => {
836 add_and_report_issues!(vhea_caret_slope_rise, Plist::as_i64)
837 }
838 "openTypeVheaCaretOffset" => {
839 add_and_report_issues!(vhea_caret_offset, Plist::as_i64)
840 }
841 "meta Table" => {
842 add_and_report_issues!(meta_table, MetaTableValues::from_plist)
843 }
844 "Don't use Production Names" => {
845 add_and_report_issues!(dont_use_production_names, Plist::as_bool)
846 }
847 "openTypeNameUniqueID"
851 | "openTypeNameVersion"
852 | "openTypeOS2FamilyClass"
853 | "openTypeHeadFlags" => {
854 log::warn!("unhandled custom param '{name}'")
855 }
856
857 "Virtual Master" => match value.as_virtual_master() {
858 Some(val) => virtual_masters.push(val),
859 None => log::warn!("failed to parse virtual master '{value:?}'"),
860 },
861 "panose" => panose = value.as_vec_of_ints(),
862 "openTypeOS2Panose" => panose_old = value.as_vec_of_ints(),
863 "glyphOrder" => add_and_report_issues!(glyph_order, Plist::as_vec_of_string),
864 "gasp Table" => add_and_report_issues!(gasp_table, Plist::as_gasp_table),
865 "Feature for Feature Variations" => {
866 add_and_report_issues!(feature_for_feature_variations, Plist::as_str, into)
867 }
868 _ => log::warn!("unknown custom parameter '{name}'"),
869 }
870 }
871 params.panose = panose.or(panose_old);
872 params.virtual_masters = Some(virtual_masters).filter(|x| !x.is_empty());
873 Ok(params)
874 }
875
876 fn get(&self, name: &str) -> Option<&Plist> {
878 let item = self.0.iter().find(|val| (val.name == name))?;
879 (item.disabled != Some(true)).then_some(&item.value)
880 }
881
882 fn contains(&self, name: &str) -> bool {
883 self.0.iter().any(|val| val.name == name)
884 }
885
886 fn string(&self, name: &str) -> Option<&str> {
887 self.get(name).and_then(Plist::as_str)
888 }
889
890 fn axes(&self) -> Option<Vec<Axis>> {
891 self.get("Axes").and_then(Plist::as_axes)
892 }
893
894 fn axis_mappings(&self) -> Option<Vec<AxisMapping>> {
895 self.get("Axis Mappings").and_then(Plist::as_axis_mappings)
896 }
897
898 fn axis_locations(&self) -> Option<Vec<AxisLocation>> {
899 self.get("Axis Location").and_then(Plist::as_axis_locations)
900 }
901}
902
903#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
904pub struct AxisLocation {
905 axis_name: String,
906 location: OrderedFloat<f64>,
907}
908
909#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
910pub struct AxisMapping {
911 tag: String,
913 user_to_design: Vec<(OrderedFloat<f64>, OrderedFloat<f64>)>,
914}
915
916#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromPlist)]
917struct RawMetric {
918 type_: String,
920}
921
922#[derive(Default, Clone, Debug, PartialEq, Eq, Hash, FromPlist)]
923struct RawName {
924 key: String,
925 value: Option<String>,
926 values: Vec<RawNameValue>,
927}
928
929impl RawName {
930 fn is_empty(&self) -> bool {
931 self.value.is_none() && self.values.is_empty()
932 }
933
934 fn get_value(&self) -> Option<&str> {
935 if let Some(value) = &self.value {
936 return Some(value.as_str());
937 }
938
939 self.values
944 .iter()
945 .enumerate()
946 .map(|(i, raw)| match raw.language.as_str() {
948 "dflt" => (-3, raw.value.as_str()),
949 "default" => (-2, raw.value.as_str()),
950 "ENG" => (-1, raw.value.as_str()),
951 _ => (i as i32, raw.value.as_str()),
952 })
953 .min()
954 .map(|(_, raw)| raw)
955 }
956}
957
958#[derive(Default, Clone, Debug, PartialEq, Eq, Hash, FromPlist)]
959struct RawNameValue {
960 language: String,
961 value: String,
962}
963
964#[derive(Default, Clone, Debug, PartialEq, Eq, Hash, FromPlist)]
965struct RawFeature {
966 automatic: Option<i64>,
967 disabled: Option<i64>,
968 name: Option<String>,
969 tag: Option<String>,
970 notes: Option<String>,
971 code: String,
972 labels: Vec<RawNameValue>,
973
974 #[fromplist(ignore)]
975 other_stuff: BTreeMap<String, Plist>,
976}
977
978#[derive(Default, Clone, Debug, PartialEq, Eq, Hash, FromPlist)]
979pub struct Axis {
980 #[fromplist(alt_name = "Name")]
981 pub name: String,
982 #[fromplist(alt_name = "Tag")]
983 pub tag: String,
984 pub hidden: Option<bool>,
985}
986
987#[derive(Default, Clone, Debug, PartialEq, FromPlist)]
988struct RawGlyph {
989 layers: Vec<RawLayer>,
990 glyphname: SmolStr,
991 export: Option<bool>,
992 #[fromplist(alt_name = "leftKerningGroup")]
993 kern_left: Option<SmolStr>,
994 #[fromplist(alt_name = "rightKerningGroup")]
995 kern_right: Option<SmolStr>,
996 unicode: Option<String>,
997 category: Option<SmolStr>,
998 sub_category: Option<SmolStr>,
999 #[fromplist(alt_name = "production")]
1000 production_name: Option<SmolStr>,
1001 #[fromplist(ignore)]
1002 other_stuff: BTreeMap<String, Plist>,
1003}
1004
1005#[derive(Default, Clone, Debug, PartialEq, FromPlist)]
1006struct RawLayer {
1007 name: String,
1008 layer_id: String,
1009 associated_master_id: Option<String>,
1010 width: Option<OrderedFloat<f64>>,
1011 vert_width: Option<OrderedFloat<f64>>,
1012 vert_origin: Option<OrderedFloat<f64>>,
1013 shapes: Vec<RawShape>,
1014 paths: Vec<Path>,
1015 components: Vec<Component>,
1016 anchors: Vec<RawAnchor>,
1017 #[fromplist(alt_name = "attr")]
1018 attributes: LayerAttributes,
1019 #[fromplist(ignore)]
1020 other_stuff: BTreeMap<String, Plist>,
1021}
1022
1023impl RawLayer {
1024 fn is_draft(&self) -> bool {
1030 self.associated_master_id.is_some() && self.attributes == Default::default()
1031 }
1032
1033 fn is_bracket_layer(&self, format_version: FormatVersion) -> bool {
1038 self.associated_master_id.is_some()
1041 && self.associated_master_id.as_ref() != Some(&self.layer_id)
1042 && match format_version {
1044 FormatVersion::V2 => AxisRule::from_layer_name(&self.name).is_some(),
1045 FormatVersion::V3 => !self.attributes.axis_rules.is_empty(),
1046 }
1047 }
1048
1049 fn v2_to_v3_attributes(&mut self) {
1050 let mut brace_coordinates = Vec::new();
1053 if let (Some(start), Some(end)) = (self.name.find('{'), self.name.find('}')) {
1054 let mut tokenizer = Tokenizer::new(&self.name[start..=end]);
1055 brace_coordinates = tokenizer
1059 .parse_delimited_vec(VecDelimiters::CSV_IN_BRACES)
1060 .unwrap_or_default();
1061 }
1062 if !brace_coordinates.is_empty() {
1063 self.attributes.coordinates = brace_coordinates;
1064 }
1065 }
1067}
1068
1069#[derive(Default, Clone, Debug, PartialEq, FromPlist)]
1073struct RawShape {
1074 closed: Option<bool>,
1078 nodes: Vec<Node>,
1079
1080 #[fromplist(alt_name = "ref", alt_name = "name")]
1084 glyph_name: Option<SmolStr>,
1085
1086 anchor: Option<SmolStr>,
1089 transform: Option<String>, pos: Vec<f64>, angle: Option<f64>, scale: Vec<f64>, #[fromplist(alt_name = "attr")]
1095 attributes: ShapeAttributes,
1096}
1097
1098#[derive(Default, Clone, Debug, PartialEq, Eq, Hash, FromPlist)]
1100pub struct Path {
1101 pub closed: bool,
1102 pub nodes: Vec<Node>,
1103 pub attributes: ShapeAttributes,
1104}
1105
1106#[derive(Default, Clone, Debug, FromPlist)]
1108pub struct Component {
1109 pub name: SmolStr,
1111 pub transform: Affine,
1113 pub anchor: Option<SmolStr>,
1118 pub attributes: ShapeAttributes,
1119}
1120
1121impl PartialEq for Component {
1122 fn eq(&self, other: &Self) -> bool {
1123 self.name == other.name
1124 && Into::<AffineForEqAndHash>::into(self.transform) == other.transform.into()
1125 }
1126}
1127
1128impl Eq for Component {}
1129
1130impl Hash for Component {
1131 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1132 self.name.hash(state);
1133 Into::<AffineForEqAndHash>::into(self.transform).hash(state);
1134 }
1135}
1136
1137#[derive(Clone, Debug)]
1138pub struct Node {
1139 pub pt: Point,
1140 pub node_type: NodeType,
1141}
1142
1143impl Node {
1144 pub fn is_on_curve(&self) -> bool {
1145 !matches!(self.node_type, NodeType::OffCurve)
1146 }
1147}
1148
1149impl PartialEq for Node {
1150 fn eq(&self, other: &Self) -> bool {
1151 Into::<PointForEqAndHash>::into(self.pt) == other.pt.into()
1152 && self.node_type == other.node_type
1153 }
1154}
1155
1156impl Eq for Node {}
1157
1158impl Hash for Node {
1159 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1160 PointForEqAndHash::new(self.pt).hash(state);
1161 self.node_type.hash(state);
1162 }
1163}
1164
1165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1166pub enum NodeType {
1167 Line,
1168 LineSmooth,
1169 OffCurve,
1170 Curve,
1171 CurveSmooth,
1172 QCurve,
1173 QCurveSmooth,
1174}
1175
1176#[derive(Default, Clone, Debug, PartialEq, FromPlist)]
1177struct RawAnchor {
1178 name: SmolStr,
1179 pos: Option<Point>, position: Option<String>, }
1182
1183#[derive(Clone, Debug, PartialEq)]
1184pub struct Anchor {
1185 pub name: SmolStr,
1186 pub pos: Point,
1187}
1188
1189impl Anchor {
1190 pub(crate) fn is_origin(&self) -> bool {
1191 self.name == "*origin"
1192 }
1193
1194 pub(crate) fn origin_delta(&self) -> Option<Vec2> {
1195 self.is_origin().then_some(self.pos.to_vec2())
1196 }
1197}
1198
1199impl Hash for Anchor {
1200 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1201 self.name.hash(state);
1202 PointForEqAndHash::new(self.pos).hash(state);
1203 }
1204}
1205
1206#[derive(Clone, Debug, PartialEq, Hash)]
1207pub struct FontMaster {
1208 pub id: String,
1209 pub name: String,
1210 pub axes_values: Vec<OrderedFloat<f64>>,
1211 metric_values: BTreeMap<String, MetricValue>,
1212 pub number_values: BTreeMap<SmolStr, OrderedFloat<f64>>,
1213 pub custom_parameters: CustomParameters,
1214}
1215
1216impl FontMaster {
1217 fn read_metric(&self, metric_name: &str) -> Option<f64> {
1218 self.metric_values
1219 .get(metric_name)
1220 .map(|metric| metric.pos.into_inner())
1221 }
1222
1223 pub fn ascender(&self) -> Option<f64> {
1224 self.read_metric("ascender")
1225 }
1226
1227 pub fn descender(&self) -> Option<f64> {
1228 self.read_metric("descender")
1229 }
1230
1231 pub fn x_height(&self) -> Option<f64> {
1232 self.read_metric("x-height")
1233 }
1234
1235 pub fn cap_height(&self) -> Option<f64> {
1236 self.read_metric("cap height")
1237 }
1238
1239 pub fn italic_angle(&self) -> Option<f64> {
1240 self.read_metric("italic angle")
1241 }
1242}
1243
1244#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromPlist)]
1245struct RawFontMaster {
1246 id: String,
1247 name: Option<String>,
1248
1249 weight: Option<String>,
1250 width: Option<String>,
1251 custom: Option<String>,
1252
1253 weight_value: Option<OrderedFloat<f64>>,
1254 interpolation_weight: Option<OrderedFloat<f64>>,
1255
1256 width_value: Option<OrderedFloat<f64>>,
1257 interpolation_width: Option<OrderedFloat<f64>>,
1258
1259 custom_value: Option<OrderedFloat<f64>>,
1260
1261 typo_ascender: Option<i64>,
1262 typo_descender: Option<OrderedFloat<f64>>,
1263 typo_line_gap: Option<OrderedFloat<f64>>,
1264 win_ascender: Option<OrderedFloat<f64>>,
1265 win_descender: Option<OrderedFloat<f64>>,
1266
1267 axes_values: Vec<OrderedFloat<f64>>,
1268 metric_values: Vec<RawMetricValue>, ascender: Option<OrderedFloat<f64>>, baseline: Option<OrderedFloat<f64>>, descender: Option<OrderedFloat<f64>>, cap_height: Option<OrderedFloat<f64>>, x_height: Option<OrderedFloat<f64>>, #[fromplist(alt_name = "italic angle")]
1276 italic_angle: Option<OrderedFloat<f64>>, alignment_zones: Vec<String>, custom_parameters: RawCustomParameters,
1281 number_values: Vec<OrderedFloat<f64>>,
1282
1283 #[fromplist(ignore)]
1284 other_stuff: BTreeMap<String, Plist>,
1285}
1286
1287#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromPlist)]
1288struct RawMetricValue {
1289 pos: Option<OrderedFloat<f64>>,
1290 over: Option<OrderedFloat<f64>>,
1291}
1292
1293#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
1294pub struct MetricValue {
1295 pos: OrderedFloat<f64>,
1296 over: OrderedFloat<f64>,
1297}
1298
1299impl From<RawMetricValue> for MetricValue {
1300 fn from(src: RawMetricValue) -> MetricValue {
1301 MetricValue {
1302 pos: src.pos.unwrap_or_default(),
1303 over: src.over.unwrap_or_default(),
1304 }
1305 }
1306}
1307
1308#[derive(Clone, Debug, PartialEq, Hash)]
1309pub struct Instance {
1310 pub name: String,
1311 pub active: bool,
1312 pub type_: InstanceType,
1314 pub axis_mappings: BTreeMap<String, AxisUserToDesignMap>,
1315 pub axes_values: Vec<OrderedFloat<f64>>,
1316 pub custom_parameters: CustomParameters,
1317 properties: Vec<RawName>, }
1319
1320#[derive(Clone, Debug, PartialEq, Hash)]
1322pub enum InstanceType {
1323 Single,
1324 Variable,
1325}
1326
1327impl From<&str> for InstanceType {
1328 fn from(value: &str) -> Self {
1329 if value.eq_ignore_ascii_case("variable") {
1330 InstanceType::Variable
1331 } else {
1332 InstanceType::Single
1333 }
1334 }
1335}
1336
1337#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromPlist)]
1338struct RawInstance {
1339 name: String,
1340 exports: Option<i64>,
1341 active: Option<i64>,
1342 type_: Option<String>,
1343 axes_values: Vec<OrderedFloat<f64>>,
1344
1345 weight_value: Option<OrderedFloat<f64>>,
1346 interpolation_weight: Option<OrderedFloat<f64>>,
1347
1348 width_value: Option<OrderedFloat<f64>>,
1349 interpolation_width: Option<OrderedFloat<f64>>,
1350
1351 custom_value: Option<OrderedFloat<f64>>,
1352
1353 weight_class: Option<String>,
1354 width_class: Option<String>,
1355 properties: Vec<RawName>,
1356 custom_parameters: RawCustomParameters,
1357}
1358
1359impl RawInstance {
1360 fn is_active(&self) -> bool {
1363 self.exports.unwrap_or(1) != 0 && self.active.unwrap_or(1) != 0
1364 }
1365}
1366
1367trait GlyphsV2OrderedAxes {
1368 fn weight_value(&self) -> Option<OrderedFloat<f64>>;
1369 fn interpolation_weight(&self) -> Option<OrderedFloat<f64>>;
1370 fn width_value(&self) -> Option<OrderedFloat<f64>>;
1371 fn interpolation_width(&self) -> Option<OrderedFloat<f64>>;
1372 fn custom_value(&self) -> Option<OrderedFloat<f64>>;
1373
1374 fn value_for_nth_axis(&self, nth_axis: usize) -> Result<OrderedFloat<f64>, Error> {
1375 Ok(match nth_axis {
1381 0 => self
1382 .weight_value()
1383 .or(self.interpolation_weight())
1384 .unwrap_or(100.0.into()),
1385 1 => self
1386 .width_value()
1387 .or(self.interpolation_width())
1388 .unwrap_or(100.0.into()),
1389 2 => self.custom_value().unwrap_or(0.0.into()),
1390 _ => {
1391 return Err(Error::StructuralError(format!(
1392 "We don't know what field to use for axis {nth_axis}"
1393 )))
1394 }
1395 })
1396 }
1397
1398 fn axis_values(&self, axes: &[Axis]) -> Result<Vec<OrderedFloat<f64>>, Error> {
1399 (0..axes.len())
1400 .map(|nth_axis| self.value_for_nth_axis(nth_axis))
1401 .collect::<Result<Vec<OrderedFloat<f64>>, Error>>()
1402 }
1403}
1404
1405impl GlyphsV2OrderedAxes for RawFontMaster {
1406 fn weight_value(&self) -> Option<OrderedFloat<f64>> {
1407 self.weight_value
1408 }
1409
1410 fn interpolation_weight(&self) -> Option<OrderedFloat<f64>> {
1411 self.interpolation_weight
1412 }
1413
1414 fn width_value(&self) -> Option<OrderedFloat<f64>> {
1415 self.width_value
1416 }
1417
1418 fn interpolation_width(&self) -> Option<OrderedFloat<f64>> {
1419 self.interpolation_width
1420 }
1421
1422 fn custom_value(&self) -> Option<OrderedFloat<f64>> {
1423 self.custom_value
1424 }
1425}
1426
1427impl GlyphsV2OrderedAxes for RawInstance {
1428 fn weight_value(&self) -> Option<OrderedFloat<f64>> {
1429 self.weight_value
1430 }
1431
1432 fn interpolation_weight(&self) -> Option<OrderedFloat<f64>> {
1433 self.interpolation_weight
1434 }
1435
1436 fn width_value(&self) -> Option<OrderedFloat<f64>> {
1437 self.width_value
1438 }
1439
1440 fn interpolation_width(&self) -> Option<OrderedFloat<f64>> {
1441 self.interpolation_width
1442 }
1443
1444 fn custom_value(&self) -> Option<OrderedFloat<f64>> {
1445 self.custom_value
1446 }
1447}
1448
1449fn parse_node_from_string(value: &str) -> Node {
1450 let mut spl = value.splitn(3, ' ');
1451 let x = spl.next().unwrap().parse().unwrap();
1452 let y = spl.next().unwrap().parse().unwrap();
1453 let pt = Point::new(x, y);
1454 let mut raw_node_type = spl.next().unwrap();
1455 if raw_node_type.contains('{') {
1457 raw_node_type = raw_node_type.split('{').next().unwrap().trim_end();
1458 }
1459 let node_type = raw_node_type.parse().unwrap();
1460 Node { pt, node_type }
1461}
1462
1463fn parse_node_from_tokenizer(tokenizer: &mut Tokenizer<'_>) -> Result<Node, crate::plist::Error> {
1464 let x: f64 = tokenizer.parse()?;
1466 tokenizer.eat(b',')?;
1467 let y: f64 = tokenizer.parse()?;
1468 tokenizer.eat(b',')?;
1469 let node_type: String = tokenizer.parse()?;
1470 let node_type = NodeType::from_str(&node_type)
1471 .map_err(|_| crate::plist::Error::Parse(format!("unknown node type '{node_type}'")))?;
1472
1473 if tokenizer.eat(b',').is_ok() {
1475 tokenizer.skip_rec()?;
1476 }
1477
1478 Ok(Node {
1479 pt: Point { x, y },
1480 node_type,
1481 })
1482}
1483
1484impl std::str::FromStr for NodeType {
1485 type Err = String;
1486 fn from_str(s: &str) -> Result<Self, Self::Err> {
1487 match s {
1488 "LINE" => Ok(NodeType::Line),
1490 "LINE SMOOTH" => Ok(NodeType::LineSmooth),
1491 "OFFCURVE" => Ok(NodeType::OffCurve),
1492 "CURVE" => Ok(NodeType::Curve),
1493 "CURVE SMOOTH" => Ok(NodeType::CurveSmooth),
1494 "QCURVE" => Ok(NodeType::QCurve),
1495 "QCURVE SMOOTH" => Ok(NodeType::QCurveSmooth),
1496 "l" => Ok(NodeType::Line),
1498 "ls" => Ok(NodeType::LineSmooth),
1499 "o" => Ok(NodeType::OffCurve),
1500 "c" => Ok(NodeType::Curve),
1501 "cs" => Ok(NodeType::CurveSmooth),
1502 "q" => Ok(NodeType::QCurve),
1503 "qs" => Ok(NodeType::QCurveSmooth),
1504 _ => Err(format!("unknown node type {s}")),
1505 }
1506 }
1507}
1508
1509impl FromPlist for Node {
1511 fn parse(tokenizer: &mut Tokenizer<'_>) -> Result<Self, crate::plist::Error> {
1512 use crate::plist::Error;
1513 let tok = tokenizer.lex()?;
1514 let node = match &tok {
1515 Token::Atom(value) => parse_node_from_string(value),
1516 Token::String(value) => parse_node_from_string(value),
1517 Token::OpenParen => {
1518 let node = parse_node_from_tokenizer(tokenizer)?;
1519 tokenizer.eat(b')')?;
1520 node
1521 }
1522 _ => return Err(Error::ExpectedString),
1523 };
1524 Ok(node)
1525 }
1526}
1527
1528impl Path {
1529 pub fn new(closed: bool) -> Path {
1530 Path {
1531 nodes: Vec::new(),
1532 closed,
1533 ..Default::default()
1534 }
1535 }
1536
1537 pub fn add(&mut self, pt: impl Into<Point>, node_type: NodeType) {
1538 let pt = pt.into();
1539 self.nodes.push(Node { pt, node_type });
1540 }
1541
1542 pub fn rotate_left(&mut self, delta: usize) {
1545 self.nodes.rotate_left(delta);
1546 }
1547
1548 pub fn reverse(&mut self) {
1549 self.nodes.reverse();
1550 }
1551}
1552
1553fn v2_to_v3_name(v2_prop: Option<&str>, v3_name: &str) -> Option<RawName> {
1554 v2_prop.map(|value| {
1557 if v3_name.ends_with('s') {
1558 RawName {
1559 key: v3_name.into(),
1560 value: None,
1561 values: vec![RawNameValue {
1562 language: "dflt".into(),
1563 value: value.to_string(),
1564 }],
1565 }
1566 } else {
1567 RawName {
1568 key: v3_name.into(),
1569 value: Some(value.to_string()),
1570 values: vec![],
1571 }
1572 }
1573 })
1574}
1575
1576impl RawFont {
1577 pub fn load_from_string(raw_content: &str) -> Result<Self, crate::plist::Error> {
1578 let raw_content = preprocess_unparsed_plist(raw_content);
1579 Self::parse_plist(&raw_content)
1580 }
1581
1582 pub fn load(glyphs_file: &path::Path) -> Result<Self, Error> {
1583 if glyphs_file.extension() == Some(OsStr::new("glyphspackage")) {
1584 return Self::load_package(glyphs_file);
1585 }
1586
1587 debug!("Read glyphs {glyphs_file:?}");
1588 let raw_content = fs::read_to_string(glyphs_file).map_err(Error::IoError)?;
1589 Self::load_from_string(&raw_content)
1590 .map_err(|e| Error::ParseError(glyphs_file.to_path_buf(), e.to_string()))
1591 }
1592
1593 fn load_package(glyphs_package: &path::Path) -> Result<RawFont, Error> {
1595 if !glyphs_package.is_dir() {
1596 return Err(Error::NotAGlyphsPackage(glyphs_package.to_path_buf()));
1597 }
1598 debug!("Read glyphs package {glyphs_package:?}");
1599
1600 let fontinfo_file = glyphs_package.join("fontinfo.plist");
1601 let fontinfo_data = fs::read_to_string(&fontinfo_file).map_err(Error::IoError)?;
1602 let mut raw_font = RawFont::parse_plist(&fontinfo_data)
1603 .map_err(|e| Error::ParseError(fontinfo_file.to_path_buf(), format!("{e}")))?;
1604
1605 let mut glyphs: HashMap<SmolStr, RawGlyph> = HashMap::new();
1606 let glyphs_dir = glyphs_package.join("glyphs");
1607 if glyphs_dir.is_dir() {
1608 for entry in fs::read_dir(glyphs_dir).map_err(Error::IoError)? {
1609 let entry = entry.map_err(Error::IoError)?;
1610 let path = entry.path();
1611 if path.extension() == Some(OsStr::new("glyph")) {
1612 let glyph_data = fs::read_to_string(&path).map_err(Error::IoError)?;
1613 let glyph_data = preprocess_unparsed_plist(&glyph_data);
1614 let glyph = RawGlyph::parse_plist(&glyph_data)
1615 .map_err(|e| Error::ParseError(path.clone(), e.to_string()))?;
1616 if glyph.glyphname.is_empty() {
1617 return Err(Error::ParseError(
1618 path.clone(),
1619 "Glyph dict must have a 'glyphname' key".to_string(),
1620 ));
1621 }
1622 glyphs.insert(glyph.glyphname.clone(), glyph);
1623 }
1624 }
1625 }
1626
1627 let order_file = glyphs_package.join("order.plist");
1629 let mut ordered_glyphs = Vec::new();
1630 if order_file.exists() {
1631 let order_data = fs::read_to_string(&order_file).map_err(Error::IoError)?;
1632 let order_plist = Plist::parse(&order_data)
1633 .map_err(|e| Error::ParseError(order_file.to_path_buf(), e.to_string()))?;
1634 let order = order_plist
1635 .expect_array()
1636 .map_err(|e| Error::ParseError(order_file.to_path_buf(), e.to_string()))?;
1637 for glyph_name in order {
1638 let glyph_name = glyph_name
1639 .expect_string()
1640 .map_err(|e| Error::ParseError(order_file.to_path_buf(), e.to_string()))?;
1641 if let Some(glyph) = glyphs.remove(glyph_name.as_str()) {
1642 ordered_glyphs.push(glyph);
1643 }
1644 }
1645 }
1646 let mut glyph_names: Vec<_> = glyphs.keys().cloned().collect();
1648 glyph_names.sort();
1649 ordered_glyphs.extend(
1650 glyph_names
1651 .into_iter()
1652 .map(|glyph_name| glyphs.remove(&glyph_name).unwrap()),
1653 );
1654 assert!(glyphs.is_empty());
1655 raw_font.glyphs = ordered_glyphs;
1656
1657 Ok(raw_font)
1660 }
1661
1662 fn v2_to_v3_axes(&mut self) -> Result<Vec<String>, Error> {
1663 let mut tags = Vec::new();
1664 if let Some(v2_axes) = self.custom_parameters.axes() {
1665 for v2_axis in v2_axes {
1666 tags.push(v2_axis.tag.clone());
1667 self.axes.push(v2_axis.clone());
1668 }
1669 }
1670
1671 if self.axes.is_empty() {
1674 self.axes.push(Axis {
1675 name: "Weight".into(),
1676 tag: "wght".into(),
1677 hidden: None,
1678 });
1679 self.axes.push(Axis {
1680 name: "Width".into(),
1681 tag: "wdth".into(),
1682 hidden: None,
1683 });
1684 self.axes.push(Axis {
1685 name: "Custom".into(),
1686 tag: "XXXX".into(),
1687 hidden: None,
1688 });
1689 }
1690
1691 if self.axes.len() > 3 {
1692 return Err(Error::StructuralError(
1693 "We only understand 0..3 axes for Glyphs v2".into(),
1694 ));
1695 }
1696
1697 for master in self.font_master.iter_mut() {
1700 master.axes_values = master.axis_values(&self.axes)?;
1701 }
1702 for instance in self.instances.iter_mut() {
1703 instance.axes_values = instance.axis_values(&self.axes)?;
1704 }
1705
1706 Ok(tags)
1707 }
1708
1709 fn v2_to_v3_metrics(&mut self) -> Result<(), Error> {
1710 self.metrics = V3_METRIC_NAMES
1712 .iter()
1713 .map(|n| RawMetric {
1714 type_: n.to_string(),
1715 })
1716 .collect();
1717
1718 let mut used_metrics = [false; 6];
1719 for m in &self.font_master {
1721 for (i, val) in [
1722 m.ascender,
1723 m.baseline,
1724 m.descender,
1725 m.cap_height,
1726 m.x_height,
1727 m.italic_angle,
1728 ]
1729 .iter()
1730 .enumerate()
1731 {
1732 used_metrics[i] |= val.is_some();
1733 }
1734 }
1735
1736 self.metrics = V3_METRIC_NAMES
1738 .into_iter()
1739 .zip(used_metrics)
1740 .filter(|(_, used)| *used)
1741 .map(|(name, _)| RawMetric {
1742 type_: name.to_string(),
1743 })
1744 .collect();
1745
1746 let mut non_metric_alignment_zones = vec![vec![]; self.font_master.len()];
1747
1748 for (i, master) in self.font_master.iter_mut().enumerate() {
1750 let mut metric_values = vec![RawMetricValue::default(); self.metrics.len()];
1754 for (metric_idx, name) in self.metrics.iter().enumerate() {
1755 let value = match name.type_.as_ref() {
1756 "ascender" => master.ascender,
1757 "baseline" => master.baseline,
1758 "descender" => master.descender,
1759 "cap height" => master.cap_height,
1760 "x-height" => master.x_height,
1761 "italic angle" => master.italic_angle,
1762 _ => unreachable!("only these values exist in V3_METRIC_NAMES"),
1763 };
1764 metric_values[metric_idx].pos = value;
1765 }
1766
1767 for alignment_zone in &master.alignment_zones {
1769 let Some((pos, over)) = parse_alignment_zone(alignment_zone) else {
1770 warn!("Confusing alignment zone '{alignment_zone}', skipping");
1771 continue;
1772 };
1773
1774 if over == 0. {
1776 continue;
1777 }
1778
1779 if pos == 0. {
1783 if let Some((idx, _)) = self
1784 .metrics
1785 .iter()
1786 .enumerate()
1787 .find(|(_, name)| name.type_ == "baseline")
1788 {
1789 metric_values[idx].over = Some(over);
1790 }
1791 continue;
1792 }
1793
1794 if let Some(metric) = metric_values.iter_mut().find(|x| x.pos == Some(pos)) {
1797 metric.over = Some(over);
1798 } else {
1799 non_metric_alignment_zones[i].push((pos, over))
1800 }
1801 }
1802 master.metric_values = metric_values;
1803 }
1804
1805 let mut new_metrics = HashMap::new();
1808 for pos in non_metric_alignment_zones
1809 .iter()
1810 .flat_map(|master| master.iter().map(|(pos, _)| *pos))
1811 {
1812 if !new_metrics.contains_key(&pos) {
1813 let next_zone = new_metrics.len() + 1;
1814 let idx = self.metrics.len();
1815 self.metrics.push(RawMetric {
1816 type_: format!("zone {next_zone}"),
1817 });
1818 new_metrics.insert(pos, idx);
1819 }
1820 }
1821
1822 let new_metrics: BTreeMap<_, _> = new_metrics.into_iter().map(|(k, v)| (v, k)).collect();
1824
1825 for (idx, metrics) in non_metric_alignment_zones.into_iter().enumerate() {
1827 for pos_to_add in new_metrics.values().copied() {
1828 let to_add = metrics.iter().copied().find_map(|(pos, over)| {
1829 (pos == pos_to_add).then_some(RawMetricValue {
1830 pos: Some(pos),
1831 over: Some(over),
1832 })
1833 });
1834
1835 self.font_master[idx]
1836 .metric_values
1837 .push(to_add.unwrap_or_default());
1838 }
1839 }
1840 Ok(())
1841 }
1842
1843 fn v2_to_v3_master_names(&mut self) -> Result<(), Error> {
1844 for master in self.font_master.iter_mut() {
1853 if master.name.is_some() {
1858 continue;
1859 }
1860
1861 let mut names = [
1863 master.width.as_deref(),
1864 master.weight.as_deref(),
1865 master.custom.as_deref(),
1866 ]
1867 .iter()
1868 .flatten()
1869 .flat_map(|n| n.split_ascii_whitespace())
1870 .filter(|x| *x != "Regular")
1871 .collect::<Vec<_>>();
1872
1873 if let Some(italic_angle) = master.italic_angle {
1875 if italic_angle != 0.0
1876 && (names.is_empty()
1877 || !names
1878 .iter()
1879 .any(|name| *name == "Italic" || *name == "Oblique"))
1880 {
1881 names.push("Italic");
1882 }
1883 }
1884 master.name = if names.is_empty() {
1886 Some("Regular".into())
1887 } else {
1888 Some(names.join(" "))
1889 };
1890 }
1891 Ok(())
1892 }
1893
1894 fn v2_to_v3_names(&mut self) -> Result<(), Error> {
1895 let mut properties = std::mem::take(&mut self.properties);
1899
1900 properties.extend(v2_to_v3_name(self.copyright.as_deref(), "copyrights"));
1901 properties.extend(v2_to_v3_name(self.designer.as_deref(), "designers"));
1902 properties.extend(v2_to_v3_name(self.designerURL.as_deref(), "designerURL"));
1903 properties.extend(v2_to_v3_name(self.manufacturer.as_deref(), "manufacturers"));
1904 properties.extend(v2_to_v3_name(
1905 self.manufacturerURL.as_deref(),
1906 "manufacturerURL",
1907 ));
1908
1909 let mut v2_to_v3_param = |v2_names: &[&str], v3_name: &str| {
1912 if properties.iter().any(|n| n.key == v3_name && !n.is_empty()) {
1913 return;
1914 }
1915 for v2_name in v2_names {
1916 if let Some(value) = v2_to_v3_name(self.custom_parameters.string(v2_name), v3_name)
1917 {
1918 properties.push(value);
1919 return;
1920 }
1921 }
1922 };
1923
1924 v2_to_v3_param(&["description", "openTypeNameDescription"], "descriptions");
1925 v2_to_v3_param(&["licenseURL", "openTypeNameLicenseURL"], "licenseURL");
1926 v2_to_v3_param(&["versionString", "openTypeNameVersion"], "versionString");
1927 v2_to_v3_param(&["compatibleFullName"], "compatibleFullNames");
1928 v2_to_v3_param(&["license", "openTypeNameLicense"], "licenses");
1929 v2_to_v3_param(&["uniqueID", "openTypeNameUniqueID"], "uniqueID");
1930 v2_to_v3_param(&["trademark"], "trademarks");
1931 v2_to_v3_param(&["sampleText", "openTypeNameSampleText"], "sampleTexts");
1932 v2_to_v3_param(&["postscriptFullName"], "postscriptFullName");
1933 v2_to_v3_param(&["postscriptFontName"], "postscriptFontName");
1934 v2_to_v3_param(
1935 &["WWSFamilyName", "openTypeNameWWSFamilyName"],
1936 "WWSFamilyName",
1937 );
1938 v2_to_v3_param(&["vendorID", "openTypeOS2VendorID"], "vendorID");
1939
1940 self.properties = properties;
1941
1942 Ok(())
1943 }
1944
1945 fn v2_to_v3_instances(&mut self) -> Result<(), Error> {
1946 for instance in self.instances.iter_mut() {
1947 if let Some(custom_weight_class) = instance.custom_parameters.get("weightClass") {
1948 instance.weight_class = custom_weight_class.to_string().into();
1949 }
1950 for (tag, opt) in [
1952 ("wght", &mut instance.weight_class),
1953 ("wdth", &mut instance.width_class),
1954 ] {
1955 let Some(value) = opt.as_ref() else {
1956 continue;
1957 };
1958 if f64::from_str(value).is_ok() {
1959 continue;
1960 };
1961 let Some(value) = lookup_class_value(tag, value) else {
1962 return Err(Error::UnknownValueName(value.clone()));
1963 };
1964 let _ = opt.insert(value.to_string());
1965 }
1966
1967 instance.properties.extend(v2_to_v3_name(
1968 instance.custom_parameters.string("postscriptFontName"),
1969 "postscriptFontName",
1970 ));
1971 }
1972
1973 Ok(())
1974 }
1975
1976 fn v2_to_v3_layer_attributes(&mut self) {
1977 for raw_glyph in self.glyphs.iter_mut() {
1978 for layer in raw_glyph.layers.iter_mut() {
1979 layer.v2_to_v3_attributes();
1980 }
1981 }
1982 }
1983
1984 fn v2_to_v3(&mut self) -> Result<(), Error> {
1986 self.v2_to_v3_master_names()?;
1987 self.v2_to_v3_axes()?;
1988 self.v2_to_v3_metrics()?;
1989 self.v2_to_v3_instances()?;
1990 self.v2_to_v3_names()?; self.v2_to_v3_layer_attributes();
1992 Ok(())
1993 }
1994}
1995
1996fn parse_alignment_zone(zone: &str) -> Option<(OrderedFloat<f64>, OrderedFloat<f64>)> {
1998 let (one, two) = zone.split_once(',')?;
1999 let one = one.trim_start_matches(['{', ' ']).parse::<i32>().ok()?;
2000 let two = two.trim_start().trim_end_matches('}').parse::<i32>().ok()?;
2001 Some((OrderedFloat(one as f64), OrderedFloat(two as f64)))
2002}
2003
2004fn make_glyph_order(glyphs: &[RawGlyph], custom_order: Option<Vec<SmolStr>>) -> Vec<SmolStr> {
2005 let mut valid_names: HashSet<_> = glyphs.iter().map(|g| &g.glyphname).collect();
2006 let mut glyph_order = Vec::new();
2007
2008 for name in custom_order.into_iter().flatten() {
2011 if valid_names.remove(&name) {
2012 glyph_order.push(name.clone());
2013 }
2014 }
2015
2016 glyph_order.extend(
2018 glyphs
2019 .iter()
2020 .filter(|g| valid_names.contains(&g.glyphname))
2021 .map(|g| g.glyphname.clone()),
2022 );
2023
2024 glyph_order
2025}
2026
2027fn parse_codepoint_str(s: &str, radix: u32) -> BTreeSet<u32> {
2029 s.split(',')
2030 .map(|cp| u32::from_str_radix(cp, radix).unwrap())
2031 .collect()
2032}
2033
2034fn default_master_idx(raw_font: &RawFont) -> usize {
2036 if let Some(master_idx) = raw_font
2039 .custom_parameters
2040 .string("Variable Font Origin")
2041 .and_then(|origin| {
2042 raw_font
2043 .font_master
2044 .iter()
2045 .position(|master| master.id == origin)
2046 })
2047 {
2048 return master_idx;
2049 }
2050
2051 let contenders = raw_font
2055 .font_master
2056 .iter()
2057 .enumerate()
2058 .filter_map(|(i, m)| {
2059 m.name
2060 .as_deref()
2061 .map(|name| (i, whitespace_separated_tokens(name)))
2062 })
2063 .collect::<Vec<_>>();
2064
2065 if contenders.is_empty() {
2067 return 0;
2068 }
2069
2070 let mut common_words = contenders[0].1.clone();
2072 for (_, words) in contenders.iter().skip(1) {
2073 common_words.retain(|w| words.contains(w));
2074 }
2075
2076 let mut best_idx = 0;
2083 for (idx, mut words) in contenders {
2084 if *common_words == words {
2086 best_idx = idx;
2087 break;
2088 }
2089
2090 words.retain(|w| *w != "Regular");
2093 if *common_words == words {
2094 best_idx = idx;
2095 }
2096 }
2097 best_idx
2098}
2099
2100fn whitespace_separated_tokens(s: &str) -> Vec<&str> {
2101 s.split_whitespace().collect()
2102}
2103
2104fn axis_index(axes: &[Axis], pred: impl Fn(&Axis) -> bool) -> Option<usize> {
2105 axes.iter()
2106 .enumerate()
2107 .find_map(|(i, a)| if pred(a) { Some(i) } else { None })
2108}
2109
2110fn user_to_design_from_axis_mapping(
2111 from: &RawFont,
2112) -> Option<BTreeMap<String, AxisUserToDesignMap>> {
2113 let mappings = from.custom_parameters.axis_mappings()?;
2114 let mut axis_mappings: BTreeMap<String, AxisUserToDesignMap> = BTreeMap::new();
2115 for mapping in mappings {
2116 let Some(axis_index) = axis_index(&from.axes, |a| a.tag == mapping.tag) else {
2117 log::warn!(
2118 "axis mapping includes tag {:?} not included in font",
2119 mapping.tag
2120 );
2121 continue;
2122 };
2123 let axis_name = &from.axes.get(axis_index).unwrap().name;
2124 for (user, design) in mapping.user_to_design.iter() {
2125 axis_mappings
2126 .entry(axis_name.clone())
2127 .or_default()
2128 .add_if_new(*user, *design);
2129 }
2130 }
2131 Some(axis_mappings)
2132}
2133
2134fn user_to_design_from_axis_location(
2135 from: &RawFont,
2136) -> Option<BTreeMap<String, AxisUserToDesignMap>> {
2137 let master_locations: Vec<_> = from
2140 .font_master
2141 .iter()
2142 .filter_map(|m| m.custom_parameters.axis_locations())
2143 .collect();
2144 if master_locations.len() != from.font_master.len() {
2145 if !master_locations.is_empty() {
2146 warn!(
2147 "{}/{} masters have Axis Location; ignoring",
2148 master_locations.len(),
2149 from.font_master.len()
2150 );
2151 }
2152 return None;
2153 }
2154
2155 let mut axis_mappings: BTreeMap<String, AxisUserToDesignMap> = BTreeMap::new();
2156 for (master, axis_locations) in from.font_master.iter().zip(master_locations) {
2157 for axis_location in axis_locations {
2158 let Some(axis_index) = axis_index(&from.axes, |a| a.name == axis_location.axis_name)
2159 else {
2160 panic!("Axis has no index {axis_location:?}");
2161 };
2162 let user = axis_location.location;
2163 let design = master.axes_values[axis_index];
2164
2165 axis_mappings
2166 .entry(axis_location.axis_name.clone())
2167 .or_default()
2168 .add_if_new(user, design);
2169 }
2170 }
2171 Some(axis_mappings)
2172}
2173
2174impl AxisUserToDesignMap {
2175 fn add_any_new(&mut self, incoming: &AxisUserToDesignMap) {
2176 for (user, design) in incoming.0.iter() {
2177 self.add_if_new(*user, *design);
2178 }
2179 }
2180
2181 fn add_if_new(&mut self, user: OrderedFloat<f64>, design: OrderedFloat<f64>) {
2182 if self.0.iter().any(|(u, _)| *u == user) {
2184 return;
2185 }
2186 self.0.push((user, design));
2187 }
2188
2189 fn add_identity_map(&mut self, value: OrderedFloat<f64>) {
2190 if self.0.iter().any(|(u, d)| *u == value || *d == value) {
2192 return;
2193 }
2194 self.0.push((value, value));
2195 }
2196
2197 pub fn iter(&self) -> impl Iterator<Item = &(OrderedFloat<f64>, OrderedFloat<f64>)> {
2198 self.0.iter()
2199 }
2200
2201 pub fn is_identity(&self) -> bool {
2202 self.0.iter().all(|(u, d)| u == d)
2203 }
2204}
2205
2206impl UserToDesignMapping {
2207 fn new(from: &RawFont, instances: &[Instance]) -> Self {
2211 let from_axis_mapping = user_to_design_from_axis_mapping(from);
2212 let from_axis_location = user_to_design_from_axis_location(from);
2213 let (result, incomplete_mapping) = match (from_axis_mapping, from_axis_location) {
2214 (Some(from_mapping), Some(..)) => {
2215 warn!("Axis Mapping *and* Axis Location are defined; using Axis Mapping");
2216 (from_mapping, false)
2217 }
2218 (Some(from_mapping), None) => (from_mapping, false),
2219 (None, Some(from_location)) => (from_location, true),
2220 (None, None) => (BTreeMap::new(), true),
2221 };
2222 let mut result = Self(result);
2223 if incomplete_mapping {
2224 result.add_instance_mappings_if_new(instances);
2226 if result.0.is_empty() || result.0.values().all(|v| v.is_identity()) {
2227 result.add_master_mappings_if_new(from);
2228 }
2229 }
2230 result
2231 }
2232
2233 pub fn contains(&self, axis_name: &str) -> bool {
2234 self.0.contains_key(axis_name)
2235 }
2236
2237 pub fn get(&self, axis_name: &str) -> Option<&AxisUserToDesignMap> {
2238 self.0.get(axis_name)
2239 }
2240
2241 fn add_instance_mappings_if_new(&mut self, instances: &[Instance]) {
2244 for instance in instances
2245 .iter()
2246 .filter(|i| i.active && i.type_ == InstanceType::Single)
2247 {
2248 for (axis_name, inst_mapping) in instance.axis_mappings.iter() {
2249 self.0
2250 .entry(axis_name.clone())
2251 .or_default()
2252 .add_any_new(inst_mapping);
2253 }
2254 }
2255 }
2256
2257 fn add_master_mappings_if_new(&mut self, from: &RawFont) {
2258 for master in from.font_master.iter() {
2259 for (axis, value) in from.axes.iter().zip(&master.axes_values) {
2260 self.0
2261 .entry(axis.name.clone())
2262 .or_default()
2263 .add_identity_map(*value);
2264 }
2265 }
2266 }
2267}
2268
2269impl TryFrom<RawShape> for Shape {
2270 type Error = Error;
2271
2272 fn try_from(from: RawShape) -> Result<Self, Self::Error> {
2273 let shape = if let Some(glyph_name) = from.glyph_name {
2277 assert!(!glyph_name.is_empty(), "A pointless component");
2278
2279 let mut transform = if let Some(transform) = from.transform {
2281 Affine::parse_plist(&transform)?
2282 } else {
2283 Affine::IDENTITY
2284 };
2285
2286 if !from.pos.is_empty() {
2291 if from.pos.len() != 2 {
2292 return Err(Error::StructuralError(format!("Bad pos: {:?}", from.pos)));
2293 }
2294 transform *= Affine::translate((from.pos[0], from.pos[1]));
2295 }
2296 if let Some(angle) = from.angle {
2297 transform *= normalized_rotation(angle);
2298 }
2299 if !from.scale.is_empty() {
2300 if from.scale.len() != 2 {
2301 return Err(Error::StructuralError(format!(
2302 "Bad scale: {:?}",
2303 from.scale
2304 )));
2305 }
2306 transform *= Affine::scale_non_uniform(from.scale[0], from.scale[1]);
2307 }
2308
2309 Shape::Component(Component {
2310 name: glyph_name,
2311 transform,
2312 anchor: from.anchor,
2313 attributes: from.attributes,
2314 })
2315 } else {
2316 Shape::Path(Path {
2318 closed: from.closed.unwrap_or_default(),
2319 nodes: from.nodes.clone(),
2320 attributes: from.attributes,
2321 })
2322 };
2323 Ok(shape)
2324 }
2325}
2326
2327fn normalized_rotation(angle_deg: f64) -> Affine {
2337 const ROT_90: Affine = Affine::new([0.0, 1.0, -1.0, 0.0, 0.0, 0.0]);
2338 const ROT_180: Affine = Affine::new([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0]);
2339 const ROT_270: Affine = Affine::new([0.0, -1.0, 1.0, 0.0, 0.0, 0.0]);
2340 let normalized_angle = angle_deg.rem_euclid(360.0);
2342
2343 match normalized_angle {
2344 0.0 => Affine::IDENTITY,
2345 90.0 => ROT_90,
2346 180.0 => ROT_180,
2347 270.0 => ROT_270,
2348 _ => Affine::rotate(angle_deg.to_radians()),
2349 }
2350}
2351
2352fn map_and_push_if_present<T, U>(dest: &mut Vec<T>, src: Vec<U>, map: fn(U) -> T) {
2353 src.into_iter().map(map).for_each(|v| dest.push(v));
2354}
2355
2356impl RawLayer {
2357 fn build(self, format_version: FormatVersion) -> Result<Layer, Error> {
2358 const DEFAULT_LAYER_WIDTH: f64 = 600.;
2364 let mut shapes = Vec::new();
2365
2366 map_and_push_if_present(&mut shapes, self.paths, Shape::Path);
2368 map_and_push_if_present(&mut shapes, self.components, Shape::Component);
2369
2370 for raw_shape in self.shapes {
2372 shapes.push(raw_shape.try_into()?);
2373 }
2374
2375 let anchors = self
2376 .anchors
2377 .into_iter()
2378 .map(|ra| {
2379 let pos = if let Some(pos) = ra.pos {
2380 pos
2381 } else if let Some(raw) = ra.position {
2382 Point::parse_plist(&raw).unwrap()
2383 } else {
2384 Point::ZERO
2385 };
2386 Anchor { name: ra.name, pos }
2387 })
2388 .collect();
2389
2390 let mut attributes = self.attributes;
2391 if let Some(axis_rule) =
2393 AxisRule::from_layer_name(&self.name).filter(|_| format_version.is_v2())
2394 {
2395 assert!(
2396 attributes.axis_rules.is_empty(),
2397 "glyphs v2 does not use axisRules attr"
2398 );
2399 attributes.axis_rules.push(axis_rule);
2400 }
2401 Ok(Layer {
2402 layer_id: self.layer_id,
2403 associated_master_id: self.associated_master_id,
2404 width: self.width.unwrap_or(DEFAULT_LAYER_WIDTH.into()),
2405 vert_width: self.vert_width,
2406 vert_origin: self.vert_origin,
2407 shapes,
2408 anchors,
2409 attributes,
2410 })
2411 }
2412}
2413
2414impl RawGlyph {
2415 fn build(self, format_version: FormatVersion, glyph_data: &GlyphData) -> Result<Glyph, Error> {
2417 let mut instances = Vec::new();
2418 let mut bracket_layers = Vec::new();
2419 for layer in self.layers {
2420 if layer.is_bracket_layer(format_version) {
2421 bracket_layers.push(layer.build(format_version)?);
2422 } else if !layer.is_draft() {
2423 instances.push(layer.build(format_version)?);
2424 }
2425 }
2426 fn parse_category<T>(s: Option<&str>, glyph: &SmolStr) -> Option<T>
2431 where
2432 T: FromStr<Err = SmolStr>,
2433 {
2434 match s.filter(|s| !s.is_empty()).map(T::from_str).transpose() {
2435 Ok(x) => x,
2436 Err(err) => {
2438 log::warn!("Unknown category '{err}' for glyph '{glyph}'");
2439 None
2440 }
2441 }
2442 }
2443
2444 let mut category = parse_category(self.category.as_deref(), &self.glyphname);
2445 let mut sub_category = parse_category(self.sub_category.as_deref(), &self.glyphname);
2446 let mut production_name = self.production_name;
2447
2448 let codepoints = self
2449 .unicode
2450 .map(|s| parse_codepoint_str(&s, format_version.codepoint_radix()))
2451 .unwrap_or_default();
2452
2453 if category.is_none() || sub_category.is_none() || production_name.is_none() {
2454 if let Some(result) = glyph_data.query(&self.glyphname, Some(&codepoints)) {
2455 category = category.or(Some(result.category));
2457 sub_category = sub_category.or(result.subcategory);
2458 production_name = production_name.or(result.production_name.map(Into::into));
2459 }
2460 }
2461
2462 Ok(Glyph {
2463 name: self.glyphname,
2464 export: self.export.unwrap_or(true),
2465 layers: instances,
2466 bracket_layers,
2467 left_kern: self.kern_left,
2468 right_kern: self.kern_right,
2469 unicode: codepoints,
2470 category,
2471 sub_category,
2472 production_name,
2473 })
2474 }
2475}
2476
2477#[rustfmt::skip]
2479static GLYPHS_TO_OPENTYPE_LANGUAGE_ID: &[(&str, i32)] = &[
2480 ("AFK", 0x0436), ("ARA", 0x0C01), ("ASM", 0x044D), ("AZE", 0x042C), ("BEL", 0x0423),
2481 ("BEN", 0x0845), ("BGR", 0x0402), ("BRE", 0x047E), ("CAT", 0x0403), ("CSY", 0x0405),
2482 ("DAN", 0x0406), ("DEU", 0x0407), ("ELL", 0x0408), ("ENG", 0x0409), ("ESP", 0x0C0A),
2483 ("ETI", 0x0425), ("EUQ", 0x042D), ("FIN", 0x040B), ("FLE", 0x0813), ("FOS", 0x0438),
2484 ("FRA", 0x040C), ("FRI", 0x0462), ("GRN", 0x046F), ("GUJ", 0x0447), ("HAU", 0x0468),
2485 ("HIN", 0x0439), ("HRV", 0x041A), ("HUN", 0x040E), ("HVE", 0x042B), ("IRI", 0x083C),
2486 ("ISL", 0x040F), ("ITA", 0x0410), ("IWR", 0x040D), ("JPN", 0x0411), ("KAN", 0x044B),
2487 ("KAT", 0x0437), ("KAZ", 0x043F), ("KHM", 0x0453), ("KOK", 0x0457), ("LAO", 0x0454),
2488 ("LSB", 0x082E), ("LTH", 0x0427), ("LVI", 0x0426), ("MAR", 0x044E), ("MKD", 0x042F),
2489 ("MLR", 0x044C), ("MLY", 0x043E), ("MNG", 0x0352), ("MTS", 0x043A), ("NEP", 0x0461),
2490 ("NLD", 0x0413), ("NOB", 0x0414), ("ORI", 0x0448), ("PAN", 0x0446), ("PAS", 0x0463),
2491 ("PLK", 0x0415), ("PTG", 0x0816), ("PTG-BR", 0x0416), ("RMS", 0x0417), ("ROM", 0x0418),
2492 ("RUS", 0x0419), ("SAN", 0x044F), ("SKY", 0x041B), ("SLV", 0x0424), ("SQI", 0x041C),
2493 ("SRB", 0x081A), ("SVE", 0x041D), ("TAM", 0x0449), ("TAT", 0x0444), ("TEL", 0x044A),
2494 ("THA", 0x041E), ("TIB", 0x0451), ("TRK", 0x041F), ("UKR", 0x0422), ("URD", 0x0420),
2495 ("USB", 0x042E), ("UYG", 0x0480), ("UZB", 0x0443), ("VIT", 0x042A), ("WEL", 0x0452),
2496 ("ZHH", 0x0C04), ("ZHS", 0x0804), ("ZHT", 0x0404),
2497 ("dflt", 0x0409),
2498];
2499
2500impl RawFeature {
2501 fn autostr(&self) -> &str {
2503 match self.automatic {
2504 Some(1) => "# automatic\n",
2505 _ => "",
2506 }
2507 }
2508
2509 fn name(&self) -> Result<&str, Error> {
2510 self.name
2511 .as_deref()
2512 .or(self.tag.as_deref())
2513 .ok_or_else(|| Error::StructuralError(format!("{self:?} missing name and tag")))
2514 }
2515
2516 fn disabled(&self) -> bool {
2517 self.disabled == Some(1)
2518 }
2519
2520 fn legacy_name_record_maybe(&self) -> Option<String> {
2525 let name = self.notes.as_deref()?.strip_prefix("Name:")?.trim();
2526 if name.is_empty() {
2527 None
2528 } else {
2529 Some(format!("name 3 1 0x409 \"{name}\";"))
2530 }
2531 }
2532
2533 fn feature_names(&self) -> String {
2535 let labels = self
2536 .labels
2537 .iter()
2538 .filter_map(|label| label.to_fea())
2539 .chain(self.legacy_name_record_maybe())
2540 .collect::<Vec<_>>()
2541 .join("\n");
2542 if labels.is_empty() {
2543 Default::default()
2544 } else {
2545 format!("featureNames {{\n{labels}\n}};\n")
2546 }
2547 }
2548
2549 fn prefix_to_feature(&self) -> Result<FeatureSnippet, Error> {
2551 let name = self.name.as_deref().unwrap_or_default();
2552 let code = format!("# Prefix: {}\n{}{}", name, self.autostr(), self.code);
2553 Ok(FeatureSnippet::new(code, self.disabled()))
2554 }
2555
2556 fn class_to_feature(&self) -> Result<FeatureSnippet, Error> {
2558 let name = self.name()?;
2559 let code = format!(
2560 "{}{}{name} = [ {}\n];",
2561 self.autostr(),
2562 if name.starts_with('@') { "" } else { "@" },
2563 self.code
2564 );
2565 Ok(FeatureSnippet::new(code, self.disabled()))
2566 }
2567
2568 fn raw_feature_to_feature(&self) -> Result<FeatureSnippet, Error> {
2570 let name = self.name()?;
2571 let insert_mark = self.insert_mark_if_manual_kern_feature();
2572 let code = format!(
2573 "feature {name} {{\n{}{}{}{insert_mark}\n}} {name};",
2574 self.autostr(),
2575 self.feature_names(),
2576 self.code
2577 );
2578 Ok(FeatureSnippet::new(code, self.disabled()))
2579 }
2580
2581 fn insert_mark_if_manual_kern_feature(&self) -> &str {
2583 if self.name().unwrap_or_default() == "kern"
2584 && self.automatic != Some(1)
2585 && !self.code.contains("# Automatic Code")
2586 {
2587 "# Automatic Code\n"
2588 } else {
2589 ""
2590 }
2591 }
2592}
2593
2594impl RawNameValue {
2595 fn to_fea(&self) -> Option<String> {
2596 if self.value.is_empty() {
2597 return None;
2600 }
2601
2602 match GLYPHS_TO_OPENTYPE_LANGUAGE_ID
2603 .binary_search_by_key(&self.language.as_str(), |entry| entry.0)
2604 {
2605 Ok(idx) => {
2606 let language_id = GLYPHS_TO_OPENTYPE_LANGUAGE_ID[idx].1;
2607 let name = self.value.replace("\\", "\\005c").replace("\"", "\\0022");
2608 Some(format!(" name 3 1 0x{language_id:04X} \"{name}\";"))
2609 }
2610 Err(_) => {
2611 warn!("Unknown feature label language: {}", self.language);
2612 None
2613 }
2614 }
2615 }
2616}
2617
2618fn lookup_class_value(axis_tag: &str, user_class: &str) -> Option<u16> {
2620 let user_class = match user_class {
2621 value if !value.is_empty() => {
2622 let mut value = value.to_ascii_lowercase();
2623 value.retain(|c| c != ' ');
2624 value
2625 }
2626 _ => String::from(""),
2627 };
2628 match (axis_tag, user_class.as_str()) {
2629 ("wght", "thin") => Some(100),
2630 ("wght", "extralight" | "ultralight") => Some(200),
2631 ("wght", "light") => Some(300),
2632 ("wght", "" | "normal" | "regular") => Some(400),
2633 ("wght", "medium") => Some(500),
2634 ("wght", "demibold" | "semibold") => Some(600),
2635 ("wght", "bold") => Some(700),
2636 ("wght", "ultrabold" | "extrabold") => Some(800),
2637 ("wght", "black" | "heavy") => Some(900),
2638 ("wdth", "ultracondensed") => Some(1),
2639 ("wdth", "extracondensed") => Some(2),
2640 ("wdth", "condensed") => Some(3),
2641 ("wdth", "semicondensed") => Some(4),
2642 ("wdth", "" | "Medium (normal)") => Some(5),
2643 ("wdth", "semiexpanded") => Some(6),
2644 ("wdth", "expanded") => Some(7),
2645 ("wdth", "extraexpanded") => Some(8),
2646 ("wdth", "ultraexpanded") => Some(9),
2647 _ => {
2648 warn!("Unrecognized ('{axis_tag}', '{user_class}')");
2649 None
2650 }
2651 }
2652}
2653
2654fn add_mapping_if_new(
2655 axis_mappings: &mut BTreeMap<String, AxisUserToDesignMap>,
2656 axes: &[Axis],
2657 axis_tag: &str,
2658 axes_values: &[OrderedFloat<f64>],
2659 value: f64,
2660) {
2661 let Some(idx) = axes.iter().position(|a| a.tag == axis_tag) else {
2662 return;
2663 };
2664 let axis = &axes[idx];
2665 let Some(design) = axes_values.get(idx) else {
2666 return;
2667 };
2668
2669 axis_mappings
2670 .entry(axis.name.clone())
2671 .or_default()
2672 .add_if_new(value.into(), *design);
2673}
2674
2675impl Instance {
2676 fn new(
2681 axes: &[Axis],
2682 value: &RawInstance,
2683 masters_have_axis_locations: bool,
2684 ) -> Result<Self, Error> {
2685 let active = value.is_active();
2686 let mut axis_mappings: BTreeMap<String, AxisUserToDesignMap> = BTreeMap::new();
2687
2688 let mut tags_done = BTreeSet::new();
2691 for axis_location in value.custom_parameters.axis_locations().unwrap_or_default() {
2692 let Some(axis_index) = axis_index(axes, |a| a.name == axis_location.axis_name) else {
2693 log::warn!(
2694 "{} instance's 'Axis Location' includes axis {:?} not included in font",
2695 value.name,
2696 axis_location.axis_name
2697 );
2698 continue;
2699 };
2700 let user = axis_location.location;
2701 let design = value.axes_values[axis_index];
2702
2703 axis_mappings
2704 .entry(axis_location.axis_name.clone())
2705 .or_default()
2706 .add_if_new(user, design);
2707
2708 tags_done.insert(axes[axis_index].tag.as_str());
2709 }
2710
2711 if !masters_have_axis_locations && !tags_done.contains("wght") {
2713 add_mapping_if_new(
2715 &mut axis_mappings,
2716 axes,
2717 "wght",
2718 &value.axes_values,
2719 value
2720 .weight_class
2721 .as_ref()
2722 .map(|v| f64::from_str(v).unwrap())
2723 .unwrap_or(400.0),
2724 );
2725 }
2726
2727 if !masters_have_axis_locations && !tags_done.contains("wdth") {
2728 add_mapping_if_new(
2731 &mut axis_mappings,
2732 axes,
2733 "wdth",
2734 value.axes_values.as_ref(),
2735 value
2736 .width_class
2737 .as_ref()
2738 .map(|v| match WidthClass::try_from(u16::from_str(v).unwrap()) {
2739 Ok(width_class) => width_class.to_percent(),
2740 Err(err) => {
2741 warn!("{err}");
2742 100.0
2743 }
2744 })
2745 .unwrap_or(100.0),
2746 );
2747 }
2748
2749 Ok(Instance {
2750 name: value.name.clone(),
2751 active,
2752 type_: value
2753 .type_
2754 .as_ref()
2755 .map(|v| v.as_str().into())
2756 .unwrap_or(InstanceType::Single),
2757 axis_mappings,
2758 axes_values: value.axes_values.clone(),
2759 properties: value.properties.clone(),
2760 custom_parameters: value.custom_parameters.to_custom_params()?,
2761 })
2762 }
2763
2764 fn family_name(&self) -> Option<&str> {
2765 self.properties
2766 .iter()
2767 .find(|raw| raw.key == "familyNames")
2768 .and_then(RawName::get_value)
2769 }
2774
2775 pub fn postscript_name(&self) -> Option<&str> {
2777 ["variablePostscriptFontName", "postscriptFontName"]
2780 .iter()
2781 .find_map(|key| {
2782 self.properties
2783 .iter()
2784 .find(|raw| raw.key == *key)
2785 .and_then(RawName::get_value)
2786 })
2787 }
2788}
2789
2790fn codepage_range_bit(codepage: u32) -> Result<u32, Error> {
2794 Ok(match codepage {
2795 1252 => 0, 1250 => 1, 1251 => 2, 1253 => 3, 1254 => 4, 1255 => 5, 1256 => 6, 1257 => 7, 1258 => 8, 874 => 16, 932 => 17, 936 => 18, 949 => 19, 950 => 20, 1361 => 21, 869 => 48, 866 => 49, 865 => 50, 864 => 51, 863 => 52, 862 => 53, 861 => 54, 860 => 55, 857 => 56, 855 => 57, 852 => 58, 775 => 59, 737 => 60, 708 => 61, 850 => 62, 437 => 63, v if v < 64 => v, _ => return Err(Error::InvalidCodePage(codepage)),
2829 })
2830}
2831
2832fn update_names(names: &mut BTreeMap<String, String>, raw_names: &[RawName]) {
2833 for name in raw_names {
2834 if let Some(value) = name.get_value() {
2836 names.insert(name.key.clone(), value.to_string());
2837 }
2838 }
2839}
2840
2841impl TryFrom<RawFont> for Font {
2842 type Error = Error;
2843
2844 fn try_from(mut from: RawFont) -> Result<Self, Self::Error> {
2845 if from.format_version.is_v2() {
2846 from.v2_to_v3()?;
2847 } else {
2848 from.v2_to_v3_names()?;
2850 }
2851
2852 let glyph_data = GlyphData::default();
2854
2855 let mut custom_parameters = from.custom_parameters.to_custom_params()?;
2856 let glyph_order = make_glyph_order(&from.glyphs, custom_parameters.glyph_order.take());
2857
2858 let default_master_idx = default_master_idx(&from);
2859
2860 let masters_have_axis_locations = from
2867 .font_master
2868 .iter()
2869 .all(|master| master.custom_parameters.contains("Axis Location"));
2870
2871 let instances: Vec<_> = from
2872 .instances
2873 .iter()
2874 .map(|ri| Instance::new(&from.axes, ri, masters_have_axis_locations))
2875 .collect::<Result<Vec<_>, Error>>()?;
2876
2877 let axis_mappings = UserToDesignMapping::new(&from, &instances);
2878
2879 let mut glyphs = BTreeMap::new();
2880 for raw_glyph in from.glyphs.into_iter() {
2881 glyphs.insert(
2882 raw_glyph.glyphname.clone(),
2883 raw_glyph.build(from.format_version, &glyph_data)?,
2884 );
2885 }
2886
2887 let mut features = Vec::new();
2888 for class in from.classes {
2889 features.push(class.class_to_feature()?);
2890 }
2891 for prefix in from.feature_prefixes {
2892 features.push(prefix.prefix_to_feature()?);
2893 }
2894 for feature in from.features {
2895 features.push(feature.raw_feature_to_feature()?);
2896 }
2897
2898 let units_per_em = from.units_per_em.ok_or(Error::NoUnitsPerEm)?;
2899 let units_per_em = units_per_em.try_into().map_err(Error::InvalidUpem)?;
2900
2901 let mut names = BTreeMap::new();
2902 update_names(&mut names, &from.properties);
2903 for instance in &instances {
2905 if instance.active
2906 && instance.type_ == InstanceType::Variable
2907 && instance.family_name().map(|name| name == from.family_name.as_str()).unwrap_or(true)
2911 {
2912 update_names(&mut names, &instance.properties);
2913 }
2914 }
2915 names.insert("familyNames".into(), from.family_name);
2917 if let Some(version) = names.remove("versionString") {
2918 names.insert("version".into(), version);
2919 }
2920
2921 let metric_names: BTreeMap<usize, String> = from
2922 .metrics
2923 .into_iter()
2924 .enumerate()
2925 .map(|(idx, metric)| (idx, metric.type_))
2926 .collect();
2927
2928 let masters = from
2929 .font_master
2930 .into_iter()
2931 .map(|m| {
2932 let custom_parameters = m.custom_parameters.to_custom_params()?;
2933 Ok(FontMaster {
2934 id: m.id,
2935 name: m.name.unwrap_or_default(),
2936 axes_values: m.axes_values,
2937 metric_values: m
2938 .metric_values
2939 .into_iter()
2940 .enumerate()
2941 .filter_map(|(idx, value)| {
2942 metric_names.get(&idx).map(|name| (name.clone(), value))
2943 })
2944 .fold(BTreeMap::new(), |mut acc, (name, value)| {
2945 acc.entry(name).or_insert(value.into());
2949 acc
2950 }),
2951 number_values: from
2952 .numbers
2953 .iter()
2954 .zip(m.number_values.iter())
2955 .map(|(k, v)| (k.name.clone(), *v))
2956 .collect(),
2957 custom_parameters,
2958 })
2959 })
2960 .collect::<Result<_, Error>>()?;
2961
2962 let virtual_masters = custom_parameters.virtual_masters.take().unwrap_or_default();
2963 Ok(Font {
2964 units_per_em,
2965 axes: from.axes,
2966 masters,
2967 default_master_idx,
2968 glyphs,
2969 glyph_order,
2970 axis_mappings,
2971 virtual_masters,
2972 features,
2973 names,
2974 instances,
2975 version_major: from.versionMajor.unwrap_or_default() as i32,
2976 version_minor: from.versionMinor.unwrap_or_default() as u32,
2977 date: from.date,
2978 kerning_ltr: from.kerning_LTR,
2979 kerning_rtl: from.kerning_RTL,
2980 custom_parameters,
2981 })
2982 }
2983}
2984
2985fn preprocess_unparsed_plist(s: &str) -> Cow<'_, str> {
2986 let unicode_re =
2989 Regex::new(r"(?m)^(?P<prefix>\s*unicode\s*=\s*)[(]?(?P<value>[0-9a-zA-Z,]+)[)]?;\s*$")
2990 .unwrap();
2991 unicode_re.replace_all(s, r#"$prefix"$value";"#)
2992}
2993
2994fn variable_instance_for<'a>(instances: &'a [Instance], name: &str) -> Option<&'a Instance> {
2995 instances
2996 .iter()
2997 .find(|i| i.active && i.type_ == InstanceType::Variable && i.name == name)
2998}
2999
3000impl Font {
3001 pub fn load_from_string(data: &str) -> Result<Font, Error> {
3002 let raw_font = RawFont::load_from_string(data)?;
3003 let mut font = Font::try_from(raw_font)?;
3004 font.preprocess();
3005 Ok(font)
3006 }
3007
3008 pub fn load(glyphs_file: &path::Path) -> Result<Font, Error> {
3009 let mut font = Self::load_raw(glyphs_file)?;
3010 font.preprocess();
3011 Ok(font)
3012 }
3013
3014 fn preprocess(&mut self) {
3015 self.align_bracket_layers();
3018
3019 if self.custom_parameters.propagate_anchors.unwrap_or(true) {
3021 self.propagate_all_anchors();
3022 }
3023 }
3024
3025 fn align_bracket_layers(&mut self) {
3031 let todo = self.depth_sorted_composite_glyphs();
3033
3034 for name in &todo {
3035 let mut needed = IndexMap::new();
3036 let glyph = self.glyphs.get(name).unwrap();
3037 let my_bracket_layers = glyph
3038 .bracket_layers
3039 .iter()
3040 .map(|l| l.bracket_info(&self.axes))
3041 .collect::<IndexSet<_>>();
3042 for layer in &glyph.layers {
3043 for comp in layer.components() {
3044 let comp_glyph = self.glyphs.get(&comp.name).unwrap();
3045
3046 let comp_bracket_layers = comp_glyph
3047 .bracket_layers
3048 .iter()
3049 .map(|l| l.bracket_info(&self.axes))
3050 .collect::<IndexSet<_>>();
3051 if comp_bracket_layers != my_bracket_layers {
3052 let missing = comp_bracket_layers.difference(&my_bracket_layers);
3053 needed
3054 .entry(layer.layer_id.clone())
3055 .or_insert(IndexSet::new())
3056 .extend(missing.cloned());
3057 }
3058 }
3059 }
3060
3061 if needed.is_empty() {
3062 continue;
3064 }
3065
3066 let mut new_layers = Vec::new();
3067 for (master, needed_brackets) in needed {
3068 let master_name = self
3069 .masters
3070 .iter()
3071 .find_map(|m| (m.id == master).then_some(m.name.as_str()))
3072 .unwrap_or(master.as_str());
3073 if !my_bracket_layers.is_empty() {
3074 log::warn!(
3075 "Glyph {name} in master {master_name} has different bracket layers \
3076 than its components. This is not supported, so some bracket layers \
3077 will not be applied. Consider fixing the source instead."
3078 );
3079 }
3080
3081 let base_layer = glyph.layers.iter().find(|l| l.layer_id == master).unwrap();
3082 for box_ in needed_brackets {
3083 log::debug!("synthesized layer {box_:?} in master {master_name} for '{name}'",);
3084 let new_layer = synthesize_bracket_layer(base_layer, box_, &self.axes);
3085 new_layers.push(new_layer);
3086 }
3087 }
3088
3089 self.glyphs
3090 .get_mut(name)
3091 .unwrap()
3092 .bracket_layers
3093 .extend_from_slice(&new_layers);
3094 }
3095 }
3096
3097 pub(crate) fn load_raw(glyphs_file: impl AsRef<path::Path>) -> Result<Font, Error> {
3099 RawFont::load(glyphs_file.as_ref()).and_then(Font::try_from)
3100 }
3101
3102 pub fn default_master(&self) -> &FontMaster {
3103 &self.masters[self.default_master_idx]
3104 }
3105
3106 pub fn variable_export_settings(&self, master: &FontMaster) -> Option<&Instance> {
3108 variable_instance_for(&self.instances, &master.name)
3109 }
3110
3111 pub fn vendor_id(&self) -> Option<&String> {
3112 self.names.get("vendorID")
3113 }
3114}
3115
3116fn synthesize_bracket_layer(
3118 old_layer: &Layer,
3119 box_: BTreeMap<String, (Option<i64>, Option<i64>)>,
3120 axes: &[Axis],
3121) -> Layer {
3122 let mut new_layer = old_layer.clone();
3123 new_layer.associated_master_id = Some(std::mem::take(&mut new_layer.layer_id));
3124 new_layer.attributes.axis_rules = axes
3125 .iter()
3126 .map(|axis| {
3127 if let Some((min, max)) = box_.get(&axis.tag) {
3128 AxisRule {
3129 min: min.map(|x| x as _),
3130 max: max.map(|x| x as _),
3131 }
3132 } else {
3133 Default::default()
3134 }
3135 })
3136 .collect();
3137
3138 new_layer
3139}
3140
3141#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3143struct PointForEqAndHash {
3144 x: OrderedFloat<f64>,
3145 y: OrderedFloat<f64>,
3146}
3147
3148impl PointForEqAndHash {
3149 fn new(point: Point) -> PointForEqAndHash {
3150 point.into()
3151 }
3152}
3153
3154impl From<Point> for PointForEqAndHash {
3155 fn from(value: Point) -> Self {
3156 PointForEqAndHash {
3157 x: value.x.into(),
3158 y: value.y.into(),
3159 }
3160 }
3161}
3162
3163#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3165struct AffineForEqAndHash([OrderedFloat<f64>; 6]);
3166
3167impl From<Affine> for AffineForEqAndHash {
3168 fn from(value: Affine) -> Self {
3169 Self(value.as_coeffs().map(|coeff| coeff.into()))
3170 }
3171}
3172
3173#[cfg(test)]
3174mod tests {
3175 use super::*;
3176 use crate::{plist::FromPlist, Font, FontMaster, Node, Shape};
3177 use std::{
3178 collections::{BTreeMap, BTreeSet, HashSet},
3179 path::{Path, PathBuf},
3180 };
3181
3182 use ordered_float::OrderedFloat;
3183
3184 use pretty_assertions::assert_eq;
3185
3186 use kurbo::{Affine, Point};
3187
3188 use rstest::rstest;
3189 use smol_str::ToSmolStr;
3190
3191 fn testdata_dir() -> PathBuf {
3192 let mut dir = Path::new("../resources/testdata");
3194 if !dir.is_dir() {
3195 dir = Path::new("./resources/testdata");
3196 }
3197 assert!(dir.is_dir());
3198 dir.to_path_buf()
3199 }
3200
3201 fn glyphs2_dir() -> PathBuf {
3202 testdata_dir().join("glyphs2")
3203 }
3204
3205 fn glyphs3_dir() -> PathBuf {
3206 testdata_dir().join("glyphs3")
3207 }
3208
3209 fn round(transform: Affine, digits: u8) -> Affine {
3210 let m = 10f64.powi(digits as i32);
3211 let mut coeffs = transform.as_coeffs();
3212 for c in coeffs.iter_mut() {
3213 *c = (*c * m).round() / m;
3214 }
3215 Affine::new(coeffs)
3216 }
3217
3218 #[test]
3219 fn v2_format_version() {
3220 let v2_font = glyphs2_dir().join("Mono.glyphs");
3221 let as_str = std::fs::read_to_string(&v2_font).unwrap();
3222 assert!(!as_str.contains(".formatVersion"), "only exists in v3");
3223 let font = RawFont::load(&v2_font).unwrap();
3224 assert_eq!(font.format_version, FormatVersion::V2);
3226 }
3227
3228 #[test]
3229 fn v3_format_version() {
3230 let v3_font = glyphs3_dir().join("MasterNames.glyphs");
3231 let as_str = std::fs::read_to_string(&v3_font).unwrap();
3232 assert!(as_str.contains(".formatVersion"), "exists in v3");
3233 let font = RawFont::load(&v3_font).unwrap();
3234 assert_eq!(font.format_version, FormatVersion::V3);
3236 }
3237
3238 #[test]
3239 fn test_glyphs3_node() {
3240 let node: Node = Node::parse_plist("(354, 183, l)").unwrap();
3241 assert_eq!(
3242 Node {
3243 node_type: crate::NodeType::Line,
3244 pt: super::Point { x: 354.0, y: 183.0 }
3245 },
3246 node
3247 );
3248 }
3249
3250 #[test]
3251 fn test_glyphs2_node() {
3252 let node: Node = Node::parse_plist("\"354 183 LINE\"").unwrap();
3253 assert_eq!(
3254 Node {
3255 node_type: crate::NodeType::Line,
3256 pt: super::Point { x: 354.0, y: 183.0 }
3257 },
3258 node
3259 );
3260 }
3261
3262 #[test]
3263 fn test_glyphs3_node_userdata() {
3264 let node = Node::parse_plist("(354, 183, l,{name = hr00;})").unwrap();
3265 assert_eq!(
3266 Node {
3267 node_type: crate::NodeType::Line,
3268 pt: super::Point { x: 354.0, y: 183.0 }
3269 },
3270 node
3271 );
3272 }
3273
3274 #[test]
3275 fn test_glyphs2_node_userdata() {
3276 let node = Node::parse_plist("\"354 183 LINE {name=duck}\"").unwrap();
3277 assert_eq!(
3278 Node {
3279 node_type: crate::NodeType::Line,
3280 pt: super::Point { x: 354.0, y: 183.0 }
3281 },
3282 node
3283 );
3284 }
3285
3286 #[test]
3289 fn survive_unquoted_infinity() {
3290 Font::load(&glyphs3_dir().join("infinity.glyphs")).unwrap();
3292 }
3293
3294 fn assert_wght_var_metrics(font: &Font) {
3295 let default_master = font.default_master();
3296 assert_eq!(737.0, default_master.ascender().unwrap());
3297 assert_eq!(-42.0, default_master.descender().unwrap());
3298 }
3299
3300 #[test]
3301 fn read_wght_var_2_metrics() {
3302 assert_wght_var_metrics(&Font::load(&glyphs2_dir().join("WghtVar.glyphs")).unwrap());
3303 }
3304
3305 #[test]
3306 fn read_wght_var_3_metrics() {
3307 assert_wght_var_metrics(&Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap());
3308 }
3309
3310 enum LoadCompare {
3312 Glyphs,
3313 GlyphsAndPackage,
3314 }
3315
3316 fn assert_load_v2_matches_load_v3(name: &str, compare: LoadCompare) {
3317 let has_package = matches!(compare, LoadCompare::GlyphsAndPackage);
3318 let _ = env_logger::builder().is_test(true).try_init();
3319 let filename = format!("{name}.glyphs");
3320 let pkgname = format!("{name}.glyphspackage");
3321 let g2_file = glyphs2_dir().join(filename.clone());
3322 let g3_file = glyphs3_dir().join(filename.clone());
3323 let g2 = Font::load(&g2_file).unwrap();
3324 let g3 = Font::load(&g3_file).unwrap();
3325
3326 std::fs::write("/tmp/g2.glyphs.txt", format!("{g2:#?}")).unwrap();
3328 std::fs::write("/tmp/g3.glyphs.txt", format!("{g3:#?}")).unwrap();
3329
3330 assert_eq!(g2.axes, g3.axes, "axes mismatch {g2_file:?} vs {g3_file:?}");
3332 for (g2m, g3m) in g2.masters.iter().zip(g3.masters.iter()) {
3333 assert_eq!(g2m, g3m, "master mismatch {g2_file:?} vs {g3_file:?}");
3334 }
3335 assert_eq!(g2, g3, "g2 should match g3 {g2_file:?} vs {g3_file:?}");
3336
3337 if has_package {
3338 let g2_pkg = Font::load(&glyphs2_dir().join(pkgname.clone())).unwrap();
3339 let g3_pkg = Font::load(&glyphs3_dir().join(pkgname.clone())).unwrap();
3340
3341 std::fs::write("/tmp/g2.glyphspackage.txt", format!("{g2_pkg:#?}")).unwrap();
3342 std::fs::write("/tmp/g3.glyphspackage.txt", format!("{g3_pkg:#?}")).unwrap();
3343
3344 assert_eq!(g2_pkg, g3_pkg, "g2_pkg should match g3_pkg");
3345 assert_eq!(g3_pkg, g3, "g3_pkg should match g3");
3346 }
3347 }
3348
3349 #[test]
3350 fn read_wght_var_2_and_3() {
3351 assert_load_v2_matches_load_v3("WghtVar", LoadCompare::GlyphsAndPackage);
3352 }
3353
3354 #[test]
3355 fn read_wght_var_avar_2_and_3() {
3356 assert_load_v2_matches_load_v3("WghtVar_Avar", LoadCompare::GlyphsAndPackage);
3357 }
3358
3359 #[test]
3360 fn read_wght_var_instances_2_and_3() {
3361 assert_load_v2_matches_load_v3("WghtVar_Instances", LoadCompare::GlyphsAndPackage);
3362 }
3363
3364 #[test]
3365 fn read_wght_var_os2_2_and_3() {
3366 assert_load_v2_matches_load_v3("WghtVar_OS2", LoadCompare::GlyphsAndPackage);
3367 }
3368
3369 #[test]
3370 fn read_wght_var_anchors_2_and_3() {
3371 assert_load_v2_matches_load_v3("WghtVar_Anchors", LoadCompare::GlyphsAndPackage);
3372 }
3373
3374 #[test]
3375 fn read_infinity_2_and_3() {
3376 assert_load_v2_matches_load_v3("infinity", LoadCompare::GlyphsAndPackage);
3377 }
3378
3379 #[test]
3380 fn read_wght_var_noexport_2_and_3() {
3381 assert_load_v2_matches_load_v3("WghtVar_NoExport", LoadCompare::Glyphs);
3382 }
3383
3384 #[test]
3385 fn read_master_names_2_and_3() {
3386 assert_load_v2_matches_load_v3("MasterNames", LoadCompare::Glyphs);
3387 }
3388
3389 #[test]
3390 fn read_master_names_with_italic_2_and_3() {
3391 assert_load_v2_matches_load_v3("MasterNames-Italic", LoadCompare::Glyphs);
3392 }
3393
3394 fn only_shape_in_only_layer<'a>(font: &'a Font, glyph_name: &str) -> &'a Shape {
3395 let glyph = font.glyphs.get(glyph_name).unwrap();
3396 assert_eq!(1, glyph.layers.len());
3397 assert_eq!(1, glyph.layers[0].shapes.len());
3398 &glyph.layers[0].shapes[0]
3399 }
3400
3401 fn check_v2_to_v3_transform(glyphs_file: &str, glyph_name: &str, expected: Affine) {
3402 let g2 = Font::load(&glyphs2_dir().join(glyphs_file)).unwrap();
3403 let g3 = Font::load(&glyphs3_dir().join(glyphs_file)).unwrap();
3404
3405 let g2_shape = only_shape_in_only_layer(&g2, glyph_name);
3407 let g3_shape = only_shape_in_only_layer(&g3, glyph_name);
3408
3409 let Shape::Component(g2_shape) = g2_shape else {
3410 panic!("{g2_shape:?} should be a component");
3411 };
3412 let Shape::Component(g3_shape) = g3_shape else {
3413 panic!("{g3_shape:?} should be a component");
3414 };
3415
3416 assert_eq!(expected, round(g2_shape.transform, 4));
3417 assert_eq!(expected, round(g3_shape.transform, 4));
3418 }
3419
3420 #[test]
3421 fn read_transformed_component_2_and_3_uniform_scale() {
3422 let expected = Affine::new([1.6655, 1.1611, -1.1611, 1.6655, -233.0, -129.0]);
3423 check_v2_to_v3_transform("Component.glyphs", "comma", expected);
3424 }
3425
3426 #[test]
3427 fn read_transformed_component_2_and_3_nonuniform_scale() {
3428 let expected = Affine::new([0.8452, 0.5892, -1.1611, 1.6655, -233.0, -129.0]);
3429 check_v2_to_v3_transform("Component.glyphs", "non_uniform_scale", expected);
3430 }
3431
3432 #[test]
3433 fn upgrade_2_to_3_with_implicit_axes() {
3434 let font = Font::load(&glyphs2_dir().join("WghtVar_ImplicitAxes.glyphs")).unwrap();
3435 assert_eq!(
3436 font.axes
3437 .iter()
3438 .map(|a| a.tag.as_str())
3439 .collect::<Vec<&str>>(),
3440 vec!["wght", "wdth", "XXXX"]
3441 );
3442 }
3443
3444 #[test]
3445 fn understand_v2_style_unquoted_hex_unicode() {
3446 let font = Font::load(&glyphs2_dir().join("Unicode-UnquotedHex.glyphs")).unwrap();
3447 assert_eq!(
3448 BTreeSet::from([0x1234]),
3449 font.glyphs.get("name").unwrap().unicode,
3450 );
3451 assert_eq!(1, font.glyphs.len());
3452 }
3453
3454 #[test]
3455 fn understand_v2_style_quoted_hex_unicode_sequence() {
3456 let font = Font::load(&glyphs2_dir().join("Unicode-QuotedHexSequence.glyphs")).unwrap();
3457 assert_eq!(
3458 BTreeSet::from([0x2044, 0x200D, 0x2215]),
3459 font.glyphs.get("name").unwrap().unicode,
3460 );
3461 assert_eq!(1, font.glyphs.len());
3462 }
3463
3464 #[test]
3465 fn understand_v3_style_unquoted_decimal_unicode() {
3466 let font = Font::load(&glyphs3_dir().join("Unicode-UnquotedDec.glyphs")).unwrap();
3467 assert_eq!(
3468 BTreeSet::from([182]),
3469 font.glyphs.get("name").unwrap().unicode
3470 );
3471 assert_eq!(1, font.glyphs.len());
3472 }
3473
3474 #[test]
3475 fn understand_v3_style_unquoted_decimal_unicode_sequence() {
3476 let font = Font::load(&glyphs3_dir().join("Unicode-UnquotedDecSequence.glyphs")).unwrap();
3477 assert_eq!(
3478 BTreeSet::from([1619, 1764]),
3479 font.glyphs.get("name").unwrap().unicode,
3480 );
3481 assert_eq!(1, font.glyphs.len());
3482 }
3483
3484 #[test]
3485 fn axes_not_hidden() {
3486 let font = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap();
3487 assert_eq!(
3488 font.axes.iter().map(|a| a.hidden).collect::<Vec<_>>(),
3489 vec![None]
3490 );
3491 }
3492
3493 #[test]
3494 fn axis_hidden() {
3495 let font = Font::load(&glyphs3_dir().join("WghtVar_3master_CustomOrigin.glyphs")).unwrap();
3496 assert_eq!(
3497 font.axes.iter().map(|a| a.hidden).collect::<Vec<_>>(),
3498 vec![Some(true)]
3499 );
3500 }
3501
3502 #[test]
3503 fn vf_origin_single_axis_default() {
3504 let font = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap();
3505 assert_eq!(0, font.default_master_idx);
3506 }
3507
3508 #[test]
3509 fn vf_origin_multi_axis_default() {
3510 let font = Font::load(&glyphs2_dir().join("WghtVar_ImplicitAxes.glyphs")).unwrap();
3511 assert_eq!(0, font.default_master_idx);
3512 }
3513
3514 #[test]
3515 fn vf_origin_multi_axis_custom() {
3516 let font = Font::load(&glyphs3_dir().join("WghtVar_3master_CustomOrigin.glyphs")).unwrap();
3517 assert_eq!(2, font.default_master_idx);
3518 }
3519
3520 #[test]
3521 fn vf_origin_unquoted_string() {
3522 let font = Font::load(&glyphs3_dir().join("CustomOrigin.glyphs")).unwrap();
3529 assert_eq!(1, font.default_master_idx);
3530 }
3531
3532 #[rstest]
3533 #[case::base_style_without_regular(
3534 &[
3535 "Expanded Thin Italic",
3536 "Expanded Italic",
3537 "Expanded Bold Italic",
3538 ],
3539 "Expanded Italic" )]
3541 #[case::base_style_contains_regular(
3542 &[
3543 "Regular Foo Bar",
3544 "Regular Foo Baz",
3545 "Regular Foo",
3546 ],
3547 "Regular Foo" )]
3549 #[case::base_style_with_regular_omitted(
3550 &[
3551 "Condensed Thin",
3552 "Condensed Light",
3553 "Condensed Regular",
3554 ],
3555 "Condensed Regular"
3557 )]
3558 #[case::default_to_regular(
3560 &["Thin", "Light", "Regular", "Medium", "Bold"],
3561 "Regular"
3562 )]
3563 #[case::default_to_first(&["Foo", "Bar", "Baz"], "Foo")]
3565 fn find_default_master(#[case] master_names: &[&str], #[case] expected: &str) {
3566 let mut font = RawFont::default();
3567 for name in master_names {
3568 let master = RawFontMaster {
3569 name: Some(name.to_string()),
3570 ..Default::default()
3571 };
3572 font.font_master.push(master);
3573 }
3574
3575 let idx = default_master_idx(&font);
3576
3577 assert_eq!(expected, font.font_master[idx].name.as_deref().unwrap());
3578 }
3579
3580 #[test]
3581 fn glyph_order_default_is_file_order() {
3582 let font = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap();
3583 assert_eq!(
3584 vec![
3585 "space",
3586 "exclam",
3587 "hyphen",
3588 "bracketleft",
3589 "bracketright",
3590 "manual-component"
3591 ],
3592 font.glyph_order
3593 );
3594 }
3595
3596 #[test]
3597 fn glyph_order_override_obeyed() {
3598 let _ = env_logger::builder().is_test(true).try_init();
3599 let font = Font::load(&glyphs3_dir().join("WghtVar_GlyphOrder.glyphs")).unwrap();
3600 assert_eq!(vec!["hyphen", "space", "exclam"], font.glyph_order);
3601 }
3602
3603 #[test]
3604 fn loads_global_axis_mappings_from_glyphs2() {
3605 let font = Font::load(&glyphs2_dir().join("OpszWghtVar_AxisMappings.glyphs")).unwrap();
3606
3607 assert_eq!(
3609 UserToDesignMapping(BTreeMap::from([
3610 (
3611 "Optical Size".into(),
3612 AxisUserToDesignMap(vec![
3613 (OrderedFloat(12.0), OrderedFloat(12.0)),
3614 (OrderedFloat(72.0), OrderedFloat(72.0))
3615 ])
3616 ),
3617 (
3618 "Weight".into(),
3619 AxisUserToDesignMap(vec![
3620 (OrderedFloat(100.0), OrderedFloat(40.0)),
3621 (OrderedFloat(200.0), OrderedFloat(46.0)),
3622 (OrderedFloat(300.0), OrderedFloat(51.0)),
3623 (OrderedFloat(400.0), OrderedFloat(57.0)),
3624 (OrderedFloat(450.0), OrderedFloat(57.0)), (OrderedFloat(500.0), OrderedFloat(62.0)),
3626 (OrderedFloat(600.0), OrderedFloat(68.0)),
3627 (OrderedFloat(700.0), OrderedFloat(73.0)),
3628 ])
3629 ),
3630 ])),
3631 font.axis_mappings
3632 );
3633 }
3634
3635 #[test]
3636 fn loads_global_axis_locations_from_glyphs3() {
3637 let font = Font::load(&glyphs3_dir().join("WghtVar_AxisLocation.glyphs")).unwrap();
3638
3639 assert_eq!(
3641 UserToDesignMapping(BTreeMap::from([(
3642 "Weight".into(),
3643 AxisUserToDesignMap(vec![
3644 (OrderedFloat(400.0), OrderedFloat(0.0)),
3645 (OrderedFloat(500.0), OrderedFloat(8.0)),
3646 (OrderedFloat(700.0), OrderedFloat(10.0)),
3647 (OrderedFloat(600.0), OrderedFloat(8.5)),
3649 ])
3653 ),])),
3654 font.axis_mappings
3655 );
3656 }
3657
3658 #[test]
3659 fn loads_global_axis_mappings_from_instances_wght_glyphs3() {
3660 let font = Font::load(&glyphs3_dir().join("WghtVar_Avar_From_Instances.glyphs")).unwrap();
3661
3662 let wght_idx = font.axes.iter().position(|a| a.tag == "wght").unwrap();
3663 assert_eq!(
3664 vec![60.0, 80.0, 132.0],
3665 font.masters
3666 .iter()
3667 .map(|m| m.axes_values[wght_idx].into_inner())
3668 .collect::<Vec<_>>()
3669 );
3670 assert_eq!(
3672 (132.0, 2),
3673 (
3674 font.default_master().axes_values[wght_idx].into_inner(),
3675 font.default_master_idx
3676 )
3677 );
3678
3679 assert_eq!(
3681 UserToDesignMapping(BTreeMap::from([(
3682 "Weight".into(),
3683 AxisUserToDesignMap(vec![
3684 (OrderedFloat(300.0), OrderedFloat(60.0)),
3685 (OrderedFloat(400.0), OrderedFloat(80.0)),
3690 (OrderedFloat(500.0), OrderedFloat(100.0)),
3691 (OrderedFloat(700.0), OrderedFloat(132.0)),
3692 ])
3693 ),])),
3694 font.axis_mappings
3695 );
3696 }
3697
3698 #[test]
3699 fn loads_global_axis_mappings_from_instances_wdth_glyphs3() {
3700 let font = Font::load(&glyphs3_dir().join("WdthVar.glyphs")).unwrap();
3701
3702 assert_eq!(font.axes.len(), 1);
3703 assert_eq!(font.axes[0].tag, "wdth");
3704 assert_eq!(
3705 vec![22.0, 62.0],
3706 font.masters
3707 .iter()
3708 .map(|m| m.axes_values[0].into_inner())
3709 .collect::<Vec<_>>()
3710 );
3711 assert_eq!(
3713 (22.0, 0),
3714 (
3715 font.default_master().axes_values[0].into_inner(),
3716 font.default_master_idx
3717 )
3718 );
3719 assert_eq!(
3721 UserToDesignMapping(BTreeMap::from([(
3722 "Width".into(),
3723 AxisUserToDesignMap(vec![
3724 (OrderedFloat(50.0), OrderedFloat(22.0)),
3727 (OrderedFloat(100.0), OrderedFloat(41.0)),
3732 (OrderedFloat(200.0), OrderedFloat(62.0)),
3735 ])
3736 ),])),
3737 font.axis_mappings
3738 );
3739 }
3740
3741 #[test]
3742 fn fea_for_class() {
3743 let font = Font::load(&glyphs2_dir().join("Fea_Class.glyphs")).unwrap();
3744 assert_eq!(
3745 vec![
3746 concat!("# automatic\n", "@Uppercase = [ A B C\n", "];",),
3747 concat!("@Lowercase = [ a b c\n", "];",),
3748 ],
3749 font.features
3750 .iter()
3751 .filter_map(|f| f.str_if_enabled())
3752 .collect::<Vec<_>>()
3753 )
3754 }
3755
3756 #[test]
3757 fn fea_for_prefix() {
3758 let font = Font::load(&glyphs2_dir().join("Fea_Prefix.glyphs")).unwrap();
3759 assert_eq!(
3760 vec![
3761 "\
3762# Prefix: Languagesystems
3763# automatic
3764languagesystem DFLT dflt;
3765
3766languagesystem latn dflt;
3767and more;
3768",
3769 "# Prefix: \n# automatic\nthanks for all the fish;",
3770 ],
3771 font.features
3772 .iter()
3773 .filter_map(|f| f.str_if_enabled())
3774 .collect::<Vec<_>>()
3775 )
3776 }
3777
3778 #[test]
3779 fn fea_for_feature() {
3780 let font = Font::load(&glyphs2_dir().join("Fea_Feature.glyphs")).unwrap();
3781 assert_eq!(
3782 vec![
3783 "\
3784feature aalt {
3785feature locl;
3786feature tnum;
3787} aalt;",
3788 "\
3789feature ccmp {
3790# automatic
3791lookup ccmp_Other_2 {
3792 sub @Markscomb' @MarkscombCase by @MarkscombCase;
3793 sub @MarkscombCase @Markscomb' by @MarkscombCase;
3794} ccmp_Other_2;
3795
3796etc;
3797} ccmp;",
3798 ],
3799 font.features
3800 .iter()
3801 .filter_map(|f| f.str_if_enabled())
3802 .collect::<Vec<_>>()
3803 )
3804 }
3805
3806 #[test]
3807 fn fea_order() {
3808 let font = Font::load(&glyphs2_dir().join("Fea_Order.glyphs")).unwrap();
3809 assert_eq!(
3810 vec![
3811 "@class_first = [ meh\n];",
3812 "# Prefix: second\nmeh",
3813 "feature third {\nmeh\n} third;",
3814 ],
3815 font.features
3816 .iter()
3817 .filter_map(|f| f.str_if_enabled())
3818 .collect::<Vec<_>>()
3819 )
3820 }
3821
3822 #[test]
3823 fn fea_labels() {
3824 let font = Font::load(&glyphs3_dir().join("Fea_Labels.glyphs")).unwrap();
3825 assert_eq!(
3826 vec![
3827 concat!(
3828 "feature ss01 {\n",
3829 "# automatic\n",
3830 "featureNames {\n",
3831 " name 3 1 0x0409 \"Test 1\";\n",
3832 " name 3 1 0x0C01 \"اختبار ١\";\n",
3833 "};\n",
3834 "sub a by a.ss01;\n",
3835 "sub b by b.ss01;\n\n",
3836 "} ss01;",
3837 ),
3838 concat!(
3839 "feature ss02 {\n",
3840 "featureNames {\n",
3841 " name 3 1 0x0409 \"Test 2\";\n",
3842 "};\n",
3843 "sub c by c.alt;\n",
3844 "} ss02;",
3845 ),
3846 ],
3847 font.features
3848 .iter()
3849 .filter_map(|f| f.str_if_enabled())
3850 .collect::<Vec<_>>()
3851 )
3852 }
3853
3854 #[test]
3855 fn tags_make_excellent_names() {
3856 let raw = RawFeature {
3857 tag: Some("aalt".to_string()),
3858 code: "blah".to_string(),
3859 ..Default::default()
3860 };
3861 assert_eq!("aalt", raw.name().unwrap());
3862 }
3863
3864 #[test]
3865 fn manual_kern_always_gets_insert_mark() {
3866 let feature = RawFeature {
3867 tag: Some("kern".into()),
3868 ..Default::default()
3869 };
3870
3871 assert!(feature
3872 .raw_feature_to_feature()
3873 .unwrap()
3874 .content
3875 .contains("# Automatic Code"))
3876 }
3877
3878 #[test]
3879 fn but_automatic_does_not() {
3880 let feature = RawFeature {
3881 tag: Some("kern".into()),
3882 automatic: Some(1),
3883 ..Default::default()
3884 };
3885
3886 assert!(!feature
3887 .raw_feature_to_feature()
3888 .unwrap()
3889 .content
3890 .contains("# Automatic Code"))
3891 }
3892
3893 #[test]
3894 fn v2_to_v3_simple_names() {
3895 let v2 = Font::load(&glyphs2_dir().join("WghtVar.glyphs")).unwrap();
3896 let v3 = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap();
3897 assert_eq!(v3.names, v2.names);
3898 }
3899
3900 #[test]
3901 fn v2_to_v3_more_names() {
3902 let v2 = Font::load(&glyphs2_dir().join("TheBestNames.glyphs")).unwrap();
3903 let v3 = Font::load(&glyphs3_dir().join("TheBestNames.glyphs")).unwrap();
3904 assert_eq!(v3.names, v2.names);
3905 }
3906
3907 #[test]
3908 fn v2_long_param_names() {
3909 let v2 = Font::load(&glyphs2_dir().join("LongParamNames.glyphs")).unwrap();
3910 assert_eq!(v2.names.get("vendorID").cloned().as_deref(), Some("DERP"));
3911 assert_eq!(
3912 v2.names.get("descriptions").cloned().as_deref(),
3913 Some("legacy description")
3914 );
3915 assert_eq!(
3916 v2.names.get("licenseURL").cloned().as_deref(),
3917 Some("www.example.com/legacy")
3918 );
3919 assert_eq!(
3920 v2.names.get("version").cloned().as_deref(),
3921 Some("legacy version")
3922 );
3923 assert_eq!(
3924 v2.names.get("licenses").cloned().as_deref(),
3925 Some("legacy license")
3926 );
3927 assert_eq!(
3928 v2.names.get("uniqueID").cloned().as_deref(),
3929 Some("legacy unique id")
3930 );
3931 assert_eq!(
3932 v2.names.get("sampleTexts").cloned().as_deref(),
3933 Some("legacy sample text")
3934 );
3935 }
3936
3937 #[test]
3938 fn v2_style_names_in_a_v3_file() {
3939 let v3_mixed_with_v2 =
3940 Font::load(&glyphs3_dir().join("TheBestV2NamesInAV3File.glyphs")).unwrap();
3941 let v3 = Font::load(&glyphs3_dir().join("TheBestNames.glyphs")).unwrap();
3942 assert_eq!(v3.names, v3_mixed_with_v2.names);
3943 }
3944
3945 fn assert_wghtvar_avar_master_and_axes(glyphs_file: &Path) {
3946 let font = Font::load(glyphs_file).unwrap();
3947 let wght_idx = font.axes.iter().position(|a| a.tag == "wght").unwrap();
3948 assert_eq!(
3949 vec![300.0, 400.0, 700.0],
3950 font.masters
3951 .iter()
3952 .map(|m| m.axes_values[wght_idx].into_inner())
3953 .collect::<Vec<_>>()
3954 );
3955 assert_eq!(
3956 (400.0, 1),
3957 (
3958 font.default_master().axes_values[wght_idx].into_inner(),
3959 font.default_master_idx
3960 )
3961 );
3962 }
3963
3964 #[test]
3965 fn favor_regular_as_origin_glyphs2() {
3966 assert_wghtvar_avar_master_and_axes(&glyphs2_dir().join("WghtVar_Avar.glyphs"));
3967 }
3968
3969 #[test]
3970 fn favor_regular_as_origin_glyphs3() {
3971 assert_wghtvar_avar_master_and_axes(&glyphs3_dir().join("WghtVar_Avar.glyphs"));
3972 }
3973
3974 #[test]
3975 fn have_all_the_best_instances() {
3976 let font = Font::load(&glyphs3_dir().join("WghtVar_Instances.glyphs")).unwrap();
3977 assert_eq!(
3978 vec![
3979 ("Regular", vec![("Weight", 400.0)]),
3980 ("Bold", vec![("Weight", 700.0)])
3981 ],
3982 font.instances
3983 .iter()
3984 .map(|inst| (
3985 inst.name.as_str(),
3986 font.axes
3987 .iter()
3988 .zip(&inst.axes_values)
3989 .map(|(a, v)| (a.name.as_str(), v.0))
3990 .collect::<Vec<_>>()
3991 ))
3992 .collect::<Vec<_>>()
3993 );
3994 }
3995
3996 #[test]
3997 fn read_typo_whatsits() {
3998 let font = Font::load(&glyphs2_dir().join("WghtVar_OS2.glyphs")).unwrap();
3999 let master = font.default_master();
4000 assert_eq!(Some(1193), master.custom_parameters.typo_ascender);
4001 assert_eq!(Some(-289), master.custom_parameters.typo_descender);
4002 }
4003
4004 #[test]
4005 fn read_os2_flags_default_set() {
4006 let font = Font::load(&glyphs2_dir().join("WghtVar.glyphs")).unwrap();
4007 assert_eq!(font.custom_parameters.use_typo_metrics, Some(true));
4008 assert_eq!(font.custom_parameters.has_wws_names, Some(true));
4009 }
4010
4011 #[test]
4012 fn read_os2_flags_default_unset() {
4013 let font = Font::load(&glyphs2_dir().join("WghtVar_OS2.glyphs")).unwrap();
4014 assert_eq!(font.custom_parameters.use_typo_metrics, None);
4015 assert_eq!(font.custom_parameters.has_wws_names, None);
4016 }
4017
4018 #[test]
4019 fn read_simple_kerning() {
4020 let font = Font::load(&glyphs3_dir().join("WghtVar.glyphs")).unwrap();
4021 assert_eq!(
4022 HashSet::from(["m01", "E09E0C54-128D-4FEA-B209-1B70BEFE300B",]),
4023 font.kerning_ltr
4024 .keys()
4025 .map(|k| k.as_str())
4026 .collect::<HashSet<_>>()
4027 );
4028
4029 let actual_groups: Vec<_> = font
4030 .glyphs
4031 .iter()
4032 .filter_map(|(name, glyph)| {
4033 if glyph.left_kern.is_some() || glyph.right_kern.is_some() {
4034 Some((
4035 name.as_str(),
4036 glyph.left_kern.as_deref(),
4037 glyph.right_kern.as_deref(),
4038 ))
4039 } else {
4040 None
4041 }
4042 })
4043 .collect();
4044
4045 let actual_kerning = font
4046 .kerning_ltr
4047 .get("m01")
4048 .unwrap()
4049 .iter()
4050 .map(|((n1, n2), value)| (n1.as_str(), n2.as_str(), value.0))
4051 .collect::<Vec<_>>();
4052
4053 assert_eq!(
4054 (
4055 vec![
4056 ("bracketleft", Some("bracketleft_L"), Some("bracketleft_R")),
4057 (
4058 "bracketright",
4059 Some("bracketright_L"),
4060 Some("bracketright_R")
4061 ),
4062 ],
4063 vec![
4064 ("@MMK_L_bracketleft_R", "exclam", -165.),
4065 ("bracketleft", "bracketright", -300.),
4066 ("exclam", "@MMK_R_bracketright_L", -160.),
4067 ("exclam", "exclam", -360.),
4068 ("exclam", "hyphen", 20.),
4069 ("hyphen", "hyphen", -150.),
4070 ],
4071 ),
4072 (actual_groups, actual_kerning),
4073 "{:?}",
4074 font.kerning_ltr
4075 );
4076 }
4077
4078 #[test]
4079 fn kern_floats() {
4080 let font = Font::load(&glyphs3_dir().join("KernFloats.glyphs")).unwrap();
4081
4082 let kerns = font.kerning_ltr.get("m01").unwrap();
4083 let key = ("space".to_smolstr(), "space".to_smolstr());
4084 assert_eq!(kerns.get(&key), Some(&OrderedFloat(4.2001)));
4085 }
4086
4087 #[test]
4088 fn read_simple_anchor() {
4089 let font = Font::load(&glyphs3_dir().join("WghtVar_Anchors.glyphs")).unwrap();
4090 assert_eq!(
4091 vec![
4092 ("m01", "top", Point::new(300.0, 700.0)),
4093 ("l2", "top", Point::new(325.0, 725.0))
4094 ],
4095 font.glyphs
4096 .get("A")
4097 .unwrap()
4098 .layers
4099 .iter()
4100 .flat_map(|l| l.anchors.iter().map(|a| (
4101 l.layer_id.as_str(),
4102 a.name.as_str(),
4103 a.pos
4104 )))
4105 .collect::<Vec<_>>()
4106 );
4107 }
4108
4109 #[test]
4110 fn read_export_glyph() {
4111 let font = Font::load(&glyphs3_dir().join("WghtVar_NoExport.glyphs")).unwrap();
4112 assert_eq!(
4113 vec![
4114 ("bracketleft", true),
4115 ("bracketright", true),
4116 ("exclam", true),
4117 ("hyphen", false),
4118 ("manual-component", true),
4119 ("space", true),
4120 ],
4121 font.glyphs
4122 .iter()
4123 .map(|(name, glyph)| (name.as_str(), glyph.export))
4124 .collect::<Vec<_>>()
4125 );
4126 }
4127
4128 #[test]
4129 fn read_fstype_none() {
4130 let font = Font::load(&glyphs3_dir().join("infinity.glyphs")).unwrap();
4131 assert!(font.custom_parameters.fs_type.is_none());
4132 }
4133
4134 #[test]
4135 fn read_fstype_zero() {
4136 let font = Font::load(&glyphs3_dir().join("fstype_0x0000.glyphs")).unwrap();
4137 assert_eq!(Some(0), font.custom_parameters.fs_type);
4138 }
4139
4140 #[test]
4141 fn read_fstype_bits() {
4142 let font = Font::load(&glyphs3_dir().join("fstype_0x0104.glyphs")).unwrap();
4143 assert_eq!(Some(0x104), font.custom_parameters.fs_type);
4144 }
4145
4146 #[test]
4147 fn anchor_components() {
4148 let font = Font::load(&glyphs3_dir().join("ComponentAnchor.glyphs")).unwrap();
4149 let glyph = font.glyphs.get("A_Aacute").unwrap();
4150 let acute_comb = glyph.layers[0]
4151 .shapes
4152 .iter()
4153 .find_map(|shape| match shape {
4154 Shape::Component(c) if c.name == "acutecomb" => Some(c),
4155 _ => None,
4156 })
4157 .unwrap();
4158 assert_eq!(acute_comb.anchor.as_deref(), Some("top_2"));
4159 }
4160
4161 #[test]
4162 fn parse_alignment_zone_smoke_test() {
4163 assert_eq!(
4164 super::parse_alignment_zone("{1, -12}").map(|x| (x.0 .0, x.1 .0)),
4165 Some((1., -12.))
4166 );
4167 assert_eq!(
4168 super::parse_alignment_zone("{-5001, 12}").map(|x| (x.0 .0, x.1 .0)),
4169 Some((-5001., 12.))
4170 );
4171 }
4172
4173 impl FontMaster {
4175 fn get_metric(&self, name: &str) -> Option<(f64, f64)> {
4176 self.metric_values
4177 .get(name)
4178 .map(|raw| (raw.pos.0, raw.over.0))
4179 }
4180 }
4181
4182 #[test]
4183 fn v2_alignment_zones_to_metrics() {
4184 let font = Font::load(&glyphs2_dir().join("alignment_zones_v2.glyphs")).unwrap();
4185 let master = font.default_master();
4186
4187 assert_eq!(master.get_metric("ascender"), Some((800., 17.)));
4188 assert_eq!(master.get_metric("cap height"), Some((700., 16.)));
4189 assert_eq!(master.get_metric("baseline"), Some((0., -16.)));
4190 assert_eq!(master.get_metric("descender"), Some((-200., -17.)));
4191 assert_eq!(master.get_metric("x-height"), Some((500., 15.)));
4192 assert_eq!(master.get_metric("italic angle"), None);
4193 }
4194
4195 #[test]
4196 fn v3_duplicate_metrics_first_wins() {
4197 let font = Font::load(&glyphs3_dir().join("WghtVar_OS2.glyphs")).unwrap();
4203 let master = font.default_master();
4204
4205 assert_eq!(master.get_metric("x-height"), Some((501., 0.)));
4206 }
4207
4208 #[test]
4209 fn v2_preserve_custom_alignment_zones() {
4210 let font = Font::load(&glyphs2_dir().join("alignment_zones_v2.glyphs")).unwrap();
4211 let master = font.default_master();
4212 assert_eq!(master.get_metric("zone 1"), Some((1000., 20.)));
4213 assert_eq!(master.get_metric("zone 2"), Some((-100., -15.)));
4214 }
4215
4216 #[test]
4218 fn unknown_glyph_category() {
4219 let raw = super::RawGlyph {
4220 glyphname: "A".into(),
4221 category: Some("Fake".into()),
4222 ..Default::default()
4223 };
4224
4225 let cooked = raw.build(FormatVersion::V2, &GlyphData::default()).unwrap();
4226 assert_eq!(
4227 (cooked.category, cooked.sub_category),
4228 (Some(Category::Letter), None)
4229 );
4230 }
4231
4232 #[test]
4233 fn custom_params_disable() {
4234 let font = Font::load(&glyphs3_dir().join("custom_param_disable.glyphs")).unwrap();
4235
4236 assert!(font.custom_parameters.fs_type.is_none())
4237 }
4238
4239 #[test]
4240 fn parse_numbers() {
4241 let font = Font::load(&glyphs3_dir().join("number_value.glyphs")).unwrap();
4242 assert_eq!(
4243 font.masters[0].number_values.get("foo"),
4244 Some(&OrderedFloat(12.4f64))
4245 );
4246 assert_eq!(
4247 font.masters[1].number_values.get("foo"),
4248 Some(&OrderedFloat(0f64))
4249 );
4250 }
4251
4252 #[test]
4253 fn read_font_metrics() {
4254 let font =
4255 Font::load(&glyphs3_dir().join("GlobalMetrics_font_customParameters.glyphs")).unwrap();
4256 assert_eq!(Some(950), font.custom_parameters.typo_ascender);
4257 assert_eq!(Some(-350), font.custom_parameters.typo_descender);
4258 assert_eq!(Some(0), font.custom_parameters.typo_line_gap);
4259 assert_eq!(Some(950), font.custom_parameters.hhea_ascender);
4260 assert_eq!(Some(-350), font.custom_parameters.hhea_descender);
4261 assert_eq!(Some(0), font.custom_parameters.hhea_line_gap);
4262 assert_eq!(Some(1185), font.custom_parameters.win_ascent);
4263 assert_eq!(Some(420), font.custom_parameters.win_descent);
4264 assert_eq!(
4265 Some(OrderedFloat(42_f64)),
4266 font.custom_parameters.underline_thickness
4267 );
4268 assert_eq!(
4269 Some(OrderedFloat(-300_f64)),
4270 font.custom_parameters.underline_position
4271 );
4272 }
4273
4274 #[test]
4275 fn parse_legacy_stylistic_set_name() {
4276 let font = Font::load(&glyphs2_dir().join("FeaLegacyName.glyphs")).unwrap();
4277 assert_eq!(font.features.len(), 2);
4278 let [ss01, ss02] = font.features.as_slice() else {
4279 panic!("wrong number of features");
4280 };
4281
4282 assert!(ss01
4283 .content
4284 .contains("name 3 1 0x409 \"Alternate placeholder\""));
4285 assert!(!ss02.content.contains("name 3 1"))
4286 }
4287
4288 #[test]
4290 fn one_italic_is_enough() {
4291 let font = Font::load(&glyphs2_dir().join("ItalicItalic.glyphs")).unwrap();
4292 for master in font.masters {
4293 let mut fragments = master.name.split_ascii_whitespace().collect::<Vec<_>>();
4294 fragments.sort();
4295 for words in fragments.windows(2) {
4296 assert!(
4297 words[0] != words[1],
4298 "Multiple instances of {} in {}",
4299 words[0],
4300 master.name
4301 );
4302 }
4303 }
4304 }
4305
4306 #[test]
4308 fn ignore_masters_if_axis_mapping() {
4309 let font = Font::load(&glyphs2_dir().join("MasterNotMapped.glyphs")).unwrap();
4310 let mapping = &font.axis_mappings.0.get("Weight").unwrap().0;
4311 assert_eq!(
4312 vec![
4313 (OrderedFloat(400_f64), OrderedFloat(40.0)),
4314 (OrderedFloat(700_f64), OrderedFloat(70.0))
4315 ],
4316 *mapping
4317 );
4318 }
4319
4320 #[rstest]
4321 #[case::rotate_0(0.0, Affine::new([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]))]
4322 #[case::rotate_360(360.0, Affine::new([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]))]
4323 #[case::rotate_90(90.0, Affine::new([0.0, 1.0, -1.0, 0.0, 0.0, 0.0]))]
4324 #[case::rotate_minus_90(-90.0, Affine::new([0.0, -1.0, 1.0, 0.0, 0.0, 0.0]))]
4325 #[case::rotate_180(180.0, Affine::new([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0]))]
4326 #[case::rotate_minus_180(-180.0, Affine::new([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0]))]
4327 #[case::rotate_270(270.0, Affine::new([0.0, -1.0, 1.0, 0.0, 0.0, 0.0]))]
4328 #[case::rotate_minus_270(-270.0, Affine::new([0.0, 1.0, -1.0, 0.0, 0.0, 0.0]))]
4329 #[case::rotate_450(450.0, Affine::new([0.0, 1.0, -1.0, 0.0, 0.0, 0.0]))]
4330 #[case::rotate_540(540.0, Affine::new([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0]))]
4331 fn cardinal_rotation_contain_exact_zeros_and_ones(
4332 #[case] angle: f64,
4333 #[case] expected: Affine,
4334 ) {
4335 assert_eq!(expected, normalized_rotation(angle));
4341 }
4342
4343 #[rstest]
4344 #[case::rotate_30(30.0, 4, Affine::new([0.866, 0.5, -0.5, 0.866, 0.0, 0.0]))]
4345 #[case::rotate_minus_30(-30.0, 4, Affine::new([0.866, -0.5, 0.5, 0.866, 0.0, 0.0]))]
4346 #[case::rotate_almost_90(
4347 90.01, 8, Affine::new([-0.00017453, 0.99999998, -0.99999998, -0.00017453, 0.0, 0.0])
4348 )]
4349 fn non_cardinal_rotation_left_untouched(
4350 #[case] angle: f64,
4351 #[case] precision: u8,
4352 #[case] expected: Affine,
4353 ) {
4354 assert_eq!(expected, round(normalized_rotation(angle), precision));
4356 }
4357
4358 #[test]
4359 fn parse_colrv1_identify_colr_glyphs() {
4360 let font = Font::load(&glyphs3_dir().join("COLRv1-simple.glyphs")).unwrap();
4361 let expected_colr = HashSet::from(["A", "B", "C", "D", "K", "L", "M", "N"]);
4362 assert_eq!(
4363 expected_colr,
4364 font.glyphs
4365 .values()
4366 .filter(|g| g.layers.iter().all(|l| l.attributes.color))
4367 .map(|g| g.name.as_str())
4368 .collect::<HashSet<_>>()
4369 );
4370 }
4371
4372 #[test]
4373 fn parse_colrv1_gradients() {
4374 let font = Font::load(&glyphs3_dir().join("COLRv1-simple.glyphs")).unwrap();
4375 let expected_colr = HashSet::from([
4376 (
4377 "A",
4378 Gradient {
4379 start: vec![OrderedFloat(0.1), OrderedFloat(0.1)],
4380 end: vec![OrderedFloat(0.9), OrderedFloat(0.9)],
4381 colors: vec![
4382 Color {
4383 r: 255,
4384 g: 0,
4385 b: 0,
4386 a: 255,
4387 stop_offset: 0.into(),
4388 },
4389 Color {
4390 r: 0,
4391 g: 0,
4392 b: 255,
4393 a: 255,
4394 stop_offset: 1.into(),
4395 },
4396 ],
4397 ..Default::default()
4398 },
4399 ),
4400 (
4401 "N",
4402 Gradient {
4403 start: vec![OrderedFloat(1.0), OrderedFloat(1.0)],
4404 end: vec![OrderedFloat(0.0), OrderedFloat(0.0)],
4405 colors: vec![
4406 Color {
4407 r: 255,
4408 g: 0,
4409 b: 0,
4410 a: 255,
4411 stop_offset: 0.into(),
4412 },
4413 Color {
4414 r: 0,
4415 g: 0,
4416 b: 255,
4417 a: 255,
4418 stop_offset: 1.into(),
4419 },
4420 ],
4421 style: "circle".to_string(),
4422 },
4423 ),
4424 ]);
4425 assert_eq!(
4426 expected_colr,
4427 font.glyphs
4428 .values()
4429 .filter(|g| expected_colr.iter().any(|(name, _)| *name == g.name))
4430 .flat_map(|g| g
4431 .layers
4432 .iter()
4433 .flat_map(|l| l.shapes.iter())
4434 .map(|s| (g.name.as_str(), s.attributes().gradient.clone())))
4435 .collect::<HashSet<_>>()
4436 );
4437 }
4438
4439 #[test]
4440 fn parse_grayscale_colors() {
4441 let font = Font::load(&glyphs3_dir().join("COLRv1-grayscale.glyphs")).unwrap();
4442 assert_eq!(
4443 vec![
4444 Color {
4445 r: 64,
4446 g: 64,
4447 b: 64,
4448 a: 255,
4449 stop_offset: 0.into(),
4450 },
4451 Color {
4452 r: 0,
4453 g: 0,
4454 b: 0,
4455 a: 255,
4456 stop_offset: 1.into(),
4457 },
4458 ],
4459 font.glyphs
4460 .values()
4461 .flat_map(|g| g
4462 .layers
4463 .iter()
4464 .flat_map(|l| l.shapes.iter())
4465 .flat_map(|s| (s.attributes().gradient.colors.iter().cloned())))
4466 .collect::<Vec<_>>()
4467 );
4468 }
4469
4470 #[test]
4472 fn missing_width_no_problem() {
4473 let font = Font::load(&glyphs3_dir().join("MissingWidth.glyphs")).unwrap();
4474 let glyph = font.glyphs.get("widthless").unwrap();
4475 assert_eq!(glyph.layers[0].width, 600.);
4476 }
4477
4478 #[test]
4479 fn read_preferred_names_from_properties() {
4480 let font = Font::load(&glyphs3_dir().join("PreferableNames.glyphs")).unwrap();
4481 assert_eq!(
4482 vec![
4483 Some("Pref Family Name"),
4484 Some("Pref Regular"),
4485 Some("Name 25?!")
4486 ],
4487 vec![
4488 font.names.get("preferredFamilyNames").map(|s| s.as_str()),
4489 font.names
4490 .get("preferredSubfamilyNames")
4491 .map(|s| s.as_str()),
4492 font.names
4493 .get("variationsPostScriptNamePrefix")
4494 .map(|s| s.as_str()),
4495 ]
4496 );
4497 }
4498
4499 #[test]
4500 fn zero_value_metrics() {
4501 let font = Font::load(&glyphs3_dir().join("ZeroMetrics.glyphs")).unwrap();
4502 let master = font.default_master();
4503 assert_eq!(master.ascender(), Some(789.));
4504 assert_eq!(master.cap_height(), Some(0.));
4506 assert_eq!(master.x_height(), Some(0.));
4509 assert_eq!(master.italic_angle(), None);
4511 }
4512
4513 #[test]
4516 fn names_from_instances() {
4517 let font = Font::load(&glyphs3_dir().join("InstanceNames.glyphs")).unwrap();
4518 assert_eq!(font.names.get("preferredSubfamilyNames").unwrap(), "Italic")
4519 }
4520
4521 #[rstest]
4522 #[case::v2(glyphs2_dir())]
4523 #[case::v3(glyphs3_dir())]
4524 fn glyph_production_names(#[case] glyphs_dir: PathBuf) {
4525 let font = Font::load(&glyphs_dir.join("ProductionNames.glyphs")).unwrap();
4526 let glyphs = font.glyphs;
4527
4528 assert_eq!(glyphs.get("A").unwrap().production_name, None);
4530
4531 assert_eq!(
4533 glyphs.get("idotless").unwrap().production_name,
4534 Some("uni0131".into())
4535 );
4536
4537 assert_eq!(
4540 glyphs.get("nbspace").unwrap().production_name,
4541 Some("uni00A0".into())
4542 );
4543 }
4544
4545 #[test]
4546 fn parse_axis_rules() {
4547 let raw = RawFont::load(&glyphs3_dir().join("AxisRules.glyphs")).unwrap();
4548 let space = &raw.glyphs[0];
4549 let axes = &space.layers[0].attributes.axis_rules;
4550 assert_eq!(
4551 axes,
4552 &[
4553 AxisRule {
4554 min: None,
4555 max: Some(400)
4556 },
4557 AxisRule {
4558 min: Some(100),
4559 max: None,
4560 },
4561 AxisRule {
4562 min: None,
4563 max: None,
4564 },
4565 ]
4566 )
4567 }
4568
4569 #[test]
4570 fn parse_layer_normal() {
4571 for name in &["[60]", "Light Extended [ 60 ]"] {
4572 let rule = AxisRule::from_layer_name(name);
4573 assert_eq!(
4574 rule,
4575 Some(AxisRule {
4576 min: Some(60),
4577 max: None
4578 }),
4579 "{name}"
4580 )
4581 }
4582 }
4583
4584 #[test]
4585 fn parse_layer_reversed() {
4586 for name in &["]60]", "Light Extended ] 60 ]"] {
4587 let rule = AxisRule::from_layer_name(name);
4588 assert_eq!(
4589 rule,
4590 Some(AxisRule {
4591 min: None,
4592 max: Some(60)
4593 })
4594 )
4595 }
4596 }
4597
4598 #[test]
4599 fn parse_layer_fails() {
4600 for name in &["[hi]", "[45opsz]", "Medium [499‹wg]"] {
4601 assert!(AxisRule::from_layer_name(name).is_none(), "{name}")
4602 }
4603 }
4604
4605 #[test]
4606 fn v2_bracket_layers() {
4607 let font = Font::load(&glyphs2_dir().join("WorkSans-minimal-bracketlayer.glyphs")).unwrap();
4608 let glyph = font.glyphs.get("colonsign").unwrap();
4609 assert_eq!(glyph.layers.len(), 3);
4610 assert_eq!(glyph.bracket_layers.len(), 3);
4611
4612 assert!(glyph
4613 .layers
4614 .iter()
4615 .all(|l| l.attributes.axis_rules.is_empty()));
4616 assert!(glyph
4617 .bracket_layers
4618 .iter()
4619 .all(|l| !l.attributes.axis_rules.is_empty()));
4620 }
4621
4622 #[test]
4623 fn v3_bracket_layers() {
4624 let font = Font::load(&glyphs3_dir().join("LibreFranklin-bracketlayer.glyphs")).unwrap();
4625 let glyph = font.glyphs.get("peso").unwrap();
4626 assert_eq!(glyph.layers.len(), 2);
4627 assert_eq!(glyph.bracket_layers.len(), 2);
4628
4629 assert!(glyph
4630 .layers
4631 .iter()
4632 .all(|l| l.attributes.axis_rules.is_empty()));
4633 assert!(glyph
4634 .bracket_layers
4635 .iter()
4636 .all(|l| !l.attributes.axis_rules.is_empty()));
4637 }
4638
4639 #[test]
4640 fn bracket_layers_where_only_brackets_have_a_component_and_it_has_anchors() {
4641 let font = Font::load(&glyphs2_dir().join("AlumniSans-wononly.glyphs")).unwrap();
4642 let glyph = font.glyphs.get("won").unwrap();
4643
4644 assert_eq!(glyph.layers.len(), 2);
4645 assert_eq!(glyph.bracket_layers.len(), 2);
4646
4647 for layer in glyph.layers.iter().chain(glyph.bracket_layers.iter()) {
4648 assert_eq!(layer.anchors.len(), 2, "{}", layer.layer_id);
4649 }
4650 }
4651
4652 #[test]
4653 fn glyphs2_weight_class_custom_instance_parameter() {
4654 let font = Font::load(&glyphs2_dir().join("WeightClassCustomParam.glyphs")).unwrap();
4657 let mapping = &font.axis_mappings.0.get("Weight").unwrap().0;
4658 assert_eq!(
4659 mapping.as_slice(),
4660 &[
4661 (OrderedFloat(200f64), OrderedFloat(42f64)),
4662 (OrderedFloat(700.), OrderedFloat(125.)),
4663 (OrderedFloat(1000.), OrderedFloat(208.))
4664 ]
4665 )
4666 }
4667
4668 #[test]
4669 fn glyphs2_avoid_adding_mapping_from_master() {
4670 let font = Font::load(&glyphs2_dir().join("master_missing_from_instances.glyphs")).unwrap();
4675
4676 let mapping = &font.axis_mappings.0.get("Weight").unwrap().0;
4677 assert_eq!(
4678 mapping.as_slice(),
4679 &[
4680 (OrderedFloat(100f64), OrderedFloat(20f64)),
4681 (OrderedFloat(300.), OrderedFloat(50.)),
4682 (OrderedFloat(400.), OrderedFloat(71.)),
4683 (OrderedFloat(700.), OrderedFloat(156.)),
4684 (OrderedFloat(900.), OrderedFloat(226.)),
4685 ]
4686 )
4687 }
4688
4689 #[test]
4690 fn does_not_truncate_axis_location() {
4691 let font = Font::load(&glyphs3_dir().join("WghtVar_AxisLocationFloat.glyphs")).unwrap();
4692 let mapping = &font.axis_mappings.0.get("Weight").unwrap().0;
4693
4694 assert_eq!(
4696 mapping.as_slice(),
4697 &[(OrderedFloat(400.75), OrderedFloat(0f64)),]
4698 )
4699 }
4700}