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