Skip to main content

harfrust/hb/aat/
layout_trak_table.rs

1// libm used for f32::round() method
2#[cfg(not(feature = "std"))]
3#[allow(unused_imports)]
4use core_maths::CoreFloat as _;
5
6use read_fonts::tables::trak::TrackTableEntry;
7use read_fonts::types::{BigEndian, Fixed};
8use read_fonts::FontData;
9
10use crate::hb::{buffer::hb_buffer_t, face::Scale, hb_font_t, ot_shape_plan::hb_ot_shape_plan_t};
11
12pub fn apply(
13    _plan: &hb_ot_shape_plan_t,
14    face: &hb_font_t,
15    scale: Scale,
16    point_size: Option<f32>,
17    buffer: &mut hb_buffer_t,
18) -> Option<()> {
19    let trak = face.aat_tables.trak.as_ref()?;
20    let mut ptem = point_size.unwrap_or(0.0);
21    if ptem <= 0.0 {
22        ptem = 12.0; // CoreText fallback
23    }
24
25    if !buffer.have_positions {
26        buffer.clear_positions();
27    }
28
29    let advance_to_add = if buffer.direction.is_horizontal() {
30        scale.scale_x_f(trak.get_h_tracking(ptem, 0.0))
31    } else {
32        scale.scale_y_f(trak.get_v_tracking(ptem, 0.0))
33    };
34
35    foreach_grapheme!(buffer, start, end, {
36        if buffer.direction.is_horizontal() {
37            buffer.pos[start].x_advance += advance_to_add;
38        } else {
39            buffer.pos[start].y_advance += advance_to_add;
40        }
41    });
42
43    Some(())
44}
45
46trait TrakExt {
47    fn get_h_tracking(&self, ptem: f32, track: f32) -> f32;
48    fn get_v_tracking(&self, ptem: f32, track: f32) -> f32;
49}
50
51impl TrakExt for read_fonts::tables::trak::Trak<'_> {
52    // Keep tracking fractional; scale_x_f rounds once, matching HarfBuzz's
53    // em_scalef (the interpolated value is generally not a whole font unit).
54    fn get_h_tracking(&self, ptem: f32, track: f32) -> f32 {
55        self.horiz()
56            .transpose()
57            .ok()
58            .flatten()
59            .map_or(0.0, |t| t.get_tracking(self.offset_data(), ptem, track))
60    }
61
62    fn get_v_tracking(&self, ptem: f32, track: f32) -> f32 {
63        self.vert()
64            .transpose()
65            .ok()
66            .flatten()
67            .map_or(0.0, |t| t.get_tracking(self.offset_data(), ptem, track))
68    }
69}
70
71trait TrackDataExt {
72    fn get_tracking(&self, offset_data: FontData, ptem: f32, track: f32) -> f32;
73}
74
75impl TrackDataExt for read_fonts::tables::trak::TrackData<'_> {
76    fn get_tracking(&self, offset_data: FontData, ptem: f32, track: f32) -> f32 {
77        let Ok(sizes) = self.size_table(offset_data) else {
78            return 0.0;
79        };
80
81        if sizes.is_empty() {
82            return 0.0;
83        }
84
85        let tracks = self.track_table();
86        if tracks.is_empty() {
87            return 0.0;
88        }
89
90        let get_value = |entry: &TrackTableEntry| {
91            let Ok(values) = entry.per_size_values(offset_data, sizes.len() as u16) else {
92                return 0.0;
93            };
94            entry.get_value(ptem, sizes, values)
95        };
96
97        if tracks.len() == 1 {
98            return tracks.first().map_or(0.0, get_value);
99        }
100
101        let mut i = 0;
102        let mut j = tracks.len() - 1;
103
104        while i + 1 < tracks.len() && tracks.get(i + 1).map_or(0.0, |t| t.track().to_f32()) <= track
105        {
106            i += 1;
107        }
108        while j > 0 && tracks.get(j - 1).map_or(0.0, |t| t.track().to_f32()) >= track {
109            j -= 1;
110        }
111
112        if i == j {
113            return tracks.get(i).map_or(0.0, get_value);
114        }
115
116        let t0 = tracks.get(i).map_or(0.0, |t| t.track().to_f32());
117        let t1 = tracks.get(j).map_or(0.0, |t| t.track().to_f32());
118        let interp = if (t1 - t0).abs() < f32::EPSILON {
119            0.0
120        } else {
121            (track - t0) / (t1 - t0)
122        };
123
124        let a = tracks.get(i).map_or(0.0, get_value);
125        let b = tracks.get(j).map_or(0.0, get_value);
126        a + interp * (b - a)
127    }
128}
129
130trait TrackEntryExt {
131    fn get_value(&self, ptem: f32, sizes: &[BigEndian<Fixed>], values: &[BigEndian<i16>]) -> f32;
132}
133
134impl TrackEntryExt for TrackTableEntry {
135    fn get_value(&self, ptem: f32, sizes: &[BigEndian<Fixed>], values: &[BigEndian<i16>]) -> f32 {
136        let n = sizes.len().min(values.len());
137        if n == 0 {
138            return 0.0;
139        }
140
141        for i in 0..n {
142            let size_pt = sizes.get(i).map_or(0.0, |f| f.get().to_f32());
143            if size_pt >= ptem {
144                if i == 0 {
145                    return values.first().map(|v| v.get() as f32).unwrap_or_default();
146                }
147
148                let s0 = sizes.get(i - 1).map_or(0.0, |f| f.get().to_f32());
149                let s1 = size_pt;
150                let v0 = values
151                    .get(i - 1)
152                    .map(|v| v.get() as f32)
153                    .unwrap_or_default();
154                let v1 = values.get(i).map(|v| v.get() as f32).unwrap_or_default();
155
156                if (s1 - s0).abs() < f32::EPSILON {
157                    return (v0 + v1) * 0.5;
158                }
159
160                let t = (ptem - s0) / (s1 - s0);
161                return v0 + t * (v1 - v0);
162            }
163        }
164
165        values
166            .get(n - 1)
167            .map(|v| v.get() as f32)
168            .unwrap_or_default()
169    }
170}