1mod blues;
4mod scale;
5mod widths;
6
7use super::{
8 super::Target,
9 shape::{Shaper, ShaperMode},
10 style::{GlyphStyleMap, ScriptGroup, StyleClass},
11 topo::Dimension,
12};
13use crate::{attribute::Style, collections::SmallVec, FontRef};
14use alloc::vec::Vec;
15use raw::types::{F2Dot14, Fixed, GlyphId};
16#[cfg(feature = "std")]
17use std::sync::{Arc, RwLock};
18
19pub(crate) use blues::{BlueZones, ScaledBlue, ScaledBlues, UnscaledBlue, UnscaledBlues};
20pub(crate) use scale::{compute_unscaled_style_metrics, scale_style_metrics};
21
22pub(crate) const MAX_WIDTHS: usize = 16;
27
28#[derive(Clone, Default, Debug)]
35pub(crate) struct UnscaledAxisMetrics {
36 pub dim: Dimension,
37 pub widths: UnscaledWidths,
38 pub width_metrics: WidthMetrics,
39 pub blues: UnscaledBlues,
40}
41
42impl UnscaledAxisMetrics {
43 pub fn max_width(&self) -> Option<i32> {
44 self.widths.last().copied()
45 }
46}
47
48#[derive(Clone, Default, Debug)]
50pub(crate) struct ScaledAxisMetrics {
51 pub dim: Dimension,
52 pub scale: i32,
54 pub delta: i32,
56 pub widths: ScaledWidths,
57 pub width_metrics: WidthMetrics,
58 pub blues: ScaledBlues,
59}
60
61#[derive(Clone, Default, Debug)]
70pub(crate) struct UnscaledStyleMetrics {
71 pub class_ix: u16,
73 pub digits_have_same_width: bool,
75 pub axes: [UnscaledAxisMetrics; 2],
77}
78
79impl UnscaledStyleMetrics {
80 pub fn style_class(&self) -> &'static StyleClass {
81 &super::style::STYLE_CLASSES[self.class_ix as usize]
82 }
83}
84
85#[derive(Clone, Debug)]
89pub(crate) enum UnscaledStyleMetricsSet {
90 Precomputed(Vec<UnscaledStyleMetrics>),
91 #[cfg(feature = "std")]
92 Lazy(Arc<RwLock<Vec<Option<UnscaledStyleMetrics>>>>),
93}
94
95impl UnscaledStyleMetricsSet {
96 pub fn precomputed(
99 font: &FontRef,
100 coords: &[F2Dot14],
101 shaper_mode: ShaperMode,
102 style_map: &GlyphStyleMap,
103 ) -> Self {
104 let shaper = Shaper::new(font, shaper_mode);
108 let mut vec = Vec::with_capacity(style_map.metrics_count());
109 vec.extend(
110 style_map
111 .metrics_styles()
112 .map(|style| compute_unscaled_style_metrics(&shaper, coords, style)),
113 );
114 Self::Precomputed(vec)
115 }
116
117 #[cfg(feature = "std")]
120 pub fn lazy(style_map: &GlyphStyleMap) -> Self {
121 let vec = vec![None; style_map.metrics_count()];
122 Self::Lazy(Arc::new(RwLock::new(vec)))
123 }
124
125 pub fn get(
128 &self,
129 font: &FontRef,
130 coords: &[F2Dot14],
131 shaper_mode: ShaperMode,
132 style_map: &GlyphStyleMap,
133 glyph_id: GlyphId,
134 ) -> Option<UnscaledStyleMetrics> {
135 let style = style_map.style(glyph_id)?;
136 let index = style_map.metrics_index(style)?;
137 match self {
138 Self::Precomputed(metrics) => metrics.get(index).cloned(),
139 #[cfg(feature = "std")]
140 Self::Lazy(lazy) => {
141 let read = lazy.read().unwrap();
142 let entry = read.get(index)?;
143 if let Some(metrics) = &entry {
144 return Some(metrics.clone());
145 }
146 core::mem::drop(read);
147 let shaper = Shaper::new(font, shaper_mode);
151 let style_class = style.style_class()?;
152 let metrics = compute_unscaled_style_metrics(&shaper, coords, style_class);
153 let mut entry = lazy.write().unwrap();
154 *entry.get_mut(index)? = Some(metrics.clone());
155 Some(metrics)
156 }
157 }
158 }
159}
160
161#[derive(Clone, Default, Debug)]
163pub(crate) struct ScaledStyleMetrics {
164 pub scale: Scale,
166 pub axes: [ScaledAxisMetrics; 2],
168}
169
170#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
171pub(crate) struct WidthMetrics {
172 pub edge_distance_threshold: i32,
174 pub standard_width: i32,
176 pub is_extra_light: bool,
178}
179
180pub(crate) type UnscaledWidths = SmallVec<i32, MAX_WIDTHS>;
181
182#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
183pub(crate) struct ScaledWidth {
184 pub scaled: i32,
186 pub fitted: i32,
188}
189
190pub(crate) type ScaledWidths = SmallVec<ScaledWidth, MAX_WIDTHS>;
191
192#[derive(Copy, Clone, Default, Debug)]
195pub(crate) struct Scale {
196 pub x_scale: i32,
198 pub y_scale: i32,
200 pub x_delta: i32,
202 pub y_delta: i32,
204 pub size: f32,
206 pub units_per_em: i32,
208 pub flags: u32,
210}
211
212impl Scale {
213 pub fn new(
215 size: f32,
216 units_per_em: i32,
217 font_style: Style,
218 target: Target,
219 group: ScriptGroup,
220 ) -> Self {
221 let scale =
222 (Fixed::from_bits((size * 64.0) as i32) / Fixed::from_bits(units_per_em)).to_bits();
223 let mut flags = 0;
224 let is_italic = font_style != Style::Normal;
225 let is_mono = target == Target::Mono;
226 let is_light = target.is_light() || target.preserve_linear_metrics();
227 if is_mono || target.is_lcd() {
229 flags |= Self::HORIZONTAL_SNAP;
230 }
231 if is_mono || target.is_vertical_lcd() {
233 flags |= Self::VERTICAL_SNAP;
234 }
235 if !(target.is_lcd() || is_light) {
237 flags |= Self::STEM_ADJUST;
238 }
239 if is_mono {
240 flags |= Self::MONO;
241 }
242 if group == ScriptGroup::Default {
243 if target.is_lcd() || is_light || is_italic {
247 flags |= Self::NO_HORIZONTAL;
248 }
249 } else {
250 flags |= Self::NO_ADVANCE;
253 }
254 if group != ScriptGroup::Default {
257 flags |= Self::NO_ADVANCE;
258 }
259 Self {
260 x_scale: scale,
261 y_scale: scale,
262 x_delta: 0,
263 y_delta: 0,
264 size,
265 units_per_em,
266 flags,
267 }
268 }
269}
270
271impl Scale {
276 pub const HORIZONTAL_SNAP: u32 = 1 << 0;
278 pub const VERTICAL_SNAP: u32 = 1 << 1;
280 pub const STEM_ADJUST: u32 = 1 << 2;
282 pub const MONO: u32 = 1 << 3;
284 pub const NO_HORIZONTAL: u32 = 1 << 4;
286 pub const NO_VERTICAL: u32 = 1 << 5;
288 pub const NO_ADVANCE: u32 = 1 << 6;
290}
291
292pub(crate) fn sort_and_quantize_widths(widths: &mut UnscaledWidths, threshold: i32) {
294 if widths.len() <= 1 {
295 return;
296 }
297 widths.sort_unstable();
298 let table = widths.as_mut_slice();
299 let mut cur_ix = 0;
300 let mut cur_val = table[cur_ix];
301 let last_ix = table.len() - 1;
302 let mut ix = 1;
303 while ix < table.len() {
306 if (table[ix] - cur_val) > threshold || ix == last_ix {
307 let mut sum = 0;
308 if (table[ix] - cur_val <= threshold) && ix == last_ix {
310 ix += 1;
311 }
312 for val in &mut table[cur_ix..ix] {
313 sum += *val;
314 *val = 0;
315 }
316 table[cur_ix] = sum / ix as i32;
317 if ix < last_ix {
318 cur_ix = ix + 1;
319 cur_val = table[cur_ix];
320 }
321 }
322 ix += 1;
323 }
324 cur_ix = 1;
325 for ix in 1..table.len() {
327 if table[ix] != 0 {
328 table[cur_ix] = table[ix];
329 cur_ix += 1;
330 }
331 }
332 widths.truncate(cur_ix);
333}
334
335pub(crate) fn fixed_mul(a: i32, b: i32) -> i32 {
342 (Fixed::from_bits(a) * Fixed::from_bits(b)).to_bits()
343}
344
345pub(crate) fn fixed_div(a: i32, b: i32) -> i32 {
346 (Fixed::from_bits(a) / Fixed::from_bits(b)).to_bits()
347}
348
349pub(crate) fn fixed_mul_div(a: i32, b: i32, c: i32) -> i32 {
350 Fixed::from_bits(a)
351 .mul_div(Fixed::from_bits(b), Fixed::from_bits(c))
352 .to_bits()
353}
354
355pub(crate) fn pix_round(a: i32) -> i32 {
356 (a + 32) & !63
357}
358
359pub(crate) fn pix_floor(a: i32) -> i32 {
360 a & !63
361}
362
363#[cfg(test)]
364mod tests {
365 use super::{
366 super::{
367 shape::{Shaper, ShaperMode},
368 style::STYLE_CLASSES,
369 },
370 *,
371 };
372 use raw::TableProvider;
373
374 #[test]
375 fn sort_widths() {
376 assert_eq!(sort_widths_helper(&[1], 10), &[1]);
379 assert_eq!(sort_widths_helper(&[1], 20), &[1]);
380 assert_eq!(sort_widths_helper(&[60, 20, 40, 35], 10), &[20, 35, 13, 60]);
381 assert_eq!(sort_widths_helper(&[60, 20, 40, 35], 20), &[31, 60]);
382 }
383
384 fn sort_widths_helper(widths: &[i32], threshold: i32) -> Vec<i32> {
385 let mut widths2 = UnscaledWidths::new();
386 for width in widths {
387 widths2.push(*width);
388 }
389 sort_and_quantize_widths(&mut widths2, threshold);
390 widths2.into_iter().collect()
391 }
392
393 #[test]
394 fn precomputed_style_set() {
395 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
396 let coords = &[];
397 let shaper = Shaper::new(&font, ShaperMode::Nominal);
398 let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
399 let style_map = GlyphStyleMap::new(glyph_count, &shaper);
400 let style_set =
401 UnscaledStyleMetricsSet::precomputed(&font, coords, ShaperMode::Nominal, &style_map);
402 let UnscaledStyleMetricsSet::Precomputed(set) = &style_set else {
403 panic!("we definitely made a precomputed style set");
404 };
405 assert_eq!(STYLE_CLASSES[set[0].class_ix as usize].name, "Latin");
407 assert_eq!(STYLE_CLASSES[set[1].class_ix as usize].name, "Hebrew");
408 assert_eq!(
409 STYLE_CLASSES[set[2].class_ix as usize].name,
410 "CJKV ideographs"
411 );
412 assert_eq!(set.len(), 3);
413 }
414
415 #[test]
416 fn lazy_style_set() {
417 let font = FontRef::new(font_test_data::NOTOSERIFHEBREW_AUTOHINT_METRICS).unwrap();
418 let coords = &[];
419 let shaper = Shaper::new(&font, ShaperMode::Nominal);
420 let glyph_count = font.maxp().unwrap().num_glyphs() as u32;
421 let style_map = GlyphStyleMap::new(glyph_count, &shaper);
422 let style_set = UnscaledStyleMetricsSet::lazy(&style_map);
423 let all_empty = lazy_set_presence(&style_set);
424 assert_eq!(all_empty, [false; 3]);
426 let metrics2 = style_set
428 .get(
429 &font,
430 coords,
431 ShaperMode::Nominal,
432 &style_map,
433 GlyphId::new(0),
434 )
435 .unwrap();
436 assert_eq!(
437 STYLE_CLASSES[metrics2.class_ix as usize].name,
438 "CJKV ideographs"
439 );
440 let only_cjk = lazy_set_presence(&style_set);
441 assert_eq!(only_cjk, [false, false, true]);
442 let metrics1 = style_set
444 .get(
445 &font,
446 coords,
447 ShaperMode::Nominal,
448 &style_map,
449 GlyphId::new(1),
450 )
451 .unwrap();
452 assert_eq!(STYLE_CLASSES[metrics1.class_ix as usize].name, "Hebrew");
453 let hebrew_and_cjk = lazy_set_presence(&style_set);
454 assert_eq!(hebrew_and_cjk, [false, true, true]);
455 let metrics0 = style_set
457 .get(
458 &font,
459 coords,
460 ShaperMode::Nominal,
461 &style_map,
462 GlyphId::new(15),
463 )
464 .unwrap();
465 assert_eq!(STYLE_CLASSES[metrics0.class_ix as usize].name, "Latin");
466 let all_present = lazy_set_presence(&style_set);
467 assert_eq!(all_present, [true; 3]);
468 }
469
470 fn lazy_set_presence(style_set: &UnscaledStyleMetricsSet) -> Vec<bool> {
471 let UnscaledStyleMetricsSet::Lazy(set) = &style_set else {
472 panic!("we definitely made a lazy style set");
473 };
474 set.read()
475 .unwrap()
476 .iter()
477 .map(|opt| opt.is_some())
478 .collect()
479 }
480}