1use read_fonts::{
26 tables::{
27 glyf::Glyf, gvar::Gvar, hmtx::LongMetric, hvar::Hvar, loca::Loca, os2::SelectionFlags,
28 },
29 types::{BigEndian, Fixed, GlyphId},
30 FontRef, TableProvider,
31};
32
33use crate::{
34 outline::{pen::ControlBoundsPen, DrawSettings},
35 MetadataProvider,
36};
37
38use super::instance::{LocationRef, NormalizedCoord, Size};
39
40pub type BoundingBox = read_fonts::types::BoundingBox<f32>;
42
43#[derive(Copy, Clone, PartialEq, Default, Debug)]
48pub struct Decoration {
49 pub offset: f32,
51 pub thickness: f32,
53}
54
55#[derive(Copy, Clone, PartialEq, Default, Debug)]
72pub struct Metrics {
73 pub units_per_em: u16,
75 pub glyph_count: u16,
77 pub is_monospace: bool,
79 pub italic_angle: f32,
82 pub ascent: f32,
84 pub descent: f32,
86 pub leading: f32,
88 pub cap_height: Option<f32>,
90 pub x_height: Option<f32>,
93 pub average_width: Option<f32>,
95 pub max_width: Option<f32>,
97 pub underline: Option<Decoration>,
99 pub strikeout: Option<Decoration>,
101 pub bounds: Option<BoundingBox>,
103}
104
105impl Metrics {
106 pub fn new<'a>(font: &FontRef<'a>, size: Size, location: impl Into<LocationRef<'a>>) -> Self {
109 let head = font.head();
110 let mut metrics = Metrics {
111 units_per_em: head.map(|head| head.units_per_em()).unwrap_or_default(),
112 ..Default::default()
113 };
114 let coords = location.into().effective_coords();
115 let scale = size.linear_scale(metrics.units_per_em);
116 if let Ok(head) = font.head() {
117 metrics.bounds = Some(BoundingBox {
118 x_min: head.x_min() as f32 * scale,
119 y_min: head.y_min() as f32 * scale,
120 x_max: head.x_max() as f32 * scale,
121 y_max: head.y_max() as f32 * scale,
122 });
123 }
124 if let Ok(maxp) = font.maxp() {
125 metrics.glyph_count = maxp.num_glyphs();
126 }
127 if let Ok(post) = font.post() {
128 metrics.is_monospace = post.is_fixed_pitch() != 0;
129 metrics.italic_angle = post.italic_angle().to_f64() as f32;
130 metrics.underline = Some(Decoration {
131 offset: post.underline_position().to_i16() as f32 * scale,
132 thickness: post.underline_thickness().to_i16() as f32 * scale,
133 });
134 }
135 let hhea = font.hhea();
136 if let Ok(hhea) = &hhea {
137 metrics.max_width = Some(hhea.advance_width_max().to_u16() as f32 * scale);
138 }
139 let os2 = font.os2().ok();
152 let mut used_typo_metrics = false;
153 if let Some(os2) = &os2 {
154 if os2
155 .fs_selection()
156 .contains(SelectionFlags::USE_TYPO_METRICS)
157 {
158 metrics.ascent = os2.s_typo_ascender() as f32 * scale;
159 metrics.descent = os2.s_typo_descender() as f32 * scale;
160 metrics.leading = os2.s_typo_line_gap() as f32 * scale;
161 used_typo_metrics = true;
162 }
163 metrics.average_width = Some(os2.x_avg_char_width() as f32 * scale);
164 metrics.cap_height = os2.s_cap_height().map(|v| v as f32 * scale);
165 metrics.x_height = os2.sx_height().map(|v| v as f32 * scale);
166 metrics.strikeout = Some(Decoration {
167 offset: os2.y_strikeout_position() as f32 * scale,
168 thickness: os2.y_strikeout_size() as f32 * scale,
169 });
170 }
171 if !used_typo_metrics {
172 if let Ok(hhea) = font.hhea() {
173 metrics.ascent = hhea.ascender().to_i16() as f32 * scale;
174 metrics.descent = hhea.descender().to_i16() as f32 * scale;
175 metrics.leading = hhea.line_gap().to_i16() as f32 * scale;
176 }
177 if metrics.ascent == 0.0 && metrics.descent == 0.0 {
178 if let Some(os2) = &os2 {
179 if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 {
180 metrics.ascent = os2.s_typo_ascender() as f32 * scale;
181 metrics.descent = os2.s_typo_descender() as f32 * scale;
182 metrics.leading = os2.s_typo_line_gap() as f32 * scale;
183 } else {
184 metrics.ascent = os2.us_win_ascent() as f32 * scale;
185 metrics.descent = -(os2.us_win_descent() as f32 * scale);
188 }
189 }
190 }
191 }
192 if let (Ok(mvar), true) = (font.mvar(), !coords.is_empty()) {
193 use read_fonts::tables::mvar::tags::*;
194 let metric_delta =
195 |tag| mvar.metric_delta(tag, coords).unwrap_or_default().to_f64() as f32 * scale;
196 metrics.ascent += metric_delta(HASC);
197 metrics.descent += metric_delta(HDSC);
198 metrics.leading += metric_delta(HLGP);
199 if let Some(cap_height) = &mut metrics.cap_height {
200 *cap_height += metric_delta(CPHT);
201 }
202 if let Some(x_height) = &mut metrics.x_height {
203 *x_height += metric_delta(XHGT);
204 }
205 if let Some(underline) = &mut metrics.underline {
206 underline.offset += metric_delta(UNDO);
207 underline.thickness += metric_delta(UNDS);
208 }
209 if let Some(strikeout) = &mut metrics.strikeout {
210 strikeout.offset += metric_delta(STRO);
211 strikeout.thickness += metric_delta(STRS);
212 }
213 }
214 metrics
215 }
216}
217
218#[derive(Clone)]
220pub struct GlyphMetrics<'a> {
221 font: FontRef<'a>,
222 size: Size,
223 glyph_count: u32,
224 fixed_scale: FixedScaleFactor,
225 h_metrics: &'a [LongMetric],
226 default_advance_width: u16,
227 lsbs: &'a [BigEndian<i16>],
228 hvar: Option<Hvar<'a>>,
229 gvar: Option<Gvar<'a>>,
230 loca_glyf: Option<(Loca<'a>, Glyf<'a>)>,
231 coords: &'a [NormalizedCoord],
232}
233
234impl<'a> GlyphMetrics<'a> {
235 pub fn new(font: &FontRef<'a>, size: Size, location: impl Into<LocationRef<'a>>) -> Self {
238 let glyph_count = font
239 .maxp()
240 .map(|maxp| maxp.num_glyphs() as u32)
241 .unwrap_or_default();
242 let upem = font
243 .head()
244 .map(|head| head.units_per_em())
245 .unwrap_or_default();
246 let fixed_scale = FixedScaleFactor(size.fixed_linear_scale(upem));
247 let coords = location.into().effective_coords();
248 let (h_metrics, default_advance_width, lsbs) = font
249 .hmtx()
250 .map(|hmtx| {
251 let h_metrics = hmtx.h_metrics();
252 let default_advance_width = h_metrics.last().map(|m| m.advance.get()).unwrap_or(0);
253 let lsbs = hmtx.left_side_bearings();
254 (h_metrics, default_advance_width, lsbs)
255 })
256 .unwrap_or_default();
257 let hvar = font.hvar().ok();
258 let gvar = font.gvar().ok();
259 let loca_glyf = if let (Ok(loca), Ok(glyf)) = (font.loca(None), font.glyf()) {
260 Some((loca, glyf))
261 } else {
262 None
263 };
264 Self {
265 font: font.clone(),
266 size,
267 glyph_count,
268 fixed_scale,
269 h_metrics,
270 default_advance_width,
271 lsbs,
272 hvar,
273 gvar,
274 loca_glyf,
275 coords,
276 }
277 }
278
279 pub fn glyph_count(&self) -> u32 {
281 self.glyph_count
282 }
283
284 pub fn advance_width(&self, glyph_id: GlyphId) -> Option<f32> {
292 if glyph_id.to_u32() >= self.glyph_count {
293 return None;
294 }
295 let mut advance = self
296 .h_metrics
297 .get(glyph_id.to_u32() as usize)
298 .map(|metric| metric.advance())
299 .unwrap_or(self.default_advance_width) as i32;
300 if let Some(hvar) = &self.hvar {
301 advance += hvar
302 .advance_width_delta(glyph_id, self.coords)
303 .map(|delta| delta.to_f64() as i32)
306 .unwrap_or(0);
307 } else if self.gvar.is_some() {
308 advance += self.metric_deltas_from_gvar(glyph_id).unwrap_or_default()[1];
309 }
310 Some(self.fixed_scale.apply(advance))
311 }
312
313 pub fn left_side_bearing(&self, glyph_id: GlyphId) -> Option<f32> {
321 if glyph_id.to_u32() >= self.glyph_count {
322 return None;
323 }
324 let gid_index = glyph_id.to_u32() as usize;
325 let mut lsb = self
326 .h_metrics
327 .get(gid_index)
328 .map(|metric| metric.side_bearing())
329 .unwrap_or_else(|| {
330 self.lsbs
331 .get(gid_index.saturating_sub(self.h_metrics.len()))
332 .map(|lsb| lsb.get())
333 .unwrap_or_default()
334 }) as i32;
335 if let Some(hvar) = &self.hvar {
336 lsb += hvar
337 .lsb_delta(glyph_id, self.coords)
338 .map(|delta| delta.to_f64() as i32)
341 .unwrap_or(0);
342 } else if self.gvar.is_some() {
343 lsb += self.metric_deltas_from_gvar(glyph_id).unwrap_or_default()[0];
344 }
345 Some(self.fixed_scale.apply(lsb))
346 }
347
348 pub fn bounds(&self, glyph_id: GlyphId) -> Option<BoundingBox> {
353 if self.gvar.is_some() || self.font.cff().ok().is_some() || self.font.cff2().ok().is_some()
354 {
355 return self.bounds_from_outline(glyph_id);
356 }
357 let (loca, glyf) = self.loca_glyf.as_ref()?;
358 Some(match loca.get_glyf(glyph_id, glyf).ok()? {
359 Some(glyph) => BoundingBox {
360 x_min: self.fixed_scale.apply(glyph.x_min() as i32),
361 y_min: self.fixed_scale.apply(glyph.y_min() as i32),
362 x_max: self.fixed_scale.apply(glyph.x_max() as i32),
363 y_max: self.fixed_scale.apply(glyph.y_max() as i32),
364 },
365 None => BoundingBox::default(),
367 })
368 }
369}
370
371impl GlyphMetrics<'_> {
372 fn metric_deltas_from_gvar(&self, glyph_id: GlyphId) -> Option<[i32; 2]> {
373 let (loca, glyf) = self.loca_glyf.as_ref()?;
374 let mut deltas = self
375 .gvar
376 .as_ref()?
377 .phantom_point_deltas(glyf, loca, self.coords, glyph_id)
378 .ok()
379 .flatten()?;
380 deltas[1] -= deltas[0];
381 Some([deltas[0], deltas[1]].map(|delta| delta.x.to_i32()))
382 }
383
384 fn bounds_from_outline(&self, glyph_id: GlyphId) -> Option<BoundingBox> {
385 if let Some(outline) = self.font.outline_glyphs().get(glyph_id) {
386 let settings = DrawSettings::unhinted(self.size, self.coords);
387 let mut pen = ControlBoundsPen::default();
388 outline.draw(settings, &mut pen).ok()?;
389 pen.bounding_box()
390 } else {
391 None
392 }
393 }
394}
395
396#[derive(Copy, Clone)]
397struct FixedScaleFactor(Fixed);
398
399impl FixedScaleFactor {
400 #[inline(always)]
401 fn apply(self, value: i32) -> f32 {
402 self.0
405 .mul_div(Fixed::from_bits(value), Fixed::from_bits(64))
406 .to_f32()
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use font_test_data::{NOTO_SANS_JP_CFF, SIMPLE_GLYF, VAZIRMATN_VAR};
414 use read_fonts::FontRef;
415
416 #[test]
417 fn metrics() {
418 let font = FontRef::new(SIMPLE_GLYF).unwrap();
419 let metrics = font.metrics(Size::unscaled(), LocationRef::default());
420 let expected = Metrics {
421 units_per_em: 1024,
422 glyph_count: 3,
423 bounds: Some(BoundingBox {
424 x_min: 51.0,
425 y_min: -250.0,
426 x_max: 998.0,
427 y_max: 950.0,
428 }),
429 average_width: Some(1275.0),
430 max_width: None,
431 x_height: Some(512.0),
432 cap_height: Some(717.0),
433 is_monospace: false,
434 italic_angle: 0.0,
435 ascent: 950.0,
436 descent: -250.0,
437 leading: 0.0,
438 underline: None,
439 strikeout: Some(Decoration {
440 offset: 307.0,
441 thickness: 51.0,
442 }),
443 };
444 assert_eq!(metrics, expected);
445 }
446
447 #[test]
448 fn metrics_missing_os2() {
449 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
450 let metrics = font.metrics(Size::unscaled(), LocationRef::default());
451 let expected = Metrics {
452 units_per_em: 2048,
453 glyph_count: 4,
454 bounds: Some(BoundingBox {
455 x_min: 29.0,
456 y_min: 0.0,
457 x_max: 1310.0,
458 y_max: 1847.0,
459 }),
460 average_width: None,
461 max_width: Some(1336.0),
462 x_height: None,
463 cap_height: None,
464 is_monospace: false,
465 italic_angle: 0.0,
466 ascent: 2100.0,
467 descent: -1100.0,
468 leading: 0.0,
469 underline: None,
470 strikeout: None,
471 };
472 assert_eq!(metrics, expected);
473 }
474
475 #[test]
476 fn glyph_metrics() {
477 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
478 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
479 let expected = &[
481 (908.0, 100.0),
482 (1336.0, 29.0),
483 (1336.0, 29.0),
484 (633.0, 57.0),
485 ];
486 let result = (0..4)
487 .map(|i| {
488 let gid = GlyphId::new(i as u32);
489 let advance_width = glyph_metrics.advance_width(gid).unwrap();
490 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
491 (advance_width, lsb)
492 })
493 .collect::<Vec<_>>();
494 assert_eq!(expected, &result[..]);
495 }
496
497 #[test]
502 fn glyph_metrics_unscaled_matches_upem_scale() {
503 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
504 let upem = font.head().unwrap().units_per_em() as f32;
505 let unscaled_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
506 let upem_metrics = font.glyph_metrics(Size::new(upem), LocationRef::default());
507 for i in 0..unscaled_metrics.glyph_count() {
508 let gid = GlyphId::new(i);
509 assert_eq!(
510 unscaled_metrics.advance_width(gid),
511 upem_metrics.advance_width(gid)
512 );
513 assert_eq!(
514 unscaled_metrics.left_side_bearing(gid),
515 upem_metrics.left_side_bearing(gid)
516 );
517 }
518 }
519
520 #[test]
521 fn glyph_metrics_var() {
522 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
523 let coords = &[NormalizedCoord::from_f32(-0.8)];
524 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::new(coords));
525 let expected = &[
527 (908.0, 100.0),
528 (1246.0, 29.0),
529 (1246.0, 29.0),
530 (556.0, 57.0),
531 ];
532 let result = (0..4)
533 .map(|i| {
534 let gid = GlyphId::new(i as u32);
535 let advance_width = glyph_metrics.advance_width(gid).unwrap();
536 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
537 (advance_width, lsb)
538 })
539 .collect::<Vec<_>>();
540 assert_eq!(expected, &result[..]);
541
542 let coords = &[NormalizedCoord::from_f32(-1.0)];
544 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::new(coords));
545 let bounds = glyph_metrics.bounds(GlyphId::new(1)).unwrap();
546 assert_eq!(
547 bounds,
548 BoundingBox {
549 x_min: 33.0,
550 y_min: 0.0,
551 x_max: 1189.0,
552 y_max: 1456.0
553 }
554 );
555 }
556
557 #[test]
558 fn glyph_metrics_cff() {
559 let font = FontRef::new(NOTO_SANS_JP_CFF).unwrap();
560 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
561 let bounds = glyph_metrics.bounds(GlyphId::new(34)).unwrap();
562 assert_eq!(
563 bounds,
564 BoundingBox {
565 x_min: 4.0,
566 y_min: 0.0,
567 x_max: 604.0,
568 y_max: 733.0
569 }
570 );
571 }
572
573 #[test]
574 fn glyph_metrics_missing_hvar() {
575 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
576 let glyph_count = font.maxp().unwrap().num_glyphs();
577 for coord in [-1.0, -0.8, 0.0, 0.75, 1.0] {
579 let coords = &[NormalizedCoord::from_f32(coord)];
580 let location = LocationRef::new(coords);
581 let glyph_metrics = font.glyph_metrics(Size::unscaled(), location);
582 let mut glyph_metrics_no_hvar = glyph_metrics.clone();
583 glyph_metrics_no_hvar.hvar = None;
585 for gid in 0..glyph_count {
586 let gid = GlyphId::from(gid);
587 assert_eq!(
588 glyph_metrics.advance_width(gid),
589 glyph_metrics_no_hvar.advance_width(gid)
590 );
591 assert_eq!(
592 glyph_metrics.left_side_bearing(gid),
593 glyph_metrics_no_hvar.left_side_bearing(gid)
594 );
595 }
596 }
597 }
598
599 #[test]
603 fn match_freetype_glyph_metric_scaling() {
604 let font_unit_advances = [639, 561, 524, 258];
612 #[allow(clippy::excessive_precision)]
613 let scaled_advances = [
614 15.33595275878906250000,
615 13.46395874023437500000,
616 12.57595825195312500000,
617 6.19198608398437500000,
618 ];
619 let fixed_scale = FixedScaleFactor(Size::new(24.0).fixed_linear_scale(1000));
620 for (font_unit_advance, expected_scaled_advance) in
621 font_unit_advances.iter().zip(scaled_advances)
622 {
623 let scaled_advance = fixed_scale.apply(*font_unit_advance);
624 assert_eq!(scaled_advance, expected_scaled_advance);
625 }
626 }
627}