xref: /aosp_15_r20/external/skia/src/ports/fontations/src/ffi.rs (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 use ffi::{FillLinearParams, FillRadialParams};
2 // Copyright 2023 Google LLC
3 // Use of this source code is governed by a BSD-style license that can be found
4 // in the LICENSE file.
5 use font_types::{BoundingBox, GlyphId};
6 use read_fonts::{
7     tables::{colr::CompositeMode, cpal::Cpal, os2::SelectionFlags},
8     FileRef, FontRef, ReadError, TableProvider,
9 };
10 use skrifa::{
11     attribute::Style,
12     charmap::MappingIndex,
13     color::{Brush, ColorGlyphFormat, ColorPainter, Transform},
14     instance::{Location, Size},
15     metrics::{GlyphMetrics, Metrics},
16     outline::{
17         pen::NullPen, DrawSettings, Engine, HintingInstance, HintingOptions, OutlinePen,
18         SmoothMode, Target,
19     },
20     setting::VariationSetting,
21     string::{LocalizedStrings, StringId},
22     MetadataProvider, OutlineGlyphCollection, Tag,
23 };
24 use std::pin::Pin;
25 
26 use crate::bitmap::{bitmap_glyph, bitmap_metrics, has_bitmap_glyph, png_data, BridgeBitmapGlyph};
27 
28 use crate::ffi::{
29     AxisWrapper, BridgeFontStyle, BridgeLocalizedName, BridgeScalerMetrics, ClipBox,
30     ColorPainterWrapper, ColorStop, FfiPoint, PaletteOverride, SkiaDesignCoordinate,
31 };
32 
33 const PATH_EXTRACTION_RESERVE: usize = 150;
34 
make_mapping_index<'a>(font_ref: &'a BridgeFontRef) -> Box<BridgeMappingIndex>35 fn make_mapping_index<'a>(font_ref: &'a BridgeFontRef) -> Box<BridgeMappingIndex> {
36     font_ref
37         .with_font(|f| Some(Box::new(BridgeMappingIndex(MappingIndex::new(f)))))
38         .unwrap()
39 }
40 
no_hinting_instance<'a>() -> Box<BridgeHintingInstance>41 unsafe fn no_hinting_instance<'a>() -> Box<BridgeHintingInstance> {
42     Box::new(BridgeHintingInstance(None))
43 }
44 
make_hinting_instance<'a>( outlines: &BridgeOutlineCollection, size: f32, coords: &BridgeNormalizedCoords, do_light_hinting: bool, do_lcd_antialiasing: bool, lcd_orientation_vertical: bool, force_autohinting: bool, ) -> Box<BridgeHintingInstance>45 unsafe fn make_hinting_instance<'a>(
46     outlines: &BridgeOutlineCollection,
47     size: f32,
48     coords: &BridgeNormalizedCoords,
49     do_light_hinting: bool,
50     do_lcd_antialiasing: bool,
51     lcd_orientation_vertical: bool,
52     force_autohinting: bool,
53 ) -> Box<BridgeHintingInstance> {
54     let hinting_instance = match &outlines.0 {
55         Some(outlines) => {
56             let smooth_mode = match (
57                 do_light_hinting,
58                 do_lcd_antialiasing,
59                 lcd_orientation_vertical,
60             ) {
61                 (true, _, _) => SmoothMode::Light,
62                 (false, true, false) => SmoothMode::Lcd,
63                 (false, true, true) => SmoothMode::VerticalLcd,
64                 _ => SmoothMode::Normal,
65             };
66 
67             let hinting_target = Target::Smooth {
68                 mode: smooth_mode,
69                 // See https://docs.rs/skrifa/latest/skrifa/outline/enum.Target.html#variant.Smooth.field.mode
70                 // Configure additional params to match FreeType.
71                 symmetric_rendering: true,
72                 preserve_linear_metrics: false,
73             };
74 
75             let engine_type = if force_autohinting {
76                 Engine::Auto(None)
77             } else {
78                 Engine::AutoFallback
79             };
80 
81             HintingInstance::new(
82                 outlines,
83                 Size::new(size),
84                 &coords.normalized_coords,
85                 HintingOptions {
86                     engine: engine_type,
87                     target: hinting_target,
88                 },
89             )
90             .ok()
91         }
92         _ => None,
93     };
94     Box::new(BridgeHintingInstance(hinting_instance))
95 }
96 
make_mono_hinting_instance<'a>( outlines: &BridgeOutlineCollection, size: f32, coords: &BridgeNormalizedCoords, ) -> Box<BridgeHintingInstance>97 unsafe fn make_mono_hinting_instance<'a>(
98     outlines: &BridgeOutlineCollection,
99     size: f32,
100     coords: &BridgeNormalizedCoords,
101 ) -> Box<BridgeHintingInstance> {
102     let hinting_instance = outlines.0.as_ref().and_then(|outlines| {
103         HintingInstance::new(
104             outlines,
105             Size::new(size),
106             &coords.normalized_coords,
107             skrifa::outline::HintingMode::Strong,
108         )
109         .ok()
110     });
111     Box::new(BridgeHintingInstance(hinting_instance))
112 }
113 
lookup_glyph_or_zero(font_ref: &BridgeFontRef, map: &BridgeMappingIndex, codepoint: u32) -> u16114 fn lookup_glyph_or_zero(font_ref: &BridgeFontRef, map: &BridgeMappingIndex, codepoint: u32) -> u16 {
115     font_ref
116         .with_font(|f| {
117             let glyph_id = map.0.charmap(f).map(codepoint)?.to_u32();
118             // Remove conversion and change return type to u32 when
119             // implementing large glyph id support in Skia.
120             glyph_id.try_into().ok()
121         })
122         .unwrap_or_default()
123 }
124 
num_glyphs(font_ref: &BridgeFontRef) -> u16125 fn num_glyphs(font_ref: &BridgeFontRef) -> u16 {
126     font_ref
127         .with_font(|f| Some(f.maxp().ok()?.num_glyphs()))
128         .unwrap_or_default()
129 }
130 
fill_glyph_to_unicode_map(font_ref: &BridgeFontRef, map: &mut [u32])131 fn fill_glyph_to_unicode_map(font_ref: &BridgeFontRef, map: &mut [u32]) {
132     map.fill(0);
133     font_ref.with_font(|f| {
134         let mappings = f.charmap().mappings();
135         for item in mappings {
136             if map[item.1.to_u32() as usize] == 0 {
137                 map[item.1.to_u32() as usize] = item.0;
138             }
139         }
140         Some(())
141     });
142 }
143 
144 struct VerbsPointsPen<'a> {
145     verbs: &'a mut Vec<u8>,
146     points: &'a mut Vec<FfiPoint>,
147     started: bool,
148     current: FfiPoint,
149 }
150 
151 impl FfiPoint {
new(x: f32, y: f32) -> Self152     fn new(x: f32, y: f32) -> Self {
153         Self { x, y }
154     }
155 }
156 
157 // Values need to match SkPathVerb.
158 #[repr(u8)]
159 enum PathVerb {
160     MoveTo = 0,
161     LineTo = 1,
162     QuadTo = 2,
163     CubicTo = 4,
164     Close = 5,
165 }
166 
167 impl<'a> VerbsPointsPen<'a> {
new(verbs: &'a mut Vec<u8>, points: &'a mut Vec<FfiPoint>) -> Self168     fn new(verbs: &'a mut Vec<u8>, points: &'a mut Vec<FfiPoint>) -> Self {
169         verbs.clear();
170         points.clear();
171         verbs.reserve(PATH_EXTRACTION_RESERVE);
172         points.reserve(PATH_EXTRACTION_RESERVE);
173         Self {
174             verbs,
175             points,
176             started: false,
177             current: FfiPoint::default(),
178         }
179     }
180 
going_to(&mut self, point: &FfiPoint)181     fn going_to(&mut self, point: &FfiPoint) {
182         if !self.started {
183             self.started = true;
184             self.verbs.push(PathVerb::MoveTo as u8);
185             self.points.push(self.current);
186         }
187         self.current = *point;
188     }
189 
current_is_not(&self, point: &FfiPoint) -> bool190     fn current_is_not(&self, point: &FfiPoint) -> bool {
191         self.current != *point
192     }
193 }
194 
195 impl<'a> OutlinePen for VerbsPointsPen<'a> {
move_to(&mut self, x: f32, y: f32)196     fn move_to(&mut self, x: f32, y: f32) {
197         let pt0 = FfiPoint::new(x, -y);
198         if self.started {
199             self.close();
200             self.started = false;
201         }
202         self.current = pt0;
203     }
204 
line_to(&mut self, x: f32, y: f32)205     fn line_to(&mut self, x: f32, y: f32) {
206         let pt0 = FfiPoint::new(x, -y);
207         if self.current_is_not(&pt0) {
208             self.going_to(&pt0);
209             self.verbs.push(PathVerb::LineTo as u8);
210             self.points.push(pt0);
211         }
212     }
213 
quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32)214     fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
215         let pt0 = FfiPoint::new(cx0, -cy0);
216         let pt1 = FfiPoint::new(x, -y);
217         if self.current_is_not(&pt0) || self.current_is_not(&pt1) {
218             self.going_to(&pt1);
219             self.verbs.push(PathVerb::QuadTo as u8);
220             self.points.push(pt0);
221             self.points.push(pt1);
222         }
223     }
224 
curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32)225     fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
226         let pt0 = FfiPoint::new(cx0, -cy0);
227         let pt1 = FfiPoint::new(cx1, -cy1);
228         let pt2 = FfiPoint::new(x, -y);
229         if self.current_is_not(&pt0) || self.current_is_not(&pt1) || self.current_is_not(&pt2) {
230             self.going_to(&pt2);
231             self.verbs.push(PathVerb::CubicTo as u8);
232             self.points.push(pt0);
233             self.points.push(pt1);
234             self.points.push(pt2);
235         }
236     }
237 
close(&mut self)238     fn close(&mut self) {
239         if let Some(verb) = self.verbs.last().cloned() {
240             if verb == PathVerb::QuadTo as u8
241                 || verb == PathVerb::CubicTo as u8
242                 || verb == PathVerb::LineTo as u8
243                 || verb == PathVerb::MoveTo as u8
244             {
245                 self.verbs.push(PathVerb::Close as u8);
246             }
247         }
248     }
249 }
250 
251 struct ColorPainterImpl<'a> {
252     color_painter_wrapper: Pin<&'a mut ffi::ColorPainterWrapper>,
253     clip_level: usize,
254 }
255 
256 impl<'a> ColorPainter for ColorPainterImpl<'a> {
push_transform(&mut self, transform: Transform)257     fn push_transform(&mut self, transform: Transform) {
258         if self.clip_level > 0 {
259             return;
260         }
261         self.color_painter_wrapper
262             .as_mut()
263             .push_transform(&ffi::Transform {
264                 xx: transform.xx,
265                 xy: transform.xy,
266                 yx: transform.yx,
267                 yy: transform.yy,
268                 dx: transform.dx,
269                 dy: transform.dy,
270             });
271     }
272 
pop_transform(&mut self)273     fn pop_transform(&mut self) {
274         if self.clip_level > 0 {
275             return;
276         }
277         self.color_painter_wrapper.as_mut().pop_transform();
278     }
279 
push_clip_glyph(&mut self, glyph: GlyphId)280     fn push_clip_glyph(&mut self, glyph: GlyphId) {
281         if self.clip_level == 0 {
282             // TODO(drott): Handle large glyph ids in clip operation.
283             self.color_painter_wrapper
284                 .as_mut()
285                 .push_clip_glyph(glyph.to_u32().try_into().ok().unwrap_or_default());
286         }
287         if self.color_painter_wrapper.as_mut().is_bounds_mode() {
288             self.clip_level += 1;
289         }
290     }
291 
push_clip_box(&mut self, clip_box: BoundingBox<f32>)292     fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
293         if self.clip_level == 0 {
294             self.color_painter_wrapper.as_mut().push_clip_rectangle(
295                 clip_box.x_min,
296                 clip_box.y_min,
297                 clip_box.x_max,
298                 clip_box.y_max,
299             );
300         }
301         if self.color_painter_wrapper.as_mut().is_bounds_mode() {
302             self.clip_level += 1;
303         }
304     }
305 
pop_clip(&mut self)306     fn pop_clip(&mut self) {
307         if self.color_painter_wrapper.as_mut().is_bounds_mode() {
308             self.clip_level -= 1;
309         }
310         if self.clip_level == 0 {
311             self.color_painter_wrapper.as_mut().pop_clip();
312         }
313     }
314 
fill(&mut self, fill_type: Brush)315     fn fill(&mut self, fill_type: Brush) {
316         if self.clip_level > 0 {
317             return;
318         }
319         let color_painter = self.color_painter_wrapper.as_mut();
320         match fill_type {
321             Brush::Solid {
322                 palette_index,
323                 alpha,
324             } => {
325                 color_painter.fill_solid(palette_index, alpha);
326             }
327 
328             Brush::LinearGradient {
329                 p0,
330                 p1,
331                 color_stops,
332                 extend,
333             } => {
334                 let mut bridge_color_stops = BridgeColorStops {
335                     stops_iterator: Box::new(color_stops.iter()),
336                     num_stops: color_stops.len(),
337                 };
338                 color_painter.fill_linear(
339                     &FillLinearParams {
340                         x0: p0.x,
341                         y0: p0.y,
342                         x1: p1.x,
343                         y1: p1.y,
344                     },
345                     &mut bridge_color_stops,
346                     extend as u8,
347                 );
348             }
349             Brush::RadialGradient {
350                 c0,
351                 r0,
352                 c1,
353                 r1,
354                 color_stops,
355                 extend,
356             } => {
357                 let mut bridge_color_stops = BridgeColorStops {
358                     stops_iterator: Box::new(color_stops.iter()),
359                     num_stops: color_stops.len(),
360                 };
361                 color_painter.fill_radial(
362                     &FillRadialParams {
363                         x0: c0.x,
364                         y0: c0.y,
365                         r0,
366                         x1: c1.x,
367                         y1: c1.y,
368                         r1,
369                     },
370                     &mut bridge_color_stops,
371                     extend as u8,
372                 );
373             }
374             Brush::SweepGradient {
375                 c0,
376                 start_angle,
377                 end_angle,
378                 color_stops,
379                 extend,
380             } => {
381                 let mut bridge_color_stops = BridgeColorStops {
382                     stops_iterator: Box::new(color_stops.iter()),
383                     num_stops: color_stops.len(),
384                 };
385                 color_painter.fill_sweep(
386                     &ffi::FillSweepParams {
387                         x0: c0.x,
388                         y0: c0.y,
389                         start_angle,
390                         end_angle,
391                     },
392                     &mut bridge_color_stops,
393                     extend as u8,
394                 );
395             }
396         }
397     }
398 
fill_glyph(&mut self, glyph: GlyphId, brush_transform: Option<Transform>, brush: Brush)399     fn fill_glyph(&mut self, glyph: GlyphId, brush_transform: Option<Transform>, brush: Brush) {
400         if self.color_painter_wrapper.as_mut().is_bounds_mode() {
401             self.push_clip_glyph(glyph);
402             self.pop_clip();
403             return;
404         }
405 
406         let color_painter = self.color_painter_wrapper.as_mut();
407         let brush_transform = brush_transform.unwrap_or_default();
408         match brush {
409             Brush::Solid {
410                 palette_index,
411                 alpha,
412             } => {
413                 // TODO(drott): Handle large glyph ids in fill glyph operation.
414                 color_painter.fill_glyph_solid(
415                     glyph.to_u32().try_into().ok().unwrap_or_default(),
416                     palette_index,
417                     alpha,
418                 );
419             }
420             Brush::LinearGradient {
421                 p0,
422                 p1,
423                 color_stops,
424                 extend,
425             } => {
426                 let mut bridge_color_stops = BridgeColorStops {
427                     stops_iterator: Box::new(color_stops.iter()),
428                     num_stops: color_stops.len(),
429                 };
430                 color_painter.fill_glyph_linear(
431                     // TODO(drott): Handle large glyph ids in fill glyph operation.
432                     glyph.to_u32().try_into().ok().unwrap_or_default(),
433                     &ffi::Transform {
434                         xx: brush_transform.xx,
435                         xy: brush_transform.xy,
436                         yx: brush_transform.yx,
437                         yy: brush_transform.yy,
438                         dx: brush_transform.dx,
439                         dy: brush_transform.dy,
440                     },
441                     &FillLinearParams {
442                         x0: p0.x,
443                         y0: p0.y,
444                         x1: p1.x,
445                         y1: p1.y,
446                     },
447                     &mut bridge_color_stops,
448                     extend as u8,
449                 );
450             }
451             Brush::RadialGradient {
452                 c0,
453                 r0,
454                 c1,
455                 r1,
456                 color_stops,
457                 extend,
458             } => {
459                 let mut bridge_color_stops = BridgeColorStops {
460                     stops_iterator: Box::new(color_stops.iter()),
461                     num_stops: color_stops.len(),
462                 };
463                 color_painter.fill_glyph_radial(
464                     // TODO(drott): Handle large glyph ids in fill glyph operation.
465                     glyph.to_u32().try_into().ok().unwrap_or_default(),
466                     &ffi::Transform {
467                         xx: brush_transform.xx,
468                         xy: brush_transform.xy,
469                         yx: brush_transform.yx,
470                         yy: brush_transform.yy,
471                         dx: brush_transform.dx,
472                         dy: brush_transform.dy,
473                     },
474                     &FillRadialParams {
475                         x0: c0.x,
476                         y0: c0.y,
477                         r0,
478                         x1: c1.x,
479                         y1: c1.y,
480                         r1,
481                     },
482                     &mut bridge_color_stops,
483                     extend as u8,
484                 );
485             }
486             Brush::SweepGradient {
487                 c0,
488                 start_angle,
489                 end_angle,
490                 color_stops,
491                 extend,
492             } => {
493                 let mut bridge_color_stops = BridgeColorStops {
494                     stops_iterator: Box::new(color_stops.iter()),
495                     num_stops: color_stops.len(),
496                 };
497                 color_painter.fill_glyph_sweep(
498                     // TODO(drott): Handle large glyph ids in fill glyph operation.
499                     glyph.to_u32().try_into().ok().unwrap_or_default(),
500                     &ffi::Transform {
501                         xx: brush_transform.xx,
502                         xy: brush_transform.xy,
503                         yx: brush_transform.yx,
504                         yy: brush_transform.yy,
505                         dx: brush_transform.dx,
506                         dy: brush_transform.dy,
507                     },
508                     &ffi::FillSweepParams {
509                         x0: c0.x,
510                         y0: c0.y,
511                         start_angle,
512                         end_angle,
513                     },
514                     &mut bridge_color_stops,
515                     extend as u8,
516                 );
517             }
518         }
519     }
520 
push_layer(&mut self, composite_mode: CompositeMode)521     fn push_layer(&mut self, composite_mode: CompositeMode) {
522         if self.clip_level > 0 {
523             return;
524         }
525         self.color_painter_wrapper
526             .as_mut()
527             .push_layer(composite_mode as u8);
528     }
pop_layer(&mut self)529     fn pop_layer(&mut self) {
530         if self.clip_level > 0 {
531             return;
532         }
533         self.color_painter_wrapper.as_mut().pop_layer();
534     }
535 }
536 
get_path_verbs_points( outlines: &BridgeOutlineCollection, glyph_id: u16, size: f32, coords: &BridgeNormalizedCoords, hinting_instance: &BridgeHintingInstance, verbs: &mut Vec<u8>, points: &mut Vec<FfiPoint>, scaler_metrics: &mut BridgeScalerMetrics, ) -> bool537 fn get_path_verbs_points(
538     outlines: &BridgeOutlineCollection,
539     glyph_id: u16,
540     size: f32,
541     coords: &BridgeNormalizedCoords,
542     hinting_instance: &BridgeHintingInstance,
543     verbs: &mut Vec<u8>,
544     points: &mut Vec<FfiPoint>,
545     scaler_metrics: &mut BridgeScalerMetrics,
546 ) -> bool {
547     outlines
548         .0
549         .as_ref()
550         .and_then(|outlines| {
551             let glyph = outlines.get(GlyphId::from(glyph_id))?;
552 
553             let draw_settings = match &hinting_instance.0 {
554                 Some(instance) => DrawSettings::hinted(instance, false),
555                 _ => DrawSettings::unhinted(Size::new(size), &coords.normalized_coords),
556             };
557 
558             let mut verbs_points_pen = VerbsPointsPen::new(verbs, points);
559             match glyph.draw(draw_settings, &mut verbs_points_pen) {
560                 Err(_) => None,
561                 Ok(metrics) => {
562                     scaler_metrics.has_overlaps = metrics.has_overlaps;
563                     Some(())
564                 }
565             }
566         })
567         .is_some()
568 }
569 
shrink_verbs_points_if_needed(verbs: &mut Vec<u8>, points: &mut Vec<FfiPoint>)570 fn shrink_verbs_points_if_needed(verbs: &mut Vec<u8>, points: &mut Vec<FfiPoint>) {
571     verbs.shrink_to(PATH_EXTRACTION_RESERVE);
572     points.shrink_to(PATH_EXTRACTION_RESERVE);
573 }
574 
unhinted_advance_width_or_zero( font_ref: &BridgeFontRef, size: f32, coords: &BridgeNormalizedCoords, glyph_id: u16, ) -> f32575 fn unhinted_advance_width_or_zero(
576     font_ref: &BridgeFontRef,
577     size: f32,
578     coords: &BridgeNormalizedCoords,
579     glyph_id: u16,
580 ) -> f32 {
581     font_ref
582         .with_font(|f| {
583             GlyphMetrics::new(f, Size::new(size), coords.normalized_coords.coords())
584                 .advance_width(GlyphId::from(glyph_id))
585         })
586         .unwrap_or_default()
587 }
588 
scaler_hinted_advance_width( outlines: &BridgeOutlineCollection, hinting_instance: &BridgeHintingInstance, glyph_id: u16, out_advance_width: &mut f32, ) -> bool589 fn scaler_hinted_advance_width(
590     outlines: &BridgeOutlineCollection,
591     hinting_instance: &BridgeHintingInstance,
592     glyph_id: u16,
593     out_advance_width: &mut f32,
594 ) -> bool {
595     hinting_instance
596         .0
597         .as_ref()
598         .and_then(|instance| {
599             let draw_settings = DrawSettings::hinted(instance, false);
600 
601             let outlines = outlines.0.as_ref()?;
602             let glyph = outlines.get(GlyphId::from(glyph_id))?;
603             let mut null_pen = NullPen {};
604             let adjusted_metrics = glyph.draw(draw_settings, &mut null_pen).ok()?;
605             adjusted_metrics.advance_width.map(|adjusted_advance| {
606                 *out_advance_width = adjusted_advance;
607                 ()
608             })
609         })
610         .is_some()
611 }
612 
units_per_em_or_zero(font_ref: &BridgeFontRef) -> u16613 fn units_per_em_or_zero(font_ref: &BridgeFontRef) -> u16 {
614     font_ref
615         .with_font(|f| Some(f.head().ok()?.units_per_em()))
616         .unwrap_or_default()
617 }
618 
convert_metrics(skrifa_metrics: &Metrics) -> ffi::Metrics619 fn convert_metrics(skrifa_metrics: &Metrics) -> ffi::Metrics {
620     ffi::Metrics {
621         top: skrifa_metrics.bounds.map_or(0.0, |b| b.y_max),
622         bottom: skrifa_metrics.bounds.map_or(0.0, |b| b.y_min),
623         x_min: skrifa_metrics.bounds.map_or(0.0, |b| b.x_min),
624         x_max: skrifa_metrics.bounds.map_or(0.0, |b| b.x_max),
625         ascent: skrifa_metrics.ascent,
626         descent: skrifa_metrics.descent,
627         leading: skrifa_metrics.leading,
628         avg_char_width: skrifa_metrics.average_width.unwrap_or(0.0),
629         max_char_width: skrifa_metrics.max_width.unwrap_or(0.0),
630         x_height: -skrifa_metrics.x_height.unwrap_or(0.0),
631         cap_height: -skrifa_metrics.cap_height.unwrap_or(0.0),
632         underline_position: skrifa_metrics.underline.map_or(f32::NAN, |u| u.offset),
633         underline_thickness: skrifa_metrics.underline.map_or(f32::NAN, |u| u.thickness),
634         strikeout_position: skrifa_metrics.strikeout.map_or(f32::NAN, |s| s.offset),
635         strikeout_thickness: skrifa_metrics.strikeout.map_or(f32::NAN, |s| s.thickness),
636     }
637 }
638 
get_skia_metrics( font_ref: &BridgeFontRef, size: f32, coords: &BridgeNormalizedCoords, ) -> ffi::Metrics639 fn get_skia_metrics(
640     font_ref: &BridgeFontRef,
641     size: f32,
642     coords: &BridgeNormalizedCoords,
643 ) -> ffi::Metrics {
644     font_ref
645         .with_font(|f| {
646             let fontations_metrics =
647                 Metrics::new(f, Size::new(size), coords.normalized_coords.coords());
648             Some(convert_metrics(&fontations_metrics))
649         })
650         .unwrap_or_default()
651 }
652 
get_unscaled_metrics(font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords) -> ffi::Metrics653 fn get_unscaled_metrics(font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords) -> ffi::Metrics {
654     font_ref
655         .with_font(|f| {
656             let fontations_metrics =
657                 Metrics::new(f, Size::unscaled(), coords.normalized_coords.coords());
658             Some(convert_metrics(&fontations_metrics))
659         })
660         .unwrap_or_default()
661 }
662 
get_localized_strings<'a>(font_ref: &'a BridgeFontRef<'a>) -> Box<BridgeLocalizedStrings<'a>>663 fn get_localized_strings<'a>(font_ref: &'a BridgeFontRef<'a>) -> Box<BridgeLocalizedStrings<'a>> {
664     Box::new(BridgeLocalizedStrings {
665         localized_strings: font_ref
666             .with_font(|f| Some(f.localized_strings(StringId::FAMILY_NAME)))
667             .unwrap_or_default(),
668     })
669 }
670 
localized_name_next( bridge_localized_strings: &mut BridgeLocalizedStrings, out_localized_name: &mut BridgeLocalizedName, ) -> bool671 fn localized_name_next(
672     bridge_localized_strings: &mut BridgeLocalizedStrings,
673     out_localized_name: &mut BridgeLocalizedName,
674 ) -> bool {
675     match bridge_localized_strings.localized_strings.next() {
676         Some(localized_string) => {
677             out_localized_name.string = localized_string.to_string();
678             // TODO(b/307906051): Remove the suffix before shipping.
679             out_localized_name.string.push_str(" (Fontations)");
680             out_localized_name.language = localized_string
681                 .language()
682                 .map(|l| l.to_string())
683                 .unwrap_or_default();
684             true
685         }
686         _ => false,
687     }
688 }
689 
english_or_first_font_name(font_ref: &BridgeFontRef, name_id: StringId) -> Option<String>690 fn english_or_first_font_name(font_ref: &BridgeFontRef, name_id: StringId) -> Option<String> {
691     font_ref.with_font(|f| {
692         f.localized_strings(name_id)
693             .english_or_first()
694             .map(|localized_string| localized_string.to_string())
695     })
696 }
697 
family_name(font_ref: &BridgeFontRef) -> String698 fn family_name(font_ref: &BridgeFontRef) -> String {
699     font_ref
700         .with_font(|f| {
701             // https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection
702             // Bit 8 of the `fsSelection' field in the `OS/2' table indicates a WWS-only font face.
703             // When this bit is set it means *do not* use the WWS strings.
704             let use_wws = !f
705                 .os2()
706                 .map(|t| t.fs_selection().contains(SelectionFlags::WWS))
707                 .unwrap_or_default();
708             use_wws
709                 .then(|| english_or_first_font_name(font_ref, StringId::WWS_FAMILY_NAME))
710                 .flatten()
711                 .or_else(|| english_or_first_font_name(font_ref, StringId::TYPOGRAPHIC_FAMILY_NAME))
712                 .or_else(|| english_or_first_font_name(font_ref, StringId::FAMILY_NAME))
713         })
714         .unwrap_or_default()
715 }
716 
postscript_name(font_ref: &BridgeFontRef, out_string: &mut String) -> bool717 fn postscript_name(font_ref: &BridgeFontRef, out_string: &mut String) -> bool {
718     let postscript_name = english_or_first_font_name(font_ref, StringId::POSTSCRIPT_NAME);
719     match postscript_name {
720         Some(name) => {
721             *out_string = name;
722             true
723         }
724         _ => false,
725     }
726 }
727 
resolve_palette( font_ref: &BridgeFontRef, base_palette: u16, palette_overrides: &[PaletteOverride], ) -> Vec<u32>728 fn resolve_palette(
729     font_ref: &BridgeFontRef,
730     base_palette: u16,
731     palette_overrides: &[PaletteOverride],
732 ) -> Vec<u32> {
733     let cpal_to_vector = |cpal: &Cpal, palette_index| -> Option<Vec<u32>> {
734         let start_index: usize = cpal
735             .color_record_indices()
736             .get(usize::from(palette_index))?
737             .get()
738             .into();
739         let num_entries: usize = cpal.num_palette_entries().into();
740         let color_records = cpal.color_records_array()?.ok()?;
741         Some(
742             color_records
743                 .get(start_index..start_index + num_entries)?
744                 .iter()
745                 .map(|record| {
746                     u32::from_be_bytes([record.alpha, record.red, record.green, record.blue])
747                 })
748                 .collect(),
749         )
750     };
751 
752     font_ref
753         .with_font(|f| {
754             let cpal = f.cpal().ok()?;
755 
756             let mut palette = cpal_to_vector(&cpal, base_palette).or(cpal_to_vector(&cpal, 0))?;
757 
758             for override_entry in palette_overrides {
759                 let index = override_entry.index as usize;
760                 if index < palette.len() {
761                     palette[index] = override_entry.color_8888;
762                 }
763             }
764             Some(palette)
765         })
766         .unwrap_or_default()
767 }
768 
has_colr_glyph(font_ref: &BridgeFontRef, format: ColorGlyphFormat, glyph_id: u16) -> bool769 fn has_colr_glyph(font_ref: &BridgeFontRef, format: ColorGlyphFormat, glyph_id: u16) -> bool {
770     font_ref
771         .with_font(|f| {
772             let colrv1_paintable = f
773                 .color_glyphs()
774                 .get_with_format(GlyphId::from(glyph_id), format);
775             Some(colrv1_paintable.is_some())
776         })
777         .unwrap_or_default()
778 }
779 
has_colrv1_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool780 fn has_colrv1_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool {
781     has_colr_glyph(font_ref, ColorGlyphFormat::ColrV1, glyph_id)
782 }
783 
has_colrv0_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool784 fn has_colrv0_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool {
785     has_colr_glyph(font_ref, ColorGlyphFormat::ColrV0, glyph_id)
786 }
787 
get_colrv1_clip_box( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, glyph_id: u16, size: f32, clip_box: &mut ClipBox, ) -> bool788 fn get_colrv1_clip_box(
789     font_ref: &BridgeFontRef,
790     coords: &BridgeNormalizedCoords,
791     glyph_id: u16,
792     size: f32,
793     clip_box: &mut ClipBox,
794 ) -> bool {
795     let size = match size {
796         x if x == 0.0 => {
797             return false;
798         }
799         _ => Size::new(size),
800     };
801     font_ref
802         .with_font(|f| {
803             match f
804                 .color_glyphs()
805                 .get_with_format(GlyphId::from(glyph_id), ColorGlyphFormat::ColrV1)?
806                 .bounding_box(coords.normalized_coords.coords(), size)
807             {
808                 Some(bounding_box) => {
809                     *clip_box = ClipBox {
810                         x_min: bounding_box.x_min,
811                         y_min: bounding_box.y_min,
812                         x_max: bounding_box.x_max,
813                         y_max: bounding_box.y_max,
814                     };
815                     Some(true)
816                 }
817                 _ => None,
818             }
819         })
820         .unwrap_or_default()
821 }
822 
823 /// Implements the behavior expected for `SkTypeface::getTableData`, compare
824 /// documentation for this method and the FreeType implementation in Skia.
825 /// * If the target data array is empty, do not copy any data into it, but
826 ///   return the size of the table.
827 /// * If the target data buffer is shorted than from offset to the end of the
828 ///   table, truncate the data.
829 /// * If offset is longer than the table's length, return 0.
table_data(font_ref: &BridgeFontRef, tag: u32, offset: usize, data: &mut [u8]) -> usize830 fn table_data(font_ref: &BridgeFontRef, tag: u32, offset: usize, data: &mut [u8]) -> usize {
831     let table_data = font_ref
832         .with_font(|f| f.table_data(Tag::from_be_bytes(tag.to_be_bytes())))
833         .unwrap_or_default();
834     let table_data = table_data.as_ref();
835     // Remaining table data size measured from offset to end, or 0 if offset is
836     // too large.
837     let mut to_copy_length = table_data.len().saturating_sub(offset);
838     match data.len() {
839         0 => to_copy_length,
840         _ => {
841             to_copy_length = to_copy_length.min(data.len());
842             let table_offset_data = table_data
843                 .get(offset..offset + to_copy_length)
844                 .unwrap_or_default();
845             data.get_mut(..table_offset_data.len())
846                 .map_or(0, |data_slice| {
847                     data_slice.copy_from_slice(table_offset_data);
848                     data_slice.len()
849                 })
850         }
851     }
852 }
853 
table_tags(font_ref: &BridgeFontRef, tags: &mut [u32]) -> u16854 fn table_tags(font_ref: &BridgeFontRef, tags: &mut [u32]) -> u16 {
855     font_ref
856         .with_font(|f| {
857             let table_directory = &f.table_directory;
858             let table_tags_iter = table_directory
859                 .table_records()
860                 .iter()
861                 .map(|table| u32::from_be_bytes(table.tag.get().into_bytes()));
862             tags.iter_mut()
863                 .zip(table_tags_iter)
864                 .for_each(|(out_tag, table_tag)| *out_tag = table_tag);
865             Some(table_directory.num_tables())
866         })
867         .unwrap_or_default()
868 }
869 
variation_position( coords: &BridgeNormalizedCoords, coordinates: &mut [SkiaDesignCoordinate], ) -> isize870 fn variation_position(
871     coords: &BridgeNormalizedCoords,
872     coordinates: &mut [SkiaDesignCoordinate],
873 ) -> isize {
874     if !coordinates.is_empty() {
875         if coords.filtered_user_coords.len() > coordinates.len() {
876             return -1;
877         }
878         let skia_design_coordinates =
879             coords
880                 .filtered_user_coords
881                 .iter()
882                 .map(|setting| SkiaDesignCoordinate {
883                     axis: u32::from_be_bytes(setting.selector.into_bytes()),
884                     value: setting.value,
885                 });
886         for (i, coord) in skia_design_coordinates.enumerate() {
887             coordinates[i] = coord;
888         }
889     }
890     coords.filtered_user_coords.len().try_into().unwrap()
891 }
892 
coordinates_for_shifted_named_instance_index( font_ref: &BridgeFontRef, shifted_index: u32, coords: &mut [SkiaDesignCoordinate], ) -> isize893 fn coordinates_for_shifted_named_instance_index(
894     font_ref: &BridgeFontRef,
895     shifted_index: u32,
896     coords: &mut [SkiaDesignCoordinate],
897 ) -> isize {
898     font_ref
899         .with_font(|f| {
900             let fvar = f.fvar().ok()?;
901             let instances = fvar.instances().ok()?;
902             let index: usize = ((shifted_index >> 16) - 1).try_into().unwrap();
903             let instance_coords = instances.get(index).ok()?.coordinates;
904 
905             if coords.len() != 0 {
906                 if coords.len() < instance_coords.len() {
907                     return None;
908                 }
909                 let axis_coords = f.axes().iter().zip(instance_coords.iter()).enumerate();
910                 for (i, axis_coord) in axis_coords {
911                     coords[i] = SkiaDesignCoordinate {
912                         axis: u32::from_be_bytes(axis_coord.0.tag().to_be_bytes()),
913                         value: axis_coord.1.get().to_f32(),
914                     };
915                 }
916             }
917 
918             Some(instance_coords.len() as isize)
919         })
920         .unwrap_or(-1)
921 }
922 
num_axes(font_ref: &BridgeFontRef) -> usize923 fn num_axes(font_ref: &BridgeFontRef) -> usize {
924     font_ref
925         .with_font(|f| Some(f.axes().len()))
926         .unwrap_or_default()
927 }
928 
populate_axes(font_ref: &BridgeFontRef, mut axis_wrapper: Pin<&mut AxisWrapper>) -> isize929 fn populate_axes(font_ref: &BridgeFontRef, mut axis_wrapper: Pin<&mut AxisWrapper>) -> isize {
930     font_ref
931         .with_font(|f| {
932             let axes = f.axes();
933             // Populate incoming allocated SkFontParameters::Variation::Axis[] only when a
934             // buffer is passed.
935             if axis_wrapper.as_ref().size() > 0 {
936                 for (i, axis) in axes.iter().enumerate() {
937                     if !axis_wrapper.as_mut().populate_axis(
938                         i,
939                         u32::from_be_bytes(axis.tag().into_bytes()),
940                         axis.min_value(),
941                         axis.default_value(),
942                         axis.max_value(),
943                         axis.is_hidden(),
944                     ) {
945                         return None;
946                     }
947                 }
948             }
949             isize::try_from(axes.len()).ok()
950         })
951         .unwrap_or(-1)
952 }
953 
make_font_ref_internal<'a>(font_data: &'a [u8], index: u32) -> Result<FontRef<'a>, ReadError>954 fn make_font_ref_internal<'a>(font_data: &'a [u8], index: u32) -> Result<FontRef<'a>, ReadError> {
955     match FileRef::new(font_data) {
956         Ok(file_ref) => match file_ref {
957             FileRef::Font(font_ref) => {
958                 // Indices with the higher bits set are meaningful here and do not result in an
959                 // error, as they may refer to a named instance and are taken into account by the
960                 // Fontations typeface implementation,
961                 // compare `coordinates_for_shifted_named_instance_index()`.
962                 if index & 0xFFFF > 0 {
963                     Err(ReadError::InvalidCollectionIndex(index))
964                 } else {
965                     Ok(font_ref)
966                 }
967             }
968             FileRef::Collection(collection) => collection.get(index),
969         },
970         Err(e) => Err(e),
971     }
972 }
973 
make_font_ref<'a>(font_data: &'a [u8], index: u32) -> Box<BridgeFontRef<'a>>974 fn make_font_ref<'a>(font_data: &'a [u8], index: u32) -> Box<BridgeFontRef<'a>> {
975     let font = make_font_ref_internal(font_data, index).ok();
976     let has_any_color = font
977         .as_ref()
978         .map(|f| {
979             f.cbdt().is_ok() ||
980             f.sbix().is_ok() ||
981             // ColorGlyphCollection::get_with_format() first thing checks for presence of colr(),
982             // so we do the same:
983             f.colr().is_ok()
984         })
985         .unwrap_or_default();
986 
987     Box::new(BridgeFontRef {
988         font,
989         has_any_color,
990     })
991 }
992 
font_ref_is_valid(bridge_font_ref: &BridgeFontRef) -> bool993 fn font_ref_is_valid(bridge_font_ref: &BridgeFontRef) -> bool {
994     bridge_font_ref.font.is_some()
995 }
996 
has_any_color_table(bridge_font_ref: &BridgeFontRef) -> bool997 fn has_any_color_table(bridge_font_ref: &BridgeFontRef) -> bool {
998     bridge_font_ref.has_any_color
999 }
1000 
get_outline_collection<'a>(font_ref: &'a BridgeFontRef<'a>) -> Box<BridgeOutlineCollection<'a>>1001 fn get_outline_collection<'a>(font_ref: &'a BridgeFontRef<'a>) -> Box<BridgeOutlineCollection<'a>> {
1002     Box::new(
1003         font_ref
1004             .with_font(|f| Some(BridgeOutlineCollection(Some(f.outline_glyphs()))))
1005             .unwrap_or_default(),
1006     )
1007 }
1008 
font_or_collection<'a>(font_data: &'a [u8], num_fonts: &mut u32) -> bool1009 fn font_or_collection<'a>(font_data: &'a [u8], num_fonts: &mut u32) -> bool {
1010     match FileRef::new(font_data) {
1011         Ok(FileRef::Collection(collection)) => {
1012             *num_fonts = collection.len();
1013             true
1014         }
1015         Ok(FileRef::Font(_)) => {
1016             *num_fonts = 0u32;
1017             true
1018         }
1019         _ => false,
1020     }
1021 }
1022 
num_named_instances(font_ref: &BridgeFontRef) -> usize1023 fn num_named_instances(font_ref: &BridgeFontRef) -> usize {
1024     font_ref
1025         .with_font(|f| Some(f.named_instances().len()))
1026         .unwrap_or_default()
1027 }
1028 
resolve_into_normalized_coords( font_ref: &BridgeFontRef, design_coords: &[SkiaDesignCoordinate], ) -> Box<BridgeNormalizedCoords>1029 fn resolve_into_normalized_coords(
1030     font_ref: &BridgeFontRef,
1031     design_coords: &[SkiaDesignCoordinate],
1032 ) -> Box<BridgeNormalizedCoords> {
1033     let variation_tuples = design_coords
1034         .iter()
1035         .map(|coord| (Tag::from_be_bytes(coord.axis.to_be_bytes()), coord.value));
1036     let bridge_normalized_coords = font_ref
1037         .with_font(|f| {
1038             let merged_defaults_with_user = f
1039                 .axes()
1040                 .iter()
1041                 .map(|axis| (axis.tag(), axis.default_value()))
1042                 .chain(design_coords.iter().map(|user_coord| {
1043                     (
1044                         Tag::from_be_bytes(user_coord.axis.to_be_bytes()),
1045                         user_coord.value,
1046                     )
1047                 }));
1048             Some(BridgeNormalizedCoords {
1049                 filtered_user_coords: f.axes().filter(merged_defaults_with_user).collect(),
1050                 normalized_coords: f.axes().location(variation_tuples),
1051             })
1052         })
1053         .unwrap_or_default();
1054     Box::new(bridge_normalized_coords)
1055 }
1056 
normalized_coords_equal(a: &BridgeNormalizedCoords, b: &BridgeNormalizedCoords) -> bool1057 fn normalized_coords_equal(a: &BridgeNormalizedCoords, b: &BridgeNormalizedCoords) -> bool {
1058     a.normalized_coords.coords() == b.normalized_coords.coords()
1059 }
1060 
draw_colr_glyph( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, glyph_id: u16, color_painter: Pin<&mut ColorPainterWrapper>, ) -> bool1061 fn draw_colr_glyph(
1062     font_ref: &BridgeFontRef,
1063     coords: &BridgeNormalizedCoords,
1064     glyph_id: u16,
1065     color_painter: Pin<&mut ColorPainterWrapper>,
1066 ) -> bool {
1067     let mut color_painter_impl = ColorPainterImpl {
1068         color_painter_wrapper: color_painter,
1069         // In bounds mode, we do not need to track or forward to the client anything below the
1070         // first clip layer, as the bounds cannot grow after that.
1071         clip_level: 0,
1072     };
1073     font_ref
1074         .with_font(|f| {
1075             let paintable = f.color_glyphs().get(GlyphId::from(glyph_id))?;
1076             paintable
1077                 .paint(coords.normalized_coords.coords(), &mut color_painter_impl)
1078                 .ok()
1079         })
1080         .is_some()
1081 }
1082 
next_color_stop(color_stops: &mut BridgeColorStops, out_stop: &mut ColorStop) -> bool1083 fn next_color_stop(color_stops: &mut BridgeColorStops, out_stop: &mut ColorStop) -> bool {
1084     if let Some(color_stop) = color_stops.stops_iterator.next() {
1085         out_stop.alpha = color_stop.alpha;
1086         out_stop.stop = color_stop.offset;
1087         out_stop.palette_index = color_stop.palette_index;
1088         true
1089     } else {
1090         false
1091     }
1092 }
1093 
num_color_stops(color_stops: &BridgeColorStops) -> usize1094 fn num_color_stops(color_stops: &BridgeColorStops) -> usize {
1095     color_stops.num_stops
1096 }
1097 
1098 #[allow(non_upper_case_globals)]
get_font_style( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, style: &mut BridgeFontStyle, ) -> bool1099 fn get_font_style(
1100     font_ref: &BridgeFontRef,
1101     coords: &BridgeNormalizedCoords,
1102     style: &mut BridgeFontStyle,
1103 ) -> bool {
1104     const SKIA_SLANT_UPRIGHT: i32 = 0; /* kUpright_Slant */
1105     const SKIA_SLANT_ITALIC: i32 = 1; /* kItalic_Slant */
1106     const SKIA_SLANT_OBLIQUE: i32 = 2; /* kOblique_Slant */
1107 
1108     font_ref
1109         .with_font(|f| {
1110             let attrs = f.attributes();
1111             let mut skia_weight = attrs.weight.value().round() as i32;
1112             let mut skia_slant = match attrs.style {
1113                 Style::Normal => SKIA_SLANT_UPRIGHT,
1114                 Style::Italic => SKIA_SLANT_ITALIC,
1115                 _ => SKIA_SLANT_OBLIQUE,
1116             };
1117             //0.5, 0.625, 0.75, 0.875, 1.0, 1.125, 1.25, 1.5, 2.0 map to 1-9
1118             let mut skia_width = match attrs.stretch.ratio() {
1119                 x if x <= 0.5625 => 1,
1120                 x if x <= 0.6875 => 2,
1121                 x if x <= 0.8125 => 3,
1122                 x if x <= 0.9375 => 4,
1123                 x if x <= 1.0625 => 5,
1124                 x if x <= 1.1875 => 6,
1125                 x if x <= 1.3750 => 7,
1126                 x if x <= 1.7500 => 8,
1127                 _ => 9,
1128             };
1129 
1130             const wght: Tag = Tag::new(b"wght");
1131             const wdth: Tag = Tag::new(b"wdth");
1132             const slnt: Tag = Tag::new(b"slnt");
1133 
1134             for user_coord in coords.filtered_user_coords.iter() {
1135                 match user_coord.selector {
1136                     wght => skia_weight = user_coord.value.round() as i32,
1137                     // 50, 62.5, 75, 87.5, 100, 112.5, 125, 150, 200 map to 1-9
1138                     wdth => {
1139                         skia_width = match user_coord.value {
1140                             x if x <= 56.25 => 1,
1141                             x if x <= 68.75 => 2,
1142                             x if x <= 81.25 => 3,
1143                             x if x <= 93.75 => 4,
1144                             x if x <= 106.25 => 5,
1145                             x if x <= 118.75 => 6,
1146                             x if x <= 137.50 => 7,
1147                             x if x <= 175.00 => 8,
1148                             _ => 9,
1149                         }
1150                     }
1151                     slnt => {
1152                         if skia_slant != SKIA_SLANT_ITALIC {
1153                             if user_coord.value == 0.0 {
1154                                 skia_slant = SKIA_SLANT_UPRIGHT;
1155                             } else {
1156                                 skia_slant = SKIA_SLANT_OBLIQUE
1157                             }
1158                         }
1159                     }
1160                     _ => (),
1161                 }
1162             }
1163 
1164             *style = BridgeFontStyle {
1165                 weight: skia_weight,
1166                 slant: skia_slant,
1167                 width: skia_width,
1168             };
1169             Some(true)
1170         })
1171         .unwrap_or_default()
1172 }
1173 
is_embeddable(font_ref: &BridgeFontRef) -> bool1174 fn is_embeddable(font_ref: &BridgeFontRef) -> bool {
1175     font_ref
1176         .with_font(|f| {
1177             let fs_type = f.os2().ok()?.fs_type();
1178             // https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fstype
1179             // Bit 2 and bit 9 must be cleared, "Restricted License embedding" and
1180             // "Bitmap embedding only" must both be unset.
1181             // Implemented to match SkTypeface_FreeType::onGetAdvancedMetrics.
1182             Some(fs_type & 0x202 == 0)
1183         })
1184         .unwrap_or(true)
1185 }
1186 
is_subsettable(font_ref: &BridgeFontRef) -> bool1187 fn is_subsettable(font_ref: &BridgeFontRef) -> bool {
1188     font_ref
1189         .with_font(|f| {
1190             let fs_type = f.os2().ok()?.fs_type();
1191             // https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fstype
1192             Some((fs_type & 0x100) == 0)
1193         })
1194         .unwrap_or(true)
1195 }
1196 
is_fixed_pitch(font_ref: &BridgeFontRef) -> bool1197 fn is_fixed_pitch(font_ref: &BridgeFontRef) -> bool {
1198     font_ref
1199         .with_font(|f| {
1200             // Compare DWriteFontTypeface::onGetAdvancedMetrics().
1201             Some(
1202                 f.post().ok()?.is_fixed_pitch() != 0
1203                     || f.hhea().ok()?.number_of_long_metrics() == 1,
1204             )
1205         })
1206         .unwrap_or_default()
1207 }
1208 
is_serif_style(font_ref: &BridgeFontRef) -> bool1209 fn is_serif_style(font_ref: &BridgeFontRef) -> bool {
1210     const FAMILY_TYPE_TEXT_AND_DISPLAY: u8 = 2;
1211     const SERIF_STYLE_COVE: u8 = 2;
1212     const SERIF_STYLE_TRIANGLE: u8 = 10;
1213     font_ref
1214         .with_font(|f| {
1215             // Compare DWriteFontTypeface::onGetAdvancedMetrics().
1216             let panose = f.os2().ok()?.panose_10();
1217             let family_type = panose[0];
1218 
1219             match family_type {
1220                 FAMILY_TYPE_TEXT_AND_DISPLAY => {
1221                     let serif_style = panose[1];
1222                     Some((SERIF_STYLE_COVE..=SERIF_STYLE_TRIANGLE).contains(&serif_style))
1223                 }
1224                 _ => None,
1225             }
1226         })
1227         .unwrap_or_default()
1228 }
1229 
is_script_style(font_ref: &BridgeFontRef) -> bool1230 fn is_script_style(font_ref: &BridgeFontRef) -> bool {
1231     const FAMILY_TYPE_SCRIPT: u8 = 3;
1232     font_ref
1233         .with_font(|f| {
1234             // Compare DWriteFontTypeface::onGetAdvancedMetrics().
1235             let family_type = f.os2().ok()?.panose_10()[0];
1236             Some(family_type == FAMILY_TYPE_SCRIPT)
1237         })
1238         .unwrap_or_default()
1239 }
1240 
italic_angle(font_ref: &BridgeFontRef) -> i321241 fn italic_angle(font_ref: &BridgeFontRef) -> i32 {
1242     font_ref
1243         .with_font(|f| Some(f.post().ok()?.italic_angle().to_i32()))
1244         .unwrap_or_default()
1245 }
1246 
1247 pub struct BridgeFontRef<'a> {
1248     font: Option<FontRef<'a>>,
1249     has_any_color: bool,
1250 }
1251 
1252 impl<'a> BridgeFontRef<'a> {
with_font<T>(&'a self, f: impl FnOnce(&'a FontRef) -> Option<T>) -> Option<T>1253     fn with_font<T>(&'a self, f: impl FnOnce(&'a FontRef) -> Option<T>) -> Option<T> {
1254         f(self.font.as_ref()?)
1255     }
1256 }
1257 
1258 #[derive(Default)]
1259 struct BridgeOutlineCollection<'a>(Option<OutlineGlyphCollection<'a>>);
1260 
1261 #[derive(Default)]
1262 struct BridgeNormalizedCoords {
1263     normalized_coords: Location,
1264     filtered_user_coords: Vec<VariationSetting>,
1265 }
1266 
1267 struct BridgeLocalizedStrings<'a> {
1268     #[allow(dead_code)]
1269     localized_strings: LocalizedStrings<'a>,
1270 }
1271 
1272 pub struct BridgeColorStops<'a> {
1273     pub stops_iterator: Box<dyn Iterator<Item = &'a skrifa::color::ColorStop> + 'a>,
1274     pub num_stops: usize,
1275 }
1276 
1277 mod bitmap {
1278 
1279     use read_fonts::{
1280         tables::{
1281             bitmap::{BitmapContent, BitmapData, BitmapDataFormat, BitmapMetrics, BitmapSize},
1282             sbix::{GlyphData, Strike},
1283         },
1284         FontRef, TableProvider,
1285     };
1286 
1287     use font_types::{BoundingBox, GlyphId};
1288     use skrifa::{
1289         instance::{LocationRef, Size},
1290         metrics::GlyphMetrics,
1291     };
1292 
1293     use crate::{ffi::BitmapMetrics as FfiBitmapMetrics, BridgeFontRef};
1294 
1295     pub enum BitmapPixelData<'a> {
1296         PngData(&'a [u8]),
1297     }
1298 
1299     struct CblcGlyph<'a> {
1300         bitmap_data: BitmapData<'a>,
1301         ppem_x: u8,
1302         ppem_y: u8,
1303     }
1304 
1305     struct SbixGlyph<'a> {
1306         glyph_data: GlyphData<'a>,
1307         ppem: u16,
1308     }
1309 
1310     #[derive(Default)]
1311     pub struct BridgeBitmapGlyph<'a> {
1312         pub data: Option<BitmapPixelData<'a>>,
1313         pub metrics: FfiBitmapMetrics,
1314     }
1315 
1316     trait StrikeSizeRetrievable {
strike_size(&self) -> f321317         fn strike_size(&self) -> f32;
1318     }
1319 
1320     impl StrikeSizeRetrievable for &BitmapSize {
strike_size(&self) -> f321321         fn strike_size(&self) -> f32 {
1322             self.ppem_y() as f32
1323         }
1324     }
1325 
1326     impl StrikeSizeRetrievable for Strike<'_> {
strike_size(&self) -> f321327         fn strike_size(&self) -> f32 {
1328             self.ppem() as f32
1329         }
1330     }
1331 
1332     // Find the nearest larger strike size, or if no larger one is available, the nearest smaller.
best_strike_size<T>(strikes: impl Iterator<Item = T>, font_size: f32) -> Option<T> where T: StrikeSizeRetrievable,1333     fn best_strike_size<T>(strikes: impl Iterator<Item = T>, font_size: f32) -> Option<T>
1334     where
1335         T: StrikeSizeRetrievable,
1336     {
1337         // After a bigger strike size is found, the order of strike sizes smaller
1338         // than the requested font size does not matter anymore. A new strike size
1339         // is only an improvement if it gets closer to the requested font size (and
1340         // is smaller than the current best, but bigger than font size). And vice
1341         // versa: As long as we have found only smaller ones so far, only any strike
1342         // size matters that is bigger than the current best.
1343         strikes.reduce(|best, entry| {
1344             let entry_size = entry.strike_size();
1345             if (entry_size >= font_size && entry_size < best.strike_size())
1346                 || (best.strike_size() < font_size && entry_size > best.strike_size())
1347             {
1348                 entry
1349             } else {
1350                 best
1351             }
1352         })
1353     }
1354 
sbix_glyph<'a>( font_ref: &'a FontRef, glyph_id: GlyphId, font_size: Option<f32>, ) -> Option<SbixGlyph<'a>>1355     fn sbix_glyph<'a>(
1356         font_ref: &'a FontRef,
1357         glyph_id: GlyphId,
1358         font_size: Option<f32>,
1359     ) -> Option<SbixGlyph<'a>> {
1360         let sbix = font_ref.sbix().ok()?;
1361         let mut strikes = sbix.strikes().iter().filter_map(|strike| strike.ok());
1362 
1363         let best_strike = match font_size {
1364             Some(size) => best_strike_size(strikes, size),
1365             _ => strikes.next(),
1366         }?;
1367 
1368         Some(SbixGlyph {
1369             ppem: best_strike.ppem(),
1370             glyph_data: best_strike.glyph_data(glyph_id).ok()??,
1371         })
1372     }
1373 
cblc_glyph<'a>( font_ref: &'a FontRef, glyph_id: GlyphId, font_size: Option<f32>, ) -> Option<CblcGlyph<'a>>1374     fn cblc_glyph<'a>(
1375         font_ref: &'a FontRef,
1376         glyph_id: GlyphId,
1377         font_size: Option<f32>,
1378     ) -> Option<CblcGlyph<'a>> {
1379         let cblc = font_ref.cblc().ok()?;
1380         let cbdt = font_ref.cbdt().ok()?;
1381 
1382         let strikes = &cblc.bitmap_sizes();
1383         let best_strike = font_size
1384             .and_then(|size| best_strike_size(strikes.iter(), size))
1385             .or(strikes.get(0))?;
1386 
1387         let location = best_strike.location(cblc.offset_data(), glyph_id).ok()?;
1388 
1389         Some(CblcGlyph {
1390             bitmap_data: cbdt.data(&location).ok()?,
1391             ppem_x: best_strike.ppem_x,
1392             ppem_y: best_strike.ppem_y,
1393         })
1394     }
1395 
has_bitmap_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool1396     pub fn has_bitmap_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool {
1397         font_ref
1398             .with_font(|font| {
1399                 let glyph_id = GlyphId::from(glyph_id);
1400                 let has_sbix = sbix_glyph(font, glyph_id, None).is_some();
1401                 let has_cblc = cblc_glyph(font, glyph_id, None).is_some();
1402                 Some(has_sbix || has_cblc)
1403             })
1404             .unwrap_or_default()
1405     }
1406 
glyf_bounds(font_ref: &FontRef, glyph_id: GlyphId) -> Option<BoundingBox<i16>>1407     fn glyf_bounds(font_ref: &FontRef, glyph_id: GlyphId) -> Option<BoundingBox<i16>> {
1408         let glyf_table = font_ref.glyf().ok()?;
1409         let glyph = font_ref
1410             .loca(None)
1411             .ok()?
1412             .get_glyf(glyph_id, &glyf_table)
1413             .ok()??;
1414         Some(BoundingBox {
1415             x_min: glyph.x_min(),
1416             y_min: glyph.y_min(),
1417             x_max: glyph.x_max(),
1418             y_max: glyph.y_max(),
1419         })
1420     }
1421 
bitmap_glyph<'a>( font_ref: &'a BridgeFontRef, glyph_id: u16, font_size: f32, ) -> Box<BridgeBitmapGlyph<'a>>1422     pub unsafe fn bitmap_glyph<'a>(
1423         font_ref: &'a BridgeFontRef,
1424         glyph_id: u16,
1425         font_size: f32,
1426     ) -> Box<BridgeBitmapGlyph<'a>> {
1427         let glyph_id = GlyphId::from(glyph_id);
1428         font_ref
1429             .with_font(|font| {
1430                 if let Some(sbix_glyph) = sbix_glyph(font, glyph_id, Some(font_size)) {
1431                     // https://learn.microsoft.com/en-us/typography/opentype/spec/sbix
1432                     // "If there is a glyph contour, the glyph design space
1433                     // origin for the graphic is placed at the lower left corner
1434                     // of the glyph bounding box (xMin, yMin)."
1435                     let glyf_bb = glyf_bounds(font, glyph_id).unwrap_or_default();
1436                     let glyf_left_side_bearing =
1437                         GlyphMetrics::new(font, Size::unscaled(), LocationRef::default())
1438                             .left_side_bearing(glyph_id)
1439                             .unwrap_or_default();
1440 
1441                     return Some(Box::new(BridgeBitmapGlyph {
1442                         data: Some(BitmapPixelData::PngData(sbix_glyph.glyph_data.data())),
1443                         metrics: FfiBitmapMetrics {
1444                             bearing_x: glyf_left_side_bearing,
1445                             bearing_y: glyf_bb.y_min as f32,
1446                             inner_bearing_x: sbix_glyph.glyph_data.origin_offset_x() as f32,
1447                             inner_bearing_y: sbix_glyph.glyph_data.origin_offset_y() as f32,
1448                             ppem_x: sbix_glyph.ppem as f32,
1449                             ppem_y: sbix_glyph.ppem as f32,
1450                             placement_origin_bottom_left: true,
1451                             advance: f32::NAN,
1452                         },
1453                     }));
1454                 } else if let Some(cblc_glyph) = cblc_glyph(font, glyph_id, Some(font_size)) {
1455                     let (bearing_x, bearing_y, advance) = match cblc_glyph.bitmap_data.metrics {
1456                         BitmapMetrics::Small(small_metrics) => (
1457                             small_metrics.bearing_x() as f32,
1458                             small_metrics.bearing_y() as f32,
1459                             small_metrics.advance as f32,
1460                         ),
1461                         BitmapMetrics::Big(big_metrics) => (
1462                             big_metrics.hori_bearing_x() as f32,
1463                             big_metrics.hori_bearing_y() as f32,
1464                             big_metrics.hori_advance as f32,
1465                         ),
1466                     };
1467                     if let BitmapContent::Data(BitmapDataFormat::Png, png_buffer) =
1468                         cblc_glyph.bitmap_data.content
1469                     {
1470                         return Some(Box::new(BridgeBitmapGlyph {
1471                             data: Some(BitmapPixelData::PngData(png_buffer)),
1472                             metrics: FfiBitmapMetrics {
1473                                 bearing_x: 0.0,
1474                                 bearing_y: 0.0,
1475                                 inner_bearing_x: bearing_x,
1476                                 inner_bearing_y: bearing_y,
1477                                 ppem_x: cblc_glyph.ppem_x as f32,
1478                                 ppem_y: cblc_glyph.ppem_y as f32,
1479                                 placement_origin_bottom_left: false,
1480                                 advance: advance,
1481                             },
1482                         }));
1483                     }
1484                 }
1485                 None
1486             })
1487             .unwrap_or_default()
1488     }
1489 
png_data<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a [u8]1490     pub unsafe fn png_data<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a [u8] {
1491         match bitmap_glyph.data {
1492             Some(BitmapPixelData::PngData(glyph_data)) => glyph_data,
1493             _ => &[],
1494         }
1495     }
1496 
bitmap_metrics<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a FfiBitmapMetrics1497     pub unsafe fn bitmap_metrics<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a FfiBitmapMetrics {
1498         &bitmap_glyph.metrics
1499     }
1500 }
1501 
1502 pub struct BridgeMappingIndex(MappingIndex);
1503 pub struct BridgeHintingInstance(Option<HintingInstance>);
1504 
1505 #[cxx::bridge(namespace = "fontations_ffi")]
1506 mod ffi {
1507     struct ColorStop {
1508         stop: f32,
1509         palette_index: u16,
1510         alpha: f32,
1511     }
1512 
1513     #[derive(Default)]
1514     struct Metrics {
1515         top: f32,
1516         ascent: f32,
1517         descent: f32,
1518         bottom: f32,
1519         leading: f32,
1520         avg_char_width: f32,
1521         max_char_width: f32,
1522         x_min: f32,
1523         x_max: f32,
1524         x_height: f32,
1525         cap_height: f32,
1526         underline_position: f32,
1527         underline_thickness: f32,
1528         strikeout_position: f32,
1529         strikeout_thickness: f32,
1530     }
1531 
1532     #[derive(Clone, Copy, Default, PartialEq)]
1533     struct FfiPoint {
1534         x: f32,
1535         y: f32,
1536     }
1537 
1538     struct BridgeLocalizedName {
1539         string: String,
1540         language: String,
1541     }
1542 
1543     #[derive(PartialEq, Debug, Default)]
1544     struct SkiaDesignCoordinate {
1545         axis: u32,
1546         value: f32,
1547     }
1548 
1549     struct BridgeScalerMetrics {
1550         has_overlaps: bool,
1551     }
1552 
1553     struct PaletteOverride {
1554         index: u16,
1555         color_8888: u32,
1556     }
1557 
1558     struct ClipBox {
1559         x_min: f32,
1560         y_min: f32,
1561         x_max: f32,
1562         y_max: f32,
1563     }
1564 
1565     struct Transform {
1566         xx: f32,
1567         xy: f32,
1568         yx: f32,
1569         yy: f32,
1570         dx: f32,
1571         dy: f32,
1572     }
1573 
1574     struct FillLinearParams {
1575         x0: f32,
1576         y0: f32,
1577         x1: f32,
1578         y1: f32,
1579     }
1580 
1581     struct FillRadialParams {
1582         x0: f32,
1583         y0: f32,
1584         r0: f32,
1585         x1: f32,
1586         y1: f32,
1587         r1: f32,
1588     }
1589 
1590     struct FillSweepParams {
1591         x0: f32,
1592         y0: f32,
1593         start_angle: f32,
1594         end_angle: f32,
1595     }
1596 
1597     // This type is used to mirror SkFontStyle values for Weight, Slant and Width
1598     #[derive(Default)]
1599     pub struct BridgeFontStyle {
1600         pub weight: i32,
1601         pub slant: i32,
1602         pub width: i32,
1603     }
1604 
1605     #[derive(Default)]
1606     struct BitmapMetrics {
1607         // Outer glyph bearings that affect the computed bounds. We distinguish
1608         // those here from `inner_bearing_*` to account for CoreText behavior in
1609         // SBIX placement. Where the sbix originOffsetX/Y are applied only
1610         // within the bounds. Specified in font units.
1611         // 0 for CBDT, CBLC.
1612         bearing_x: f32,
1613         bearing_y: f32,
1614         // Scale factors to scale image to 1em.
1615         ppem_x: f32,
1616         ppem_y: f32,
1617         // Account for the fact that Sbix and CBDT/CBLC have a different origin
1618         // definition.
1619         placement_origin_bottom_left: bool,
1620         // Specified as a pixel value, to be scaled by `ppem_*` as an
1621         // offset applied to placing the image within the bounds rectangle.
1622         inner_bearing_x: f32,
1623         inner_bearing_y: f32,
1624         // Some, but not all, bitmap glyphs have a special bitmap advance
1625         advance: f32,
1626     }
1627 
1628     extern "Rust" {
1629         type BridgeFontRef<'a>;
make_font_ref<'a>(font_data: &'a [u8], index: u32) -> Box<BridgeFontRef<'a>>1630         unsafe fn make_font_ref<'a>(font_data: &'a [u8], index: u32) -> Box<BridgeFontRef<'a>>;
1631         // Returns whether BridgeFontRef is a valid font containing at
1632         // least a valid sfnt structure from which tables can be
1633         // accessed. This is what instantiation in make_font_ref checks
1634         // for. (see FontRef::new in read_fonts's lib.rs). Implemented
1635         // by returning whether the option is Some() and thus whether a
1636         // FontRef instantiation succeeded and a table directory was
1637         // accessible.
font_ref_is_valid(bridge_font_ref: &BridgeFontRef) -> bool1638         fn font_ref_is_valid(bridge_font_ref: &BridgeFontRef) -> bool;
1639 
1640         // Optimization to quickly rule out that the font has any color tables.
has_any_color_table(bridge_font_ref: &BridgeFontRef) -> bool1641         fn has_any_color_table(bridge_font_ref: &BridgeFontRef) -> bool;
1642 
1643         type BridgeOutlineCollection<'a>;
get_outline_collection<'a>( font_ref: &'a BridgeFontRef<'a>, ) -> Box<BridgeOutlineCollection<'a>>1644         unsafe fn get_outline_collection<'a>(
1645             font_ref: &'a BridgeFontRef<'a>,
1646         ) -> Box<BridgeOutlineCollection<'a>>;
1647 
1648         /// Returns true on a font or collection, sets `num_fonts``
1649         /// to 0 if single font file, and to > 0 for a TrueType collection.
1650         /// Returns false if the data cannot be interpreted as a font or collection.
font_or_collection<'a>(font_data: &'a [u8], num_fonts: &mut u32) -> bool1651         unsafe fn font_or_collection<'a>(font_data: &'a [u8], num_fonts: &mut u32) -> bool;
1652 
num_named_instances(font_ref: &BridgeFontRef) -> usize1653         unsafe fn num_named_instances(font_ref: &BridgeFontRef) -> usize;
1654 
1655         type BridgeMappingIndex;
make_mapping_index<'a>(font_ref: &'a BridgeFontRef) -> Box<BridgeMappingIndex>1656         unsafe fn make_mapping_index<'a>(font_ref: &'a BridgeFontRef) -> Box<BridgeMappingIndex>;
1657 
1658         type BridgeHintingInstance;
make_hinting_instance<'a>( outlines: &BridgeOutlineCollection, size: f32, coords: &BridgeNormalizedCoords, do_light_hinting: bool, do_lcd_antialiasing: bool, lcd_orientation_vertical: bool, force_autohinting: bool, ) -> Box<BridgeHintingInstance>1659         unsafe fn make_hinting_instance<'a>(
1660             outlines: &BridgeOutlineCollection,
1661             size: f32,
1662             coords: &BridgeNormalizedCoords,
1663             do_light_hinting: bool,
1664             do_lcd_antialiasing: bool,
1665             lcd_orientation_vertical: bool,
1666             force_autohinting: bool,
1667         ) -> Box<BridgeHintingInstance>;
make_mono_hinting_instance<'a>( outlines: &BridgeOutlineCollection, size: f32, coords: &BridgeNormalizedCoords, ) -> Box<BridgeHintingInstance>1668         unsafe fn make_mono_hinting_instance<'a>(
1669             outlines: &BridgeOutlineCollection,
1670             size: f32,
1671             coords: &BridgeNormalizedCoords,
1672         ) -> Box<BridgeHintingInstance>;
no_hinting_instance<'a>() -> Box<BridgeHintingInstance>1673         unsafe fn no_hinting_instance<'a>() -> Box<BridgeHintingInstance>;
1674 
lookup_glyph_or_zero( font_ref: &BridgeFontRef, map: &BridgeMappingIndex, codepoint: u32, ) -> u161675         fn lookup_glyph_or_zero(
1676             font_ref: &BridgeFontRef,
1677             map: &BridgeMappingIndex,
1678             codepoint: u32,
1679         ) -> u16;
1680 
get_path_verbs_points( outlines: &BridgeOutlineCollection, glyph_id: u16, size: f32, coords: &BridgeNormalizedCoords, hinting_instance: &BridgeHintingInstance, verbs: &mut Vec<u8>, points: &mut Vec<FfiPoint>, scaler_metrics: &mut BridgeScalerMetrics, ) -> bool1681         fn get_path_verbs_points(
1682             outlines: &BridgeOutlineCollection,
1683             glyph_id: u16,
1684             size: f32,
1685             coords: &BridgeNormalizedCoords,
1686             hinting_instance: &BridgeHintingInstance,
1687             verbs: &mut Vec<u8>,
1688             points: &mut Vec<FfiPoint>,
1689             scaler_metrics: &mut BridgeScalerMetrics,
1690         ) -> bool;
1691 
shrink_verbs_points_if_needed(verbs: &mut Vec<u8>, points: &mut Vec<FfiPoint>)1692         fn shrink_verbs_points_if_needed(verbs: &mut Vec<u8>, points: &mut Vec<FfiPoint>);
1693 
unhinted_advance_width_or_zero( font_ref: &BridgeFontRef, size: f32, coords: &BridgeNormalizedCoords, glyph_id: u16, ) -> f321694         fn unhinted_advance_width_or_zero(
1695             font_ref: &BridgeFontRef,
1696             size: f32,
1697             coords: &BridgeNormalizedCoords,
1698             glyph_id: u16,
1699         ) -> f32;
scaler_hinted_advance_width( outlines: &BridgeOutlineCollection, hinting_instance: &BridgeHintingInstance, glyph_id: u16, out_advance_width: &mut f32, ) -> bool1700         fn scaler_hinted_advance_width(
1701             outlines: &BridgeOutlineCollection,
1702             hinting_instance: &BridgeHintingInstance,
1703             glyph_id: u16,
1704             out_advance_width: &mut f32,
1705         ) -> bool;
units_per_em_or_zero(font_ref: &BridgeFontRef) -> u161706         fn units_per_em_or_zero(font_ref: &BridgeFontRef) -> u16;
get_skia_metrics( font_ref: &BridgeFontRef, size: f32, coords: &BridgeNormalizedCoords, ) -> Metrics1707         fn get_skia_metrics(
1708             font_ref: &BridgeFontRef,
1709             size: f32,
1710             coords: &BridgeNormalizedCoords,
1711         ) -> Metrics;
get_unscaled_metrics( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, ) -> Metrics1712         fn get_unscaled_metrics(
1713             font_ref: &BridgeFontRef,
1714             coords: &BridgeNormalizedCoords,
1715         ) -> Metrics;
num_glyphs(font_ref: &BridgeFontRef) -> u161716         fn num_glyphs(font_ref: &BridgeFontRef) -> u16;
fill_glyph_to_unicode_map(font_ref: &BridgeFontRef, map: &mut [u32])1717         fn fill_glyph_to_unicode_map(font_ref: &BridgeFontRef, map: &mut [u32]);
family_name(font_ref: &BridgeFontRef) -> String1718         fn family_name(font_ref: &BridgeFontRef) -> String;
postscript_name(font_ref: &BridgeFontRef, out_string: &mut String) -> bool1719         fn postscript_name(font_ref: &BridgeFontRef, out_string: &mut String) -> bool;
1720 
1721         /// Receives a slice of palette overrides that will be merged
1722         /// with the specified base palette of the font. The result is a
1723         /// palette of RGBA, 8-bit per component, colors, consisting of
1724         /// palette entries merged with overrides.
resolve_palette( font_ref: &BridgeFontRef, base_palette: u16, palette_overrides: &[PaletteOverride], ) -> Vec<u32>1725         fn resolve_palette(
1726             font_ref: &BridgeFontRef,
1727             base_palette: u16,
1728             palette_overrides: &[PaletteOverride],
1729         ) -> Vec<u32>;
1730 
has_colrv1_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool1731         fn has_colrv1_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool;
has_colrv0_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool1732         fn has_colrv0_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool;
get_colrv1_clip_box( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, glyph_id: u16, size: f32, clip_box: &mut ClipBox, ) -> bool1733         fn get_colrv1_clip_box(
1734             font_ref: &BridgeFontRef,
1735             coords: &BridgeNormalizedCoords,
1736             glyph_id: u16,
1737             size: f32,
1738             clip_box: &mut ClipBox,
1739         ) -> bool;
1740 
1741         type BridgeBitmapGlyph<'a>;
has_bitmap_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool1742         fn has_bitmap_glyph(font_ref: &BridgeFontRef, glyph_id: u16) -> bool;
bitmap_glyph<'a>( font_ref: &'a BridgeFontRef, glyph_id: u16, font_size: f32, ) -> Box<BridgeBitmapGlyph<'a>>1743         unsafe fn bitmap_glyph<'a>(
1744             font_ref: &'a BridgeFontRef,
1745             glyph_id: u16,
1746             font_size: f32,
1747         ) -> Box<BridgeBitmapGlyph<'a>>;
png_data<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a [u8]1748         unsafe fn png_data<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a [u8];
bitmap_metrics<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a BitmapMetrics1749         unsafe fn bitmap_metrics<'a>(bitmap_glyph: &'a BridgeBitmapGlyph) -> &'a BitmapMetrics;
1750 
table_data(font_ref: &BridgeFontRef, tag: u32, offset: usize, data: &mut [u8]) -> usize1751         fn table_data(font_ref: &BridgeFontRef, tag: u32, offset: usize, data: &mut [u8]) -> usize;
table_tags(font_ref: &BridgeFontRef, tags: &mut [u32]) -> u161752         fn table_tags(font_ref: &BridgeFontRef, tags: &mut [u32]) -> u16;
variation_position( coords: &BridgeNormalizedCoords, coordinates: &mut [SkiaDesignCoordinate], ) -> isize1753         fn variation_position(
1754             coords: &BridgeNormalizedCoords,
1755             coordinates: &mut [SkiaDesignCoordinate],
1756         ) -> isize;
1757         // Fills the passed-in slice with the axis coordinates for a given
1758         // shifted named instance index. A shifted named instance index is a
1759         // 32bit value that contains the index to a named instance left-shifted
1760         // by 16bits and offset by 1. This mirrors FreeType behavior to smuggle
1761         // named instance identifiers through a TrueType collection index.
1762         // Returns the number of coordinates copied. If the slice length is 0,
1763         // performs no copy but only returns the number of axis coordinates for
1764         // the given shifted index. Returns -1 on error.
coordinates_for_shifted_named_instance_index( font_ref: &BridgeFontRef, shifted_index: u32, coords: &mut [SkiaDesignCoordinate], ) -> isize1765         fn coordinates_for_shifted_named_instance_index(
1766             font_ref: &BridgeFontRef,
1767             shifted_index: u32,
1768             coords: &mut [SkiaDesignCoordinate],
1769         ) -> isize;
1770 
num_axes(font_ref: &BridgeFontRef) -> usize1771         fn num_axes(font_ref: &BridgeFontRef) -> usize;
1772 
populate_axes(font_ref: &BridgeFontRef, axis_wrapper: Pin<&mut AxisWrapper>) -> isize1773         fn populate_axes(font_ref: &BridgeFontRef, axis_wrapper: Pin<&mut AxisWrapper>) -> isize;
1774 
1775         type BridgeLocalizedStrings<'a>;
get_localized_strings<'a>( font_ref: &'a BridgeFontRef<'a>, ) -> Box<BridgeLocalizedStrings<'a>>1776         unsafe fn get_localized_strings<'a>(
1777             font_ref: &'a BridgeFontRef<'a>,
1778         ) -> Box<BridgeLocalizedStrings<'a>>;
localized_name_next( bridge_localized_strings: &mut BridgeLocalizedStrings, out_localized_name: &mut BridgeLocalizedName, ) -> bool1779         fn localized_name_next(
1780             bridge_localized_strings: &mut BridgeLocalizedStrings,
1781             out_localized_name: &mut BridgeLocalizedName,
1782         ) -> bool;
1783 
1784         type BridgeNormalizedCoords;
resolve_into_normalized_coords( font_ref: &BridgeFontRef, design_coords: &[SkiaDesignCoordinate], ) -> Box<BridgeNormalizedCoords>1785         fn resolve_into_normalized_coords(
1786             font_ref: &BridgeFontRef,
1787             design_coords: &[SkiaDesignCoordinate],
1788         ) -> Box<BridgeNormalizedCoords>;
1789 
normalized_coords_equal(a: &BridgeNormalizedCoords, b: &BridgeNormalizedCoords) -> bool1790         fn normalized_coords_equal(a: &BridgeNormalizedCoords, b: &BridgeNormalizedCoords) -> bool;
1791 
draw_colr_glyph( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, glyph_id: u16, color_painter: Pin<&mut ColorPainterWrapper>, ) -> bool1792         fn draw_colr_glyph(
1793             font_ref: &BridgeFontRef,
1794             coords: &BridgeNormalizedCoords,
1795             glyph_id: u16,
1796             color_painter: Pin<&mut ColorPainterWrapper>,
1797         ) -> bool;
1798 
1799         type BridgeColorStops<'a>;
next_color_stop(color_stops: &mut BridgeColorStops, stop: &mut ColorStop) -> bool1800         fn next_color_stop(color_stops: &mut BridgeColorStops, stop: &mut ColorStop) -> bool;
num_color_stops(color_stops: &BridgeColorStops) -> usize1801         fn num_color_stops(color_stops: &BridgeColorStops) -> usize;
1802 
get_font_style( font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords, font_style: &mut BridgeFontStyle, ) -> bool1803         fn get_font_style(
1804             font_ref: &BridgeFontRef,
1805             coords: &BridgeNormalizedCoords,
1806             font_style: &mut BridgeFontStyle,
1807         ) -> bool;
1808 
1809         // Additional low-level access functions needed for generateAdvancedMetrics().
is_embeddable(font_ref: &BridgeFontRef) -> bool1810         fn is_embeddable(font_ref: &BridgeFontRef) -> bool;
is_subsettable(font_ref: &BridgeFontRef) -> bool1811         fn is_subsettable(font_ref: &BridgeFontRef) -> bool;
is_fixed_pitch(font_ref: &BridgeFontRef) -> bool1812         fn is_fixed_pitch(font_ref: &BridgeFontRef) -> bool;
is_serif_style(font_ref: &BridgeFontRef) -> bool1813         fn is_serif_style(font_ref: &BridgeFontRef) -> bool;
is_script_style(font_ref: &BridgeFontRef) -> bool1814         fn is_script_style(font_ref: &BridgeFontRef) -> bool;
italic_angle(font_ref: &BridgeFontRef) -> i321815         fn italic_angle(font_ref: &BridgeFontRef) -> i32;
1816     }
1817 
1818     unsafe extern "C++" {
1819 
1820         include!("src/ports/fontations/src/skpath_bridge.h");
1821 
1822         type AxisWrapper;
1823 
populate_axis( self: Pin<&mut AxisWrapper>, i: usize, axis: u32, min: f32, def: f32, max: f32, hidden: bool, ) -> bool1824         fn populate_axis(
1825             self: Pin<&mut AxisWrapper>,
1826             i: usize,
1827             axis: u32,
1828             min: f32,
1829             def: f32,
1830             max: f32,
1831             hidden: bool,
1832         ) -> bool;
size(self: Pin<&AxisWrapper>) -> usize1833         fn size(self: Pin<&AxisWrapper>) -> usize;
1834 
1835         type ColorPainterWrapper;
1836 
is_bounds_mode(self: Pin<&mut ColorPainterWrapper>) -> bool1837         fn is_bounds_mode(self: Pin<&mut ColorPainterWrapper>) -> bool;
push_transform(self: Pin<&mut ColorPainterWrapper>, transform: &Transform)1838         fn push_transform(self: Pin<&mut ColorPainterWrapper>, transform: &Transform);
pop_transform(self: Pin<&mut ColorPainterWrapper>)1839         fn pop_transform(self: Pin<&mut ColorPainterWrapper>);
push_clip_glyph(self: Pin<&mut ColorPainterWrapper>, glyph_id: u16)1840         fn push_clip_glyph(self: Pin<&mut ColorPainterWrapper>, glyph_id: u16);
push_clip_rectangle( self: Pin<&mut ColorPainterWrapper>, x_min: f32, y_min: f32, x_max: f32, y_max: f32, )1841         fn push_clip_rectangle(
1842             self: Pin<&mut ColorPainterWrapper>,
1843             x_min: f32,
1844             y_min: f32,
1845             x_max: f32,
1846             y_max: f32,
1847         );
pop_clip(self: Pin<&mut ColorPainterWrapper>)1848         fn pop_clip(self: Pin<&mut ColorPainterWrapper>);
1849 
fill_solid(self: Pin<&mut ColorPainterWrapper>, palette_index: u16, alpha: f32)1850         fn fill_solid(self: Pin<&mut ColorPainterWrapper>, palette_index: u16, alpha: f32);
fill_linear( self: Pin<&mut ColorPainterWrapper>, fill_linear_params: &FillLinearParams, color_stops: &mut BridgeColorStops, extend_mode: u8, )1851         fn fill_linear(
1852             self: Pin<&mut ColorPainterWrapper>,
1853             fill_linear_params: &FillLinearParams,
1854             color_stops: &mut BridgeColorStops,
1855             extend_mode: u8,
1856         );
fill_radial( self: Pin<&mut ColorPainterWrapper>, fill_radial_params: &FillRadialParams, color_stops: &mut BridgeColorStops, extend_mode: u8, )1857         fn fill_radial(
1858             self: Pin<&mut ColorPainterWrapper>,
1859             fill_radial_params: &FillRadialParams,
1860             color_stops: &mut BridgeColorStops,
1861             extend_mode: u8,
1862         );
fill_sweep( self: Pin<&mut ColorPainterWrapper>, fill_sweep_params: &FillSweepParams, color_stops: &mut BridgeColorStops, extend_mode: u8, )1863         fn fill_sweep(
1864             self: Pin<&mut ColorPainterWrapper>,
1865             fill_sweep_params: &FillSweepParams,
1866             color_stops: &mut BridgeColorStops,
1867             extend_mode: u8,
1868         );
1869 
1870         // Optimized functions.
fill_glyph_solid( self: Pin<&mut ColorPainterWrapper>, glyph_id: u16, palette_index: u16, alpha: f32, )1871         fn fill_glyph_solid(
1872             self: Pin<&mut ColorPainterWrapper>,
1873             glyph_id: u16,
1874             palette_index: u16,
1875             alpha: f32,
1876         );
fill_glyph_linear( self: Pin<&mut ColorPainterWrapper>, glyph_id: u16, fill_transform: &Transform, fill_linear_params: &FillLinearParams, color_stops: &mut BridgeColorStops, extend_mode: u8, )1877         fn fill_glyph_linear(
1878             self: Pin<&mut ColorPainterWrapper>,
1879             glyph_id: u16,
1880             fill_transform: &Transform,
1881             fill_linear_params: &FillLinearParams,
1882             color_stops: &mut BridgeColorStops,
1883             extend_mode: u8,
1884         );
fill_glyph_radial( self: Pin<&mut ColorPainterWrapper>, glyph_id: u16, fill_transform: &Transform, fill_radial_params: &FillRadialParams, color_stops: &mut BridgeColorStops, extend_mode: u8, )1885         fn fill_glyph_radial(
1886             self: Pin<&mut ColorPainterWrapper>,
1887             glyph_id: u16,
1888             fill_transform: &Transform,
1889             fill_radial_params: &FillRadialParams,
1890             color_stops: &mut BridgeColorStops,
1891             extend_mode: u8,
1892         );
fill_glyph_sweep( self: Pin<&mut ColorPainterWrapper>, glyph_id: u16, fill_transform: &Transform, fill_sweep_params: &FillSweepParams, color_stops: &mut BridgeColorStops, extend_mode: u8, )1893         fn fill_glyph_sweep(
1894             self: Pin<&mut ColorPainterWrapper>,
1895             glyph_id: u16,
1896             fill_transform: &Transform,
1897             fill_sweep_params: &FillSweepParams,
1898             color_stops: &mut BridgeColorStops,
1899             extend_mode: u8,
1900         );
1901 
push_layer(self: Pin<&mut ColorPainterWrapper>, colrv1_composite_mode: u8)1902         fn push_layer(self: Pin<&mut ColorPainterWrapper>, colrv1_composite_mode: u8);
pop_layer(self: Pin<&mut ColorPainterWrapper>)1903         fn pop_layer(self: Pin<&mut ColorPainterWrapper>);
1904 
1905     }
1906 }
1907 
1908 /// Tests to exercise COLR and CPAL parts of the Fontations FFI.
1909 /// Run using `$ bazel test --with_fontations //src/ports/fontations:test_ffi`
1910 #[cfg(test)]
1911 mod test {
1912     use crate::{
1913         coordinates_for_shifted_named_instance_index,
1914         ffi::{BridgeFontStyle, PaletteOverride, SkiaDesignCoordinate},
1915         font_or_collection, font_ref_is_valid, get_font_style, make_font_ref, num_axes,
1916         num_named_instances, resolve_into_normalized_coords, resolve_palette,
1917     };
1918     use std::fs;
1919 
1920     const TEST_FONT_FILENAME: &str = "resources/fonts/test_glyphs-glyf_colr_1_variable.ttf";
1921     const TEST_COLLECTION_FILENAME: &str = "resources/fonts/test.ttc";
1922     const TEST_CONDENSED_BOLD_ITALIC: &str = "resources/fonts/cond-bold-italic.ttf";
1923     const TEST_VARIABLE: &str = "resources/fonts/Variable.ttf";
1924 
1925     #[test]
test_palette_override()1926     fn test_palette_override() {
1927         let file_buffer =
1928             fs::read(TEST_FONT_FILENAME).expect("COLRv0/v1 test font could not be opened.");
1929         let font_ref = make_font_ref(&file_buffer, 0);
1930         assert!(font_ref_is_valid(&font_ref));
1931 
1932         let override_color = 0xFFEEEEEE;
1933         let valid_overrides = [
1934             PaletteOverride {
1935                 index: 9,
1936                 color_8888: override_color,
1937             },
1938             PaletteOverride {
1939                 index: 10,
1940                 color_8888: override_color,
1941             },
1942             PaletteOverride {
1943                 index: 11,
1944                 color_8888: override_color,
1945             },
1946         ];
1947 
1948         let palette = resolve_palette(&font_ref, 0, &valid_overrides);
1949 
1950         assert_eq!(palette.len(), 14);
1951         assert_eq!(palette[9], override_color);
1952         assert_eq!(palette[10], override_color);
1953         assert_eq!(palette[11], override_color);
1954 
1955         let out_of_bounds_overrides = [
1956             PaletteOverride {
1957                 index: 15,
1958                 color_8888: override_color,
1959             },
1960             PaletteOverride {
1961                 index: 16,
1962                 color_8888: override_color,
1963             },
1964             PaletteOverride {
1965                 index: 17,
1966                 color_8888: override_color,
1967             },
1968         ];
1969 
1970         let palette = resolve_palette(&font_ref, 0, &out_of_bounds_overrides);
1971 
1972         assert_eq!(palette.len(), 14);
1973         assert_eq!(
1974             (palette[11], palette[12], palette[13],),
1975             (0xff68c7e8, 0xffffdc01, 0xff808080)
1976         );
1977     }
1978 
1979     #[test]
test_default_palette_for_invalid_index()1980     fn test_default_palette_for_invalid_index() {
1981         let file_buffer =
1982             fs::read(TEST_FONT_FILENAME).expect("COLRv0/v1 test font could not be opened.");
1983         let font_ref = make_font_ref(&file_buffer, 0);
1984         assert!(font_ref_is_valid(&font_ref));
1985         let palette = resolve_palette(&font_ref, 65535, &[]);
1986         assert_eq!(palette.len(), 14);
1987         assert_eq!(
1988             (palette[0], palette[6], palette[13],),
1989             (0xFFFF0000, 0xFFEE82EE, 0xFF808080)
1990         );
1991     }
1992 
1993     #[test]
test_num_fonts_in_collection()1994     fn test_num_fonts_in_collection() {
1995         let collection_buffer = fs::read(TEST_COLLECTION_FILENAME)
1996             .expect("Unable to open TrueType collection test file.");
1997         let font_buffer =
1998             fs::read(TEST_FONT_FILENAME).expect("COLRv0/v1 test font could not be opened.");
1999         let garbage: [u8; 12] = [
2000             b'0', b'a', b'b', b'0', b'a', b'b', b'0', b'a', b'b', b'0', b'a', b'b',
2001         ];
2002 
2003         let mut num_fonts = 0;
2004         let result_collection = font_or_collection(&collection_buffer, &mut num_fonts);
2005         assert!(result_collection && num_fonts == 2);
2006 
2007         let result_font_file = font_or_collection(&font_buffer, &mut num_fonts);
2008         assert!(result_font_file);
2009         assert!(num_fonts == 0u32);
2010 
2011         let result_garbage = font_or_collection(&garbage, &mut num_fonts);
2012         assert!(!result_garbage);
2013     }
2014 
2015     #[test]
test_font_attributes()2016     fn test_font_attributes() {
2017         let file_buffer = fs::read(TEST_CONDENSED_BOLD_ITALIC)
2018             .expect("Font to test font styles could not be opened.");
2019         let font_ref = make_font_ref(&file_buffer, 0);
2020         let coords = resolve_into_normalized_coords(&font_ref, &[]);
2021         assert!(font_ref_is_valid(&font_ref));
2022 
2023         let mut font_style = BridgeFontStyle::default();
2024 
2025         if get_font_style(font_ref.as_ref(), &coords, &mut font_style) {
2026             assert_eq!(font_style.width, 5); // The font should have condenced width attribute but
2027                                              // it's condenced itself so we have the normal width
2028             assert_eq!(font_style.slant, 1); // Skia italic
2029             assert_eq!(font_style.weight, 700); // Skia bold
2030         } else {
2031             assert!(false);
2032         }
2033     }
2034 
2035     #[test]
test_variable_font_attributes()2036     fn test_variable_font_attributes() {
2037         let file_buffer =
2038             fs::read(TEST_VARIABLE).expect("Font to test font styles could not be opened.");
2039         let font_ref = make_font_ref(&file_buffer, 0);
2040         let coords = resolve_into_normalized_coords(&font_ref, &[]);
2041         assert!(font_ref_is_valid(&font_ref));
2042 
2043         let mut font_style = BridgeFontStyle::default();
2044 
2045         assert!(get_font_style(font_ref.as_ref(), &coords, &mut font_style));
2046         assert_eq!(font_style.width, 5); // Skia normal
2047         assert_eq!(font_style.slant, 0); // Skia upright
2048         assert_eq!(font_style.weight, 400); // Skia normal
2049     }
2050 
2051     #[test]
test_no_instances()2052     fn test_no_instances() {
2053         let font_buffer = fs::read(TEST_CONDENSED_BOLD_ITALIC)
2054             .expect("Font to test font styles could not be opened.");
2055         let font_ref = make_font_ref(&font_buffer, 0);
2056         let num_instances = num_named_instances(font_ref.as_ref());
2057         assert!(num_instances == 0);
2058     }
2059 
2060     #[test]
test_no_axes()2061     fn test_no_axes() {
2062         let font_buffer = fs::read(TEST_CONDENSED_BOLD_ITALIC)
2063             .expect("Font to test font styles could not be opened.");
2064         let font_ref = make_font_ref(&font_buffer, 0);
2065         let size = num_axes(&font_ref);
2066         assert_eq!(0, size);
2067     }
2068 
2069     #[test]
test_named_instances()2070     fn test_named_instances() {
2071         let font_buffer =
2072             fs::read(TEST_VARIABLE).expect("Font to test font styles could not be opened.");
2073 
2074         let font_ref = make_font_ref(&font_buffer, 0);
2075         let num_instances = num_named_instances(font_ref.as_ref());
2076         assert!(num_instances == 5);
2077 
2078         let mut index = 0;
2079         loop {
2080             if index >= num_instances {
2081                 break;
2082             }
2083             let named_instance_index: u32 = ((index + 1) << 16) as u32;
2084             let num_coords = coordinates_for_shifted_named_instance_index(
2085                 &font_ref,
2086                 named_instance_index,
2087                 &mut [],
2088             );
2089             assert_eq!(num_coords, 2);
2090 
2091             let mut received_coords: [SkiaDesignCoordinate; 2] = Default::default();
2092             let num_coords = coordinates_for_shifted_named_instance_index(
2093                 &font_ref,
2094                 named_instance_index,
2095                 &mut received_coords,
2096             );
2097             let size = num_axes(&font_ref) as isize;
2098             assert_eq!(num_coords, size);
2099             if (index + 1) == 5 {
2100                 assert_eq!(num_coords, 2);
2101                 assert_eq!(
2102                     received_coords[0],
2103                     SkiaDesignCoordinate {
2104                         axis: u32::from_be_bytes([b'w', b'g', b'h', b't']),
2105                         value: 400.0
2106                     }
2107                 );
2108                 assert_eq!(
2109                     received_coords[1],
2110                     SkiaDesignCoordinate {
2111                         axis: u32::from_be_bytes([b'w', b'd', b't', b'h']),
2112                         value: 200.0
2113                     }
2114                 );
2115             };
2116             index += 1;
2117         }
2118     }
2119 
2120     #[test]
test_shifted_named_instance_index()2121     fn test_shifted_named_instance_index() {
2122         let file_buffer =
2123             fs::read(TEST_VARIABLE).expect("Font to test named instances could not be opened.");
2124         let font_ref = make_font_ref(&file_buffer, 0);
2125         assert!(font_ref_is_valid(&font_ref));
2126         // Named instances are 1-indexed.
2127         const SHIFTED_NAMED_INSTANCE_INDEX: u32 = 5 << 16;
2128         const OUT_OF_BOUNDS_NAMED_INSTANCE_INDEX: u32 = 6 << 16;
2129 
2130         let num_coords = coordinates_for_shifted_named_instance_index(
2131             &font_ref,
2132             SHIFTED_NAMED_INSTANCE_INDEX,
2133             &mut [],
2134         );
2135         assert_eq!(num_coords, 2);
2136 
2137         let mut too_small: [SkiaDesignCoordinate; 1] = Default::default();
2138         let num_coords = coordinates_for_shifted_named_instance_index(
2139             &font_ref,
2140             SHIFTED_NAMED_INSTANCE_INDEX,
2141             &mut too_small,
2142         );
2143         assert_eq!(num_coords, -1);
2144 
2145         let mut received_coords: [SkiaDesignCoordinate; 2] = Default::default();
2146         let num_coords = coordinates_for_shifted_named_instance_index(
2147             &font_ref,
2148             SHIFTED_NAMED_INSTANCE_INDEX,
2149             &mut received_coords,
2150         );
2151         assert_eq!(num_coords, 2);
2152         assert_eq!(
2153             received_coords[0],
2154             SkiaDesignCoordinate {
2155                 axis: u32::from_be_bytes([b'w', b'g', b'h', b't']),
2156                 value: 400.0
2157             }
2158         );
2159         assert_eq!(
2160             received_coords[1],
2161             SkiaDesignCoordinate {
2162                 axis: u32::from_be_bytes([b'w', b'd', b't', b'h']),
2163                 value: 200.0
2164             }
2165         );
2166 
2167         let mut too_large: [SkiaDesignCoordinate; 5] = Default::default();
2168         let num_coords = coordinates_for_shifted_named_instance_index(
2169             &font_ref,
2170             SHIFTED_NAMED_INSTANCE_INDEX,
2171             &mut too_large,
2172         );
2173         assert_eq!(num_coords, 2);
2174 
2175         // Index out of bounds:
2176         let num_coords = coordinates_for_shifted_named_instance_index(
2177             &font_ref,
2178             OUT_OF_BOUNDS_NAMED_INSTANCE_INDEX,
2179             &mut [],
2180         );
2181         assert_eq!(num_coords, -1);
2182     }
2183 }
2184