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