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