1#[cfg(feature = "libm")]
4#[allow(unused_imports)]
5use core_maths::CoreFloat;
6
7use crate::tables::glyf::outline::{
8 Buffers, OutlineContext, OutlinePlan, ScaleF32 as OutlineScaleF32,
9};
10use crate::{
11 mem::with_scratch,
12 model::{
13 glyph::{
14 bitmap::BitmapStrikes,
15 color::ColorGlyphs,
16 outline::{OutlineGlyphs, OutlineSource},
17 GlyphSource, GlyphSourceOrder,
18 },
19 metrics::{LineExtents, Metrics, Scale, Scale26Dot6, ScaleF32, ScaledGlyphMetrics},
20 Font, Kind,
21 },
22 ps::{cff::CffFontRef, charstring::CommandSink, type1::Type1Instance},
23 tables::{glyf::Glyf, hmtx::GlyphMetricRecords, loca::Loca},
24};
25use types::{BoundingBox, F2Dot14, F48Dot16, Fixed, GlyphId, Point};
26
27#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
32pub struct GlyphExtents<T> {
33 pub x_bearing: T,
35 pub y_bearing: T,
37 pub width: T,
39 pub height: T,
43}
44
45impl<T> From<BoundingBox<T>> for GlyphExtents<T>
46where
47 T: Copy + PartialOrd + core::ops::Sub<Output = T>,
48{
49 fn from(bounds: BoundingBox<T>) -> Self {
54 let (left, right) = if bounds.x_min <= bounds.x_max {
55 (bounds.x_min, bounds.x_max)
56 } else {
57 (bounds.x_max, bounds.x_min)
58 };
59 let (bottom, top) = if bounds.y_min <= bounds.y_max {
60 (bounds.y_min, bounds.y_max)
61 } else {
62 (bounds.y_max, bounds.y_min)
63 };
64 Self {
65 x_bearing: left,
66 y_bearing: top,
67 width: right - left,
68 height: top - bottom,
69 }
70 }
71}
72
73#[derive(Clone, Copy)]
77pub struct GlyphMetrics<'a> {
78 h_metrics: &'a GlyphMetricRecords<'a>,
79 font: &'a Font,
80 global: &'a Metrics,
81 coords: &'a [F2Dot14],
82 num_glyphs: u32,
83 units_per_em: u16,
84 order: GlyphSourceOrder,
85 bitmap_ppem: Option<f32>,
86}
87
88impl<'a> GlyphMetrics<'a> {
89 #[inline]
91 pub(crate) fn new(font: &'a Font, global: &'a Metrics, coords: &'a [F2Dot14]) -> Self {
92 Self {
93 h_metrics: font.h_metric_records(),
94 font,
95 global,
96 coords,
97 num_glyphs: global.num_glyphs,
98 units_per_em: global.units_per_em,
99 order: GlyphSourceOrder::DEFAULT,
100 bitmap_ppem: None,
101 }
102 }
103
104 #[inline]
106 pub fn scaled<S: Scale>(self, scale: S) -> ScaledGlyphMetrics<'a, 'static, S> {
107 ScaledGlyphMetrics::new(self, scale)
108 }
109
110 #[inline]
112 pub fn scaled_f32(self, ppem: f32) -> ScaledGlyphMetrics<'a, 'static, ScaleF32> {
113 let units_per_em = self.units_per_em;
114 self.scaled(ScaleF32::from_ppem(ppem, units_per_em))
115 }
116
117 #[inline]
119 pub fn scaled_26dot6(self, ppem: f32) -> ScaledGlyphMetrics<'a, 'static, Scale26Dot6> {
120 let units_per_em = self.units_per_em;
121 self.scaled(Scale26Dot6::from_ppem(ppem, units_per_em))
122 }
123
124 pub fn with_extents_source_order(self, order: GlyphSourceOrder) -> Self {
130 Self { order, ..self }
131 }
132
133 pub(crate) fn with_bitmap_ppem(self, ppem: Option<f32>) -> Self {
141 Self {
142 bitmap_ppem: ppem,
143 ..self
144 }
145 }
146
147 pub fn extents_source_order(&self) -> GlyphSourceOrder {
149 self.order
150 }
151
152 #[inline]
157 pub fn h_advance(&self, glyph: GlyphId) -> f32 {
158 self.h_advance_exact(glyph).to_f32()
159 }
160
161 #[inline]
167 pub fn h_advance_exact(&self, glyph: GlyphId) -> F48Dot16 {
168 let mut width = F48Dot16::ZERO;
169 self.h_advance_batched(|value| value, core::iter::once((glyph, &mut width)));
170 width
171 }
172
173 #[inline]
178 pub fn h_advance_batched<'o, V: 'o>(
179 &self,
180 convert: impl Fn(F48Dot16) -> V,
181 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
182 ) {
183 let raw = self.h_metrics;
184 if raw.is_empty() {
185 match self.font.kind() {
190 Kind::Type1(_) => {
191 if let Some(font) = self.font.type1_instance() {
192 return self.h_advance_batched_type1(font, convert, glyphs);
193 }
194 }
195 Kind::Cff(cff, _) => {
196 return self.h_advance_batched_standalone_cff(&cff, convert, glyphs)
197 }
198 _ => {}
199 }
200 let half_em = F48Dot16::from_i32(self.units_per_em as i32 / 2);
203 for (_, out) in glyphs {
204 *out = convert(half_em);
205 }
206 return;
207 }
208 let coords = self.coords;
209 if coords.is_empty() {
210 return raw.run(self.num_glyphs, convert, glyphs);
211 }
212 self.h_advance_batched_varied(raw, coords, convert, glyphs)
213 }
214
215 #[inline]
220 pub fn v_advance(&self, glyph: GlyphId) -> f32 {
221 self.v_advance_exact(glyph).to_f32()
222 }
223
224 #[inline]
230 pub fn v_advance_exact(&self, glyph: GlyphId) -> F48Dot16 {
231 let mut height = F48Dot16::ZERO;
232 self.v_advance_batched(|value| value, core::iter::once((glyph, &mut height)));
233 height
234 }
235
236 #[inline]
241 pub fn v_advance_batched<'o, V: 'o>(
242 &self,
243 convert: impl Fn(F48Dot16) -> V,
244 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
245 ) {
246 let raw = self.font.v_metric_records();
247 if raw.is_empty() {
248 let height = self.line_height();
252 for (_, out) in glyphs {
253 *out = convert(height);
254 }
255 return;
256 }
257 let coords = self.coords;
258 if coords.is_empty() {
259 return raw.run(self.num_glyphs, convert, glyphs);
260 }
261 self.v_advance_batched_varied(raw, coords, convert, glyphs)
262 }
263
264 #[inline]
270 pub fn v_origin_y(&self, glyph: GlyphId) -> f32 {
271 self.v_origin_y_exact(glyph).to_f32()
272 }
273
274 #[inline]
276 pub fn v_origin_y_exact(&self, glyph: GlyphId) -> F48Dot16 {
277 let mut origin = F48Dot16::ZERO;
278 self.v_origin_y_batched(|value| value, core::iter::once((glyph, &mut origin)));
279 origin
280 }
281
282 pub fn v_origin_y_batched<'o, V: 'o>(
288 &self,
289 convert: impl Fn(F48Dot16) -> V,
290 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
291 ) {
292 let mut line = None;
293 self.v_origin_y_batched_with(
294 |glyph, origin| {
295 convert(origin.unwrap_or_else(|| {
296 self.origin_against_ink(glyph, *line.get_or_insert_with(|| self.origin_line()))
297 }))
298 },
299 |glyphs| {
300 let line = self.origin_line();
301 self.extents_batched(
302 |extents| convert(Self::origin_from_extents(extents, line)),
303 glyphs,
304 );
305 },
306 glyphs,
307 );
308 }
309
310 #[inline]
320 pub fn extents(&self, glyph: GlyphId) -> Option<GlyphExtents<f32>> {
321 self.extents_exact(glyph).map(|extents| GlyphExtents {
322 x_bearing: extents.x_bearing.to_f32(),
323 y_bearing: extents.y_bearing.to_f32(),
324 width: extents.width.to_f32(),
325 height: extents.height.to_f32(),
326 })
327 }
328
329 #[inline]
334 pub fn extents_exact(&self, glyph: GlyphId) -> Option<GlyphExtents<F48Dot16>> {
335 let mut extents = None;
336 self.extents_batched(|value| value, core::iter::once((glyph, &mut extents)));
337 extents
338 }
339
340 pub fn extents_batched<'o, V: 'o>(
349 &self,
350 convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
351 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
352 ) {
353 if let Some(base) = self.only_base_source() {
356 return self.extents_batched_one(base, convert, glyphs);
357 }
358 let sources = ExtentsSources::new(self);
359 if let Some(only) = sources.only() {
360 return self.extents_batched_one(only, convert, glyphs);
361 }
362 if sources.is_empty() {
363 for (_, out) in glyphs {
365 *out = convert(None);
366 }
367 return;
368 }
369 self.extents_batched_mixed(&sources, convert, glyphs);
370 }
371
372 fn only_base_source(&self) -> Option<ExtentsSource<'a>> {
379 let font = self.font;
380 if font.has_layered_source_in(self.order) {
381 return None;
382 }
383 if let Some(type1) = font.type1_instance() {
384 return self
385 .order
386 .contains(GlyphSource::Type1)
387 .then_some(ExtentsSource::Type1(type1));
388 }
389 self.order.iter().find_map(|source| match source {
390 GlyphSource::Glyf => font
391 .selected_glyf_loca()
392 .map(|(glyf, loca)| ExtentsSource::Glyf(glyf, loca)),
393 GlyphSource::Cff2 => font
394 .charstrings(OutlineSource::Cff2)
395 .map(|cff| ExtentsSource::Cff(cff, OutlineSource::Cff2)),
396 GlyphSource::Cff => font
397 .charstrings(OutlineSource::Cff)
398 .map(|cff| ExtentsSource::Cff(cff, OutlineSource::Cff)),
399 _ => None,
400 })
401 }
402
403 fn extents_batched_one<'o, V: 'o>(
405 &self,
406 source: ExtentsSource<'_>,
407 convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
408 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
409 ) {
410 match source {
411 ExtentsSource::Glyf(glyf, loca) => {
412 if self.coords.is_empty() {
413 for (glyph, out) in glyphs {
414 *out = convert(self.stated_glyf_extents(glyf, loca, glyph));
415 }
416 } else {
417 self.extents_batched_glyf_varied(convert, glyphs);
418 }
419 }
420 ExtentsSource::Cff(cff, source) => {
421 self.extents_batched_cff(cff, source, convert, glyphs)
422 }
423 ExtentsSource::Type1(font) => self.extents_batched_type1(font, convert, glyphs),
424 source => {
425 for (glyph, out) in glyphs {
426 *out = convert(self.extents_from_source(&source, glyph));
427 }
428 }
429 }
430 }
431
432 fn stated_glyf_extents(
438 &self,
439 glyf: &Glyf<'_>,
440 loca: &Loca<'_>,
441 glyph: GlyphId,
442 ) -> Option<GlyphExtents<F48Dot16>> {
443 if glyph.to_u32() >= self.num_glyphs {
444 return None;
445 }
446 let outline = loca.get_glyf(glyph, glyf).ok()?;
447 let Some(outline) = outline else {
448 return Some(GlyphExtents::default());
449 };
450 if outline.number_of_contours() == 0 {
451 return Some(GlyphExtents::default());
452 }
453 let mut extents = extents_from_corners(
454 F48Dot16::from_i32(outline.x_min() as i32),
455 F48Dot16::from_i32(outline.y_min() as i32),
456 F48Dot16::from_i32(outline.x_max() as i32),
457 F48Dot16::from_i32(outline.y_max() as i32),
458 );
459 extents.x_bearing = self
460 .h_metrics
461 .stated_side_bearing(glyph)
462 .unwrap_or(F48Dot16::ZERO);
463 Some(extents)
464 }
465
466 fn cff_extents(
468 &self,
469 cff: &CffFontRef<'_>,
470 source: OutlineSource,
471 glyph: GlyphId,
472 ) -> Option<GlyphExtents<F48Dot16>> {
473 let index = cff.subfont_index(glyph)?;
474 let subfont = self.font.cff_subfont(source, index)?;
475 let bounds = cff.evaluate_extents(&subfont, glyph, self.coords).ok()?;
476 Some(bounds.map(extents_from_bounds).unwrap_or_default())
477 }
478
479 fn type1_extents(
481 &self,
482 font: Type1Instance<'_>,
483 glyph: GlyphId,
484 ) -> Option<GlyphExtents<F48Dot16>> {
485 let bounds = font.evaluate_extents(glyph).ok()?;
486 Some(bounds.map(extents_from_bounds).unwrap_or_default())
487 }
488
489 fn extents_from_source(
492 &self,
493 source: &ExtentsSource<'_>,
494 glyph: GlyphId,
495 ) -> Option<GlyphExtents<F48Dot16>> {
496 match *source {
497 ExtentsSource::Bitmap(strikes) => {
498 let bitmap = strikes.select(self.bitmap_ppem)?.get(glyph)?;
502 Some(exact_extents(bitmap.extents()))
503 }
504 ExtentsSource::Color(colors, outlines) => {
505 colors.extents(glyph, outlines).map(exact_extents)
506 }
507 ExtentsSource::Varc(outlines) => {
508 if !outlines.varc_covers(glyph) {
511 return None;
512 }
513 Some(exact_extents(outlines.get(glyph)?.extents().ok()?))
514 }
515 ExtentsSource::Glyf(glyf, loca) => {
516 if self.coords.is_empty() {
517 return self.stated_glyf_extents(glyf, loca, glyph);
518 }
519 if glyph.to_u32() >= self.num_glyphs {
520 return None;
521 }
522 let context = self.font;
523 varied_control_box(&context, glyph)
524 }
525 ExtentsSource::Cff(cff, source) => self.cff_extents(cff, source, glyph),
526 ExtentsSource::Type1(font) => self.type1_extents(font, glyph),
527 }
528 }
529
530 #[inline(never)]
537 fn extents_batched_mixed<'o, V: 'o>(
538 &self,
539 sources: &ExtentsSources<'_>,
540 convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
541 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
542 ) {
543 for (glyph, out) in glyphs {
544 let extents = if glyph.to_u32() < self.num_glyphs {
545 sources
546 .iter()
547 .find_map(|source| self.extents_from_source(source, glyph))
548 } else {
549 None
550 };
551 *out = convert(extents);
552 }
553 }
554
555 #[inline]
557 pub(crate) fn line_extents(&self) -> Option<LineExtents<F48Dot16>> {
558 self.global.h_line().map(|line| line.extents())
559 }
560
561 #[cfg(test)]
566 pub(crate) fn stated_v_origin(&self, glyph: GlyphId) -> Option<F48Dot16> {
567 let vorg = self.font.vorg()?;
568 let origin = F48Dot16::from_i32(vorg.vertical_origin_y(glyph) as i32);
569 let delta = if self.coords.is_empty() {
570 F48Dot16::ZERO
571 } else {
572 self.font
573 .vvar()
574 .and_then(|vvar| {
575 vvar.v_origin_y_delta_with_scalars(glyph, self.coords, self.font.vvar_scalars())
576 })
577 .unwrap_or(F48Dot16::ZERO)
578 };
579 Some(origin.saturating_add(delta))
580 }
581
582 #[cfg(test)]
586 pub(crate) fn stated_v_bearing(&self, glyph: GlyphId) -> Option<F48Dot16> {
587 self.font.v_metric_records().stated_side_bearing(glyph)
588 }
589
590 #[cfg(test)]
593 pub(crate) fn glyf_v_origin(&self, glyph: GlyphId) -> Option<F48Dot16> {
594 if self.font.v_metric_records().is_empty() || self.font.selected_glyf_loca().is_none() {
595 return None;
596 }
597 let context = self.font;
598 glyf_v_origin_from_context(
599 &context,
600 &OutlineScaleF32::new(None, self.units_per_em),
601 glyph,
602 )
603 }
604
605 pub(crate) fn v_origin_y_batched_with<'o, V: 'o, I>(
610 &self,
611 mut convert: impl FnMut(GlyphId, Option<F48Dot16>) -> V,
612 fallback: impl FnOnce(I),
613 glyphs: I,
614 ) where
615 I: Iterator<Item = (GlyphId, &'o mut V)>,
616 {
617 if let Some(vorg) = self.font.vorg() {
618 let vvar = if self.coords.is_empty() {
619 None
620 } else {
621 self.font.vvar()
622 };
623 let scalars = if vvar.is_some() {
624 self.font.vvar_scalars()
625 } else {
626 &[]
627 };
628 for (glyph, out) in glyphs {
629 let origin = F48Dot16::from_i32(vorg.vertical_origin_y(glyph) as i32);
630 let delta = vvar
631 .and_then(|vvar| {
632 vvar.v_origin_y_delta_with_scalars(glyph, self.coords, scalars)
633 })
634 .unwrap_or(F48Dot16::ZERO);
635 *out = convert(glyph, Some(origin.saturating_add(delta)));
636 }
637 return;
638 }
639 if !self.font.v_metric_records().is_empty() && self.font.selected_glyf_loca().is_some() {
640 let context = self.font;
641 let scale = OutlineScaleF32::new(None, self.units_per_em);
642 for (glyph, out) in glyphs {
643 *out = convert(glyph, glyf_v_origin_from_context(&context, &scale, glyph));
644 }
645 return;
646 }
647 fallback(glyphs);
648 }
649
650 #[inline]
655 pub(crate) fn states_vertical_advances(&self) -> bool {
656 !self.font.v_metric_records().is_empty()
657 }
658
659 pub(crate) fn origin_line(&self) -> LineExtents<F48Dot16> {
660 self.line_extents().unwrap_or_else(|| {
661 let em = self.units_per_em as f64;
662 let ascender = F48Dot16::from_f64(em * 0.8);
663 LineExtents {
664 ascender,
665 descender: ascender - F48Dot16::from_i32(self.units_per_em as i32),
666 }
667 })
668 }
669
670 fn origin_against_ink(&self, glyph: GlyphId, line: LineExtents<F48Dot16>) -> F48Dot16 {
671 Self::origin_from_extents(self.extents_exact(glyph), line)
672 }
673
674 fn origin_from_extents(
675 extents: Option<GlyphExtents<F48Dot16>>,
676 line: LineExtents<F48Dot16>,
677 ) -> F48Dot16 {
678 let Some(extents) = extents else {
679 return line.ascender;
680 };
681 let height = line.ascender - line.descender;
682 extents.y_bearing + F48Dot16::from_bits((height - extents.height).to_bits() >> 1)
683 }
684
685 #[inline(never)]
694 fn extents_batched_glyf_varied<'o, V: 'o>(
695 &self,
696 convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
697 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
698 ) {
699 let context = self.font;
700 for (glyph, out) in glyphs {
701 *out = convert(
702 (glyph.to_u32() < self.num_glyphs)
703 .then(|| varied_control_box(&context, glyph))
704 .flatten(),
705 );
706 }
707 }
708
709 #[inline(never)]
714 fn extents_batched_cff<'o, V: 'o>(
715 &self,
716 cff: &CffFontRef<'_>,
717 source: OutlineSource,
718 convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
719 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
720 ) {
721 for (glyph, out) in glyphs {
722 *out = convert(self.cff_extents(cff, source, glyph));
723 }
724 }
725
726 #[inline(never)]
731 fn extents_batched_type1<'o, V: 'o>(
732 &self,
733 font: Type1Instance<'_>,
734 convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
735 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
736 ) {
737 for (glyph, out) in glyphs {
738 *out = convert(self.type1_extents(font, glyph));
739 }
740 }
741
742 fn line_height(&self) -> F48Dot16 {
748 match self.global.h_line() {
749 Some(line) => line.ascender - line.descender,
750 None => F48Dot16::from_i32(self.units_per_em as i32),
751 }
752 }
753
754 #[inline(never)]
758 fn v_advance_batched_varied<'o, V: 'o>(
759 &self,
760 raw: &GlyphMetricRecords<'_>,
761 coords: &[F2Dot14],
762 convert: impl Fn(F48Dot16) -> V,
763 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
764 ) {
765 if let Some(vvar) = self.font.vvar() {
767 let scalars = self.font.vvar_scalars();
768 return raw.run_varied(
769 self.num_glyphs,
770 |gid| {
771 vvar.advance_delta_with_scalars(gid, coords, scalars)
772 .unwrap_or(F48Dot16::ZERO)
773 },
774 convert,
775 glyphs,
776 );
777 }
778 if let (Some(gvar), Some((glyf, loca))) =
782 (self.font.selected_gvar(), self.font.selected_glyf_loca())
783 {
784 let scalars = self.font.gvar_scalars();
785 return raw.run_varied(
786 self.num_glyphs,
787 |gid| {
788 gvar.phantom_point_deltas_with_scalars(glyf, loca, coords, scalars, gid)
791 .map_or(F48Dot16::ZERO, |deltas| {
792 (deltas[2].y - deltas[3].y).to_f48dot16()
793 })
794 },
795 convert,
796 glyphs,
797 );
798 }
799 raw.run(self.num_glyphs, convert, glyphs)
801 }
802
803 #[inline(never)]
809 fn h_advance_batched_varied<'o, V: 'o>(
810 &self,
811 raw: &GlyphMetricRecords<'_>,
812 coords: &[F2Dot14],
813 convert: impl Fn(F48Dot16) -> V,
814 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
815 ) {
816 if let Some(hvar) = self.font.hvar() {
818 let scalars = self.font.hvar_scalars();
819 return raw.run_varied(
820 self.num_glyphs,
821 |gid| {
822 hvar.advance_delta_with_scalars(gid, coords, scalars)
823 .unwrap_or(F48Dot16::ZERO)
824 },
825 convert,
826 glyphs,
827 );
828 }
829 if let (Some(gvar), Some((glyf, loca))) =
833 (self.font.selected_gvar(), self.font.selected_glyf_loca())
834 {
835 let scalars = self.font.gvar_scalars();
836 return raw.run_varied(
837 self.num_glyphs,
838 |gid| {
839 gvar.phantom_point_deltas_with_scalars(glyf, loca, coords, scalars, gid)
842 .map_or(F48Dot16::ZERO, |deltas| {
843 (deltas[1].x - deltas[0].x).to_f48dot16()
844 })
845 },
846 convert,
847 glyphs,
848 );
849 }
850 raw.run(self.num_glyphs, convert, glyphs)
852 }
853
854 #[inline(never)]
856 fn h_advance_batched_standalone_cff<'o, V: 'o>(
857 &self,
858 cff: &CffFontRef<'_>,
859 convert: impl Fn(F48Dot16) -> V,
860 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
861 ) {
862 for (glyph, out) in glyphs {
863 let width = (glyph.to_u32() < self.num_glyphs)
864 .then(|| cff.subfont_index(glyph))
865 .flatten()
866 .and_then(|index| self.font.cff_subfont(OutlineSource::Cff, index))
867 .and_then(|subfont| {
868 cff.evaluate_width(&subfont, glyph, self.coords)
869 .ok()
870 .flatten()
871 })
872 .map(|width| width.to_f48dot16())
873 .unwrap_or(F48Dot16::ZERO)
874 .max(F48Dot16::ZERO);
875 *out = convert(width);
876 }
877 }
878
879 #[inline(never)]
885 fn h_advance_batched_type1<'o, V: 'o>(
886 &self,
887 font: Type1Instance<'_>,
888 convert: impl Fn(F48Dot16) -> V,
889 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
890 ) {
891 struct WidthOnly;
893
894 impl CommandSink for WidthOnly {
895 fn move_to(&mut self, _x: Fixed, _y: Fixed) {}
896 fn line_to(&mut self, _x: Fixed, _y: Fixed) {}
897 fn curve_to(
898 &mut self,
899 _cx0: Fixed,
900 _cy0: Fixed,
901 _cx1: Fixed,
902 _cy1: Fixed,
903 _x: Fixed,
904 _y: Fixed,
905 ) {
906 }
907 fn close(&mut self) {}
908 }
909
910 let transform = font.font().transform(None);
914 for (gid, out) in glyphs {
915 let width = font
918 .evaluate_charstring(gid, &mut WidthOnly)
919 .ok()
920 .flatten()
921 .map(|width| transform.transform_h_metric(width).to_f48dot16())
922 .unwrap_or(F48Dot16::ZERO)
923 .max(F48Dot16::ZERO);
924 *out = convert(width);
925 }
926 }
927}
928
929impl<'a> GlyphMetricRecords<'a> {
930 #[inline]
932 pub(crate) fn is_empty(&self) -> bool {
933 self.metrics.is_empty()
934 }
935
936 #[inline]
941 fn run<'o, V: 'o>(
942 &self,
943 num_glyphs: u32,
944 convert: impl Fn(F48Dot16) -> V,
945 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
946 ) {
947 for (gid, out) in glyphs {
948 *out = convert(self.stated_advance(num_glyphs, gid));
949 }
950 }
951
952 #[inline]
961 fn run_varied<'o, V: 'o>(
962 &self,
963 num_glyphs: u32,
964 delta: impl Fn(GlyphId) -> F48Dot16,
965 convert: impl Fn(F48Dot16) -> V,
966 glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
967 ) {
968 for (gid, out) in glyphs {
969 let advance = if gid.to_u32() < num_glyphs {
974 self.stated_advance(num_glyphs, gid)
975 .saturating_add(delta(gid))
976 .max(F48Dot16::ZERO)
977 } else {
978 F48Dot16::ZERO
979 };
980 *out = convert(advance);
981 }
982 }
983
984 #[inline]
994 pub(crate) fn stated_advance(&self, num_glyphs: u32, glyph: GlyphId) -> F48Dot16 {
995 if glyph.to_u32() >= num_glyphs {
996 return F48Dot16::ZERO;
997 }
998 let index = (glyph.to_u32() as usize).min(self.metrics.len().saturating_sub(1));
999 match self.metrics.get(index) {
1000 Some(metric) => F48Dot16::from_i32(metric.advance() as i32),
1001 None => F48Dot16::ZERO,
1002 }
1003 }
1004
1005 pub(crate) fn stated_side_bearing(&self, glyph: GlyphId) -> Option<F48Dot16> {
1008 let index = glyph.to_u32() as usize;
1009 if let Some(metric) = self.metrics.get(index) {
1010 return Some(F48Dot16::from_i32(metric.side_bearing() as i32));
1011 }
1012 let tail = index.checked_sub(self.metrics.len())?;
1013 Some(F48Dot16::from_i32(self.bearings.get(tail)?.get() as i32))
1014 }
1015}
1016
1017#[derive(Clone, Copy)]
1019enum ExtentsSource<'a> {
1020 Bitmap(BitmapStrikes<'a>),
1022 Color(ColorGlyphs<'a>, OutlineGlyphs<'a>),
1025 Varc(OutlineGlyphs<'a>),
1027 Glyf(&'a Glyf<'a>, &'a Loca<'a>),
1028 Cff(&'a CffFontRef<'a>, OutlineSource),
1029 Type1(Type1Instance<'a>),
1030}
1031
1032struct ExtentsSources<'a> {
1037 items: [Option<ExtentsSource<'a>>; MAX_EXTENTS_SOURCES],
1038 len: usize,
1039}
1040
1041const MAX_EXTENTS_SOURCES: usize = 6;
1043
1044impl<'a> ExtentsSources<'a> {
1045 fn new(metrics: &GlyphMetrics<'a>) -> Self {
1046 let font = metrics.font;
1047 let mut sources = Self {
1048 items: [None; MAX_EXTENTS_SOURCES],
1049 len: 0,
1050 };
1051 let outlines = font.outline_glyphs_with(metrics.order);
1054 let mut base_taken = false;
1055 for format in metrics.order.iter() {
1056 let source = match format {
1057 GlyphSource::Sbix | GlyphSource::Cbdt | GlyphSource::Ebdt => {
1058 let bitmaps = font.bitmap_strikes_with(GlyphSourceOrder::new(&[format]));
1059 (!bitmaps.is_empty()).then_some(ExtentsSource::Bitmap(bitmaps))
1060 }
1061 GlyphSource::Colr => {
1062 let colors = font.color_glyphs();
1063 let clips = if outlines.is_empty() {
1067 font.outline_glyphs()
1068 } else {
1069 outlines
1070 };
1071 (!colors.is_empty()).then_some(ExtentsSource::Color(colors, clips))
1072 }
1073 GlyphSource::Varc => outlines.has_varc().then_some(ExtentsSource::Varc(outlines)),
1074 GlyphSource::Glyf | GlyphSource::Cff2 | GlyphSource::Cff | GlyphSource::Type1 => {
1075 let base = outlines
1078 .underlying_source()
1079 .filter(|base| !base_taken && GlyphSource::from(*base) == format);
1080 let Some(base) = base else {
1081 continue;
1082 };
1083 base_taken = true;
1084 match base {
1085 OutlineSource::Glyf => font
1086 .selected_glyf_loca()
1087 .map(|(glyf, loca)| ExtentsSource::Glyf(glyf, loca)),
1088 OutlineSource::Cff2 | OutlineSource::Cff => {
1091 outlines.cff_font().map(|cff| ExtentsSource::Cff(cff, base))
1092 }
1093 OutlineSource::Type1 => match font.kind() {
1094 Kind::Type1(_) => font.type1_instance().map(ExtentsSource::Type1),
1095 _ => None,
1096 },
1097 OutlineSource::Varc => None,
1098 }
1099 }
1100 };
1101 if let Some(source) = source {
1102 sources.items[sources.len] = Some(source);
1103 sources.len += 1;
1104 }
1105 }
1106 sources
1107 }
1108
1109 fn is_empty(&self) -> bool {
1110 self.len == 0
1111 }
1112
1113 fn only(&self) -> Option<ExtentsSource<'a>> {
1115 if self.len == 1 {
1116 self.items[0]
1117 } else {
1118 None
1119 }
1120 }
1121
1122 fn iter(&self) -> impl Iterator<Item = &ExtentsSource<'a>> {
1123 self.items[..self.len].iter().flatten()
1124 }
1125}
1126
1127fn exact_extents(extents: GlyphExtents<f32>) -> GlyphExtents<F48Dot16> {
1129 let exact = |value: f32| F48Dot16::from_f64(value as f64);
1130 GlyphExtents {
1131 x_bearing: exact(extents.x_bearing),
1132 y_bearing: exact(extents.y_bearing),
1133 width: exact(extents.width),
1134 height: exact(extents.height),
1135 }
1136}
1137
1138pub(crate) fn empty() -> &'static GlyphMetricRecords<'static> {
1140 static EMPTY: GlyphMetricRecords<'static> = GlyphMetricRecords {
1141 metrics: &[],
1142 bearings: &[],
1143 };
1144 &EMPTY
1145}
1146
1147fn extents_from_corners(
1152 x_min: F48Dot16,
1153 y_min: F48Dot16,
1154 x_max: F48Dot16,
1155 y_max: F48Dot16,
1156) -> GlyphExtents<F48Dot16> {
1157 GlyphExtents::from(BoundingBox {
1158 x_min,
1159 y_min,
1160 x_max,
1161 y_max,
1162 })
1163}
1164
1165fn extents_from_bounds(bounds: BoundingBox<Fixed>) -> GlyphExtents<F48Dot16> {
1167 extents_from_corners(
1168 bounds.x_min.to_f48dot16(),
1169 bounds.y_min.to_f48dot16(),
1170 bounds.x_max.to_f48dot16(),
1171 bounds.y_max.to_f48dot16(),
1172 )
1173}
1174
1175fn control_box(points: &[Point<f32>]) -> Option<GlyphExtents<F48Dot16>> {
1181 let first = points.first()?;
1182 let (mut x_min, mut y_min) = (first.x, first.y);
1183 let (mut x_max, mut y_max) = (first.x, first.y);
1184 for point in &points[1..] {
1185 x_min = x_min.min(point.x);
1186 y_min = y_min.min(point.y);
1187 x_max = x_max.max(point.x);
1188 y_max = y_max.max(point.y);
1189 }
1190 let as_fixed = |value: f32| F48Dot16::from_f64(value as f64);
1191 Some(extents_from_corners(
1192 as_fixed(x_min),
1193 as_fixed(y_min),
1194 as_fixed(x_max),
1195 as_fixed(y_max),
1196 ))
1197}
1198
1199fn varied_control_box<'a>(
1205 context: &'a dyn OutlineContext<'a>,
1206 glyph: GlyphId,
1207) -> Option<GlyphExtents<F48Dot16>> {
1208 let plan = OutlinePlan::new(context, glyph).ok()?;
1209 let scale = OutlineScaleF32::new(None, context.units_per_em());
1210 let lengths = plan.buffer_lengths::<OutlineScaleF32>();
1211 with_scratch(lengths.packed_len::<OutlineScaleF32>(), |block| {
1212 let buffers = Buffers::from_bytes(block, &lengths).ok()?;
1213 let outline = plan.load(context, &scale, buffers, None).ok()?;
1214 Some(control_box(outline.points()).unwrap_or_default())
1215 })
1216}
1217
1218fn glyf_v_origin_from_context<'a>(
1220 context: &'a dyn OutlineContext<'a>,
1221 scale: &OutlineScaleF32,
1222 glyph: GlyphId,
1223) -> Option<F48Dot16> {
1224 let plan = OutlinePlan::new(context, glyph).ok()?;
1225 let lengths = plan.buffer_lengths::<OutlineScaleF32>();
1226 with_scratch(lengths.packed_len::<OutlineScaleF32>(), |block| {
1227 let buffers = Buffers::from_bytes(block, &lengths).ok()?;
1228 let outline = plan.load(context, scale, buffers, None).ok()?;
1229 Some(F48Dot16::from_f64(outline.phantom_points()[2].y as f64))
1230 })
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235 use super::*;
1236 use crate::{
1237 model::{
1238 glyph::outline::{ControlBoundsSink, NullSink},
1239 Blob, NormalizedCoord,
1240 },
1241 tables::glyf::outline::{Outline, OutlineTables, PathContourStart, Unscaled},
1242 FontRef, TableProvider,
1243 };
1244 use alloc::{sync::Arc, vec, vec::Vec};
1245 use std::sync::Mutex;
1246 use types::Tag;
1247
1248 const STATIC: &[u8] = font_test_data::TINOS_SUBSET;
1249
1250 #[test]
1251 fn extended_metrics_in_the_cached_font_model() {
1252 use font_test_data::extended::metrics_font;
1253 for outline in [None, Some(*b"glyf"), Some(*b"GLYF")] {
1254 for legacy in [false, true] {
1255 let data = metrics_font(true, legacy, outline);
1256 let font = Font::new(data, 0).unwrap();
1257 assert_eq!(font.num_glyphs(), 70002);
1258 assert_eq!(
1259 font.tables().maxp_extended().unwrap().num_glyphs().to_u32(),
1260 70002
1261 );
1262 assert_eq!(
1263 font.tables().hhea_extended().unwrap().number_of_h_metrics(),
1264 70000
1265 );
1266 assert_eq!(
1267 font.tables()
1268 .vhea_extended()
1269 .unwrap()
1270 .number_of_long_ver_metrics(),
1271 70000
1272 );
1273 let metrics = font.glyph_metrics();
1274 for gid in [0, 65535, 65536, 69999] {
1275 assert_eq!(
1276 metrics.h_advance_exact(GlyphId::new(gid)),
1277 F48Dot16::from_i32((1000 + gid % 100) as i32)
1278 );
1279 assert_eq!(
1280 metrics.v_advance_exact(GlyphId::new(gid)),
1281 F48Dot16::from_i32((1200 + gid % 100) as i32)
1282 );
1283 }
1284 assert_eq!(
1285 metrics.h_advance_exact(GlyphId::new(70001)),
1286 F48Dot16::from_i32(1099)
1287 );
1288 assert_eq!(
1289 metrics.v_advance_exact(GlyphId::new(70001)),
1290 F48Dot16::from_i32(1299)
1291 );
1292 assert_eq!(metrics.h_advance_exact(GlyphId::new(70002)), F48Dot16::ZERO);
1293 }
1294 }
1295 }
1296
1297 #[test]
1298 fn extended_metrics_from_callback_tables_are_cached() {
1299 let data = font_test_data::extended::metrics_font(true, true, None);
1300 let asked = Arc::new(Mutex::new(Vec::new()));
1301 let requests = asked.clone();
1302 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1303 requests.lock().unwrap().push(tag);
1304 let font = FontRef::new(&data).ok()?;
1305 Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
1306 });
1307 let font = Font::new(source, 0).unwrap();
1308 for _ in 0..2 {
1309 let metrics = font.glyph_metrics();
1310 assert_eq!(font.num_glyphs(), 70002);
1311 assert_eq!(
1312 metrics.h_advance_exact(GlyphId::new(65536)),
1313 F48Dot16::from_i32(1036)
1314 );
1315 assert_eq!(
1316 metrics.v_advance_exact(GlyphId::new(65536)),
1317 F48Dot16::from_i32(1236)
1318 );
1319 }
1320 let asked = asked.lock().unwrap();
1321 for tag in [b"MAXP", b"HHEA", b"HMTX", b"VHEA", b"VMTX"] {
1322 assert_eq!(
1323 asked.iter().filter(|&&seen| seen == Tag::new(tag)).count(),
1324 1
1325 );
1326 }
1327 for tag in [b"maxp", b"hhea", b"hmtx", b"vhea", b"vmtx"] {
1328 assert!(!asked.contains(&Tag::new(tag)));
1329 }
1330 }
1331
1332 #[test]
1333 fn extended_outline_extents_in_the_cached_font_model() {
1334 for long_loca in [false, true] {
1335 let data = font_test_data::extended::outlines_font(true, true, true, long_loca);
1336 let font = Font::new(data, 0).unwrap();
1337 let metrics = font.glyph_metrics();
1338 for gid in [1, 65536] {
1339 let extents = metrics.extents_exact(GlyphId::new(gid)).unwrap();
1340 assert_eq!(extents.width, F48Dot16::from_i32(80));
1341 assert_eq!(extents.height, F48Dot16::from_i32(80));
1342 }
1343 }
1344 }
1345
1346 #[test]
1347 fn extended_gvar_metrics_and_extents_in_the_cached_font_model() {
1348 for long_offsets in [false, true] {
1349 for legacy in [false, true] {
1350 let data =
1351 font_test_data::extended::variable_outlines_font(true, legacy, long_offsets);
1352 let font = Font::new(data, 0).unwrap();
1353 assert_eq!(font.tables().selected_gvar().unwrap().glyph_count(), 65537);
1354 for coord in [0.0, 0.5, 1.0] {
1355 let instance = at(&font, coord);
1356 let metrics = instance.glyph_metrics();
1357 for (gid, delta, shift) in [(65536, 100.0, 40.0), (1, 60.0, 60.0)] {
1358 let id = GlyphId::new(gid);
1359 assert_eq!(
1360 metrics.h_advance_exact(id),
1361 F48Dot16::from_f64(1000.0 + delta * coord as f64)
1362 );
1363 let v_delta = if gid == 65536 { 60.0 } else { 0.0 };
1364 assert_eq!(
1365 metrics.v_advance_exact(id),
1366 F48Dot16::from_f64(1200.0 + v_delta * coord as f64)
1367 );
1368 let extents = metrics.extents_exact(id).unwrap();
1369 assert_eq!(
1370 extents.x_bearing,
1371 F48Dot16::from_f64(shift * coord as f64),
1372 "gid {gid}, coord {coord}"
1373 );
1374 assert_eq!(extents.width, F48Dot16::from_i32(80));
1375 assert_eq!(extents.height, F48Dot16::from_i32(80));
1376 }
1377 }
1378 }
1379 }
1380 }
1381
1382 const VAR: &[u8] = font_test_data::VAZIRMATN_VAR;
1384 const TAIL: &[u8] = font_test_data::MATERIAL_SYMBOLS_SUBSET;
1386 const CFF: &[u8] = font_test_data::NOTO_SANS_JP_CFF;
1388 const CFF2: &[u8] = font_test_data::ift::CFF2_FONT;
1389
1390 #[test]
1391 fn vertical_origins_in_design_units_match_their_sources_and_batch() {
1392 for data in [
1393 font_test_data::VORG,
1394 font_test_data::MPLUS1CODE_VERTICAL_SUBSET,
1395 STATIC,
1396 ] {
1397 let font = Font::new(data, 0).unwrap();
1398 let metrics = font.glyph_metrics();
1399 let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
1400 let mut batched = vec![F48Dot16::ZERO; glyphs.len()];
1401 metrics.v_origin_y_batched(
1402 |origin| origin,
1403 glyphs.iter().copied().zip(batched.iter_mut()),
1404 );
1405 for (glyph, origin) in glyphs.into_iter().zip(batched) {
1406 assert_eq!(origin, metrics.v_origin_y_exact(glyph), "glyph {glyph}");
1407 assert_eq!(origin.to_f32(), metrics.v_origin_y(glyph));
1408 if let Some(stated) = metrics.stated_v_origin(glyph) {
1409 assert_eq!(origin, stated);
1410 } else if let Some(phantom) = metrics.glyf_v_origin(glyph) {
1411 assert_eq!(origin, phantom);
1412 }
1413 }
1414 }
1415 }
1416
1417 #[test]
1418 fn a_width_is_what_hmtx_stores() {
1419 let font = Font::new(TAIL, 0).unwrap();
1421 let direct = FontRef::new(TAIL).unwrap();
1422 let hmtx = direct.hmtx().unwrap();
1423 let num_glyphs = direct.maxp().unwrap().num_glyphs();
1424 assert!(num_glyphs > 1);
1425 for gid in 0..num_glyphs as u32 {
1426 let expected = hmtx.advance(GlyphId::new(gid)).unwrap();
1427 assert_eq!(
1428 font.glyph_metrics().h_advance_exact(GlyphId::new(gid)),
1429 F48Dot16::from_i32(expected as i32)
1430 );
1431 }
1432 }
1433
1434 #[test]
1435 fn a_glyph_past_the_end_advances_by_nothing() {
1436 let font = Font::new(STATIC, 0).unwrap();
1442 let direct = FontRef::new(STATIC).unwrap();
1443 let past = GlyphId::new(direct.maxp().unwrap().num_glyphs() as u32);
1444
1445 assert_eq!(font.glyph_metrics().h_advance_exact(past), F48Dot16::ZERO);
1446 assert!(direct.hmtx().unwrap().advance(past).unwrap() > 0);
1447 assert_eq!(
1448 font.glyph_metrics().h_advance_exact(GlyphId::new(60000)),
1449 F48Dot16::ZERO
1450 );
1451 }
1452
1453 #[test]
1454 fn a_font_with_no_widths_gives_every_glyph_half_an_em() {
1455 let font = Font::new(font_test_data::NAMES_ONLY, 0).unwrap();
1456 assert_eq!(
1458 font.glyph_metrics().h_advance_exact(GlyphId::new(1)),
1459 F48Dot16::from_i32(500)
1460 );
1461 }
1462
1463 #[test]
1464 fn glyphs_past_the_long_metrics_share_the_last_advance() {
1465 let font = Font::new(TAIL, 0).unwrap();
1470 let direct = FontRef::new(TAIL).unwrap();
1471 let num_glyphs = direct.maxp().unwrap().num_glyphs() as u32;
1472 let num_long = direct.hhea().unwrap().number_of_h_metrics() as u32;
1473 assert!(num_long < num_glyphs, "this font has no tail to test");
1474
1475 let last = font
1476 .glyph_metrics()
1477 .h_advance_exact(GlyphId::new(num_long - 1));
1478 assert!(last > F48Dot16::ZERO);
1479 for gid in num_long..num_glyphs {
1480 assert_eq!(
1481 font.glyph_metrics().h_advance_exact(GlyphId::new(gid)),
1482 last,
1483 "glyph {gid} is in the tail and should share the last advance"
1484 );
1485 }
1486 assert_eq!(
1488 font.glyph_metrics()
1489 .h_advance_exact(GlyphId::new(num_glyphs)),
1490 F48Dot16::ZERO
1491 );
1492 }
1493
1494 #[test]
1495 fn glyphs_beyond_the_font_measure_zero_at_any_location() {
1496 let font = Font::new(VAR, 0).unwrap();
1500 let num_glyphs = font.num_glyphs();
1501 let instance = at(&font, -1.0);
1502 assert!(
1503 instance
1504 .glyph_metrics()
1505 .h_advance_exact(GlyphId::new(num_glyphs - 1))
1506 != font
1507 .glyph_metrics()
1508 .h_advance_exact(GlyphId::new(num_glyphs - 1)),
1509 "the last real glyph should move, or this proves nothing"
1510 );
1511 for gid in [num_glyphs, num_glyphs + 1, u32::MAX] {
1512 let gid = GlyphId::new(gid);
1513 assert_eq!(
1514 instance.glyph_metrics().h_advance_exact(gid),
1515 F48Dot16::ZERO,
1516 "glyph {gid} is beyond the font"
1517 );
1518 assert_eq!(font.glyph_metrics().h_advance_exact(gid), F48Dot16::ZERO);
1519 }
1520 }
1521
1522 const VERT_VAR: &[u8] = font_test_data::ift::CFF2_FONT;
1524 const VERT: &[u8] = font_test_data::MPLUS1CODE_VERTICAL_SUBSET;
1526
1527 #[test]
1528 fn a_type1_glyph_stacks_by_the_line() {
1529 let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap();
1538 let line = font.metrics().h_line().unwrap();
1539 let height = line.ascender - line.descender;
1540 assert!(height > F48Dot16::ZERO);
1541 let metrics = font.glyph_metrics();
1542 let mut widths = std::collections::HashSet::new();
1543 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1544 assert_eq!(metrics.v_advance_exact(gid), height, "glyph {gid}");
1545 widths.insert(metrics.h_advance_exact(gid).to_bits());
1546 }
1547 assert!(
1548 widths.len() > 1,
1549 "every glyph is the same width, so this proves nothing"
1550 );
1551 }
1552
1553 #[test]
1554 fn vertical_advances_come_from_vmtx() {
1555 let font = Font::new(VERT_VAR, 0).unwrap();
1556 let direct = FontRef::new(VERT_VAR).unwrap();
1557 let vmtx = direct.vmtx().unwrap();
1558 let metrics = font.glyph_metrics();
1559 let mut checked = 0;
1560 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1561 let Some(stated) = vmtx.advance(gid) else {
1562 continue;
1563 };
1564 assert_eq!(
1565 metrics.v_advance_exact(gid),
1566 F48Dot16::from_i32(stated as i32),
1567 "glyph {gid}"
1568 );
1569 checked += 1;
1570 }
1571 assert!(checked > 1, "this font states no vertical advances");
1572 }
1573
1574 #[test]
1575 fn vorg_reads_the_vvar_origin_delta_at_a_location() {
1576 let asked = Arc::new(Mutex::new(Vec::new()));
1580 let calls = asked.clone();
1581 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1582 calls.lock().unwrap().push(tag);
1583 let data = if tag == Tag::new(b"VORG") {
1584 font_test_data::VORG
1585 } else {
1586 VERT_VAR
1587 };
1588 let font = FontRef::new(data).ok()?;
1589 Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
1590 });
1591 let font = Font::new(source, 0).unwrap();
1592 let instance = at(&font, -1.0);
1593 let gid = GlyphId::new(1);
1594 let vorg = FontRef::new(font_test_data::VORG).unwrap().vorg().unwrap();
1595 assert_eq!(
1596 instance.glyph_metrics().stated_v_origin(gid),
1597 Some(F48Dot16::from_i32(vorg.vertical_origin_y(gid) as i32))
1598 );
1599 assert!(asked_for(&asked, b"VVAR"));
1600 }
1601
1602 #[test]
1603 fn without_vmtx_glyphs_advance_by_the_line() {
1604 let font = Font::new(STATIC, 0).unwrap();
1607 let line = font.metrics().h_line().unwrap();
1608 let expected = line.ascender - line.descender;
1609 assert!(expected > F48Dot16::ZERO);
1610 let metrics = font.glyph_metrics();
1611 for gid in (0..font.num_glyphs().min(16)).map(GlyphId::new) {
1612 assert_eq!(metrics.v_advance_exact(gid), expected, "glyph {gid}");
1613 }
1614 }
1615
1616 #[test]
1617 fn a_location_moves_a_vertical_advance() {
1618 let font = Font::new(VERT, 0).unwrap();
1619 let instance = at(&font, -1.0);
1620 let (varied, plain) = (instance.glyph_metrics(), font.glyph_metrics());
1621 let moved = (0..font.num_glyphs())
1622 .map(GlyphId::new)
1623 .filter(|gid| varied.v_advance_exact(*gid) != plain.v_advance_exact(*gid))
1624 .count();
1625 assert!(moved > 0, "no glyph moved at the light end of the axis");
1626 }
1627
1628 #[test]
1629 fn phantom_points_measure_a_glyph_the_same_as_vvar() {
1630 let with = Font::new(VERT, 0).unwrap();
1634 let asked = Arc::new(Mutex::new(Vec::new()));
1635 let without = callback_font(VERT, asked.clone(), &[b"VVAR"]);
1636 let (a, b) = (at(&with, -1.0), at(&without, -1.0));
1637 let (ma, mb) = (a.glyph_metrics(), b.glyph_metrics());
1638 let composite = GlyphId::new(2);
1642 for gid in (0..with.num_glyphs()).map(GlyphId::new) {
1643 if gid == composite {
1644 continue;
1645 }
1646 assert_eq!(
1647 ma.v_advance_exact(gid),
1648 mb.v_advance_exact(gid),
1649 "glyph {gid}"
1650 );
1651 }
1652 assert_eq!(
1653 mb.v_advance_exact(composite) - ma.v_advance_exact(composite),
1654 F48Dot16::from_i32(235)
1655 );
1656 }
1657
1658 #[test]
1659 fn vvar_answering_keeps_gvar_and_the_outlines_unread() {
1660 let asked = Arc::new(Mutex::new(Vec::new()));
1661 let font = callback_font(VERT, asked.clone(), &[]);
1662 let instance = at(&font, -1.0);
1663 let metrics = instance.glyph_metrics();
1664 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1665 assert!(metrics.v_advance_exact(gid) > F48Dot16::ZERO);
1666 }
1667 assert!(asked_for(&asked, b"VVAR"));
1668 for unread in [b"gvar", b"glyf", b"loca"] {
1669 assert!(
1670 !asked_for(&asked, unread),
1671 "{:?} was read",
1672 Tag::new(unread)
1673 );
1674 }
1675 }
1676
1677 #[test]
1678 fn a_vertical_batch_agrees_with_one_at_a_time() {
1679 for (data, coord) in [(VERT_VAR, 0.0), (VERT_VAR, -1.0), (STATIC, 0.0)] {
1680 let font = Font::new(data, 0).unwrap();
1681 let instance = at(&font, coord);
1682 let metrics = instance.glyph_metrics();
1683 let gids: Vec<_> = (0..16u32).map(GlyphId::new).collect();
1684 let mut batch = vec![F48Dot16::ZERO; 16];
1685 metrics.v_advance_batched(|v| v, gids.iter().copied().zip(batch.iter_mut()));
1686 for (gid, expected) in gids.iter().zip(&batch) {
1687 assert_eq!(metrics.v_advance_exact(*gid), *expected, "glyph {gid}");
1688 }
1689 }
1690 }
1691
1692 #[test]
1693 fn the_tables_are_parsed_once_for_the_font() {
1694 let font = Font::new(VAR, 0).unwrap();
1695 let first = font.h_metric_records() as *const GlyphMetricRecords<'_>;
1696 for _ in 0..8 {
1697 assert!(core::ptr::eq(
1698 font.h_metric_records() as *const GlyphMetricRecords<'_>,
1699 first
1700 ));
1701 }
1702 assert!(core::ptr::eq(
1704 font.clone().h_metric_records() as *const GlyphMetricRecords<'_>,
1705 first
1706 ));
1707 }
1708
1709 #[test]
1710 fn a_batch_agrees_with_one_at_a_time() {
1711 let font = Font::new(STATIC, 0).unwrap();
1712 let gids: Vec<_> = (0..16u32).map(GlyphId::new).collect();
1713 let mut batch = vec![F48Dot16::ZERO; 16];
1714 font.glyph_metrics()
1715 .h_advance_batched(|v| v, gids.iter().copied().zip(batch.iter_mut()));
1716 for (gid, expected) in gids.iter().zip(&batch) {
1717 assert_eq!(font.glyph_metrics().h_advance_exact(*gid), *expected);
1718 }
1719 }
1720
1721 #[test]
1722 fn a_type1_glyph_is_measured_by_its_charstring() {
1723 let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB, 0).unwrap();
1726 let metrics = font.glyph_metrics();
1727 let half_em = F48Dot16::from_i32(font.units_per_em() as i32 / 2);
1728 assert!(font.num_glyphs() > 1);
1729
1730 let widths: Vec<_> = (0..font.num_glyphs())
1731 .map(|gid| metrics.h_advance_exact(GlyphId::new(gid)))
1732 .collect();
1733 assert!(widths.iter().any(|width| *width > F48Dot16::ZERO));
1734 assert!(
1735 widths.iter().any(|width| *width != half_em),
1736 "every glyph reported the fallback, so no charstring was run"
1737 );
1738 }
1739
1740 fn type1_with_a_stretched_matrix() -> Vec<u8> {
1743 const FROM: &[u8] = b"/FontMatrix [0.001 0 0 0.001 0 0 ]";
1744 const TO: &[u8] = b"/FontMatrix [0.002 0 0 0.001 0 0 ]";
1745 let base = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA;
1746 let at = base
1747 .windows(FROM.len())
1748 .position(|window| window == FROM)
1749 .expect("the fixture states the matrix this test rewrites");
1750 let mut data = base.to_vec();
1751 data[at..at + TO.len()].copy_from_slice(TO);
1752 data
1753 }
1754
1755 #[test]
1756 fn a_type1_width_is_mapped_by_the_font_matrix() {
1757 let plain = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap();
1762 let stretched = Font::new(type1_with_a_stretched_matrix(), 0).unwrap();
1763 let (a, b) = (plain.glyph_metrics(), stretched.glyph_metrics());
1764 assert_eq!(plain.num_glyphs(), stretched.num_glyphs());
1765
1766 let mut stretched_any = false;
1767 for gid in (0..plain.num_glyphs()).map(GlyphId::new) {
1768 let width = a.h_advance_exact(gid);
1769 assert_eq!(
1770 b.h_advance_exact(gid),
1771 width.saturating_add(width),
1772 "glyph {gid}"
1773 );
1774 stretched_any |= width > F48Dot16::ZERO;
1775 }
1776 assert!(stretched_any, "no glyph had a width to stretch");
1777 }
1778
1779 #[test]
1780 fn a_type1_advance_is_what_drawing_the_glyph_reports() {
1781 let data = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA;
1787 let font = Font::new(data, 0).unwrap();
1788 let direct = crate::ps::type1::Type1Font::new(data).unwrap();
1789 let metrics = font.glyph_metrics();
1790 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1791 let expected = direct.draw(gid, None, &mut NullSink).ok().flatten();
1792 assert_eq!(
1793 metrics.h_advance(gid),
1794 expected.unwrap_or(0.0),
1795 "glyph {gid}"
1796 );
1797 }
1798 }
1799
1800 #[test]
1801 fn the_narrowed_advance_agrees_with_the_exact_one() {
1802 for data in [STATIC, TAIL, VAR] {
1803 let font = Font::new(data, 0).unwrap();
1804 let metrics = font.glyph_metrics();
1805 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1806 assert_eq!(
1807 metrics.h_advance(gid),
1808 metrics.h_advance_exact(gid).to_f32()
1809 );
1810 }
1811 }
1812 }
1813
1814 #[test]
1815 fn the_conversion_decides_what_a_caller_gets_back() {
1816 let font = Font::new(STATIC, 0).unwrap();
1817 let gids = [GlyphId::new(1), GlyphId::new(2)];
1818 let mut exact = [F48Dot16::ZERO; 2];
1819 let mut floats = [0.0f32; 2];
1820 font.glyph_metrics()
1821 .h_advance_batched(|v| v, gids.iter().copied().zip(exact.iter_mut()));
1822 font.glyph_metrics()
1823 .h_advance_batched(|v| v.to_f32(), gids.iter().copied().zip(floats.iter_mut()));
1824 for i in 0..2 {
1825 assert_eq!(floats[i], exact[i].to_f32());
1826 }
1827 }
1828
1829 #[test]
1830 fn a_box_is_the_one_the_glyph_states() {
1831 let font = Font::new(STATIC, 0).unwrap();
1834 let metrics = font.glyph_metrics();
1835 let direct = FontRef::new(STATIC).unwrap();
1836 let (glyf, loca, hmtx) = (
1837 direct.glyf().unwrap(),
1838 direct.loca(None).unwrap(),
1839 direct.hmtx().unwrap(),
1840 );
1841 let mut drawn = 0;
1842 let mut empty = 0;
1843 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1844 match loca.get_glyf(gid, &glyf).unwrap() {
1845 Some(glyph) => {
1846 if glyph.number_of_contours() == 0 {
1847 assert_eq!(
1848 metrics.extents_exact(gid),
1849 Some(GlyphExtents::default()),
1850 "glyph {gid}"
1851 );
1852 empty += 1;
1853 continue;
1854 }
1855 let unit = |value: i16| F48Dot16::from_i32(value as i32);
1856 assert_eq!(
1857 metrics.extents_exact(gid),
1858 Some(GlyphExtents {
1859 x_bearing: hmtx.side_bearing(gid).map(unit).unwrap_or(F48Dot16::ZERO),
1860 y_bearing: unit(glyph.y_max()),
1861 width: unit(glyph.x_max()) - unit(glyph.x_min()),
1862 height: unit(glyph.y_max()) - unit(glyph.y_min()),
1863 }),
1864 "glyph {gid}"
1865 );
1866 drawn += 1;
1867 }
1868 None => {
1870 assert_eq!(
1871 metrics.extents_exact(gid),
1872 Some(GlyphExtents::default()),
1873 "glyph {gid}"
1874 );
1875 empty += 1;
1876 }
1877 }
1878 }
1879 assert!(drawn > 0 && empty > 0, "{drawn} drawn, {empty} empty");
1880 }
1881
1882 #[test]
1883 fn glyf_ink_starts_at_the_stated_left_side_bearing() {
1884 let direct = FontRef::new(STATIC).unwrap();
1885 let loca = direct.loca(None).unwrap();
1886 let glyf = direct.glyf().unwrap();
1887 let gid = (0..direct.hmtx().unwrap().h_metrics().len() as u32)
1888 .map(GlyphId::new)
1889 .find(|gid| {
1890 loca.get_glyf(*gid, &glyf)
1891 .ok()
1892 .flatten()
1893 .is_some_and(|glyph| glyph.number_of_contours() > 0)
1894 })
1895 .unwrap();
1896 let outline = loca.get_glyf(gid, &glyf).unwrap().unwrap();
1897 let bearing = outline.x_min() + 17;
1898 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1899 let font = FontRef::new(STATIC).ok()?;
1900 let mut data = font.table_data(tag)?.as_bytes().to_vec();
1901 if tag == Tag::new(b"hmtx") {
1902 let offset = gid.to_u32() as usize * 4 + 2;
1903 data[offset..offset + 2].copy_from_slice(&bearing.to_be_bytes());
1904 }
1905 Some(Blob::from(data))
1906 });
1907 let font = Font::new(source, 0).unwrap();
1908 assert_ne!(bearing, outline.x_min());
1909 let tables = OutlineTables::new(&font.tables()).unwrap();
1910 let mut outline = Outline::<Unscaled>::new();
1911 let loaded = outline.load(&tables, gid).unwrap();
1912 let points = loaded.points();
1913 let x_min = points.iter().map(|point| point.x).min().unwrap();
1914 let x_max = points.iter().map(|point| point.x).max().unwrap();
1915 let y_min = points.iter().map(|point| point.y).min().unwrap();
1916 let y_max = points.iter().map(|point| point.y).max().unwrap();
1917 let unit = F48Dot16::from_i32;
1918 assert_eq!(
1919 font.glyph_metrics().extents_exact(gid),
1920 Some(GlyphExtents {
1921 x_bearing: unit(x_min),
1922 y_bearing: unit(y_max),
1923 width: unit(x_max - x_min),
1924 height: unit(y_max - y_min),
1925 })
1926 );
1927 }
1928
1929 #[test]
1930 fn glyf_bearing_uses_hmtx_tail_then_falls_back_to_zero() {
1931 let direct = FontRef::new(TAIL).unwrap();
1932 let loca = direct.loca(None).unwrap();
1933 let glyf = direct.glyf().unwrap();
1934 let hmtx = direct.hmtx().unwrap();
1935 let long_count = hmtx.h_metrics().len();
1936 let gid = (long_count as u32..direct.maxp().unwrap().num_glyphs() as u32)
1937 .map(GlyphId::new)
1938 .find(|gid| loca.get_glyf(*gid, &glyf).ok().flatten().is_some())
1939 .unwrap();
1940 let font = Font::new(TAIL, 0).unwrap();
1941 assert_eq!(
1942 font.glyph_metrics().extents_exact(gid).unwrap().x_bearing,
1943 F48Dot16::from_i32(hmtx.side_bearing(gid).unwrap() as i32)
1944 );
1945
1946 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1947 let font = FontRef::new(TAIL).ok()?;
1948 let mut data = font.table_data(tag)?.as_bytes().to_vec();
1949 if tag == Tag::new(b"hmtx") {
1950 data.truncate(long_count * 4);
1951 }
1952 Some(Blob::from(data))
1953 });
1954 let shortened = Font::new(source, 0).unwrap();
1955 assert_eq!(
1956 shortened
1957 .glyph_metrics()
1958 .extents_exact(gid)
1959 .unwrap()
1960 .x_bearing,
1961 F48Dot16::ZERO
1962 );
1963 let tables = OutlineTables::new(&shortened.tables()).unwrap();
1964 let mut outline = Outline::<Unscaled>::new();
1965 let loaded = outline.load(&tables, gid).unwrap();
1966 assert_eq!(loaded.points().iter().map(|point| point.x).min(), Some(0));
1967 }
1968
1969 #[test]
1970 fn a_varied_box_holds_the_outline_it_describes() {
1971 let font = Font::new(VAR, 0).unwrap();
1976 let instance = at(&font, -1.0);
1977 let metrics = instance.glyph_metrics();
1978 let direct = FontRef::new(VAR).unwrap();
1979 let tables = OutlineTables::new(&direct)
1980 .unwrap()
1981 .at(instance.normalized_coords(), &[]);
1982 let mut outline = Outline::<OutlineScaleF32>::new();
1983 let mut checked = 0;
1984 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1985 let Ok(loaded) = outline.load(&tables, gid, None) else {
1986 continue;
1987 };
1988 let mut pen = ControlBoundsSink::new();
1989 let _ = loaded.to_path(PathContourStart::ScanBackward, &mut pen);
1990 let expected = pen
1991 .bounding_box()
1992 .map(|b| {
1993 extents_from_corners(
1994 F48Dot16::from_f64(b.x_min as f64),
1995 F48Dot16::from_f64(b.y_min as f64),
1996 F48Dot16::from_f64(b.x_max as f64),
1997 F48Dot16::from_f64(b.y_max as f64),
1998 )
1999 })
2000 .or(Some(GlyphExtents::default()));
2001 assert_eq!(metrics.extents_exact(gid), expected, "glyph {gid}");
2002 checked += expected.is_some() as u32;
2003 }
2004 assert!(checked > 0);
2005 }
2006
2007 #[test]
2008 fn a_location_moves_the_box() {
2009 let font = Font::new(VAR, 0).unwrap();
2010 let instance = at(&font, -1.0);
2011 let (varied, plain) = (instance.glyph_metrics(), font.glyph_metrics());
2012 let moved = (0..font.num_glyphs())
2013 .map(GlyphId::new)
2014 .filter(|gid| varied.extents_exact(*gid) != plain.extents_exact(*gid))
2015 .count();
2016 assert!(moved > 0, "no glyph ink moved at the end of the axis");
2017 }
2018
2019 #[test]
2020 fn a_charstring_box_is_the_box_it_draws_within() {
2021 for data in [CFF, CFF2] {
2024 let font = Font::new(data, 0).unwrap();
2025 let metrics = font.glyph_metrics();
2026 let direct = FontRef::new(data).unwrap();
2027 let cff = font
2028 .charstrings(OutlineSource::Cff2)
2029 .or_else(|| font.charstrings(OutlineSource::Cff))
2030 .expect("no charstrings");
2031 assert!(direct.glyf().is_err(), "glyf would answer first");
2032 let mut checked = 0;
2033 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2034 let subfont = cff
2035 .subfont(cff.subfont_index(gid).unwrap(), &[])
2036 .expect("no subfont");
2037 let mut pen = ControlBoundsSink::new();
2038 cff.draw(&subfont, gid, &[], None, &mut pen).unwrap();
2039 let expected = pen
2040 .bounding_box()
2041 .map(|b| GlyphExtents {
2042 x_bearing: F48Dot16::from_f64(b.x_min as f64),
2043 y_bearing: F48Dot16::from_f64(b.y_max as f64),
2044 width: F48Dot16::from_f64((b.x_max - b.x_min) as f64),
2045 height: F48Dot16::from_f64((b.y_max - b.y_min) as f64),
2046 })
2047 .or(Some(GlyphExtents::default()));
2048 assert_eq!(metrics.extents_exact(gid), expected, "glyph {gid}");
2049 checked += expected.is_some() as u32;
2050 }
2051 assert!(checked > 0);
2052 }
2053 }
2054
2055 #[test]
2056 fn a_type1_box_is_the_box_it_draws_within() {
2057 let data = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA;
2058 let font = Font::new(data, 0).unwrap();
2059 let metrics = font.glyph_metrics();
2060 let direct = crate::ps::type1::Type1Font::new(data).unwrap();
2061 let mut checked = 0;
2062 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2063 let mut pen = ControlBoundsSink::new();
2064 direct.draw(gid, None, &mut pen).unwrap();
2065 let expected = pen
2066 .bounding_box()
2067 .map(|b| GlyphExtents {
2068 x_bearing: F48Dot16::from_f64(b.x_min as f64),
2069 y_bearing: F48Dot16::from_f64(b.y_max as f64),
2070 width: F48Dot16::from_f64((b.x_max - b.x_min) as f64),
2071 height: F48Dot16::from_f64((b.y_max - b.y_min) as f64),
2072 })
2073 .or(Some(GlyphExtents::default()));
2074 assert_eq!(metrics.extents_exact(gid), expected, "glyph {gid}");
2075 checked += expected.is_some() as u32;
2076 }
2077 assert!(checked > 0);
2078 }
2079
2080 #[test]
2081 fn every_source_measures_a_run_as_it_measures_one() {
2082 for (data, coord) in [(STATIC, 0.0), (VAR, -1.0), (CFF, 0.0), (CFF2, 0.0)] {
2085 let font = Font::new(data, 0).unwrap();
2086 let instance = if coord == 0.0 {
2087 font.clone()
2088 } else {
2089 at(&font, coord)
2090 };
2091 let metrics = instance.glyph_metrics();
2092 let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
2093 let mut batched = vec![None; glyphs.len()];
2094 metrics.extents_batched(
2095 |extents| extents,
2096 glyphs.iter().copied().zip(batched.iter_mut()),
2097 );
2098 for (gid, batched) in glyphs.iter().copied().zip(batched) {
2099 assert_eq!(metrics.extents_exact(gid), batched, "glyph {gid}");
2100 }
2101 }
2102 }
2103
2104 fn extents_of(bounds: BoundingBox<f32>) -> GlyphExtents<F48Dot16> {
2106 exact_extents(bounds.into())
2107 }
2108
2109 #[test]
2110 fn bitmaps_answer_before_outlines_in_the_default_order() {
2111 let font = Font::new(font_test_data::CBDT, 0).unwrap();
2112 let gid = GlyphId::new(2);
2113 let bitmaps = font.bitmap_strikes();
2114 let from_strike = |ppem| exact_extents(bitmaps.select_bitmap(gid, ppem).unwrap().extents());
2115 let metrics = font.glyph_metrics();
2118 assert_eq!(metrics.extents_exact(gid), Some(from_strike(None)));
2119 let at_16 = metrics.with_bitmap_ppem(Some(12.0));
2121 assert_eq!(at_16.extents_exact(gid), Some(from_strike(Some(12.0))));
2122 assert_ne!(from_strike(None), from_strike(Some(12.0)));
2123 let outlines_only = metrics.with_extents_source_order(GlyphSourceOrder::OUTLINES);
2125 let expected = font
2126 .outline_glyphs()
2127 .get(gid)
2128 .map(|glyph| exact_extents(glyph.extents().unwrap()));
2129 assert_eq!(outlines_only.extents_exact(gid), expected);
2130 assert_ne!(outlines_only.extents_exact(gid), metrics.extents_exact(gid));
2131 let past = GlyphId::new(font.num_glyphs());
2133 assert_eq!(metrics.extents_exact(past), None);
2134 }
2135
2136 fn cbdt_without_glyph_2_at_16() -> alloc::vec::Vec<u8> {
2139 let source = crate::FontRef::new(font_test_data::CBDT).unwrap();
2140 let cblc_start = source
2141 .table_directory()
2142 .table_records()
2143 .iter()
2144 .find(|record| record.tag() == crate::types::Tag::new(b"CBLC"))
2145 .unwrap()
2146 .offset() as usize;
2147 let cblc = source.cblc().unwrap();
2148 let size = cblc
2149 .bitmap_sizes()
2150 .iter()
2151 .find(|size| size.ppem_x() == 16)
2152 .unwrap();
2153 let mut data = font_test_data::CBDT.to_vec();
2154 let list = cblc_start + size.index_subtable_list_offset() as usize;
2155 for index in 0..size.number_of_index_subtables() as usize {
2156 let record = list + index * 8;
2157 let read = |at: usize| u16::from_be_bytes([data[at], data[at + 1]]);
2158 let (first, last) = (read(record), read(record + 2));
2159 if (first..=last).contains(&2) {
2160 let (at, value) = if first < 2 {
2162 (record + 2, 1u16)
2163 } else {
2164 (record, 3u16)
2165 };
2166 data[at..at + 2].copy_from_slice(&value.to_be_bytes());
2167 }
2168 }
2169 data
2170 }
2171
2172 #[test]
2175 fn a_bitmap_the_selected_strike_lacks_is_measured_by_the_next_source() {
2176 let font = Font::new(cbdt_without_glyph_2_at_16(), 0).unwrap();
2177 let gid = GlyphId::new(2);
2178 let strikes = font.bitmap_strikes();
2179 let selected = strikes.select(Some(12.0)).unwrap();
2180 assert_eq!(selected.ppem(), 16);
2181 assert!(selected.get(gid).is_none());
2182 let larger = strikes.select_bitmap(gid, Some(12.0)).unwrap();
2183 assert_eq!(larger.ppem(), 64);
2184 let larger = Some(exact_extents(larger.extents()));
2185 let outline = font
2187 .outline_glyphs()
2188 .get(gid)
2189 .and_then(|glyph| glyph.extents().ok().map(exact_extents));
2190 let at_12 = font.glyph_metrics().with_bitmap_ppem(Some(12.0));
2191 assert_eq!(at_12.extents_exact(gid), outline);
2192 assert_ne!(at_12.extents_exact(gid), larger);
2193 let at_64 = font.glyph_metrics().with_bitmap_ppem(Some(64.0));
2195 assert_eq!(at_64.extents_exact(gid), larger);
2196 }
2197
2198 #[test]
2199 fn a_color_glyph_measures_by_its_clip_box_or_its_paint() {
2200 use crate::model::glyph::color::test_glyph_defs::{CLIPBOX, COLORED_CIRCLES_V0};
2201 let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
2202 let metrics = font.glyph_metrics();
2203 let colors = font.color_glyphs();
2204 let map = |ch: char| font.charmap().map_unicode(ch).unwrap();
2205 let clipped = map(CLIPBOX[0]);
2207 let clip_box = colors.get(clipped).unwrap().clip_box().unwrap();
2208 assert_eq!(metrics.extents_exact(clipped), Some(extents_of(clip_box)));
2209 let painted = map(COLORED_CIRCLES_V0[0]);
2212 let glyph = colors.get(painted).unwrap();
2213 assert!(glyph.clip_box().is_none());
2214 assert_eq!(
2215 metrics.extents_exact(painted),
2216 Some(exact_extents(glyph.extents().unwrap()))
2217 );
2218 let outlines_only = metrics.with_extents_source_order(GlyphSourceOrder::OUTLINES);
2221 assert_ne!(
2222 outlines_only.extents_exact(clipped),
2223 metrics.extents_exact(clipped)
2224 );
2225 let plain = GlyphId::new(1);
2227 assert!(colors.get(plain).is_none());
2228 assert_eq!(
2229 metrics.extents_exact(plain),
2230 outlines_only.extents_exact(plain)
2231 );
2232 }
2233
2234 #[test]
2238 fn a_color_only_order_keeps_the_clips() {
2239 let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
2240 let metrics = font
2241 .glyph_metrics()
2242 .with_extents_source_order(GlyphSourceOrder::COLOR);
2243 let mut measured = 0;
2244 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2245 let Some(color) = font.color_glyphs().get(gid) else {
2246 continue;
2247 };
2248 let Ok(direct) = color.extents() else {
2249 continue;
2250 };
2251 if color.clip_box().is_some() || direct.width == 0.0 || direct.height == 0.0 {
2252 continue;
2253 }
2254 assert_eq!(metrics.extents(gid), Some(direct), "glyph {gid}");
2255 measured += 1;
2256 }
2257 assert!(measured > 0);
2258 }
2259
2260 #[test]
2261 fn a_version_0_color_glyph_measures_by_its_layers() {
2262 use crate::model::glyph::color::test_glyph_defs::COLORED_CIRCLES_V0;
2263 let mut data = font_test_data::COLRV0V1.to_vec();
2264 let colr = crate::FontRef::new(&data)
2265 .unwrap()
2266 .table_directory()
2267 .table_records()
2268 .iter()
2269 .find(|record| record.tag() == Tag::new(b"COLR"))
2270 .unwrap()
2271 .offset() as usize;
2272 data[colr..colr + 2].copy_from_slice(&0u16.to_be_bytes());
2275 let font = Font::new(data, 0).unwrap();
2276 let colors = font.color_glyphs();
2277 assert_eq!(colors.version(), Some(0));
2278 let metrics = font.glyph_metrics();
2279 let outlines_only = metrics.with_extents_source_order(GlyphSourceOrder::OUTLINES);
2280 let glyph = font.charmap().map_unicode(COLORED_CIRCLES_V0[0]).unwrap();
2281 let layers = exact_extents(colors.get(glyph).unwrap().extents().unwrap());
2282 assert_eq!(metrics.extents_exact(glyph), Some(layers));
2283 assert_ne!(outlines_only.extents_exact(glyph), Some(layers));
2284 let plain = GlyphId::new(1);
2286 assert!(colors.get(plain).is_none());
2287 assert_eq!(
2288 metrics.extents_exact(plain),
2289 outlines_only.extents_exact(plain)
2290 );
2291 }
2292
2293 #[test]
2294 fn a_color_glyph_at_a_location_moves_its_clip_box() {
2295 use crate::model::glyph::color::test_glyph_defs::CLIPBOX;
2296 let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
2297 let gid = font.charmap().map_unicode(CLIPBOX[0]).unwrap();
2298 let moved = font
2299 .instance_builder()
2300 .variations([("CLXI", 200.0)])
2301 .build();
2302 let (at_default, at_moved) = (
2303 font.glyph_metrics().extents_exact(gid).unwrap(),
2304 moved.glyph_metrics().extents_exact(gid).unwrap(),
2305 );
2306 assert_ne!(at_default.x_bearing, at_moved.x_bearing);
2307 let clip_box = moved.color_glyphs().get(gid).unwrap().clip_box().unwrap();
2310 assert_ne!(clip_box.x_min, clip_box.x_min.round());
2311 let rounded = BoundingBox {
2312 x_min: clip_box.x_min.round(),
2313 y_min: clip_box.y_min.round(),
2314 x_max: clip_box.x_max.round(),
2315 y_max: clip_box.y_max.round(),
2316 };
2317 assert_eq!(at_moved, extents_of(rounded));
2318 }
2319
2320 #[test]
2321 fn a_varc_composite_measures_by_what_it_draws() {
2322 let font = Font::new(font_test_data::varc::CJK_6868, 0).unwrap();
2323 let outlines = font.outline_glyphs();
2324 assert!(outlines.has_varc(), "the fixture has VARC");
2325 let metrics = font.glyph_metrics();
2326 let base_only = metrics.with_extents_source_order(GlyphSourceOrder::BASE_OUTLINES);
2327 let mut composites = 0;
2328 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2329 let glyph = outlines.get(gid).unwrap();
2330 let expected = exact_extents(glyph.extents().unwrap());
2331 if outlines.varc_covers(gid) {
2332 assert!(matches!(
2333 glyph,
2334 crate::model::glyph::outline::OutlineGlyph::Varc(_)
2335 ));
2336 assert_eq!(metrics.extents_exact(gid), Some(expected), "glyph {gid}");
2337 composites += 1;
2338 } else {
2339 assert_eq!(
2341 metrics.extents_exact(gid),
2342 base_only.extents_exact(gid),
2343 "glyph {gid}"
2344 );
2345 }
2346 }
2347 assert!(composites > 0);
2348 let composite = (0..font.num_glyphs())
2351 .map(GlyphId::new)
2352 .find(|gid| outlines.varc_covers(*gid))
2353 .unwrap();
2354 assert_ne!(
2355 base_only.extents_exact(composite),
2356 metrics.extents_exact(composite)
2357 );
2358 }
2359
2360 #[test]
2361 fn mixed_sources_measure_a_run_as_they_measure_one() {
2362 for data in [
2363 font_test_data::CBDT,
2364 font_test_data::COLRV0V1_VARIABLE,
2365 font_test_data::varc::CJK_6868,
2366 ] {
2367 let font = Font::new(data, 0).unwrap();
2368 let metrics = font.glyph_metrics();
2369 let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
2370 let mut batched = vec![None; glyphs.len()];
2371 metrics.extents_batched(
2372 |extents| extents,
2373 glyphs.iter().copied().zip(batched.iter_mut()),
2374 );
2375 for (gid, batched) in glyphs.iter().copied().zip(batched) {
2376 assert_eq!(metrics.extents_exact(gid), batched, "glyph {gid}");
2377 }
2378 }
2379 }
2380
2381 fn extents_through_every_source(
2384 metrics: &GlyphMetrics<'_>,
2385 glyph: GlyphId,
2386 ) -> Option<GlyphExtents<F48Dot16>> {
2387 let sources = ExtentsSources::new(metrics);
2388 let extents = sources
2389 .iter()
2390 .find_map(|source| metrics.extents_from_source(source, glyph));
2391 extents
2392 }
2393
2394 #[test]
2397 fn the_base_outline_shortcut_matches_every_source() {
2398 let mut fonts = alloc::vec![
2399 Font::new(STATIC, 0).unwrap(),
2400 Font::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED, 0).unwrap(),
2401 Font::new(font_test_data::CBDT, 0).unwrap(),
2402 Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap(),
2403 Font::new(font_test_data::varc::CJK_6868, 0).unwrap(),
2404 Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap(),
2405 ];
2406 let cantarell = Font::new(font_test_data::CANTARELL_VF_TRIMMED, 0).unwrap();
2407 fonts.push(
2408 cantarell
2409 .instance_builder()
2410 .variations([("wght", 650.0)])
2411 .build(),
2412 );
2413 fonts.push(cantarell);
2414 let orders = [
2415 GlyphSourceOrder::DEFAULT,
2416 GlyphSourceOrder::OUTLINES,
2417 GlyphSourceOrder::BASE_OUTLINES,
2418 GlyphSourceOrder::BITMAPS,
2419 GlyphSourceOrder::COLOR,
2420 GlyphSourceOrder::new(&[GlyphSource::Cff, GlyphSource::Glyf, GlyphSource::Colr]),
2421 GlyphSourceOrder::EMPTY,
2422 ];
2423 for (index, font) in fonts.iter().enumerate() {
2424 for order in orders {
2425 let metrics = font.glyph_metrics().with_extents_source_order(order);
2426 for glyph in (0..font.num_glyphs() + 1).map(GlyphId::new) {
2427 assert_eq!(
2428 metrics.extents_exact(glyph),
2429 extents_through_every_source(&metrics, glyph),
2430 "font {index} order {order:?} glyph {glyph}"
2431 );
2432 }
2433 }
2434 }
2435 }
2436
2437 #[test]
2440 fn an_outline_order_probes_no_other_tables() {
2441 let font = Font::new(font_test_data::CBDT, 0).unwrap();
2442 let probed = |font: &Font| font.probed_glyph_sources();
2443 let base = font
2444 .glyph_metrics()
2445 .with_extents_source_order(GlyphSourceOrder::BASE_OUTLINES);
2446 base.extents_exact(GlyphId::new(1));
2447 assert_eq!(probed(&font), 0);
2448 font.glyph_metrics().extents_exact(GlyphId::new(1));
2450 assert_ne!(probed(&font), 0);
2451 assert!(font.has_layered_source(GlyphSource::Cbdt));
2452 assert!(!font.has_layered_source(GlyphSource::Colr));
2453 }
2454
2455 #[test]
2456 fn an_empty_order_measures_nothing() {
2457 let font = Font::new(STATIC, 0).unwrap();
2458 let metrics = font
2459 .glyph_metrics()
2460 .with_extents_source_order(GlyphSourceOrder::EMPTY);
2461 assert_eq!(metrics.extents_exact(GlyphId::new(1)), None);
2462 assert_eq!(metrics.extents_source_order(), GlyphSourceOrder::EMPTY);
2463 }
2464
2465 #[test]
2466 fn a_font_stating_no_outlines_measures_nothing() {
2467 let font = Font::new(font_test_data::NAMES_ONLY, 0).unwrap();
2468 let metrics = font.glyph_metrics();
2469 for gid in (0..8).map(GlyphId::new) {
2470 assert_eq!(metrics.extents_exact(gid), None);
2471 }
2472 }
2473
2474 #[test]
2475 fn a_glyph_past_the_font_has_no_extents() {
2476 for data in [
2477 STATIC,
2478 VAR,
2479 CFF,
2480 CFF2,
2481 font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA,
2482 ] {
2483 let font = Font::new(data, 0).unwrap();
2484 let past = GlyphId::new(font.num_glyphs());
2485 assert_eq!(font.glyph_metrics().extents_exact(past), None);
2486 if data == VAR {
2487 assert_eq!(at(&font, -1.0).glyph_metrics().extents_exact(past), None);
2488 }
2489 }
2490 }
2491
2492 #[test]
2493 fn a_reversed_box_still_measures_a_size() {
2494 let unit = F48Dot16::from_i32;
2497 assert_eq!(
2498 extents_from_corners(unit(70), unit(50), unit(10), unit(-30)),
2499 GlyphExtents {
2500 x_bearing: unit(10),
2501 y_bearing: unit(50),
2502 width: unit(60),
2503 height: unit(80),
2504 }
2505 );
2506 }
2507
2508 fn at(font: &Font, coord: f32) -> Font {
2509 font.instance_builder()
2510 .normalized_coords([NormalizedCoord::from_f32(coord)])
2511 .build()
2512 }
2513
2514 fn callback_font(
2517 data: &'static [u8],
2518 asked: Arc<Mutex<Vec<Tag>>>,
2519 hide: &'static [&[u8; 4]],
2520 ) -> Font {
2521 let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag: Tag| {
2522 asked.lock().unwrap().push(tag);
2523 if hide.iter().any(|hidden| Tag::new(hidden) == tag) {
2524 return None;
2525 }
2526 let font = FontRef::new(data).ok()?;
2527 Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
2528 });
2529 Font::new(source, 0).unwrap()
2530 }
2531
2532 fn asked_for(asked: &Arc<Mutex<Vec<Tag>>>, tag: &[u8; 4]) -> bool {
2533 asked.lock().unwrap().contains(&Tag::new(tag))
2534 }
2535
2536 #[test]
2537 fn an_instance_varies_what_the_font_does_not() {
2538 let font = Font::new(VAR, 0).unwrap();
2539 let instance = at(&font, -1.0);
2540 let (varied, plain) = (instance.glyph_metrics(), font.glyph_metrics());
2541 let moved = (0..font.num_glyphs())
2542 .map(GlyphId::new)
2543 .any(|gid| varied.h_advance_exact(gid) != plain.h_advance_exact(gid));
2544 assert!(moved, "no glyph moved at the far end of the axis");
2545 }
2546
2547 #[test]
2548 fn an_advance_keeps_the_fraction_a_location_adds() {
2549 let font = Font::new(VAR, 0).unwrap();
2553 let metrics = at(&font, -0.4);
2554 let metrics = metrics.glyph_metrics();
2555 let fractional = (0..font.num_glyphs())
2556 .map(GlyphId::new)
2557 .any(|gid| metrics.h_advance_exact(gid).to_bits() & 0xFFFF != 0);
2558 assert!(fractional, "no advance carried a fraction");
2559 }
2560
2561 #[test]
2562 fn negative_varied_advances_are_clamped_before_scaling() {
2563 const DATA: &[u8] = font_test_data::NEGATIVE_VAR_ADVANCES;
2564 for hide in [&[][..], &[b"HVAR", b"VVAR"][..]] {
2565 let asked = Arc::new(Mutex::new(Vec::new()));
2566 let font = callback_font(DATA, asked.clone(), hide);
2567 for (coord, widths, heights) in [
2570 (0.0, [500.0, 500.0], [1000.0, 1000.0]),
2571 (0.5, [199.5, 550.5], [399.5, 1100.5]),
2572 (1.0, [0.0, 601.0], [0.0, 1201.0]),
2573 ] {
2574 let instance = at(&font, coord);
2575 let metrics = instance.glyph_metrics();
2576 let gids = [GlyphId::new(1), GlyphId::new(2), GlyphId::new(3)];
2577 let widths = [widths[0], widths[1], 0.0];
2578 let heights = [heights[0], heights[1], 0.0];
2579 let mut h_batch = [F48Dot16::ZERO; 3];
2580 let mut v_batch = [F48Dot16::ZERO; 3];
2581 metrics.h_advance_batched(|v| v, gids.into_iter().zip(h_batch.iter_mut()));
2582 metrics.v_advance_batched(|v| v, gids.into_iter().zip(v_batch.iter_mut()));
2583 for (i, gid) in gids.into_iter().enumerate() {
2584 assert_eq!(metrics.h_advance_exact(gid), F48Dot16::from_f64(widths[i]));
2585 assert_eq!(metrics.v_advance_exact(gid), F48Dot16::from_f64(heights[i]));
2586 assert_eq!(h_batch[i], metrics.h_advance_exact(gid));
2587 assert_eq!(v_batch[i], metrics.v_advance_exact(gid));
2588 assert_eq!(metrics.h_advance(gid), widths[i] as f32);
2589 assert_eq!(metrics.v_advance(gid), heights[i] as f32);
2590 }
2591 for factor in [-2.0, -1.0, 0.0, 1.0, 2.0] {
2592 let scaled =
2593 metrics.scaled(ScaleF32::new(factor * 1000.0, -factor * 1000.0, 1000));
2594 let mut h_batch = [0.0; 3];
2595 let mut v_batch = [0.0; 3];
2596 scaled.h_advance_batched(|v| v, gids.into_iter().zip(h_batch.iter_mut()));
2597 scaled.v_advance_batched(|v| v, gids.into_iter().zip(v_batch.iter_mut()));
2598 for (i, gid) in gids.into_iter().enumerate() {
2599 assert_eq!(scaled.h_advance(gid), widths[i] as f32 * factor);
2600 assert_eq!(scaled.v_advance(gid), heights[i] as f32 * -factor);
2601 assert_eq!(h_batch[i], scaled.h_advance(gid));
2602 assert_eq!(v_batch[i], scaled.v_advance(gid));
2603 }
2604 }
2605 }
2606 if !hide.is_empty() {
2607 assert!(asked_for(&asked, b"gvar"));
2608 }
2609 }
2610 }
2611
2612 #[test]
2613 fn a_batch_agrees_with_one_at_a_time_at_a_location() {
2614 let font = Font::new(VAR, 0).unwrap();
2615 let instance = at(&font, -0.6);
2616 let metrics = instance.glyph_metrics();
2617 let gids: Vec<_> = (0..16u32).map(GlyphId::new).collect();
2618 let mut batch = vec![F48Dot16::ZERO; 16];
2619 metrics.h_advance_batched(|v| v, gids.iter().copied().zip(batch.iter_mut()));
2620 for (gid, expected) in gids.iter().zip(&batch) {
2621 assert_eq!(metrics.h_advance_exact(*gid), *expected);
2622 }
2623 }
2624
2625 #[test]
2626 fn gvar_answers_the_same_as_hvar() {
2627 let font = Font::new(VAR, 0).unwrap();
2632 let asked = Arc::new(Mutex::new(Vec::new()));
2633 let no_hvar = callback_font(VAR, asked.clone(), &[b"HVAR"]);
2634 for coord in [-1.0, -0.8, -0.25, 0.75, 1.0] {
2635 let (with, without) = (at(&font, coord), at(&no_hvar, coord));
2636 let (with, without) = (with.glyph_metrics(), without.glyph_metrics());
2637 for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2638 assert_eq!(
2639 with.h_advance_exact(gid),
2640 without.h_advance_exact(gid),
2641 "glyph {gid} disagrees at {coord}"
2642 );
2643 }
2644 }
2645 assert!(asked_for(&asked, b"gvar"));
2647 }
2648
2649 #[test]
2650 fn a_default_location_reads_no_variation_table() {
2651 let asked = Arc::new(Mutex::new(Vec::new()));
2655 let font = callback_font(VAR, asked.clone(), &[]);
2656 let metrics = font.glyph_metrics();
2657 for gid in (0..8).map(GlyphId::new) {
2658 let _ = metrics.h_advance_exact(gid);
2659 }
2660 assert!(!asked_for(&asked, b"gvar"));
2661 assert!(!asked_for(&asked, b"HVAR"));
2662 assert!(asked_for(&asked, b"hmtx"), "but it should read hmtx");
2663 }
2664
2665 #[test]
2666 fn an_instance_at_the_default_location_reads_no_variation_table() {
2667 let asked = Arc::new(Mutex::new(Vec::new()));
2668 let font = callback_font(VAR, asked.clone(), &[]);
2669 let instance = at(&font, 0.0);
2672 assert!(instance.normalized_coords().is_empty());
2673 let _ = instance.glyph_metrics().h_advance_exact(GlyphId::new(1));
2674 assert!(!asked_for(&asked, b"gvar"));
2675 assert!(!asked_for(&asked, b"HVAR"));
2676 }
2677
2678 #[test]
2679 fn hvar_answering_keeps_gvar_and_the_outlines_unread() {
2680 let asked = Arc::new(Mutex::new(Vec::new()));
2681 let font = callback_font(VAR, asked.clone(), &[]);
2682 let _ = at(&font, -0.75)
2683 .glyph_metrics()
2684 .h_advance_exact(GlyphId::new(1));
2685 assert!(asked_for(&asked, b"HVAR"));
2686 for cold in [b"gvar", b"glyf", b"loca"] {
2687 assert!(
2688 !asked_for(&asked, cold),
2689 "HVAR answered, so {} should never have been copied",
2690 Tag::new(cold)
2691 );
2692 }
2693 }
2694
2695 #[test]
2696 fn taking_the_metrics_reads_hmtx_but_nothing_that_varies() {
2697 let asked = Arc::new(Mutex::new(Vec::new()));
2700 let font = callback_font(VAR, asked.clone(), &[]);
2701 let instance = at(&font, -0.75);
2702 let metrics = instance.glyph_metrics();
2703 assert!(asked_for(&asked, b"hmtx"));
2704 for cold in [b"HVAR", b"gvar", b"glyf", b"loca"] {
2705 assert!(!asked_for(&asked, cold), "read {}", Tag::new(cold));
2706 }
2707 let _ = metrics.h_advance_exact(GlyphId::new(1));
2709 assert!(asked_for(&asked, b"HVAR"));
2710 assert!(!asked_for(&asked, b"gvar"));
2711 }
2712}