1mod blues;
4mod scale;
5mod widths;
6
7use super::{
8 super::Target,
9 context::AutohintContext,
10 shape::{Shaper, ShaperMode},
11 style::{GlyphStyleMap, ScriptGroup, StyleClass},
12 topo::Dimension,
13 QuirksMode,
14};
15use crate::attribute::Style;
16use alloc::vec::Vec;
17use raw::types::{Fixed, GlyphId};
18use smallvec::SmallVec;
19#[cfg(feature = "std")]
20use std::sync::{Arc, RwLock};
21
22pub(crate) use blues::{BlueOutlineBuf, ScaledBlues, UnscaledBlues};
23pub use blues::{BlueZones, ScaledBlue, UnscaledBlue};
24pub(crate) use scale::{compute_unscaled_style_metrics, scale_style_metrics};
25
26pub(crate) const MAX_WIDTHS: usize = 16;
31
32#[derive(Clone, Default, Debug)]
39pub struct UnscaledAxisMetrics {
40 pub dim: Dimension,
42 pub(crate) widths: UnscaledWidths,
43 pub width_metrics: WidthMetrics,
45 pub(crate) blues: UnscaledBlues,
46}
47
48impl UnscaledAxisMetrics {
49 pub fn widths(&self) -> &[i32] {
51 self.widths.as_slice()
52 }
53
54 pub fn blues(&self) -> &[UnscaledBlue] {
56 self.blues.as_slice()
57 }
58
59 pub fn max_width(&self) -> Option<i32> {
61 self.widths.last().copied()
62 }
63}
64
65#[derive(Clone, Default, Debug)]
67pub struct ScaledAxisMetrics {
68 pub dim: Dimension,
70 pub scale: i32,
72 pub delta: i32,
74 pub(crate) widths: ScaledWidths,
75 pub width_metrics: WidthMetrics,
77 pub(crate) blues: ScaledBlues,
78}
79
80impl ScaledAxisMetrics {
81 pub fn widths(&self) -> &[ScaledWidth] {
83 self.widths.as_slice()
84 }
85
86 pub fn blues(&self) -> &[ScaledBlue] {
88 self.blues.as_slice()
89 }
90}
91
92#[derive(Clone, Default, Debug)]
101pub struct UnscaledStyleMetrics {
102 pub(crate) class_ix: u16,
104 pub digits_have_same_width: bool,
106 pub(crate) axes: [UnscaledAxisMetrics; 2],
108}
109
110impl UnscaledStyleMetrics {
111 pub fn style_class(&self) -> &'static StyleClass {
113 &super::style::STYLE_CLASSES[self.class_ix as usize]
114 }
115
116 pub fn horizontal_metrics(&self) -> &UnscaledAxisMetrics {
118 &self.axes[0]
119 }
120
121 pub fn vertical_metrics(&self) -> &UnscaledAxisMetrics {
123 &self.axes[1]
124 }
125}
126
127#[derive(Clone, Debug)]
131pub(crate) enum UnscaledStyleMetricsSet {
132 Precomputed(Vec<UnscaledStyleMetrics>),
133 #[cfg(feature = "std")]
134 Lazy(Arc<RwLock<Vec<Option<UnscaledStyleMetrics>>>>),
135}
136
137impl UnscaledStyleMetricsSet {
138 pub fn precomputed(
141 context: &dyn AutohintContext,
142 shaper_mode: ShaperMode,
143 style_map: &GlyphStyleMap,
144 ) -> Self {
145 let shaper = Shaper::new(context, shaper_mode);
149 let mut vec = Vec::with_capacity(style_map.metrics_count());
150 vec.extend(
151 style_map
152 .metrics_styles()
153 .map(|style| compute_unscaled_style_metrics(&shaper, style, QuirksMode::default())),
154 );
155 Self::Precomputed(vec)
156 }
157
158 #[cfg(feature = "std")]
161 pub fn lazy(style_map: &GlyphStyleMap) -> Self {
162 let vec = vec![None; style_map.metrics_count()];
163 Self::Lazy(Arc::new(RwLock::new(vec)))
164 }
165
166 #[cfg_attr(not(feature = "std"), allow(unused_variables))]
172 pub fn get(
173 &self,
174 make_context: impl FnOnce(&mut dyn FnMut(&dyn AutohintContext)),
175 shaper_mode: ShaperMode,
176 style_map: &GlyphStyleMap,
177 glyph_id: GlyphId,
178 ) -> Option<UnscaledStyleMetrics> {
179 let style = style_map.style(glyph_id)?;
180 let index = style_map.metrics_index(style)?;
181 match self {
182 Self::Precomputed(metrics) => metrics.get(index).cloned(),
183 #[cfg(feature = "std")]
184 Self::Lazy(lazy) => {
185 let read = lazy.read().unwrap();
186 let entry = read.get(index)?;
187 if let Some(metrics) = &entry {
188 return Some(metrics.clone());
189 }
190 core::mem::drop(read);
191 let style_class = style.style_class()?;
195 let mut metrics = None;
196 make_context(&mut |context| {
197 let shaper = Shaper::new(context, shaper_mode);
198 metrics = Some(compute_unscaled_style_metrics(
199 &shaper,
200 style_class,
201 QuirksMode::default(),
202 ));
203 });
204 let metrics = metrics?;
205 let mut entry = lazy.write().unwrap();
206 *entry.get_mut(index)? = Some(metrics.clone());
207 Some(metrics)
208 }
209 }
210 }
211}
212
213#[derive(Clone, Default, Debug)]
215pub struct ScaledStyleMetrics {
216 pub scale: Scale,
218 pub(crate) axes: [ScaledAxisMetrics; 2],
220}
221
222impl ScaledStyleMetrics {
223 pub fn horizontal_metrics(&self) -> &ScaledAxisMetrics {
225 &self.axes[0]
226 }
227
228 pub fn vertical_metrics(&self) -> &ScaledAxisMetrics {
230 &self.axes[1]
231 }
232}
233
234#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
236pub struct WidthMetrics {
237 pub edge_distance_threshold: i32,
239 pub standard_width: i32,
241 pub is_extra_light: bool,
243}
244
245pub(crate) type UnscaledWidths = SmallVec<[i32; MAX_WIDTHS]>;
246
247#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
249pub struct ScaledWidth {
250 pub scaled: i32,
252 pub fitted: i32,
254}
255
256pub(crate) type ScaledWidths = SmallVec<[ScaledWidth; MAX_WIDTHS]>;
257
258#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
260pub struct ScaleFlags(pub(crate) u32);
261
262impl ScaleFlags {
265 pub const HORIZONTAL_SNAP: Self = Self(1 << 0);
267 pub const VERTICAL_SNAP: Self = Self(1 << 1);
269 pub const STEM_ADJUST: Self = Self(1 << 2);
271 pub const MONO: Self = Self(1 << 3);
273 pub const NO_HORIZONTAL: Self = Self(1 << 4);
275 pub const NO_VERTICAL: Self = Self(1 << 5);
277 pub const NO_ADVANCE: Self = Self(1 << 6);
279}
280
281impl ScaleFlags {
282 pub const fn from_bits_truncate(bits: u32) -> Self {
284 Self(bits & 0b1111111)
285 }
286
287 pub const fn to_bits(self) -> u32 {
289 self.0
290 }
291
292 pub const fn contains(self, other: Self) -> bool {
294 (self.0 & other.0) == other.0
295 }
296
297 pub const fn intersects(self, other: Self) -> bool {
299 (self.0 & other.0) != 0
300 }
301}
302
303impl core::ops::Not for ScaleFlags {
304 type Output = Self;
305
306 fn not(self) -> Self::Output {
307 Self(!self.0)
308 }
309}
310
311impl core::ops::BitOr for ScaleFlags {
312 type Output = Self;
313
314 fn bitor(self, rhs: Self) -> Self::Output {
315 Self(self.0 | rhs.0)
316 }
317}
318
319impl core::ops::BitOrAssign for ScaleFlags {
320 fn bitor_assign(&mut self, rhs: Self) {
321 self.0 |= rhs.0;
322 }
323}
324
325impl core::ops::BitAnd for ScaleFlags {
326 type Output = Self;
327
328 fn bitand(self, rhs: Self) -> Self::Output {
329 Self(self.0 & rhs.0)
330 }
331}
332
333impl core::ops::BitAndAssign for ScaleFlags {
334 fn bitand_assign(&mut self, rhs: Self) {
335 self.0 &= rhs.0;
336 }
337}
338
339#[derive(Copy, Clone, Default, Debug)]
342pub struct Scale {
343 pub flags: ScaleFlags,
345 pub x_scale: i32,
347 pub y_scale: i32,
349 pub x_delta: i32,
351 pub y_delta: i32,
353 pub size: f32,
355 pub units_per_em: i32,
357}
358
359impl Scale {
360 pub fn new(
362 size: f32,
363 units_per_em: i32,
364 font_style: Style,
365 target: Target,
366 group: ScriptGroup,
367 ) -> Self {
368 let scale =
369 (Fixed::from_bits((size * 64.0) as i32) / Fixed::from_bits(units_per_em)).to_bits();
370 let mut flags = ScaleFlags::default();
371 let is_italic = font_style != Style::Normal;
372 let is_mono = target == Target::Mono;
373 let is_light = target.is_light() || target.preserve_linear_metrics();
374 if is_mono || target.is_lcd() {
376 flags |= ScaleFlags::HORIZONTAL_SNAP;
377 }
378 if is_mono || target.is_vertical_lcd() {
380 flags |= ScaleFlags::VERTICAL_SNAP;
381 }
382 if !(target.is_lcd() || is_light) {
384 flags |= ScaleFlags::STEM_ADJUST;
385 }
386 if is_mono {
387 flags |= ScaleFlags::MONO;
388 }
389 if group == ScriptGroup::Default {
390 if target.is_lcd() || is_light || is_italic {
394 flags |= ScaleFlags::NO_HORIZONTAL;
395 }
396 } else {
397 flags |= ScaleFlags::NO_ADVANCE;
400 }
401 if group != ScriptGroup::Default {
404 flags |= ScaleFlags::NO_ADVANCE;
405 }
406 Self {
407 x_scale: scale,
408 y_scale: scale,
409 x_delta: 0,
410 y_delta: 0,
411 size,
412 units_per_em,
413 flags,
414 }
415 }
416}
417
418pub(crate) fn sort_and_quantize_widths(widths: &mut UnscaledWidths, threshold: i32) {
420 if widths.len() <= 1 {
421 return;
422 }
423 widths.sort_unstable();
424 let table = widths.as_mut_slice();
425 let mut cur_ix = 0;
426 let mut cur_val = table[cur_ix];
427 let last_ix = table.len() - 1;
428 let mut ix = 1;
429 while ix < table.len() {
432 if (table[ix] - cur_val) > threshold || ix == last_ix {
433 let mut sum = 0;
434 if (table[ix] - cur_val <= threshold) && ix == last_ix {
436 ix += 1;
437 }
438 for val in &mut table[cur_ix..ix] {
439 sum += *val;
440 *val = 0;
441 }
442 table[cur_ix] = sum / ix as i32;
443 if ix < last_ix {
444 cur_ix = ix + 1;
445 cur_val = table[cur_ix];
446 }
447 }
448 ix += 1;
449 }
450 cur_ix = 1;
451 for ix in 1..table.len() {
453 if table[ix] != 0 {
454 table[cur_ix] = table[ix];
455 cur_ix += 1;
456 }
457 }
458 widths.truncate(cur_ix);
459}
460
461pub(crate) fn fixed_mul(a: i32, b: i32) -> i32 {
468 (Fixed::from_bits(a) * Fixed::from_bits(b)).to_bits()
469}
470
471pub(crate) fn fixed_div(a: i32, b: i32) -> i32 {
472 (Fixed::from_bits(a) / Fixed::from_bits(b)).to_bits()
473}
474
475pub(crate) fn fixed_mul_div(a: i32, b: i32, c: i32) -> i32 {
476 Fixed::from_bits(a)
477 .mul_div(Fixed::from_bits(b), Fixed::from_bits(c))
478 .to_bits()
479}
480
481pub(crate) fn pix_round(a: i32) -> i32 {
482 (a.wrapping_add(32)) & !63
483}
484
485pub(crate) fn pix_floor(a: i32) -> i32 {
486 a & !63
487}
488
489#[cfg(test)]
490mod tests {
491 use super::{
492 super::{
493 shape::{Shaper, ShaperMode},
494 style::STYLE_CLASSES,
495 },
496 *,
497 };
498 use crate::outline::autohint::context::FontRefContext;
499 use crate::FontRef;
500 use raw::TableProvider;
501
502 #[test]
503 fn sort_widths() {
504 assert_eq!(sort_widths_helper(&[1], 10), &[1]);
507 assert_eq!(sort_widths_helper(&[1], 20), &[1]);
508 assert_eq!(sort_widths_helper(&[60, 20, 40, 35], 10), &[20, 35, 13, 60]);
509 assert_eq!(sort_widths_helper(&[60, 20, 40, 35], 20), &[31, 60]);
510 }
511
512 fn sort_widths_helper(widths: &[i32], threshold: i32) -> Vec<i32> {
513 let mut widths2 = UnscaledWidths::new();
514 for width in widths {
515 widths2.push(*width);
516 }
517 sort_and_quantize_widths(&mut widths2, threshold);
518 widths2.into_iter().collect()
519 }
520
521 #[test]
522 fn precomputed_style_set() {
523 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
524 let context = FontRefContext::new(&font, &[]);
525 let shaper = Shaper::new(&context, ShaperMode::Nominal);
526 let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
527 let style_map = GlyphStyleMap::new(glyph_count, &shaper);
528 let style_set =
529 UnscaledStyleMetricsSet::precomputed(&context, ShaperMode::Nominal, &style_map);
530 let UnscaledStyleMetricsSet::Precomputed(set) = &style_set else {
531 panic!("we definitely made a precomputed style set");
532 };
533 assert_eq!(STYLE_CLASSES[set[0].class_ix as usize].name, "Latin");
535 assert_eq!(STYLE_CLASSES[set[1].class_ix as usize].name, "Hebrew");
536 assert_eq!(
537 STYLE_CLASSES[set[2].class_ix as usize].name,
538 "CJKV ideographs"
539 );
540 assert_eq!(set.len(), 3);
541 }
542
543 #[test]
544 fn lazy_style_set() {
545 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
546 let context = FontRefContext::new(&font, &[]);
547 let shaper = Shaper::new(&context, ShaperMode::Nominal);
548 let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
549 let style_map = GlyphStyleMap::new(glyph_count, &shaper);
550 let style_set = UnscaledStyleMetricsSet::lazy(&style_map);
551 let all_empty = lazy_set_presence(&style_set);
552 assert_eq!(all_empty, [false; 3]);
554 let metrics2 = style_set
556 .get(
557 |compute| compute(&context),
558 ShaperMode::Nominal,
559 &style_map,
560 GlyphId::new(0),
561 )
562 .unwrap();
563 assert_eq!(
564 STYLE_CLASSES[metrics2.class_ix as usize].name,
565 "CJKV ideographs"
566 );
567 let only_cjk = lazy_set_presence(&style_set);
568 assert_eq!(only_cjk, [false, false, true]);
569 let metrics1 = style_set
571 .get(
572 |compute| compute(&context),
573 ShaperMode::Nominal,
574 &style_map,
575 GlyphId::new(1),
576 )
577 .unwrap();
578 assert_eq!(STYLE_CLASSES[metrics1.class_ix as usize].name, "Hebrew");
579 let hebrew_and_cjk = lazy_set_presence(&style_set);
580 assert_eq!(hebrew_and_cjk, [false, true, true]);
581 let metrics0 = style_set
583 .get(
584 |compute| compute(&context),
585 ShaperMode::Nominal,
586 &style_map,
587 GlyphId::new(15),
588 )
589 .unwrap();
590 assert_eq!(STYLE_CLASSES[metrics0.class_ix as usize].name, "Latin");
591 let all_present = lazy_set_presence(&style_set);
592 assert_eq!(all_present, [true; 3]);
593 }
594
595 fn lazy_set_presence(style_set: &UnscaledStyleMetricsSet) -> Vec<bool> {
596 let UnscaledStyleMetricsSet::Lazy(set) = &style_set else {
597 panic!("we definitely made a lazy style set");
598 };
599 set.read()
600 .unwrap()
601 .iter()
602 .map(|opt| opt.is_some())
603 .collect()
604 }
605
606 #[test]
607 fn pix_round_extremes_do_not_panic() {
608 let _ = pix_round(i32::MIN);
610 let _ = pix_round(i32::MAX);
611 }
612}