skrifa/outline/autohint/metrics/
widths.rs1use super::super::{
4 derived_constant,
5 metrics::{self, UnscaledWidths, WidthMetrics, MAX_WIDTHS},
6 outline::Outline,
7 shape::{ShapedCluster, Shaper},
8 style::{ScriptGroup, StyleClass},
9 topo::{compute_segments, link_segments, Axis, Dimension},
10 QuirksMode,
11};
12use crate::MetadataProvider;
13use raw::{types::F2Dot14, TableProvider};
14
15pub(super) fn compute_widths(
22 shaper: &Shaper,
23 coords: &[F2Dot14],
24 style: &StyleClass,
25 quirks: QuirksMode,
26) -> [(WidthMetrics, UnscaledWidths); 2] {
27 let mut result: [(WidthMetrics, UnscaledWidths); 2] = Default::default();
28 let font = shaper.font();
29 let glyphs = font.outline_glyphs();
30 let units_per_em = font
31 .head()
32 .map(|head| head.units_per_em() as i32)
33 .unwrap_or_default();
34 let mut outline = Outline::default();
35 let mut axis = Axis::default();
36 let mut cluster_shaper = shaper.cluster_shaper(style);
37 let mut shaped_cluster = ShapedCluster::default();
38 let glyph = style
40 .script
41 .std_chars
42 .split(' ')
43 .filter_map(|cluster| {
44 cluster_shaper.shape(cluster, &mut shaped_cluster);
45 match shaped_cluster.as_slice() {
48 [glyph] if glyph.id.to_u32() != 0 => glyphs.get(glyph.id),
49 _ => None,
50 }
51 })
52 .next();
53 if let Some(glyph) = glyph {
54 if outline.fill(&glyph, coords, quirks).is_ok() && !outline.points.is_empty() {
55 for (dim, (_metrics, widths)) in [Dimension::Horizontal, Dimension::Vertical]
57 .into_iter()
58 .zip(result.iter_mut())
59 {
60 axis.reset(dim, outline.orientation);
61 compute_segments(&mut outline, &mut axis, ScriptGroup::Default);
64 link_segments(&outline, &mut axis, 0, ScriptGroup::Default, None);
65 let segments = axis.segments.as_slice();
66 for (segment_ix, segment) in segments.iter().enumerate() {
67 let segment_ix = segment_ix as u16;
68 let Some(link_ix) = segment.link_ix else {
69 continue;
70 };
71 let link = &segments[link_ix as usize];
72 if link_ix > segment_ix && link.link_ix == Some(segment_ix) {
73 let dist = (segment.pos as i32 - link.pos as i32).abs();
74 if widths.len() < MAX_WIDTHS {
75 widths.push(dist);
76 } else {
77 break;
78 }
79 }
80 }
81 if widths.is_empty() {
86 widths.push(0);
87 }
88 metrics::sort_and_quantize_widths(widths, units_per_em / 100);
90 }
91 }
92 }
93 for (metrics, widths) in result.iter_mut() {
94 let stdw = widths
96 .first()
97 .copied()
98 .unwrap_or_else(|| derived_constant(units_per_em, 50));
99 metrics.edge_distance_threshold = stdw / 5;
101 metrics.standard_width = stdw;
102 metrics.is_extra_light = false;
103 }
104 result
105}
106
107#[cfg(test)]
108mod tests {
109 use super::{
110 super::super::{shape::ShaperMode, style},
111 *,
112 };
113 use raw::FontRef;
114
115 #[test]
116 fn computed_widths() {
117 check_widths(
120 font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS,
121 super::StyleClass::HEBR,
122 [
123 (
124 WidthMetrics {
125 edge_distance_threshold: 10,
126 standard_width: 54,
127 is_extra_light: false,
128 },
129 &[54],
130 ),
131 (
132 WidthMetrics {
133 edge_distance_threshold: 4,
134 standard_width: 21,
135 is_extra_light: false,
136 },
137 &[21, 109],
138 ),
139 ],
140 );
141 }
142
143 #[test]
144 fn fallback_widths() {
145 check_widths(
148 font_test_data::CANTARELL_VF_TRIMMED,
149 super::StyleClass::LATN,
150 [
151 (
152 WidthMetrics {
153 edge_distance_threshold: 4,
154 standard_width: 24,
155 is_extra_light: false,
156 },
157 &[],
158 ),
159 (
160 WidthMetrics {
161 edge_distance_threshold: 4,
162 standard_width: 24,
163 is_extra_light: false,
164 },
165 &[],
166 ),
167 ],
168 );
169 }
170
171 #[test]
172 fn cjk_computed_widths() {
173 check_widths(
176 font_test_data::NOTOSERIFTC_AUTOHINT_METRICS,
177 super::StyleClass::HANI,
178 [
179 (
180 WidthMetrics {
181 edge_distance_threshold: 13,
182 standard_width: 65,
183 is_extra_light: false,
184 },
185 &[65],
186 ),
187 (
188 WidthMetrics {
189 edge_distance_threshold: 5,
190 standard_width: 29,
191 is_extra_light: false,
192 },
193 &[29],
194 ),
195 ],
196 );
197 }
198
199 fn check_widths(font_data: &[u8], style_class: usize, expected: [(WidthMetrics, &[i32]); 2]) {
200 let font = FontRef::new(font_data).unwrap();
201 let shaper = Shaper::new(&font, ShaperMode::Nominal);
202 let script = &style::STYLE_CLASSES[style_class];
203 let [(hori_metrics, hori_widths), (vert_metrics, vert_widths)] =
204 compute_widths(&shaper, Default::default(), script, QuirksMode::default());
205 assert_eq!(hori_metrics, expected[0].0);
206 assert_eq!(hori_widths.as_slice(), expected[0].1);
207 assert_eq!(vert_metrics, expected[1].0);
208 assert_eq!(vert_widths.as_slice(), expected[1].1);
209 }
210}