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 super::instance::{LocationRef, NormalizedCoord, Size};
34
35pub type BoundingBox = read_fonts::types::BoundingBox<f32>;
37
38#[derive(Copy, Clone, PartialEq, Default, Debug)]
43pub struct Decoration {
44 pub offset: f32,
46 pub thickness: f32,
48}
49
50#[derive(Copy, Clone, PartialEq, Default, Debug)]
67pub struct Metrics {
68 pub units_per_em: u16,
70 pub glyph_count: u16,
72 pub is_monospace: bool,
74 pub italic_angle: f32,
77 pub ascent: f32,
79 pub descent: f32,
81 pub leading: f32,
83 pub cap_height: Option<f32>,
85 pub x_height: Option<f32>,
88 pub average_width: Option<f32>,
90 pub max_width: Option<f32>,
92 pub underline: Option<Decoration>,
94 pub strikeout: Option<Decoration>,
96 pub bounds: Option<BoundingBox>,
98}
99
100impl Metrics {
101 pub fn new<'a>(font: &FontRef<'a>, size: Size, location: impl Into<LocationRef<'a>>) -> Self {
104 let head = font.head();
105 let mut metrics = Metrics {
106 units_per_em: head.map(|head| head.units_per_em()).unwrap_or_default(),
107 ..Default::default()
108 };
109 let coords = location.into().effective_coords();
110 let scale = size.linear_scale(metrics.units_per_em);
111 if let Ok(head) = font.head() {
112 metrics.bounds = Some(BoundingBox {
113 x_min: head.x_min() as f32 * scale,
114 y_min: head.y_min() as f32 * scale,
115 x_max: head.x_max() as f32 * scale,
116 y_max: head.y_max() as f32 * scale,
117 });
118 }
119 if let Ok(maxp) = font.maxp() {
120 metrics.glyph_count = maxp.num_glyphs();
121 }
122 if let Ok(post) = font.post() {
123 metrics.is_monospace = post.is_fixed_pitch() != 0;
124 metrics.italic_angle = post.italic_angle().to_f64() as f32;
125 metrics.underline = Some(Decoration {
126 offset: post.underline_position().to_i16() as f32 * scale,
127 thickness: post.underline_thickness().to_i16() as f32 * scale,
128 });
129 }
130 let hhea = font.hhea();
131 if let Ok(hhea) = &hhea {
132 metrics.max_width = Some(hhea.advance_width_max().to_u16() as f32 * scale);
133 }
134 let os2 = font.os2().ok();
147 let mut used_typo_metrics = false;
148 if let Some(os2) = &os2 {
149 if os2
150 .fs_selection()
151 .contains(SelectionFlags::USE_TYPO_METRICS)
152 {
153 metrics.ascent = os2.s_typo_ascender() as f32 * scale;
154 metrics.descent = os2.s_typo_descender() as f32 * scale;
155 metrics.leading = os2.s_typo_line_gap() as f32 * scale;
156 used_typo_metrics = true;
157 }
158 metrics.average_width = Some(os2.x_avg_char_width() as f32 * scale);
159 metrics.cap_height = os2.s_cap_height().map(|v| v as f32 * scale);
160 metrics.x_height = os2.sx_height().map(|v| v as f32 * scale);
161 metrics.strikeout = Some(Decoration {
162 offset: os2.y_strikeout_position() as f32 * scale,
163 thickness: os2.y_strikeout_size() as f32 * scale,
164 });
165 }
166 if !used_typo_metrics {
167 if let Ok(hhea) = font.hhea() {
168 metrics.ascent = hhea.ascender().to_i16() as f32 * scale;
169 metrics.descent = hhea.descender().to_i16() as f32 * scale;
170 metrics.leading = hhea.line_gap().to_i16() as f32 * scale;
171 }
172 if metrics.ascent == 0.0 && metrics.descent == 0.0 {
173 if let Some(os2) = &os2 {
174 if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 {
175 metrics.ascent = os2.s_typo_ascender() as f32 * scale;
176 metrics.descent = os2.s_typo_descender() as f32 * scale;
177 metrics.leading = os2.s_typo_line_gap() as f32 * scale;
178 } else {
179 metrics.ascent = os2.us_win_ascent() as f32 * scale;
180 metrics.descent = -(os2.us_win_descent() as f32 * scale);
183 }
184 }
185 }
186 }
187 if let (Ok(mvar), true) = (font.mvar(), !coords.is_empty()) {
188 use read_fonts::tables::mvar::tags::*;
189 let metric_delta =
190 |tag| mvar.metric_delta(tag, coords).unwrap_or_default().to_f64() as f32 * scale;
191 metrics.ascent += metric_delta(HASC);
192 metrics.descent += metric_delta(HDSC);
193 metrics.leading += metric_delta(HLGP);
194 if let Some(cap_height) = &mut metrics.cap_height {
195 *cap_height += metric_delta(CPHT);
196 }
197 if let Some(x_height) = &mut metrics.x_height {
198 *x_height += metric_delta(XHGT);
199 }
200 if let Some(underline) = &mut metrics.underline {
201 underline.offset += metric_delta(UNDO);
202 underline.thickness += metric_delta(UNDS);
203 }
204 if let Some(strikeout) = &mut metrics.strikeout {
205 strikeout.offset += metric_delta(STRO);
206 strikeout.thickness += metric_delta(STRS);
207 }
208 }
209 metrics
210 }
211}
212
213#[derive(Clone)]
215pub struct GlyphMetrics<'a> {
216 glyph_count: u32,
217 fixed_scale: FixedScaleFactor,
218 h_metrics: &'a [LongMetric],
219 default_advance_width: u16,
220 lsbs: &'a [BigEndian<i16>],
221 hvar: Option<Hvar<'a>>,
222 gvar: Option<Gvar<'a>>,
223 loca_glyf: Option<(Loca<'a>, Glyf<'a>)>,
224 coords: &'a [NormalizedCoord],
225}
226
227impl<'a> GlyphMetrics<'a> {
228 pub fn new(font: &FontRef<'a>, size: Size, location: impl Into<LocationRef<'a>>) -> Self {
231 let glyph_count = font
232 .maxp()
233 .map(|maxp| maxp.num_glyphs() as u32)
234 .unwrap_or_default();
235 let upem = font
236 .head()
237 .map(|head| head.units_per_em())
238 .unwrap_or_default();
239 let fixed_scale = FixedScaleFactor(size.fixed_linear_scale(upem));
240 let coords = location.into().effective_coords();
241 let (h_metrics, default_advance_width, lsbs) = font
242 .hmtx()
243 .map(|hmtx| {
244 let h_metrics = hmtx.h_metrics();
245 let default_advance_width = h_metrics.last().map(|m| m.advance.get()).unwrap_or(0);
246 let lsbs = hmtx.left_side_bearings();
247 (h_metrics, default_advance_width, lsbs)
248 })
249 .unwrap_or_default();
250 let hvar = font.hvar().ok();
251 let gvar = font.gvar().ok();
252 let loca_glyf = if let (Ok(loca), Ok(glyf)) = (font.loca(None), font.glyf()) {
253 Some((loca, glyf))
254 } else {
255 None
256 };
257 Self {
258 glyph_count,
259 fixed_scale,
260 h_metrics,
261 default_advance_width,
262 lsbs,
263 hvar,
264 gvar,
265 loca_glyf,
266 coords,
267 }
268 }
269
270 pub fn glyph_count(&self) -> u32 {
272 self.glyph_count
273 }
274
275 pub fn advance_width(&self, glyph_id: GlyphId) -> Option<f32> {
283 if glyph_id.to_u32() >= self.glyph_count {
284 return None;
285 }
286 let mut advance = self
287 .h_metrics
288 .get(glyph_id.to_u32() as usize)
289 .map(|metric| metric.advance())
290 .unwrap_or(self.default_advance_width) as i32;
291 if let Some(hvar) = &self.hvar {
292 advance += hvar
293 .advance_width_delta(glyph_id, self.coords)
294 .map(|delta| delta.to_f64() as i32)
297 .unwrap_or(0);
298 } else if self.gvar.is_some() {
299 advance += self.metric_deltas_from_gvar(glyph_id).unwrap_or_default()[1];
300 }
301 Some(self.fixed_scale.apply(advance))
302 }
303
304 pub fn left_side_bearing(&self, glyph_id: GlyphId) -> Option<f32> {
312 if glyph_id.to_u32() >= self.glyph_count {
313 return None;
314 }
315 let gid_index = glyph_id.to_u32() as usize;
316 let mut lsb = self
317 .h_metrics
318 .get(gid_index)
319 .map(|metric| metric.side_bearing())
320 .unwrap_or_else(|| {
321 self.lsbs
322 .get(gid_index.saturating_sub(self.h_metrics.len()))
323 .map(|lsb| lsb.get())
324 .unwrap_or_default()
325 }) as i32;
326 if let Some(hvar) = &self.hvar {
327 lsb += hvar
328 .lsb_delta(glyph_id, self.coords)
329 .map(|delta| delta.to_f64() as i32)
332 .unwrap_or(0);
333 } else if self.gvar.is_some() {
334 lsb += self.metric_deltas_from_gvar(glyph_id).unwrap_or_default()[0];
335 }
336 Some(self.fixed_scale.apply(lsb))
337 }
338
339 pub fn bounds(&self, glyph_id: GlyphId) -> Option<BoundingBox> {
347 let (loca, glyf) = self.loca_glyf.as_ref()?;
348 Some(match loca.get_glyf(glyph_id, glyf).ok()? {
349 Some(glyph) => BoundingBox {
350 x_min: self.fixed_scale.apply(glyph.x_min() as i32),
351 y_min: self.fixed_scale.apply(glyph.y_min() as i32),
352 x_max: self.fixed_scale.apply(glyph.x_max() as i32),
353 y_max: self.fixed_scale.apply(glyph.y_max() as i32),
354 },
355 None => BoundingBox::default(),
357 })
358 }
359}
360
361impl GlyphMetrics<'_> {
362 fn metric_deltas_from_gvar(&self, glyph_id: GlyphId) -> Option<[i32; 2]> {
363 let (loca, glyf) = self.loca_glyf.as_ref()?;
364 let mut deltas = self
365 .gvar
366 .as_ref()?
367 .phantom_point_deltas(glyf, loca, self.coords, glyph_id)
368 .ok()
369 .flatten()?;
370 deltas[1] -= deltas[0];
371 Some([deltas[0], deltas[1]].map(|delta| delta.x.to_i32()))
372 }
373}
374
375#[derive(Copy, Clone)]
376struct FixedScaleFactor(Fixed);
377
378impl FixedScaleFactor {
379 #[inline(always)]
380 fn apply(self, value: i32) -> f32 {
381 self.0
384 .mul_div(Fixed::from_bits(value), Fixed::from_bits(64))
385 .to_f32()
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::MetadataProvider as _;
393 use font_test_data::{SIMPLE_GLYF, VAZIRMATN_VAR};
394 use read_fonts::FontRef;
395
396 #[test]
397 fn metrics() {
398 let font = FontRef::new(SIMPLE_GLYF).unwrap();
399 let metrics = font.metrics(Size::unscaled(), LocationRef::default());
400 let expected = Metrics {
401 units_per_em: 1024,
402 glyph_count: 3,
403 bounds: Some(BoundingBox {
404 x_min: 51.0,
405 y_min: -250.0,
406 x_max: 998.0,
407 y_max: 950.0,
408 }),
409 average_width: Some(1275.0),
410 max_width: None,
411 x_height: Some(512.0),
412 cap_height: Some(717.0),
413 is_monospace: false,
414 italic_angle: 0.0,
415 ascent: 950.0,
416 descent: -250.0,
417 leading: 0.0,
418 underline: None,
419 strikeout: Some(Decoration {
420 offset: 307.0,
421 thickness: 51.0,
422 }),
423 };
424 assert_eq!(metrics, expected);
425 }
426
427 #[test]
428 fn metrics_missing_os2() {
429 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
430 let metrics = font.metrics(Size::unscaled(), LocationRef::default());
431 let expected = Metrics {
432 units_per_em: 2048,
433 glyph_count: 4,
434 bounds: Some(BoundingBox {
435 x_min: 29.0,
436 y_min: 0.0,
437 x_max: 1310.0,
438 y_max: 1847.0,
439 }),
440 average_width: None,
441 max_width: Some(1336.0),
442 x_height: None,
443 cap_height: None,
444 is_monospace: false,
445 italic_angle: 0.0,
446 ascent: 2100.0,
447 descent: -1100.0,
448 leading: 0.0,
449 underline: None,
450 strikeout: None,
451 };
452 assert_eq!(metrics, expected);
453 }
454
455 #[test]
456 fn glyph_metrics() {
457 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
458 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
459 let expected = &[
461 (908.0, 100.0),
462 (1336.0, 29.0),
463 (1336.0, 29.0),
464 (633.0, 57.0),
465 ];
466 let result = (0..4)
467 .map(|i| {
468 let gid = GlyphId::new(i as u32);
469 let advance_width = glyph_metrics.advance_width(gid).unwrap();
470 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
471 (advance_width, lsb)
472 })
473 .collect::<Vec<_>>();
474 assert_eq!(expected, &result[..]);
475 }
476
477 #[test]
482 fn glyph_metrics_unscaled_matches_upem_scale() {
483 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
484 let upem = font.head().unwrap().units_per_em() as f32;
485 let unscaled_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::default());
486 let upem_metrics = font.glyph_metrics(Size::new(upem), LocationRef::default());
487 for i in 0..unscaled_metrics.glyph_count() {
488 let gid = GlyphId::new(i);
489 assert_eq!(
490 unscaled_metrics.advance_width(gid),
491 upem_metrics.advance_width(gid)
492 );
493 assert_eq!(
494 unscaled_metrics.left_side_bearing(gid),
495 upem_metrics.left_side_bearing(gid)
496 );
497 }
498 }
499
500 #[test]
501 fn glyph_metrics_var() {
502 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
503 let coords = &[NormalizedCoord::from_f32(-0.8)];
504 let glyph_metrics = font.glyph_metrics(Size::unscaled(), LocationRef::new(coords));
505 let expected = &[
507 (908.0, 100.0),
508 (1246.0, 29.0),
509 (1246.0, 29.0),
510 (556.0, 57.0),
511 ];
512 let result = (0..4)
513 .map(|i| {
514 let gid = GlyphId::new(i as u32);
515 let advance_width = glyph_metrics.advance_width(gid).unwrap();
516 let lsb = glyph_metrics.left_side_bearing(gid).unwrap();
517 (advance_width, lsb)
518 })
519 .collect::<Vec<_>>();
520 assert_eq!(expected, &result[..]);
521 }
522
523 #[test]
524 fn glyph_metrics_missing_hvar() {
525 let font = FontRef::new(VAZIRMATN_VAR).unwrap();
526 let glyph_count = font.maxp().unwrap().num_glyphs();
527 for coord in [-1.0, -0.8, 0.0, 0.75, 1.0] {
529 let coords = &[NormalizedCoord::from_f32(coord)];
530 let location = LocationRef::new(coords);
531 let glyph_metrics = font.glyph_metrics(Size::unscaled(), location);
532 let mut glyph_metrics_no_hvar = glyph_metrics.clone();
533 glyph_metrics_no_hvar.hvar = None;
535 for gid in 0..glyph_count {
536 let gid = GlyphId::from(gid);
537 assert_eq!(
538 glyph_metrics.advance_width(gid),
539 glyph_metrics_no_hvar.advance_width(gid)
540 );
541 assert_eq!(
542 glyph_metrics.left_side_bearing(gid),
543 glyph_metrics_no_hvar.left_side_bearing(gid)
544 );
545 }
546 }
547 }
548
549 #[test]
553 fn match_freetype_glyph_metric_scaling() {
554 let font_unit_advances = [639, 561, 524, 258];
562 #[allow(clippy::excessive_precision)]
563 let scaled_advances = [
564 15.33595275878906250000,
565 13.46395874023437500000,
566 12.57595825195312500000,
567 6.19198608398437500000,
568 ];
569 let fixed_scale = FixedScaleFactor(Size::new(24.0).fixed_linear_scale(1000));
570 for (font_unit_advance, expected_scaled_advance) in
571 font_unit_advances.iter().zip(scaled_advances)
572 {
573 let scaled_advance = fixed_scale.apply(*font_unit_advance);
574 assert_eq!(scaled_advance, expected_scaled_advance);
575 }
576 }
577}