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