| // Copyright 2023 Google LLC |
| // Use of this source code is governed by a BSD-style license that can be found |
| // in the LICENSE file. |
| |
| use font_types::GlyphId; |
| use read_fonts::{FileRef, FontRef, ReadError, TableProvider}; |
| use skrifa::{ |
| attribute::Style, |
| charmap::MappingIndex, |
| instance::{Location, Size}, |
| metrics::{GlyphMetrics, Metrics as SkrifaMetrics}, |
| outline::OutlineGlyphFormat, |
| setting::VariationSetting, |
| MetadataProvider, OutlineGlyphCollection, Tag, |
| }; |
| use std::pin::Pin; |
| |
| use crate::ffi::{AxisWrapper, BridgeFontStyle, Metrics, OutlineFormat, SkiaDesignCoordinate}; |
| |
| pub struct BridgeFontRef<'a> { |
| font: Option<FontRef<'a>>, |
| has_any_color: bool, |
| } |
| |
| impl<'a> BridgeFontRef<'a> { |
| pub fn with_font<T>(&'a self, f: impl FnOnce(&'a FontRef) -> Option<T>) -> Option<T> { |
| f(self.font.as_ref()?) |
| } |
| } |
| |
| #[derive(Default)] |
| pub struct BridgeOutlineCollection<'a>(pub Option<OutlineGlyphCollection<'a>>); |
| |
| #[derive(Default)] |
| pub struct BridgeNormalizedCoords { |
| pub normalized_coords: Location, |
| filtered_user_coords: Vec<VariationSetting>, |
| } |
| |
| pub struct BridgeMappingIndex(MappingIndex); |
| |
| pub fn make_mapping_index<'a>(font_ref: &'a BridgeFontRef) -> Box<BridgeMappingIndex> { |
| font_ref |
| .with_font(|f| Some(Box::new(BridgeMappingIndex(MappingIndex::new(f))))) |
| .unwrap() |
| } |
| |
| pub fn lookup_glyph_or_zero( |
| font_ref: &BridgeFontRef, |
| map: &BridgeMappingIndex, |
| codepoints: &[u32], |
| glyphs: &mut [u16], |
| ) { |
| glyphs.fill(0); |
| font_ref.with_font(|f| { |
| let mappings = map.0.charmap(f); |
| for it in codepoints.iter().zip(glyphs.iter_mut()) { |
| let (codepoint, glyph) = it; |
| // Remove u16 conversion when implementing large glyph id support in Skia. |
| *glyph = u16::try_from(mappings.map(*codepoint).unwrap_or_default().to_u32()) |
| .unwrap_or_default(); |
| } |
| Some(()) |
| }); |
| } |
| |
| pub fn num_glyphs(font_ref: &BridgeFontRef) -> u16 { |
| font_ref |
| .with_font(|f| Some(f.maxp().ok()?.num_glyphs())) |
| .unwrap_or_default() |
| } |
| |
| pub fn fill_glyph_to_unicode_map(font_ref: &BridgeFontRef, map: &mut [u32]) { |
| map.fill(0); |
| font_ref.with_font(|f| { |
| let mappings = f.charmap().mappings(); |
| for (codepoint, glyphid) in mappings { |
| if let Some(c) = map.get_mut(glyphid.to_u32() as usize).filter(|c| **c == 0) { |
| *c = codepoint; |
| } |
| } |
| Some(()) |
| }); |
| } |
| |
| pub fn unhinted_advance_width_or_zero( |
| font_ref: &BridgeFontRef, |
| size: f32, |
| coords: &BridgeNormalizedCoords, |
| glyph_id: u16, |
| ) -> f32 { |
| font_ref |
| .with_font(|f| { |
| GlyphMetrics::new(f, Size::new(size), coords.normalized_coords.coords()) |
| .advance_width(GlyphId::from(glyph_id)) |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn outline_format(outlines: &BridgeOutlineCollection) -> OutlineFormat { |
| let outlines = outlines.0.as_ref(); |
| match outlines.and_then(|o| o.format()) { |
| None => OutlineFormat::NoOutlines, |
| Some(OutlineGlyphFormat::Glyf) => OutlineFormat::Glyf, |
| Some(OutlineGlyphFormat::Cff) => OutlineFormat::Cff, |
| Some(OutlineGlyphFormat::Cff2) => OutlineFormat::Cff2, |
| } |
| } |
| |
| pub fn units_per_em_or_zero(font_ref: &BridgeFontRef) -> u16 { |
| font_ref |
| .with_font(|f| Some(f.head().ok()?.units_per_em())) |
| .unwrap_or_default() |
| } |
| |
| pub fn convert_metrics(skrifa_metrics: &SkrifaMetrics) -> Metrics { |
| Metrics { |
| top: skrifa_metrics.bounds.map_or(0.0, |b| b.y_max), |
| bottom: skrifa_metrics.bounds.map_or(0.0, |b| b.y_min), |
| x_min: skrifa_metrics.bounds.map_or(0.0, |b| b.x_min), |
| x_max: skrifa_metrics.bounds.map_or(0.0, |b| b.x_max), |
| ascent: skrifa_metrics.ascent, |
| descent: skrifa_metrics.descent, |
| leading: skrifa_metrics.leading, |
| avg_char_width: skrifa_metrics.average_width.unwrap_or(0.0), |
| max_char_width: skrifa_metrics.max_width.unwrap_or(0.0), |
| x_height: -skrifa_metrics.x_height.unwrap_or(0.0), |
| cap_height: -skrifa_metrics.cap_height.unwrap_or(0.0), |
| underline_position: skrifa_metrics.underline.map_or(f32::NAN, |u| u.offset), |
| underline_thickness: skrifa_metrics.underline.map_or(f32::NAN, |u| u.thickness), |
| strikeout_position: skrifa_metrics.strikeout.map_or(f32::NAN, |s| s.offset), |
| strikeout_thickness: skrifa_metrics.strikeout.map_or(f32::NAN, |s| s.thickness), |
| } |
| } |
| |
| pub fn get_skia_metrics( |
| font_ref: &BridgeFontRef, |
| size: f32, |
| coords: &BridgeNormalizedCoords, |
| ) -> Metrics { |
| font_ref |
| .with_font(|f| { |
| let fontations_metrics = |
| SkrifaMetrics::new(f, Size::new(size), coords.normalized_coords.coords()); |
| Some(convert_metrics(&fontations_metrics)) |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn get_unscaled_metrics(font_ref: &BridgeFontRef, coords: &BridgeNormalizedCoords) -> Metrics { |
| font_ref |
| .with_font(|f| { |
| let fontations_metrics = |
| SkrifaMetrics::new(f, Size::unscaled(), coords.normalized_coords.coords()); |
| Some(convert_metrics(&fontations_metrics)) |
| }) |
| .unwrap_or_default() |
| } |
| |
| /// Implements the behavior expected for `SkTypeface::getTableData`, compare |
| /// documentation for this method and the FreeType implementation in Skia. |
| /// * If the target data array is empty, do not copy any data into it, but |
| /// return the size of the table. |
| /// * If the target data buffer is shorted than from offset to the end of the |
| /// table, truncate the data. |
| /// * If offset is longer than the table's length, return 0. |
| pub fn table_data(font_ref: &BridgeFontRef, tag: u32, offset: usize, data: &mut [u8]) -> usize { |
| let table_data = font_ref |
| .with_font(|f| f.table_data(Tag::from_be_bytes(tag.to_be_bytes()))) |
| .unwrap_or_default(); |
| let table_data = table_data.as_ref(); |
| // Remaining table data size measured from offset to end, or 0 if offset is |
| // too large. |
| let mut to_copy_length = table_data.len().saturating_sub(offset); |
| match data.len() { |
| 0 => to_copy_length, |
| _ => { |
| to_copy_length = to_copy_length.min(data.len()); |
| let table_offset_data = table_data |
| .get(offset..offset + to_copy_length) |
| .unwrap_or_default(); |
| data.get_mut(..table_offset_data.len()) |
| .map_or(0, |data_slice| { |
| data_slice.copy_from_slice(table_offset_data); |
| data_slice.len() |
| }) |
| } |
| } |
| } |
| |
| pub fn table_tags(font_ref: &BridgeFontRef, tags: &mut [u32]) -> u16 { |
| font_ref |
| .with_font(|f| { |
| let table_directory = &f.table_directory(); |
| let table_tags_iter = table_directory |
| .table_records() |
| .iter() |
| .map(|table| u32::from_be_bytes(table.tag.get().into_bytes())); |
| tags.iter_mut() |
| .zip(table_tags_iter) |
| .for_each(|(out_tag, table_tag)| *out_tag = table_tag); |
| Some(table_directory.num_tables()) |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn variation_position( |
| coords: &BridgeNormalizedCoords, |
| coordinates: &mut [SkiaDesignCoordinate], |
| ) -> isize { |
| if !coordinates.is_empty() { |
| if coords.filtered_user_coords.len() > coordinates.len() { |
| return -1; |
| } |
| let skia_design_coordinates = |
| coords |
| .filtered_user_coords |
| .iter() |
| .map(|setting| SkiaDesignCoordinate { |
| axis: u32::from_be_bytes(setting.selector.into_bytes()), |
| value: setting.value, |
| }); |
| for (i, coord) in skia_design_coordinates.enumerate() { |
| coordinates[i] = coord; |
| } |
| } |
| coords.filtered_user_coords.len().try_into().unwrap() |
| } |
| |
| pub fn coordinates_for_shifted_named_instance_index( |
| font_ref: &BridgeFontRef, |
| shifted_index: u32, |
| coords: &mut [SkiaDesignCoordinate], |
| ) -> isize { |
| font_ref |
| .with_font(|f| { |
| let fvar = f.fvar().ok()?; |
| let instances = fvar.instances().ok()?; |
| let index: usize = ((shifted_index >> 16) - 1).try_into().unwrap(); |
| let instance_coords = instances.get(index).ok()?.coordinates; |
| |
| if coords.len() != 0 { |
| if coords.len() < instance_coords.len() { |
| return None; |
| } |
| let axis_coords = f.axes().iter().zip(instance_coords.iter()).enumerate(); |
| for (i, axis_coord) in axis_coords { |
| coords[i] = SkiaDesignCoordinate { |
| axis: u32::from_be_bytes(axis_coord.0.tag().to_be_bytes()), |
| value: axis_coord.1.get().to_f32(), |
| }; |
| } |
| } |
| |
| Some(instance_coords.len() as isize) |
| }) |
| .unwrap_or(-1) |
| } |
| |
| pub fn num_axes(font_ref: &BridgeFontRef) -> usize { |
| font_ref |
| .with_font(|f| Some(f.axes().len())) |
| .unwrap_or_default() |
| } |
| |
| pub fn populate_axes(font_ref: &BridgeFontRef, mut axis_wrapper: Pin<&mut AxisWrapper>) -> isize { |
| font_ref |
| .with_font(|f| { |
| let axes = f.axes(); |
| // Populate incoming allocated SkFontParameters::Variation::Axis[] only when a |
| // buffer is passed. |
| if axis_wrapper.as_ref().size() > 0 { |
| for (i, axis) in axes.iter().enumerate() { |
| if !axis_wrapper.as_mut().populate_axis( |
| i, |
| u32::from_be_bytes(axis.tag().into_bytes()), |
| axis.min_value(), |
| axis.default_value(), |
| axis.max_value(), |
| axis.is_hidden(), |
| ) { |
| return None; |
| } |
| } |
| } |
| isize::try_from(axes.len()).ok() |
| }) |
| .unwrap_or(-1) |
| } |
| |
| fn make_font_ref_internal<'a>(font_data: &'a [u8], index: u32) -> Result<FontRef<'a>, ReadError> { |
| match FileRef::new(font_data) { |
| Ok(file_ref) => match file_ref { |
| FileRef::Font(font_ref) => { |
| // Indices with the higher bits set are meaningful here and do not result in an |
| // error, as they may refer to a named instance and are taken into account by the |
| // Fontations typeface implementation, |
| // compare `coordinates_for_shifted_named_instance_index()`. |
| if index & 0xFFFF > 0 { |
| Err(ReadError::InvalidCollectionIndex(index)) |
| } else { |
| Ok(font_ref) |
| } |
| } |
| FileRef::Collection(collection) => collection.get(index), |
| }, |
| Err(e) => Err(e), |
| } |
| } |
| |
| pub fn make_font_ref<'a>(font_data: &'a [u8], index: u32) -> Box<BridgeFontRef<'a>> { |
| let font = make_font_ref_internal(font_data, index).ok(); |
| let has_any_color = font |
| .as_ref() |
| .map(|f| { |
| f.cbdt().is_ok() || |
| f.sbix().is_ok() || |
| // ColorGlyphCollection::get_with_format() first thing checks for presence of colr(), |
| // so we do the same: |
| f.colr().is_ok() |
| }) |
| .unwrap_or_default(); |
| |
| Box::new(BridgeFontRef { |
| font, |
| has_any_color, |
| }) |
| } |
| |
| pub fn font_ref_is_valid(bridge_font_ref: &BridgeFontRef) -> bool { |
| bridge_font_ref.font.is_some() |
| } |
| |
| pub fn has_any_color_table(bridge_font_ref: &BridgeFontRef) -> bool { |
| bridge_font_ref.has_any_color |
| } |
| |
| pub fn get_outline_collection<'a>( |
| font_ref: &'a BridgeFontRef<'a>, |
| ) -> Box<BridgeOutlineCollection<'a>> { |
| Box::new( |
| font_ref |
| .with_font(|f| Some(BridgeOutlineCollection(Some(f.outline_glyphs())))) |
| .unwrap_or_default(), |
| ) |
| } |
| |
| pub fn font_or_collection<'a>(font_data: &'a [u8], num_fonts: &mut u32) -> bool { |
| match FileRef::new(font_data) { |
| Ok(FileRef::Collection(collection)) => { |
| *num_fonts = collection.len(); |
| true |
| } |
| Ok(FileRef::Font(_)) => { |
| *num_fonts = 0u32; |
| true |
| } |
| _ => false, |
| } |
| } |
| |
| pub fn num_named_instances(font_ref: &BridgeFontRef) -> usize { |
| font_ref |
| .with_font(|f| Some(f.named_instances().len())) |
| .unwrap_or_default() |
| } |
| |
| pub fn resolve_into_normalized_coords( |
| font_ref: &BridgeFontRef, |
| design_coords: &[SkiaDesignCoordinate], |
| ) -> Box<BridgeNormalizedCoords> { |
| let variation_tuples = design_coords |
| .iter() |
| .map(|coord| (Tag::from_be_bytes(coord.axis.to_be_bytes()), coord.value)); |
| let bridge_normalized_coords = font_ref |
| .with_font(|f| { |
| let merged_defaults_with_user = f |
| .axes() |
| .iter() |
| .map(|axis| (axis.tag(), axis.default_value())) |
| .chain(design_coords.iter().map(|user_coord| { |
| ( |
| Tag::from_be_bytes(user_coord.axis.to_be_bytes()), |
| user_coord.value, |
| ) |
| })); |
| Some(BridgeNormalizedCoords { |
| filtered_user_coords: f.axes().filter(merged_defaults_with_user).collect(), |
| normalized_coords: f.axes().location(variation_tuples), |
| }) |
| }) |
| .unwrap_or_default(); |
| Box::new(bridge_normalized_coords) |
| } |
| |
| pub fn normalized_coords_equal(a: &BridgeNormalizedCoords, b: &BridgeNormalizedCoords) -> bool { |
| a.normalized_coords.coords() == b.normalized_coords.coords() |
| } |
| |
| #[allow(non_upper_case_globals)] |
| pub fn get_font_style( |
| font_ref: &BridgeFontRef, |
| coords: &BridgeNormalizedCoords, |
| style: &mut BridgeFontStyle, |
| ) -> bool { |
| const SKIA_SLANT_UPRIGHT: i32 = 0; /* kUpright_Slant */ |
| const SKIA_SLANT_ITALIC: i32 = 1; /* kItalic_Slant */ |
| const SKIA_SLANT_OBLIQUE: i32 = 2; /* kOblique_Slant */ |
| |
| font_ref |
| .with_font(|f| { |
| let attrs = f.attributes(); |
| let mut skia_weight = attrs.weight.value().round() as i32; |
| let mut skia_slant = match attrs.style { |
| Style::Normal => SKIA_SLANT_UPRIGHT, |
| Style::Italic => SKIA_SLANT_ITALIC, |
| _ => SKIA_SLANT_OBLIQUE, |
| }; |
| //0.5, 0.625, 0.75, 0.875, 1.0, 1.125, 1.25, 1.5, 2.0 map to 1-9 |
| let mut skia_width = match attrs.stretch.ratio() { |
| x if x <= 0.5625 => 1, |
| x if x <= 0.6875 => 2, |
| x if x <= 0.8125 => 3, |
| x if x <= 0.9375 => 4, |
| x if x <= 1.0625 => 5, |
| x if x <= 1.1875 => 6, |
| x if x <= 1.3750 => 7, |
| x if x <= 1.7500 => 8, |
| _ => 9, |
| }; |
| |
| const wght: Tag = Tag::new(b"wght"); |
| const wdth: Tag = Tag::new(b"wdth"); |
| const slnt: Tag = Tag::new(b"slnt"); |
| |
| for user_coord in coords.filtered_user_coords.iter() { |
| match user_coord.selector { |
| wght => skia_weight = user_coord.value.round() as i32, |
| // 50, 62.5, 75, 87.5, 100, 112.5, 125, 150, 200 map to 1-9 |
| wdth => { |
| skia_width = match user_coord.value { |
| x if x <= 56.25 => 1, |
| x if x <= 68.75 => 2, |
| x if x <= 81.25 => 3, |
| x if x <= 93.75 => 4, |
| x if x <= 106.25 => 5, |
| x if x <= 118.75 => 6, |
| x if x <= 137.50 => 7, |
| x if x <= 175.00 => 8, |
| _ => 9, |
| } |
| } |
| slnt => { |
| if skia_slant != SKIA_SLANT_ITALIC { |
| if user_coord.value == 0.0 { |
| skia_slant = SKIA_SLANT_UPRIGHT; |
| } else { |
| skia_slant = SKIA_SLANT_OBLIQUE |
| } |
| } |
| } |
| _ => (), |
| } |
| } |
| |
| *style = BridgeFontStyle { |
| weight: skia_weight, |
| slant: skia_slant, |
| width: skia_width, |
| }; |
| Some(true) |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn is_embeddable(font_ref: &BridgeFontRef) -> bool { |
| font_ref |
| .with_font(|f| { |
| let fs_type = f.os2().ok()?.fs_type(); |
| // https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fstype |
| // Bit 2 and bit 9 must be cleared, "Restricted License embedding" and |
| // "Bitmap embedding only" must both be unset. |
| // Implemented to match SkTypeface_FreeType::onGetAdvancedMetrics. |
| Some(fs_type & 0x202 == 0) |
| }) |
| .unwrap_or(true) |
| } |
| |
| pub fn is_subsettable(font_ref: &BridgeFontRef) -> bool { |
| font_ref |
| .with_font(|f| { |
| let fs_type = f.os2().ok()?.fs_type(); |
| // https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fstype |
| Some((fs_type & 0x100) == 0) |
| }) |
| .unwrap_or(true) |
| } |
| |
| pub fn is_fixed_pitch(font_ref: &BridgeFontRef) -> bool { |
| font_ref |
| .with_font(|f| { |
| // Compare DWriteFontTypeface::onGetAdvancedMetrics(). |
| Some(f.post().ok()?.is_fixed_pitch() != 0 || f.hhea().ok()?.number_of_h_metrics() == 1) |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn is_serif_style(font_ref: &BridgeFontRef) -> bool { |
| const FAMILY_TYPE_TEXT_AND_DISPLAY: u8 = 2; |
| const SERIF_STYLE_COVE: u8 = 2; |
| const SERIF_STYLE_TRIANGLE: u8 = 10; |
| font_ref |
| .with_font(|f| { |
| // Compare DWriteFontTypeface::onGetAdvancedMetrics(). |
| let panose = f.os2().ok()?.panose_10(); |
| let family_type = panose[0]; |
| |
| match family_type { |
| FAMILY_TYPE_TEXT_AND_DISPLAY => { |
| let serif_style = panose[1]; |
| Some((SERIF_STYLE_COVE..=SERIF_STYLE_TRIANGLE).contains(&serif_style)) |
| } |
| _ => None, |
| } |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn is_script_style(font_ref: &BridgeFontRef) -> bool { |
| const FAMILY_TYPE_SCRIPT: u8 = 3; |
| font_ref |
| .with_font(|f| { |
| // Compare DWriteFontTypeface::onGetAdvancedMetrics(). |
| let family_type = f.os2().ok()?.panose_10()[0]; |
| Some(family_type == FAMILY_TYPE_SCRIPT) |
| }) |
| .unwrap_or_default() |
| } |
| |
| pub fn italic_angle(font_ref: &BridgeFontRef) -> i32 { |
| font_ref |
| .with_font(|f| Some(f.post().ok()?.italic_angle().to_i32())) |
| .unwrap_or_default() |
| } |
| |
| /// Tests to parts of the Fontations FFI. |
| /// Run using `$ bazel test //src/ports/fontations:test_ffi` |
| #[cfg(test)] |
| mod test { |
| use crate::{ |
| coordinates_for_shifted_named_instance_index, |
| ffi::{BridgeFontStyle, SkiaDesignCoordinate}, |
| font_or_collection, font_ref_is_valid, get_font_style, make_font_ref, num_axes, |
| num_named_instances, resolve_into_normalized_coords, |
| }; |
| use std::fs; |
| |
| const TEST_FONT_FILENAME: &str = "resources/fonts/test_glyphs-glyf_colr_1_variable.ttf"; |
| const TEST_COLLECTION_FILENAME: &str = "resources/fonts/test.ttc"; |
| const TEST_CONDENSED_BOLD_ITALIC: &str = "resources/fonts/cond-bold-italic.ttf"; |
| const TEST_VARIABLE: &str = "resources/fonts/Variable.ttf"; |
| |
| #[test] |
| fn test_num_fonts_in_collection() { |
| let collection_buffer = fs::read(TEST_COLLECTION_FILENAME) |
| .expect("Unable to open TrueType collection test file."); |
| let font_buffer = |
| fs::read(TEST_FONT_FILENAME).expect("COLRv0/v1 test font could not be opened."); |
| let garbage: [u8; 12] = [ |
| b'0', b'a', b'b', b'0', b'a', b'b', b'0', b'a', b'b', b'0', b'a', b'b', |
| ]; |
| |
| let mut num_fonts = 0; |
| let result_collection = font_or_collection(&collection_buffer, &mut num_fonts); |
| assert!(result_collection && num_fonts == 2); |
| |
| let result_font_file = font_or_collection(&font_buffer, &mut num_fonts); |
| assert!(result_font_file); |
| assert!(num_fonts == 0u32); |
| |
| let result_garbage = font_or_collection(&garbage, &mut num_fonts); |
| assert!(!result_garbage); |
| } |
| |
| #[test] |
| fn test_font_attributes() { |
| let file_buffer = fs::read(TEST_CONDENSED_BOLD_ITALIC) |
| .expect("Font to test font styles could not be opened."); |
| let font_ref = make_font_ref(&file_buffer, 0); |
| let coords = resolve_into_normalized_coords(&font_ref, &[]); |
| assert!(font_ref_is_valid(&font_ref)); |
| |
| let mut font_style = BridgeFontStyle::default(); |
| |
| if get_font_style(font_ref.as_ref(), &coords, &mut font_style) { |
| assert_eq!(font_style.width, 5); // The font should have condenced width attribute but |
| // it's condenced itself so we have the normal width |
| assert_eq!(font_style.slant, 1); // Skia italic |
| assert_eq!(font_style.weight, 700); // Skia bold |
| } else { |
| assert!(false); |
| } |
| } |
| |
| #[test] |
| fn test_variable_font_attributes() { |
| let file_buffer = |
| fs::read(TEST_VARIABLE).expect("Font to test font styles could not be opened."); |
| let font_ref = make_font_ref(&file_buffer, 0); |
| let coords = resolve_into_normalized_coords(&font_ref, &[]); |
| assert!(font_ref_is_valid(&font_ref)); |
| |
| let mut font_style = BridgeFontStyle::default(); |
| |
| assert!(get_font_style(font_ref.as_ref(), &coords, &mut font_style)); |
| assert_eq!(font_style.width, 5); // Skia normal |
| assert_eq!(font_style.slant, 0); // Skia upright |
| assert_eq!(font_style.weight, 400); // Skia normal |
| } |
| |
| #[test] |
| fn test_no_instances() { |
| let font_buffer = fs::read(TEST_CONDENSED_BOLD_ITALIC) |
| .expect("Font to test font styles could not be opened."); |
| let font_ref = make_font_ref(&font_buffer, 0); |
| let num_instances = num_named_instances(font_ref.as_ref()); |
| assert!(num_instances == 0); |
| } |
| |
| #[test] |
| fn test_no_axes() { |
| let font_buffer = fs::read(TEST_CONDENSED_BOLD_ITALIC) |
| .expect("Font to test font styles could not be opened."); |
| let font_ref = make_font_ref(&font_buffer, 0); |
| let size = num_axes(&font_ref); |
| assert_eq!(0, size); |
| } |
| |
| #[test] |
| fn test_named_instances() { |
| let font_buffer = |
| fs::read(TEST_VARIABLE).expect("Font to test font styles could not be opened."); |
| |
| let font_ref = make_font_ref(&font_buffer, 0); |
| let num_instances = num_named_instances(font_ref.as_ref()); |
| assert!(num_instances == 5); |
| |
| let mut index = 0; |
| loop { |
| if index >= num_instances { |
| break; |
| } |
| let named_instance_index: u32 = ((index + 1) << 16) as u32; |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| named_instance_index, |
| &mut [], |
| ); |
| assert_eq!(num_coords, 2); |
| |
| let mut received_coords: [SkiaDesignCoordinate; 2] = Default::default(); |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| named_instance_index, |
| &mut received_coords, |
| ); |
| let size = num_axes(&font_ref) as isize; |
| assert_eq!(num_coords, size); |
| if (index + 1) == 5 { |
| assert_eq!(num_coords, 2); |
| assert_eq!( |
| received_coords[0], |
| SkiaDesignCoordinate { |
| axis: u32::from_be_bytes([b'w', b'g', b'h', b't']), |
| value: 400.0 |
| } |
| ); |
| assert_eq!( |
| received_coords[1], |
| SkiaDesignCoordinate { |
| axis: u32::from_be_bytes([b'w', b'd', b't', b'h']), |
| value: 200.0 |
| } |
| ); |
| }; |
| index += 1; |
| } |
| } |
| |
| #[test] |
| fn test_shifted_named_instance_index() { |
| let file_buffer = |
| fs::read(TEST_VARIABLE).expect("Font to test named instances could not be opened."); |
| let font_ref = make_font_ref(&file_buffer, 0); |
| assert!(font_ref_is_valid(&font_ref)); |
| // Named instances are 1-indexed. |
| const SHIFTED_NAMED_INSTANCE_INDEX: u32 = 5 << 16; |
| const OUT_OF_BOUNDS_NAMED_INSTANCE_INDEX: u32 = 6 << 16; |
| |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| SHIFTED_NAMED_INSTANCE_INDEX, |
| &mut [], |
| ); |
| assert_eq!(num_coords, 2); |
| |
| let mut too_small: [SkiaDesignCoordinate; 1] = Default::default(); |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| SHIFTED_NAMED_INSTANCE_INDEX, |
| &mut too_small, |
| ); |
| assert_eq!(num_coords, -1); |
| |
| let mut received_coords: [SkiaDesignCoordinate; 2] = Default::default(); |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| SHIFTED_NAMED_INSTANCE_INDEX, |
| &mut received_coords, |
| ); |
| assert_eq!(num_coords, 2); |
| assert_eq!( |
| received_coords[0], |
| SkiaDesignCoordinate { |
| axis: u32::from_be_bytes([b'w', b'g', b'h', b't']), |
| value: 400.0 |
| } |
| ); |
| assert_eq!( |
| received_coords[1], |
| SkiaDesignCoordinate { |
| axis: u32::from_be_bytes([b'w', b'd', b't', b'h']), |
| value: 200.0 |
| } |
| ); |
| |
| let mut too_large: [SkiaDesignCoordinate; 5] = Default::default(); |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| SHIFTED_NAMED_INSTANCE_INDEX, |
| &mut too_large, |
| ); |
| assert_eq!(num_coords, 2); |
| |
| // Index out of bounds: |
| let num_coords = coordinates_for_shifted_named_instance_index( |
| &font_ref, |
| OUT_OF_BOUNDS_NAMED_INSTANCE_INDEX, |
| &mut [], |
| ); |
| assert_eq!(num_coords, -1); |
| } |
| } |