1use super::super::{
10 metrics::{
11 fixed_div, fixed_mul, fixed_mul_div, pix_round, BlueZones, Scale, ScaledAxisMetrics,
12 ScaledBlue, ScaledStyleMetrics, ScaledWidth, UnscaledAxisMetrics, UnscaledBlue,
13 UnscaledStyleMetrics, WidthMetrics,
14 },
15 shape::Shaper,
16 style::{ScriptGroup, StyleClass},
17 topo::{Axis, Dimension},
18};
19use crate::{prelude::Size, MetadataProvider};
20use raw::types::F2Dot14;
21
22pub(crate) fn compute_unscaled_style_metrics(
26 shaper: &Shaper,
27 coords: &[F2Dot14],
28 style: &StyleClass,
29) -> UnscaledStyleMetrics {
30 let charmap = shaper.charmap();
31 if charmap.is_symbol() {
35 return UnscaledStyleMetrics {
36 class_ix: style.index as u16,
37 axes: [
38 UnscaledAxisMetrics {
39 dim: Axis::HORIZONTAL,
40 ..Default::default()
41 },
42 UnscaledAxisMetrics {
43 dim: Axis::VERTICAL,
44 ..Default::default()
45 },
46 ],
47 ..Default::default()
48 };
49 }
50 let [hwidths, vwidths] = super::widths::compute_widths(shaper, coords, style);
51 let [hblues, vblues] = super::blues::compute_unscaled_blues(shaper, coords, style);
52 let glyph_metrics = shaper.font().glyph_metrics(Size::unscaled(), coords);
53 let mut digit_advance = None;
54 let mut digits_have_same_width = true;
55 for ch in '0'..='9' {
56 if let Some(advance) = charmap
57 .map(ch)
58 .and_then(|gid| glyph_metrics.advance_width(gid))
59 {
60 if digit_advance.is_some() && digit_advance != Some(advance) {
61 digits_have_same_width = false;
62 break;
63 }
64 digit_advance = Some(advance);
65 }
66 }
67 UnscaledStyleMetrics {
68 class_ix: style.index as u16,
69 digits_have_same_width,
70 axes: [
71 UnscaledAxisMetrics {
72 dim: Axis::HORIZONTAL,
73 blues: hblues,
74 width_metrics: hwidths.0,
75 widths: hwidths.1,
76 },
77 UnscaledAxisMetrics {
78 dim: Axis::VERTICAL,
79 blues: vblues,
80 width_metrics: vwidths.0,
81 widths: vwidths.1,
82 },
83 ],
84 }
85}
86
87pub(crate) fn scale_style_metrics(
91 unscaled_metrics: &UnscaledStyleMetrics,
92 mut scale: Scale,
93) -> ScaledStyleMetrics {
94 let scale_axis_fn = if unscaled_metrics.style_class().script.group == ScriptGroup::Default {
95 scale_default_axis_metrics
96 } else {
97 scale_cjk_axis_metrics
98 };
99 let mut scale_axis = |axis: &UnscaledAxisMetrics| {
100 scale_axis_fn(
101 axis.dim,
102 &axis.widths,
103 axis.width_metrics,
104 &axis.blues,
105 &mut scale,
106 )
107 };
108 let axes = [
109 scale_axis(&unscaled_metrics.axes[0]),
110 scale_axis(&unscaled_metrics.axes[1]),
111 ];
112 ScaledStyleMetrics { scale, axes }
113}
114
115fn scale_default_axis_metrics(
119 dim: Dimension,
120 widths: &[i32],
121 width_metrics: WidthMetrics,
122 blues: &[UnscaledBlue],
123 scale: &mut Scale,
124) -> ScaledAxisMetrics {
125 let mut axis = ScaledAxisMetrics {
126 dim,
127 ..Default::default()
128 };
129 if dim == Axis::HORIZONTAL {
130 axis.scale = scale.x_scale;
131 axis.delta = scale.x_delta;
132 } else {
133 axis.scale = scale.y_scale;
134 axis.delta = scale.y_delta;
135 };
136 if let Some(blue_ix) = blues
138 .iter()
139 .position(|blue| blue.zones.contains(BlueZones::ADJUSTMENT))
140 {
141 let unscaled_blue = &blues[blue_ix];
142 let scaled = fixed_mul(axis.scale, unscaled_blue.overshoot);
143 let fitted = (scaled + 40) & !63;
144 if scaled != fitted && dim == Axis::VERTICAL {
145 let new_scale = fixed_mul_div(axis.scale, fitted, scaled);
146 let mut max_height = scale.units_per_em;
148 for blue in blues {
149 max_height = max_height.max(blue.ascender).max(-blue.descender);
150 }
151 let mut dist = fixed_mul(max_height, new_scale - axis.scale).abs();
152 dist &= !127;
153 if dist == 0 {
154 axis.scale = new_scale;
155 scale.y_scale = new_scale;
156 }
157 }
158 }
159 axis.width_metrics = width_metrics;
161 for unscaled_width in widths {
162 let scaled = fixed_mul(axis.scale, *unscaled_width);
163 axis.widths.push(ScaledWidth {
164 scaled,
165 fitted: scaled,
166 });
167 }
168 axis.width_metrics.is_extra_light =
171 fixed_mul(axis.width_metrics.standard_width, axis.scale) < (32 + 8);
172 if dim == Axis::VERTICAL {
173 for unscaled_blue in blues {
175 let scaled_position = fixed_mul(axis.scale, unscaled_blue.position) + axis.delta;
176 let scaled_overshoot = fixed_mul(axis.scale, unscaled_blue.overshoot) + axis.delta;
177 let mut blue = ScaledBlue {
178 position: ScaledWidth {
179 scaled: scaled_position,
180 fitted: scaled_position,
181 },
182 overshoot: ScaledWidth {
183 scaled: scaled_overshoot,
184 fitted: scaled_overshoot,
185 },
186 zones: unscaled_blue.zones,
187 is_active: false,
188 };
189 let dist = fixed_mul(unscaled_blue.position - unscaled_blue.overshoot, axis.scale);
191 if (-48..=48).contains(&dist) {
192 let mut delta = dist.abs();
193 if delta < 32 {
194 delta = 0;
195 } else if delta < 48 {
196 delta = 32;
197 } else {
198 delta = 64;
199 }
200 if dist < 0 {
201 delta = -delta;
202 }
203 blue.position.fitted = pix_round(blue.position.scaled);
204 blue.overshoot.fitted = blue.position.fitted - delta;
205 blue.is_active = true;
206 }
207 axis.blues.push(blue);
208 }
209 for blue_ix in 0..axis.blues.len() {
212 let blue = axis.blues[blue_ix];
213 if !blue.zones.is_sub_top() || !blue.is_active {
214 continue;
215 }
216 for blue2 in &axis.blues {
217 if blue2.zones.is_sub_top() || !blue2.is_active {
218 continue;
219 }
220 if blue2.position.fitted <= blue.overshoot.fitted
221 && blue2.overshoot.fitted >= blue.position.fitted
222 {
223 axis.blues[blue_ix].is_active = false;
224 break;
225 }
226 }
227 }
228 }
229 axis
230}
231
232fn scale_cjk_axis_metrics(
236 dim: Dimension,
237 widths: &[i32],
238 width_metrics: WidthMetrics,
239 blues: &[UnscaledBlue],
240 scale: &mut Scale,
241) -> ScaledAxisMetrics {
242 let mut axis = ScaledAxisMetrics {
243 dim,
244 ..Default::default()
245 };
246 axis.dim = dim;
247 if dim == Axis::HORIZONTAL {
248 axis.scale = scale.x_scale;
249 axis.delta = scale.x_delta;
250 } else {
251 axis.scale = scale.y_scale;
252 axis.delta = scale.y_delta;
253 };
254 let scale = axis.scale;
255 for unscaled_blue in blues {
257 let position = fixed_mul(unscaled_blue.position, scale) + axis.delta;
258 let overshoot = fixed_mul(unscaled_blue.overshoot, scale) + axis.delta;
259 let mut blue = ScaledBlue {
260 position: ScaledWidth {
261 scaled: position,
262 fitted: position,
263 },
264 overshoot: ScaledWidth {
265 scaled: overshoot,
266 fitted: overshoot,
267 },
268 zones: unscaled_blue.zones,
269 is_active: false,
270 };
271 let dist = fixed_mul(unscaled_blue.position - unscaled_blue.overshoot, scale);
273 if (-48..=48).contains(&dist) {
274 blue.position.fitted = pix_round(blue.position.scaled);
275 let delta1 = fixed_div(blue.position.fitted, scale) - unscaled_blue.overshoot;
277 let mut delta2 = fixed_mul(delta1.abs(), scale);
278 if delta2 < 32 {
279 delta2 = 0;
280 } else {
281 delta2 = pix_round(delta2);
282 }
283 if delta1 < 0 {
284 delta2 = -delta2;
285 }
286 blue.overshoot.fitted = blue.position.fitted - delta2;
287 blue.is_active = true;
288 }
289 axis.blues.push(blue);
290 }
291 for _ in 0..widths.len() {
295 axis.widths.push(ScaledWidth::default());
296 }
297 axis.width_metrics = width_metrics;
298 axis
299}
300
301#[cfg(test)]
302mod tests {
303 use super::{
304 super::super::{shape::ShaperMode, style},
305 *,
306 };
307 use crate::attribute::Style;
308 use raw::{FontRef, TableProvider};
309
310 #[test]
311 fn scaled_metrics_default() {
312 let scaled_metrics = make_scaled_metrics(
315 font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS,
316 StyleClass::HEBR,
317 );
318 assert_eq!(scaled_metrics.scale.x_scale, 67109);
320 assert_eq!(scaled_metrics.scale.y_scale, 67109);
321 assert_eq!(scaled_metrics.scale.x_delta, 0);
322 assert_eq!(scaled_metrics.scale.y_delta, 0);
323 let h_axis = &scaled_metrics.axes[0];
325 let expected_h_widths = [55];
326 check_axis(h_axis, &expected_h_widths, &[]);
328 assert!(!h_axis.width_metrics.is_extra_light);
330 let v_axis = &scaled_metrics.axes[1];
332 let expected_v_widths = [22, 112];
333 #[rustfmt::skip]
335 let expected_v_blues = [
336 ScaledBlue::from(((606, 576), (606, 576), BlueZones::TOP, true)),
338 ScaledBlue::from(((0, 0), (-9, 0), BlueZones::default(), true)),
339 ScaledBlue::from(((-246, -256), (-246, -256), BlueZones::default(), true)),
340 ];
341 check_axis(v_axis, &expected_v_widths, &expected_v_blues);
342 assert!(v_axis.width_metrics.is_extra_light);
344 }
345
346 #[test]
347 fn cjk_scaled_metrics() {
348 let scaled_metrics = make_scaled_metrics(
351 font_test_data::NOTOSERIFTC_AUTOHINT_METRICS,
352 StyleClass::HANI,
353 );
354 assert_eq!(scaled_metrics.scale.x_scale, 67109);
356 assert_eq!(scaled_metrics.scale.y_scale, 67109);
357 assert_eq!(scaled_metrics.scale.x_delta, 0);
358 assert_eq!(scaled_metrics.scale.y_delta, 0);
359 let h_axis = &scaled_metrics.axes[0];
361 let expected_h_widths = [0];
362 check_axis(h_axis, &expected_h_widths, &[]);
363 assert!(!h_axis.width_metrics.is_extra_light);
365 let v_axis = &scaled_metrics.axes[1];
367 let expected_v_widths = [0];
368 #[rustfmt::skip]
370 let expected_v_blues = [
371 ScaledBlue::from(((857, 832), (844, 832), BlueZones::TOP, true)),
373 ScaledBlue::from(((-80, -64), (-68, -64), BlueZones::default(), true)),
374 ];
375 check_axis(v_axis, &expected_v_widths, &expected_v_blues);
377 assert!(!v_axis.width_metrics.is_extra_light);
379 }
380
381 fn make_scaled_metrics(font_data: &[u8], style_class: usize) -> ScaledStyleMetrics {
382 let font = FontRef::new(font_data).unwrap();
383 let class = &style::STYLE_CLASSES[style_class];
384 let shaper = Shaper::new(&font, ShaperMode::Nominal);
385 let unscaled_metrics = compute_unscaled_style_metrics(&shaper, Default::default(), class);
386 let scale = Scale::new(
387 16.0,
388 font.head().unwrap().units_per_em() as i32,
389 Style::Normal,
390 Default::default(),
391 class.script.group,
392 );
393 scale_style_metrics(&unscaled_metrics, scale)
394 }
395
396 fn check_axis(
397 axis: &ScaledAxisMetrics,
398 expected_widths: &[i32],
399 expected_blues: &[ScaledBlue],
400 ) {
401 let widths = axis
402 .widths
403 .iter()
404 .map(|width| width.scaled)
405 .collect::<Vec<_>>();
406 assert_eq!(widths, expected_widths);
407 assert_eq!(axis.blues.as_slice(), expected_blues);
408 }
409
410 impl From<(i32, i32)> for ScaledWidth {
411 fn from(value: (i32, i32)) -> Self {
412 Self {
413 scaled: value.0,
414 fitted: value.1,
415 }
416 }
417 }
418
419 impl From<((i32, i32), (i32, i32), BlueZones, bool)> for ScaledBlue {
420 fn from(value: ((i32, i32), (i32, i32), BlueZones, bool)) -> Self {
421 Self {
422 position: value.0.into(),
423 overshoot: value.1.into(),
424 zones: value.2,
425 is_active: value.3,
426 }
427 }
428 }
429}