swash/internal/
var.rs

1//! Font and metric variation tables.
2
3use super::{fixed::Fixed, raw_tag, Array, Bytes, RawFont, RawTag, U24};
4
5pub const FVAR: RawTag = raw_tag(b"fvar");
6pub const AVAR: RawTag = raw_tag(b"avar");
7pub const HVAR: RawTag = raw_tag(b"HVAR");
8pub const VVAR: RawTag = raw_tag(b"VVAR");
9pub const MVAR: RawTag = raw_tag(b"MVAR");
10
11/// Font variations table.
12#[derive(Copy, Clone)]
13pub struct Fvar<'a> {
14    data: Bytes<'a>,
15    axis_offset: u16,
16    axis_count: u16,
17    axis_size: u16,
18    inst_count: u16,
19    inst_size: u16,
20}
21
22impl<'a> Fvar<'a> {
23    pub fn new(data: &'a [u8]) -> Self {
24        let b = Bytes::new(data);
25        let axis_offset = b.read_or_default::<u16>(4);
26        let axis_count = b.read_or_default::<u16>(8);
27        let axis_size = b.read_or_default::<u16>(10);
28        let inst_count = b.read_or_default::<u16>(12);
29        let inst_size = b.read_or_default::<u16>(14);
30        Self {
31            data: b,
32            axis_offset,
33            axis_count,
34            axis_size,
35            inst_count,
36            inst_size,
37        }
38    }
39
40    pub fn from_font(font: impl RawFont<'a>) -> Option<Self> {
41        Some(Self::new(font.table_data(FVAR)?))
42    }
43
44    pub fn axis_count(&self) -> u16 {
45        self.axis_count
46    }
47
48    pub fn get_axis(&self, index: u16) -> Option<VarAxis> {
49        if index >= self.axis_count {
50            return None;
51        }
52        let b = &self.data;
53        let base = self.axis_offset as usize;
54        let offset = base + index as usize * self.axis_size as usize;
55        let tag = b.read::<u32>(offset)?;
56        let min = Fixed(b.read::<i32>(offset + 4)?);
57        let default = Fixed(b.read::<i32>(offset + 8)?);
58        let max = Fixed(b.read::<i32>(offset + 12)?);
59        let flags = b.read::<u16>(offset + 16)?;
60        let name_id = b.read::<u16>(offset + 18)?;
61        Some(VarAxis {
62            index,
63            tag,
64            name_id,
65            flags,
66            min,
67            default,
68            max,
69        })
70    }
71
72    pub fn get_axis_by_tag(&self, tag: RawTag) -> Option<VarAxis> {
73        let b = &self.data;
74        let base = self.axis_offset as usize;
75        let axis_size = self.axis_size as usize;
76        for i in 0..self.axis_count as usize {
77            let tag_offset = base + i * axis_size;
78            if b.read_u32(tag_offset) == Some(tag) {
79                return self.get_axis(i as u16);
80            }
81        }
82        None
83    }
84
85    pub fn instance_count(&self) -> u16 {
86        self.inst_count
87    }
88
89    pub fn get_instance(&self, index: u16) -> Option<VarInstance<'a>> {
90        if index >= self.inst_count {
91            return None;
92        }
93        let b = &self.data;
94        let base = self.axis_offset as usize + (self.axis_count as usize * self.axis_size as usize);
95        let offset = base + index as usize * self.inst_size as usize;
96        let name_id = b.read::<u16>(offset)?;
97        let values = b.read_array::<Fixed>(offset + 4, self.axis_count as usize)?;
98        let ps_name_offset = 4 + self.axis_count as usize * 4;
99        let postscript_name_id = if ps_name_offset == self.inst_size as usize - 2 {
100            b.read::<u16>(ps_name_offset)
101        } else {
102            None
103        };
104        Some(VarInstance {
105            index,
106            name_id,
107            postscript_name_id,
108            values,
109        })
110    }
111}
112
113/// Axis of variation in a variable font.
114#[derive(Copy, Clone, Default)]
115pub struct VarAxis {
116    pub index: u16,
117    pub tag: RawTag,
118    pub name_id: u16,
119    pub flags: u16,
120    pub min: Fixed,
121    pub default: Fixed,
122    pub max: Fixed,
123}
124
125impl VarAxis {
126    /// Returns true if the axis should be hidden in a user interface.
127    pub fn is_hidden(&self) -> bool {
128        self.flags & 1 != 0
129    }
130
131    /// Returns a normalized axis coordinate for the specified value in 2.14
132    /// fixed point format.
133    pub fn normalized_coord(&self, mut value: Fixed, avar: Option<(&[u8], u32)>) -> i16 {
134        use core::cmp::Ordering::*;
135        if value < self.min {
136            value = self.min;
137        } else if value > self.max {
138            value = self.max;
139        }
140        value = match value.cmp(&self.default) {
141            Less => -((self.default - value) / (self.default - self.min)),
142            Greater => (value - self.default) / (self.max - self.default),
143            Equal => Fixed(0),
144        };
145        value = value.min(Fixed::ONE).max(-Fixed::ONE);
146        value = avar
147            .and_then(|(data, avar)| adjust_axis(data, avar, self.index, value))
148            .unwrap_or(value);
149        value.to_f2dot14()
150    }
151}
152
153/// Named instance in a variable font.
154#[derive(Copy, Clone)]
155pub struct VarInstance<'a> {
156    pub index: u16,
157    pub name_id: u16,
158    pub postscript_name_id: Option<u16>,
159    pub values: Array<'a, Fixed>,
160}
161
162/// Metrics variation table.
163pub struct Mvar<'a> {
164    data: Bytes<'a>,
165    coords: &'a [i16],
166    rec_size: usize,
167    rec_count: usize,
168    store: u32,
169}
170
171impl<'a> Mvar<'a> {
172    pub fn new(data: &'a [u8], mvar: u32, coords: &'a [i16]) -> Option<Self> {
173        let b = Bytes::with_offset(data, mvar as usize)?;
174        let rec_size = b.read::<u16>(6)? as usize;
175        let rec_count = b.read::<u16>(8)? as usize;
176        let store = b.read::<u16>(10)? as u32;
177        if rec_count == 0 || store == 0 {
178            return None;
179        }
180        Some(Self {
181            data: b,
182            coords,
183            rec_size,
184            rec_count,
185            store,
186        })
187    }
188
189    pub fn delta(&self, metric: RawTag) -> f32 {
190        self.read_delta(metric).map(|d| d.to_f32()).unwrap_or(0.)
191    }
192
193    #[inline(always)]
194    fn read_delta(&self, metric: RawTag) -> Option<Fixed> {
195        let base = 12;
196        let b = &self.data;
197        let rec_size = self.rec_size;
198        let mut l = 0;
199        let mut h = self.rec_count;
200        while l < h {
201            use core::cmp::Ordering::*;
202            let i = (l + h) / 2;
203            let offset = base + i * rec_size;
204            let t = b.read::<u32>(offset)?;
205            match metric.cmp(&t) {
206                Less => h = i,
207                Greater => l = i + 1,
208                Equal => {
209                    let inner = b.read::<u16>(offset + 4)?;
210                    let outer = b.read::<u16>(offset + 6)?;
211                    return item_delta(b.data(), self.store, outer, inner, self.coords);
212                }
213            }
214        }
215        None
216    }
217}
218
219/// Returns the advance delta for the specified glyph.
220pub fn advance_delta(data: &[u8], xvar: u32, glyph_id: u16, coords: &[i16]) -> f32 {
221    metric_delta(data, xvar, 8, glyph_id, coords)
222        .map(|d| d.to_f32())
223        .unwrap_or(0.)
224}
225
226/// Returns the side bearing delta for the specified glyph.
227pub fn sb_delta(data: &[u8], xvar: u32, glyph_id: u16, coords: &[i16]) -> f32 {
228    metric_delta(data, xvar, 12, glyph_id, coords)
229        .map(|d| d.to_f32())
230        .unwrap_or(0.)
231}
232
233/// Applies adjustments to a coordinate according to the optional axis
234/// variation table.
235pub fn adjust_axis(data: &[u8], avar: u32, axis: u16, coord: Fixed) -> Option<Fixed> {
236    use skrifa::raw::{tables::avar::Avar, types::Fixed, FontData, FontRead};
237
238    if avar == 0 {
239        return None;
240    }
241    let avar = Avar::read(FontData::new(data.get(avar as usize..)?)).ok()?;
242    let mapping = avar
243        .axis_segment_maps()
244        .get(axis as usize)
245        .transpose()
246        .ok()??;
247    Some(Fixed(mapping.apply(Fixed::from_bits(coord.0)).to_bits()))
248}
249
250/// Returns a delta from an item variation store.
251pub fn item_delta(
252    data: &[u8],
253    offset: u32,
254    outer: u16,
255    inner: u16,
256    coords: &[i16],
257) -> Option<Fixed> {
258    if offset == 0 {
259        return None;
260    }
261    let b = Bytes::new(data);
262    let store = offset as usize;
263    if outer >= b.read::<u16>(store + 6)? {
264        return None;
265    }
266    let region_base = store + b.read::<u32>(store + 2)? as usize;
267    let axis_count = b.read::<u16>(region_base)? as usize;
268    let region_record_size = axis_count * 6;
269    let region_count = b.read::<u16>(region_base + 2)? as usize;
270    let data_base = store + b.read::<u32>(store + 8 + outer as usize * 4)? as usize;
271    let region_index_base = data_base + 6;
272    let region_index_count = b.read::<u16>(data_base + 4)? as usize;
273    let (short_count, mut delta_base) = {
274        let inner = inner as usize;
275        let short_count = b.read::<u16>(data_base + 2)? as usize;
276        let count = region_index_count;
277        let base = data_base + 6 + count * 2;
278        let elem_len = (count - short_count) + short_count * 2;
279        let offset = base + inner * elem_len;
280        (short_count, offset)
281    };
282    const ZERO: Fixed = Fixed::ZERO;
283    const ONE: Fixed = Fixed::ONE;
284    let mut idx = 0;
285    let mut delta = ZERO;
286    for i in 0..region_index_count {
287        let region_index = b.read::<u16>(region_index_base + i * 2)? as usize;
288        if region_index >= region_count {
289            return None;
290        }
291        let region_offset = region_base + 4 + region_index * region_record_size;
292        let mut scalar = ONE;
293        for axis in 0..axis_count {
294            let region_axis_base = region_offset + axis * 6;
295            let start = Fixed::from_f2dot14(b.read::<i16>(region_axis_base)?);
296            let peak = Fixed::from_f2dot14(b.read::<i16>(region_axis_base + 2)?);
297            let end = Fixed::from_f2dot14(b.read::<i16>(region_axis_base + 4)?);
298            let coord = coords
299                .get(axis)
300                .map(|c| Fixed::from_f2dot14(*c))
301                .unwrap_or(ZERO);
302            if start > peak || peak > end || peak == ZERO || start < ZERO && end > ZERO {
303                continue;
304            } else if coord < start || coord > end {
305                scalar = ZERO;
306                break;
307            } else if coord == peak {
308                continue;
309            } else if coord < peak {
310                scalar = scalar * (coord - start) / (peak - start);
311            } else {
312                scalar = scalar * (end - coord) / (end - peak);
313            };
314        }
315        let val = if idx >= short_count {
316            delta_base += 1;
317            b.read::<i8>(delta_base - 1)? as i16
318        } else {
319            delta_base += 2;
320            b.read::<i16>(delta_base - 2)?
321        };
322        idx += 1;
323        delta += scalar * Fixed::from_i32(val as i32);
324    }
325    Some(delta)
326}
327
328#[inline(always)]
329fn metric_delta(
330    data: &[u8],
331    base: u32,
332    which: usize,
333    glyph_id: u16,
334    coords: &[i16],
335) -> Option<Fixed> {
336    if base == 0 {
337        return None;
338    }
339    let b = Bytes::new(data);
340    let mut store = b.read::<u32>(base as usize + 4)?;
341    if store == 0 {
342        return None;
343    }
344    store += base;
345    let mut offset = b.read::<u32>(base as usize + which)? as usize;
346    if offset == 0 {
347        if which == 8 {
348            return item_delta(data, store, 0, glyph_id, coords);
349        } else {
350            return None;
351        }
352    }
353    offset += base as usize;
354    let format = b.read::<u16>(offset)? as u32;
355    let count = b.read::<u16>(offset + 2)?;
356    let bit_count = (format & 0xF) + 1;
357    let entry_size = ((format & 0x30) >> 4) + 1;
358    let base = offset + 4;
359    let index = if glyph_id >= count {
360        count - 1
361    } else {
362        glyph_id
363    } as usize;
364    let entry = match entry_size {
365        1 => b.read::<u8>(base + index)? as u32,
366        2 => b.read::<u16>(base + index * 2)? as u32,
367        3 => b.read::<U24>(base + index * 3)?.0,
368        4 => b.read::<u32>(base + index * 4)?,
369        _ => return None,
370    };
371    let outer = entry >> bit_count;
372    let inner = entry & ((1 << bit_count) - 1);
373    item_delta(data, store, outer as u16, inner as u16, coords)
374}
375
376/// Tags for metrics from the `MVAR` table.
377pub mod mvar_tags {
378    use super::{raw_tag, RawTag};
379
380    /// Horizontal ascender.
381    pub const HASC: RawTag = raw_tag(b"hasc");
382    /// Horizontal descender.
383    pub const HDSC: RawTag = raw_tag(b"hdsc");
384    /// Horizontal line gap.
385    pub const HLGP: RawTag = raw_tag(b"hlgp");
386
387    /// Horizontal caret rise.
388    pub const HCRS: RawTag = raw_tag(b"hcrs");
389    /// Horizontal caret run.
390    pub const HCRN: RawTag = raw_tag(b"hcrn");
391    /// Horizontal caret offset.
392    pub const HCOF: RawTag = raw_tag(b"hcof");
393
394    /// Horizontal clipping ascent.
395    pub const HCLA: RawTag = raw_tag(b"hcla");
396    /// Horizontal clipping descent.
397    pub const HCLD: RawTag = raw_tag(b"hcld");
398
399    /// Vertical ascender.
400    pub const VASC: RawTag = raw_tag(b"vasc");
401    /// Vertical descender.
402    pub const VDSC: RawTag = raw_tag(b"vdsc");
403    /// Vertical line gap.
404    pub const VLGP: RawTag = raw_tag(b"vlgp");
405
406    /// Vertical caret rise.
407    pub const VCRS: RawTag = raw_tag(b"vcrs");
408    /// Vertical caret run.
409    pub const VCRN: RawTag = raw_tag(b"vcrn");
410    /// Vertical caret offset.
411    pub const VCOF: RawTag = raw_tag(b"vcof");
412
413    /// X-height.
414    pub const XHGT: RawTag = raw_tag(b"xhgt");
415    /// Cap height.
416    pub const CPHT: RawTag = raw_tag(b"cpht");
417
418    /// Underline offset.
419    pub const UNDO: RawTag = raw_tag(b"undo");
420    /// Underline size.
421    pub const UNDS: RawTag = raw_tag(b"unds");
422
423    /// Strikeout offset.
424    pub const STRO: RawTag = raw_tag(b"stro");
425    /// Strikeout size.
426    pub const STRS: RawTag = raw_tag(b"strs");
427
428    /// Subscript x-offset.
429    pub const SBXO: RawTag = raw_tag(b"sbxo");
430    /// Subscript y-offset.
431    pub const SBYO: RawTag = raw_tag(b"sbyo");
432    /// Subscript x-size.
433    pub const SBXS: RawTag = raw_tag(b"sbxs");
434    /// Subscript y-size.
435    pub const SBYS: RawTag = raw_tag(b"sbys");
436
437    /// Superscript x-offset.
438    pub const SPXO: RawTag = raw_tag(b"spxo");
439    /// Superscript y-offset.
440    pub const SPYO: RawTag = raw_tag(b"spyo");
441    /// Superscript x-size.
442    pub const SPXS: RawTag = raw_tag(b"spxs");
443    /// Superscript y-size.
444    pub const SPYS: RawTag = raw_tag(b"spys");
445}