1use read_fonts::{
26 tables::{
27 glyf::Glyf, gvar::SelectedGvar, hmtx::LongMetric, hvar::Hvar, loca::Loca,
28 os2::SelectionFlags,
29 },
30 types::{BigEndian, Fixed, GlyphId},
31 FontRef, TableProvider,
32};
33
34use crate::{
35 outline::{pen::ControlBoundsPen, DrawSettings},
36 MetadataProvider,
37};
38
39use super::instance::{LocationRef, NormalizedCoord, Size};
40
41pub type BoundingBox = read_fonts::types::BoundingBox<f32>;
43
44#[derive(Copy, Clone, PartialEq, Default, Debug)]
49pub struct Decoration {
50 pub offset: f32,
52 pub thickness: f32,
54}
55
56#[derive(Copy, Clone, PartialEq, Default, Debug)]
74pub struct Metrics {
75 pub units_per_em: u16,
77 pub glyph_count: u32,
79 pub is_monospace: bool,
81 pub italic_angle: f32,
84 pub ascent: f32,
86 pub descent: f32,
89 pub leading: f32,
91 pub cap_height: Option<f32>,
93 pub x_height: Option<f32>,
96 pub average_width: Option<f32>,
98 pub max_width: Option<f32>,
100 pub underline: Option<Decoration>,
102 pub strikeout: Option<Decoration>,
104 pub bounds: Option<BoundingBox>,
106}
107
108impl Metrics {
109 pub fn new<'a>(font: &FontRef<'a>, size: Size, location: impl Into<LocationRef<'a>>) -> Self {
112 let head = font.head();
113 let mut metrics = Metrics {
114 units_per_em: head.map(|head| head.units_per_em()).unwrap_or_default(),
115 ..Default::default()
116 };
117 let coords = location.into().effective_coords();
118 let scale = size.linear_scale(metrics.units_per_em);
119 if let Ok(head) = font.head() {
120 metrics.bounds = Some(BoundingBox {
121 x_min: head.x_min() as f32 * scale,
122 y_min: head.y_min() as f32 * scale,
123 x_max: head.x_max() as f32 * scale,
124 y_max: head.y_max() as f32 * scale,
125 });
126 }
127 if let Ok(maxp) = font.selected_maxp() {
128 metrics.glyph_count = maxp.num_glyphs();
129 }
130 if let Ok(post) = font.post() {
131 metrics.is_monospace = post.is_fixed_pitch() != 0;
132 metrics.italic_angle = post.italic_angle().to_f64() as f32;
133 metrics.underline = Some(Decoration {
134 offset: post.underline_position().to_i16() as f32 * scale,
135 thickness: post.underline_thickness().to_i16() as f32 * scale,
136 });
137 }
138 let hhea = font.selected_hhea();
139 if let Ok(hhea) = &hhea {
140 metrics.max_width = Some(hhea.advance_width_max().to_u16() as f32 * scale);
141 }
142 let os2 = font.os2().ok();
155 let mut used_typo_metrics = false;
156 if let Some(os2) = &os2 {
157 if os2
158 .fs_selection()
159 .contains(SelectionFlags::USE_TYPO_METRICS)
160 {
161 metrics.ascent = os2.s_typo_ascender() as f32 * scale;
162 metrics.descent = os2.s_typo_descender() as f32 * scale;
163 metrics.leading = os2.s_typo_line_gap() as f32 * scale;
164 used_typo_metrics = true;
165 }
166 metrics.average_width = Some(os2.x_avg_char_width() as f32 * scale);
167 metrics.cap_height = os2.s_cap_height().map(|v| v as f32 * scale);
168 metrics.x_height = os2.sx_height().map(|v| v as f32 * scale);
169 metrics.strikeout = Some(Decoration {
170 offset: os2.y_strikeout_position() as f32 * scale,
171 thickness: os2.y_strikeout_size() as f32 * scale,
172 });
173 }
174 if !used_typo_metrics {
175 if let Ok(hhea) = font.selected_hhea() {
176 metrics.ascent = hhea.ascender().to_i16() as f32 * scale;
177 metrics.descent = hhea.descender().to_i16() as f32 * scale;
178 metrics.leading = hhea.line_gap().to_i16() as f32 * scale;
179 }
180 if metrics.ascent == 0.0 && metrics.descent == 0.0 {
181 if let Some(os2) = &os2 {
182 if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 {
183 metrics.ascent = os2.s_typo_ascender() as f32 * scale;
184 metrics.descent = os2.s_typo_descender() as f32 * scale;
185 metrics.leading = os2.s_typo_line_gap() as f32 * scale;
186 } else {
187 metrics.ascent = os2.us_win_ascent() as f32 * scale;
188 metrics.descent = -(os2.us_win_descent() as f32 * scale);
191 }
192 }
193 }
194 }
195 if let (Ok(mvar), true) = (font.mvar(), !coords.is_empty()) {
196 use read_fonts::tables::mvar::tags::*;
197 let metric_delta =
198 |tag| mvar.metric_delta(tag, coords).unwrap_or_default().to_f64() as f32 * scale;
199 metrics.ascent += metric_delta(HASC);
200 metrics.descent += metric_delta(HDSC);
201 metrics.leading += metric_delta(HLGP);
202 if let Some(cap_height) = &mut metrics.cap_height {
203 *cap_height += metric_delta(CPHT);
204 }
205 if let Some(x_height) = &mut metrics.x_height {
206 *x_height += metric_delta(XHGT);
207 }
208 if let Some(underline) = &mut metrics.underline {
209 underline.offset += metric_delta(UNDO);
210 underline.thickness += metric_delta(UNDS);
211 }
212 if let Some(strikeout) = &mut metrics.strikeout {
213 strikeout.offset += metric_delta(STRO);
214 strikeout.thickness += metric_delta(STRS);
215 }
216 }
217 metrics.ascent = metrics.ascent.abs();
221 metrics.descent = -metrics.descent.abs();
222 metrics
223 }
224}
225
226#[derive(Clone)]
228pub struct GlyphMetrics<'a> {
229 font: FontRef<'a>,
230 size: Size,
231 glyph_count: u32,
232 fixed_scale: FixedScaleFactor,
233 h_metrics: &'a [LongMetric],
234 default_advance_width: u16,
235 lsbs: &'a [BigEndian<i16>],
236 hvar: Option<Hvar<'a>>,
237 gvar: Option<SelectedGvar<'a>>,
238 loca_glyf: Option<(Loca<'a>, Glyf<'a>)>,
239 coords: &'a [NormalizedCoord],
240}
241
242impl<'a> GlyphMetrics<'a> {
243 pub fn new(font: &FontRef<'a>, size: Size, location: impl Into<LocationRef<'a>>) -> Self {
246 let glyph_count = font
247 .selected_maxp()
248 .map(|maxp| maxp.num_glyphs())
249 .unwrap_or_default();
250 let upem = font
251 .head()
252 .map(|head| head.units_per_em())
253 .unwrap_or_default();
254 let fixed_scale = FixedScaleFactor(size.fixed_linear_scale(upem));
255 let coords = location.into().effective_coords();
256 let (h_metrics, default_advance_width, lsbs) = font
257 .h_glyph_metric_records()
258 .map(|hmtx| {
259 let h_metrics = hmtx.long_metrics();
260 let default_advance_width = h_metrics.last().map(|m| m.advance.get()).unwrap_or(0);
261 let lsbs = hmtx.side_bearings();
262 (h_metrics, default_advance_width, lsbs)
263 })
264 .unwrap_or_default();
265 let hvar = font.hvar().ok();
266 let gvar = font.selected_gvar().ok();
267 let loca_glyf = font
268 .selected_glyf_loca(None)
269 .ok()
270 .map(|(glyf, loca)| (loca, glyf));
271 Self {
272 font: font.clone(),
273 size,
274 glyph_count,
275 fixed_scale,
276 h_metrics,
277 default_advance_width,
278 lsbs,
279 hvar,
280 gvar,
281 loca_glyf,
282 coords,
283 }
284 }
285
286 pub fn glyph_count(&self) -> u32 {
288 self.glyph_count
289 }
290
291 pub fn advance_width(&self, glyph_id: GlyphId) -> Option<f32> {
300 if glyph_id.to_u32() >= self.glyph_count {
301 return None;
302 }
303 let mut advance = self
304 .h_metrics
305 .get(glyph_id.to_u32() as usize)
306 .map(|metric| metric.advance())
307 .unwrap_or(self.default_advance_width) as i32;
308 if let Some(hvar) = &self.hvar {
309 advance += hvar
310 .advance_delta(glyph_id, self.coords)
311 .map(|delta| delta.to_i32())
315 .unwrap_or(0);
316 } else if self.gvar.is_some() {
317 advance += self.metric_deltas_from_gvar(glyph_id).unwrap_or_default()[1];
318 }
319 Some(self.fixed_scale.apply(advance.max(0)))
320 }
321
322 pub fn left_side_bearing(&self, glyph_id: GlyphId) -> Option<f32> {
330 if glyph_id.to_u32() >= self.glyph_count {
331 return None;
332 }
333 let gid_index = glyph_id.to_u32() as usize;
334 let mut lsb = self
335 .h_metrics
336 .get(gid_index)
337 .map(|metric| metric.side_bearing())
338 .unwrap_or_else(|| {
339 self.lsbs
340 .get(gid_index.saturating_sub(self.h_metrics.len()))
341 .map(|lsb| lsb.get())
342 .unwrap_or_default()
343 }) as i32;
344 if let Some(hvar) = &self.hvar {
345 lsb += hvar
346 .lsb_delta(glyph_id, self.coords)
347 .map(|delta| delta.to_i32())
351 .unwrap_or(0);
352 } else if self.gvar.is_some() {
353 lsb += self.metric_deltas_from_gvar(glyph_id).unwrap_or_default()[0];
354 }
355 Some(self.fixed_scale.apply(lsb))
356 }
357
358 pub fn bounds(&self, glyph_id: GlyphId) -> Option<BoundingBox> {
363 if self.gvar.is_some() || self.font.cff().ok().is_some() || self.font.cff2().ok().is_some()
364 {
365 return self.bounds_from_outline(glyph_id);
366 }
367 let (loca, glyf) = self.loca_glyf.as_ref()?;
368 Some(match loca.get(glyph_id, glyf)?.glyph() {
369 Some(glyph) => BoundingBox {
370 x_min: self.fixed_scale.apply(glyph.x_min() as i32),
371 y_min: self.fixed_scale.apply(glyph.y_min() as i32),
372 x_max: self.fixed_scale.apply(glyph.x_max() as i32),
373 y_max: self.fixed_scale.apply(glyph.y_max() as i32),
374 },
375 None => BoundingBox::default(),
377 })
378 }
379}
380
381impl GlyphMetrics<'_> {
382 pub(crate) fn glyf_y_min(&self, glyph_id: GlyphId) -> Option<i16> {
383 let (loca, glyf) = self.loca_glyf.as_ref()?;
384 let entry = loca.get(glyph_id, glyf)?;
385 let glyph = entry.glyph()?;
386 (glyph.number_of_contours() != 0).then(|| glyph.y_min())
387 }
388
389 fn metric_deltas_from_gvar(&self, glyph_id: GlyphId) -> Option<[i32; 2]> {
390 let (loca, glyf) = self.loca_glyf.as_ref()?;
391 let mut deltas =
392 self.gvar
393 .as_ref()?
394 .phantom_point_deltas(glyf, loca, self.coords, glyph_id)?;
395 deltas[1] -= deltas[0];
396 Some([deltas[0], deltas[1]].map(|delta| delta.x.to_i32()))
397 }
398
399 fn bounds_from_outline(&self, glyph_id: GlyphId) -> Option<BoundingBox> {
400 if let Some(outline) = self.font.outline_glyphs().get(glyph_id) {
401 let settings = DrawSettings::unhinted(self.size, self.coords);
402 let mut pen = ControlBoundsPen::default();
403 outline.draw(settings, &mut pen).ok()?;
404 pen.bounding_box()
405 } else {
406 None
407 }
408 }
409}
410
411#[derive(Copy, Clone)]
412struct FixedScaleFactor(Fixed);
413
414impl FixedScaleFactor {
415 #[inline(always)]
416 fn apply(self, value: i32) -> f32 {
417 self.0
420 .mul_div(Fixed::from_bits(value), Fixed::from_bits(64))
421 .to_f32()
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use font_test_data::{NOTO_SANS_JP_CFF, SIMPLE_GLYF, VAZIRMATN_VAR};
429 use read_fonts::FontRef;
430
431 fn positive_descender() -> Vec<u8> {
434 let mut data = font_test_data::TINOS_SUBSET.to_vec();
435 let font = FontRef::new(font_test_data::TINOS_SUBSET).unwrap();
436 let record = font
437 .table_directory()
438 .table_records()
439 .iter()
440 .find(|record| record.tag() == read_fonts::types::Tag::new(b"hhea"))
441 .unwrap();
442 let at = record.offset() as usize + 6;
444 let descender = i16::from_be_bytes([data[at], data[at + 1]]);
445 assert!(descender < 0);
446 data[at..at + 2].copy_from_slice(&(-descender).to_be_bytes());
447 data
448 }
449
450 #[test]
452 fn descenders_stored_positive_are_below_the_baseline() {
453 let flipped = positive_descender();
454 let original = FontRef::new(font_test_data::TINOS_SUBSET).unwrap();
455 let flipped = FontRef::new(&flipped).unwrap();
456 let size = Size::new(16.0);
457 let original = Metrics::new(&original, size, LocationRef::default());
458 let flipped = Metrics::new(&flipped, size, LocationRef::default());
459 assert!(flipped.descent < 0.0);
460 assert_eq!(
461 (flipped.ascent, flipped.descent, flipped.leading),
462 (original.ascent, original.descent, original.leading)
463 );
464 }
465
466 #[test]
467 fn extended_glyph_metrics_with_any_outline_tag() {
468 use font_test_data::extended::metrics_font;
469 for outline in [None, Some(*b"glyf"), Some(*b"GLYF")] {
470 for legacy in [false, true] {
471 let data = metrics_font(true, legacy, outline);
472 let font = FontRef::new(&data).unwrap();
473 let global = font.metrics(Size::unscaled(), LocationRef::default());
474 assert_eq!(global.glyph_count, 70002);
475 assert_eq!(global.ascent, 800.0);
476 assert_eq!(global.descent, -200.0);
477 let metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
478 assert_eq!(metrics.glyph_count(), 70002);
479 for gid in [0, 65535, 65536, 69999] {
480 let glyph = GlyphId::new(gid);
481 assert_eq!(
482 metrics.advance_width(glyph),
483 Some((1000 + gid % 100) as f32)
484 );
485 assert_eq!(
486 metrics.left_side_bearing(glyph),
487 Some(-((gid % 300) as f32))
488 );
489 }
490 assert_eq!(metrics.advance_width(GlyphId::new(70001)), Some(1099.0));
491 assert_eq!(metrics.left_side_bearing(GlyphId::new(70001)), Some(-11.0));
492 assert_eq!(metrics.advance_width(GlyphId::new(70002)), None);
493 }
494 }
495 }
496
497 #[test]
498 fn metrics() {
499 let font = FontRef::new(SIMPLE_GLYF).unwrap();
500 let metrics = font.metrics(Size::unscaled(), LocationRef::default());
501 let expected = Metrics {
502 units_per_em: 1024,
503 glyph_count: 3,
504 bounds: Some(BoundingBox {
505 x_min: 51.0,
506 y_min: -250.0,
507 x_max: 998.0,
508 y_max: 950.0,
509 }),
510 average_width: Some(1275.0),
511 max_width: None,
512 x_height: Some(512.0),
513 cap_height: Some(717.0),
514 is_monospace: false,
515 italic_angle: 0.0,
516 ascent: 950.0,
517 descent: -250.0,
518 leading: 0.0,
519 underline: None,
520 strikeout: Some(Decoration {
521 offset: 307.0,
522 thickness: 51.0,
523 }),
524 };
525 assert_eq!(metrics, expected);
526 }
527
528 #[test]
529 fn metrics_missing_os2() {
530 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
531 let metrics = font.metrics(Size::unscaled(), LocationRef::default());
532 let expected = Metrics {
533 units_per_em: 2048,
534 glyph_count: 4,
535 bounds: Some(BoundingBox {
536 x_min: 29.0,
537 y_min: 0.0,
538 x_max: 1310.0,
539 y_max: 1847.0,
540 }),
541 average_width: None,
542 max_width: Some(1336.0),
543 x_height: None,
544 cap_height: None,
545 is_monospace: false,
546 italic_angle: 0.0,
547 ascent: 2100.0,
548 descent: -1100.0,
549 leading: 0.0,
550 underline: None,
551 strikeout: None,
552 };
553 assert_eq!(metrics, expected);
554 }
555
556 #[test]
557 fn glyph_metrics() {
558 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
559 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
560 let expected = &[
562 (908.0, 100.0),
563 (1336.0, 29.0),
564 (1336.0, 29.0),
565 (633.0, 57.0),
566 ];
567 let result = (0..4)
568 .map(|i| {
569 let gid = GlyphId::new(i as u32);
570 let advance_width = glyph_metrics.advance_width(gid).unwrap();
571 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
572 (advance_width, lsb)
573 })
574 .collect::<Vec<_>>();
575 assert_eq!(expected, &result[..]);
576 }
577
578 #[test]
583 fn glyph_metrics_unscaled_matches_upem_scale() {
584 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
585 let upem = font.head().unwrap().units_per_em() as f32;
586 let unscaled_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
587 let upem_metrics = font.glyph_metrics(Size::new(upem), LocationRef::default());
588 for i in 0..unscaled_metrics.glyph_count() {
589 let gid = GlyphId::new(i);
590 assert_eq!(
591 unscaled_metrics.advance_width(gid),
592 upem_metrics.advance_width(gid)
593 );
594 assert_eq!(
595 unscaled_metrics.left_side_bearing(gid),
596 upem_metrics.left_side_bearing(gid)
597 );
598 }
599 }
600
601 #[test]
602 fn glyph_metrics_var() {
603 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
604 let coords = &[NormalizedCoord::from_f32(-0.8)];
605 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::new(coords));
606 let expected = &[
608 (908.0, 100.0),
609 (1246.0, 29.0),
610 (1246.0, 29.0),
611 (556.0, 57.0),
612 ];
613 let result = (0..4)
614 .map(|i| {
615 let gid = GlyphId::new(i as u32);
616 let advance_width = glyph_metrics.advance_width(gid).unwrap();
617 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
618 (advance_width, lsb)
619 })
620 .collect::<Vec<_>>();
621 assert_eq!(expected, &result[..]);
622
623 let coords = &[NormalizedCoord::from_f32(-1.0)];
625 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::new(coords));
626 let bounds = glyph_metrics.bounds(GlyphId::new(1)).unwrap();
627 assert_eq!(
628 bounds,
629 BoundingBox {
630 x_min: 33.0,
631 y_min: 0.0,
632 x_max: 1189.0,
633 y_max: 1456.0
634 }
635 );
636 }
637
638 #[test]
639 fn negative_varied_advances_are_clamped_before_scaling() {
640 let font = FontRef::new(font_test_data::NEGATIVE_VAR_ADVANCES).unwrap();
641 for (coord, expected) in [
642 (0.0, [500.0, 500.0]),
643 (0.5, [200.0, 551.0]),
645 (1.0, [0.0, 601.0]),
646 ] {
647 let coords = [NormalizedCoord::from_f32(coord)];
648 for size in [
649 Size::unscaled(),
650 Size::new(-2000.0),
651 Size::new(0.0),
652 Size::new(500.0),
653 Size::new(2000.0),
654 ] {
655 let scale = size.linear_scale(1000);
656 let metrics = font.glyph_metrics(size, LocationRef::new(&coords));
657 let mut gvar_metrics = metrics.clone();
658 gvar_metrics.hvar = None;
659 for (i, gid) in [GlyphId::new(1), GlyphId::new(2)].into_iter().enumerate() {
660 assert_eq!(metrics.advance_width(gid), Some(expected[i] * scale));
661 assert_eq!(gvar_metrics.advance_width(gid), Some(expected[i] * scale));
662 }
663 assert_eq!(metrics.advance_width(GlyphId::new(3)), None);
664 }
665 }
666 }
667
668 #[test]
669 fn glyph_metrics_cff() {
670 let font = FontRef::new(NOTO_SANS_JP_CFF).unwrap();
671 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
672 let bounds = glyph_metrics.bounds(GlyphId::new(34)).unwrap();
673 assert_eq!(
674 bounds,
675 BoundingBox {
676 x_min: 4.0,
677 y_min: 0.0,
678 x_max: 604.0,
679 y_max: 733.0
680 }
681 );
682 }
683
684 #[test]
685 fn glyph_metrics_missing_hvar() {
686 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
687 let glyph_count = font.maxp().unwrap().num_glyphs();
688 for coord in [-1.0, -0.8, 0.0, 0.75, 1.0] {
690 let coords = &[NormalizedCoord::from_f32(coord)];
691 let location = LocationRef::new(coords);
692 let glyph_metrics = font.glyph_metrics(Size::unscaled(), location);
693 let mut glyph_metrics_no_hvar = glyph_metrics.clone();
694 glyph_metrics_no_hvar.hvar = None;
696 for gid in 0..glyph_count {
697 let gid = GlyphId::from(gid);
698 assert_eq!(
699 glyph_metrics.advance_width(gid),
700 glyph_metrics_no_hvar.advance_width(gid)
701 );
702 assert_eq!(
703 glyph_metrics.left_side_bearing(gid),
704 glyph_metrics_no_hvar.left_side_bearing(gid)
705 );
706 }
707 }
708 }
709
710 #[test]
714 fn match_freetype_glyph_metric_scaling() {
715 let font_unit_advances = [639, 561, 524, 258];
723 #[allow(clippy::excessive_precision)]
724 let scaled_advances = [
725 15.33595275878906250000,
726 13.46395874023437500000,
727 12.57595825195312500000,
728 6.19198608398437500000,
729 ];
730 let fixed_scale = FixedScaleFactor(Size::new(24.0).fixed_linear_scale(1000));
731 for (font_unit_advance, expected_scaled_advance) in
732 font_unit_advances.iter().zip(scaled_advances)
733 {
734 let scaled_advance = fixed_scale.apply(*font_unit_advance);
735 assert_eq!(scaled_advance, expected_scaled_advance);
736 }
737 }
738}