Skip to main content

pdfrum_edit/font/
instance.rs

1//! Which instance of a variable face a glyph run was laid out at, and the two
2//! spellings the writer needs of it.
3//!
4//! A renderer holds an instance as *normalized* coordinates (`F2Dot14`, one per
5//! `fvar` axis, after `avar`), which is what glyph metrics are computed at.
6//! The subsetter instantiates from *user* values (`wght` 700). Going from the
7//! first to the second undoes `avar` — its segment maps are monotonic, so the
8//! inverse is the same piecewise-linear map read the other way — and then the
9//! `fvar` normalization.
10
11use skrifa::instance::{Location, NormalizedCoord};
12use skrifa::raw::TableProvider as _;
13use skrifa::{FontRef, MetadataProvider as _, Tag};
14
15/// The instance of a variable face a glyph run is drawn at.
16///
17/// ```
18/// use pdfrum_edit::{AxisValue, FontInstance};
19///
20/// let bold = FontInstance::User(vec![AxisValue { tag: *b"wght", value: 700.0 }]);
21/// assert_ne!(bold, FontInstance::Default);
22/// ```
23#[derive(Debug, Clone, PartialEq, Default)]
24pub enum FontInstance {
25    /// The face as stored: its default instance, or a static face.
26    #[default]
27    Default,
28    /// Normalized coordinates, one `F2Dot14` per `fvar` axis in the face's own
29    /// order, after `avar`: what a shaper or rasteriser holds.
30    Normalized(Vec<i16>),
31    /// User-space axis values; an axis not named stays at its default.
32    User(Vec<AxisValue>),
33}
34
35/// One axis at one user-space value.
36#[derive(Debug, Clone, Copy, PartialEq)]
37pub struct AxisValue {
38    /// The axis tag's four bytes: `*b"wght"`.
39    pub tag: [u8; 4],
40    /// The value, in the axis's own units (a weight of 700).
41    pub value: f32,
42}
43
44/// `instance` as user values, which the subsetter takes; empty for the
45/// default instance.
46pub(crate) fn user_values(font: &FontRef<'_>, instance: &FontInstance) -> Vec<AxisValue> {
47    match instance {
48        FontInstance::Default => Vec::new(),
49        FontInstance::User(values) => values.clone(),
50        FontInstance::Normalized(coords) if coords.iter().all(|&c| c == 0) => Vec::new(),
51        FontInstance::Normalized(coords) => {
52            let maps = segment_maps(font);
53            font.axes()
54                .iter()
55                .zip(coords)
56                .enumerate()
57                .map(|(index, (axis, &coord))| {
58                    let normalized = f32::from(coord) / 16384.0;
59                    let before_avar = maps
60                        .get(index)
61                        .map_or(normalized, |map| invert(map, normalized));
62                    AxisValue {
63                        tag: axis.tag().to_be_bytes(),
64                        value: denormalize(
65                            before_avar,
66                            axis.min_value(),
67                            axis.default_value(),
68                            axis.max_value(),
69                        ),
70                    }
71                })
72                .collect()
73        }
74    }
75}
76
77/// `instance` as the location glyph metrics are read at.
78pub(crate) fn location(font: &FontRef<'_>, instance: &FontInstance) -> Location {
79    match instance {
80        FontInstance::Default => Location::default(),
81        FontInstance::Normalized(coords) => {
82            let mut location = Location::new(coords.len());
83            location
84                .coords_mut()
85                .iter_mut()
86                .zip(coords)
87                .for_each(|(slot, &coord)| *slot = NormalizedCoord::from_bits(coord));
88            location
89        }
90        FontInstance::User(values) => font
91            .axes()
92            .location(values.iter().map(|axis| (Tag::new(&axis.tag), axis.value))),
93    }
94}
95
96/// Each axis's `avar` segment map as (from, to) pairs; empty without `avar`.
97fn segment_maps(font: &FontRef<'_>) -> Vec<Vec<(f32, f32)>> {
98    let Ok(avar) = font.avar() else {
99        return Vec::new();
100    };
101    avar.axis_segment_maps()
102        .iter()
103        .map(|maps| {
104            maps.map(|maps| {
105                maps.axis_value_maps()
106                    .iter()
107                    .map(|pair| {
108                        (
109                            pair.from_coordinate().to_f32(),
110                            pair.to_coordinate().to_f32(),
111                        )
112                    })
113                    .collect()
114            })
115            .unwrap_or_default()
116        })
117        .collect()
118}
119
120/// The coordinate `map` sends to `to`: the segment map read backwards. A map
121/// with fewer than two pairs is the identity, as `avar` defines it.
122fn invert(map: &[(f32, f32)], to: f32) -> f32 {
123    map.windows(2)
124        .find_map(|pair| match pair {
125            [(from_a, to_a), (from_b, to_b)] if *to_a <= to && to <= *to_b => {
126                Some(if (to_b - to_a).abs() < f32::EPSILON {
127                    *from_a
128                } else {
129                    from_a + (to - to_a) * (from_b - from_a) / (to_b - to_a)
130                })
131            }
132            _ => None,
133        })
134        .unwrap_or(to)
135}
136
137/// The user value a normalized `value` (-1..=1) stands for on an axis
138/// `min..=max` around `default`.
139fn denormalize(value: f32, min: f32, default: f32, max: f32) -> f32 {
140    if value < 0.0 {
141        default + value * (default - min)
142    } else {
143        default + value * (max - default)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::{denormalize, invert};
150
151    #[test]
152    fn denormalizing_follows_each_side_of_the_default() {
153        for (normalized, want) in [(-1.0, 100.0), (-0.5, 250.0), (0.0, 400.0), (1.0, 900.0)] {
154            assert!(
155                (denormalize(normalized, 100.0, 400.0, 900.0) - want).abs() < 1e-4,
156                "{normalized}"
157            );
158        }
159    }
160
161    #[test]
162    fn avar_is_read_backwards() {
163        let map = [(-1.0, -1.0), (0.0, 0.0), (0.5, 0.8), (1.0, 1.0)];
164        assert!((invert(&map, 0.8) - 0.5).abs() < 1e-6);
165        assert!((invert(&map, 0.4) - 0.25).abs() < 1e-6);
166        assert!((invert(&map, 0.9) - 0.75).abs() < 1e-6);
167        assert!((invert(&[], 0.3) - 0.3).abs() < 1e-6);
168    }
169}