diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e77d916..789fb0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - name: Install run: | echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | sudo debconf-set-selections; - sudo apt-get install ttf-mscorefonts-installer; + sudo apt-get install ttf-mscorefonts-installer libfontconfig-dev; - name: Build run: cargo build - name: Tests diff --git a/Cargo.toml b/Cargo.toml index a53cbc4..531e8e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "font-kit" -version = "0.14.1" +version = "0.14.2" authors = ["Patrick Walton "] description = "A cross-platform font loading library" license = "MIT OR Apache-2.0" @@ -45,7 +45,7 @@ pbr = "1.0" prettytable-rs = "0.10" [target.'cfg(target_family = "windows")'.dependencies] -dwrote = { version = "0.11", default-features = false } +dwrote = { version = "^0.11.3", default-features = false } [target.'cfg(target_family = "windows")'.dependencies.winapi] version = "0.3" @@ -63,7 +63,7 @@ freetype-sys = "0.20" yeslogic-fontconfig-sys = "6.0" [target.'cfg(not(any(target_arch = "wasm32", target_family = "windows", target_os = "android", target_env = "ohos")))'.dependencies] -dirs = "5.0" +dirs = "6.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] walkdir = "2.1" diff --git a/src/canvas.rs b/src/canvas.rs index 4c08abc..12590fe 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -85,6 +85,10 @@ impl Canvas { ) } + /// Blits to a rectangle with origin at `dst_point` and size according to `src_size`. + /// If the target area overlaps the boundaries of the canvas, only the drawable region is blitted. + /// `dst_point` and `src_size` are specified in pixels. `src_stride` is specified in bytes. + /// `src_stride` must be equal or larger than the actual data length. #[allow(dead_code)] pub(crate) fn blit_from( &mut self, @@ -94,6 +98,16 @@ impl Canvas { src_stride: usize, src_format: Format, ) { + assert_eq!( + src_stride * src_size.y() as usize, + src_bytes.len(), + "Number of pixels in src_bytes does not match stride and size." + ); + assert!( + src_stride >= src_size.x() as usize * src_format.bytes_per_pixel() as usize, + "src_stride must be >= than src_size.x()" + ); + let dst_rect = RectI::new(dst_point, src_size); let dst_rect = dst_rect.intersection(RectI::new(Vector2I::default(), self.size)); let dst_rect = match dst_rect { @@ -166,6 +180,9 @@ impl Canvas { } } + /// Blits to area `rect` using the data given in the buffer `src_bytes`. + /// `src_stride` must be specified in bytes. + /// The dimensions of `rect` must be in pixels. fn blit_from_with( &mut self, rect: RectI, diff --git a/src/loaders/core_text.rs b/src/loaders/core_text.rs index 059e9b2..3348dae 100644 --- a/src/loaders/core_text.rs +++ b/src/loaders/core_text.rs @@ -794,7 +794,7 @@ fn core_text_to_css_font_weight(core_text_weight: f32) -> Weight { fn core_text_width_to_css_stretchiness(core_text_width: f32) -> Stretch { Stretch(piecewise_linear_lookup( - (core_text_width + 1.0) * 4.0, + ((core_text_width + 0.4) * 10.0).clamp(0.0, 8.0), &Stretch::MAPPING, )) } @@ -957,6 +957,14 @@ mod test { super::core_text_width_to_css_stretchiness(-1.0), Stretch(0.5) ); + assert_eq!( + super::core_text_width_to_css_stretchiness(-0.5), + Stretch(0.5) + ); + assert_eq!( + super::core_text_width_to_css_stretchiness(0.5), + Stretch(2.0) + ); assert_eq!( super::core_text_width_to_css_stretchiness(1.0), Stretch(2.0) @@ -964,7 +972,7 @@ mod test { // Linear interpolation assert_eq!( - super::core_text_width_to_css_stretchiness(0.85), + super::core_text_width_to_css_stretchiness(0.34), Stretch(1.7) ); } diff --git a/src/loaders/directwrite.rs b/src/loaders/directwrite.rs index 6135842..f8516d9 100644 --- a/src/loaders/directwrite.rs +++ b/src/loaders/directwrite.rs @@ -106,7 +106,9 @@ impl Font { font_index -= 1; continue; } - let dwrite_font = family.get_font(family_font_index); + let Ok(dwrite_font) = family.font(family_font_index) else { + continue; + }; let dwrite_font_face = dwrite_font.create_font_face(); return Ok(Font { dwrite_font, diff --git a/src/loaders/freetype.rs b/src/loaders/freetype.rs index c38013f..3959b16 100644 --- a/src/loaders/freetype.rs +++ b/src/loaders/freetype.rs @@ -839,9 +839,9 @@ impl Font { // that mode. let bitmap = &(*(*self.freetype_face).glyph).bitmap; let bitmap_stride = bitmap.pitch as usize; + // bitmap_width is given in bytes. let bitmap_width = bitmap.width; let bitmap_height = bitmap.rows; - let bitmap_size = Vector2I::new(bitmap_width, bitmap_height); let bitmap_buffer = bitmap.buffer as *const i8 as *const u8; let bitmap_length = bitmap_stride * bitmap_height as usize; if bitmap_buffer.is_null() { @@ -859,9 +859,12 @@ impl Font { // FIXME(pcwalton): This function should return a Result instead. match bitmap.pixel_mode as u32 { FT_PIXEL_MODE_GRAY => { + let bitmap_size = Vector2I::new(bitmap_width, bitmap_height); canvas.blit_from(dst_point, buffer, bitmap_size, bitmap_stride, Format::A8); } FT_PIXEL_MODE_LCD | FT_PIXEL_MODE_LCD_V => { + // Three bytes per pixel for Rgb24 format + let bitmap_size = Vector2I::new(bitmap_width / 3, bitmap_height); canvas.blit_from( dst_point, buffer, @@ -871,6 +874,7 @@ impl Font { ); } FT_PIXEL_MODE_MONO => { + let bitmap_size = Vector2I::new(bitmap_width, bitmap_height); canvas.blit_from_bitmap_1bpp(dst_point, buffer, bitmap_size, bitmap_stride); } _ => panic!("Unexpected FreeType pixel mode!"), diff --git a/src/sources/core_text.rs b/src/sources/core_text.rs index 88ae2b7..d325bab 100644 --- a/src/sources/core_text.rs +++ b/src/sources/core_text.rs @@ -149,7 +149,7 @@ fn css_to_core_text_font_weight(css_weight: Weight) -> f32 { #[allow(dead_code)] fn css_stretchiness_to_core_text_width(css_stretchiness: Stretch) -> f32 { let css_stretchiness = utils::clamp(css_stretchiness.0, 0.5, 2.0); - 0.25 * core_text_loader::piecewise_linear_find_index(css_stretchiness, &Stretch::MAPPING) - 1.0 + 0.1 * core_text_loader::piecewise_linear_find_index(css_stretchiness, &Stretch::MAPPING) - 0.4 } fn create_handles_from_core_text_collection( @@ -234,17 +234,17 @@ mod test { ); assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(0.5)), - -1.0 + -0.4 ); assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(2.0)), - 1.0 + 0.4 ); // Linear interpolation assert_eq!( super::css_stretchiness_to_core_text_width(Stretch(1.7)), - 0.85 + 0.34 ); } } diff --git a/src/sources/directwrite.rs b/src/sources/directwrite.rs index 16a8f9f..b295279 100644 --- a/src/sources/directwrite.rs +++ b/src/sources/directwrite.rs @@ -41,7 +41,9 @@ impl DirectWriteSource { for dwrite_family in self.system_font_collection.families_iter() { for font_index in 0..dwrite_family.get_font_count() { - let dwrite_font = dwrite_family.get_font(font_index); + let Ok(dwrite_font) = dwrite_family.font(font_index) else { + continue; + }; handles.push(self.create_handle_from_dwrite_font(dwrite_font)) } } @@ -54,7 +56,7 @@ impl DirectWriteSource { Ok(self .system_font_collection .families_iter() - .map(|dwrite_family| dwrite_family.name()) + .filter_map(|dwrite_family| dwrite_family.family_name().ok()) .collect()) } @@ -63,16 +65,15 @@ impl DirectWriteSource { /// TODO(pcwalton): Case-insensitivity. pub fn select_family_by_name(&self, family_name: &str) -> Result { let mut family = FamilyHandle::new(); - let dwrite_family = match self - .system_font_collection - .get_font_family_by_name(family_name) - { - Some(dwrite_family) => dwrite_family, - None => return Err(SelectionError::NotFound), + let dwrite_family = match self.system_font_collection.font_family_by_name(family_name) { + Ok(Some(dwrite_family)) => dwrite_family, + Err(_) | Ok(None) => return Err(SelectionError::NotFound), }; for font_index in 0..dwrite_family.get_font_count() { - let dwrite_font = dwrite_family.get_font(font_index); - family.push(self.create_handle_from_dwrite_font(dwrite_font)) + let Ok(dwrite_font) = dwrite_family.font(font_index) else { + continue; + }; + family.push(self.create_handle_from_dwrite_font(dwrite_font)); } Ok(family) } diff --git a/src/sources/fs.rs b/src/sources/fs.rs index a0ed0fc..a2d50c4 100644 --- a/src/sources/fs.rs +++ b/src/sources/fs.rs @@ -221,12 +221,14 @@ fn default_font_directories() -> Vec { let mut directories = vec![ PathBuf::from("/usr/share/fonts"), PathBuf::from("/usr/local/share/fonts"), - PathBuf::from("/var/run/host/usr/share/fonts"), // Flatpak specific - PathBuf::from("/var/run/host/usr/local/share/fonts"), + // Flatpak specific + PathBuf::from("/run/host/fonts"), + PathBuf::from("/run/host/local-fonts"), + PathBuf::from("/run/host/user-fonts"), ]; if let Some(path) = dirs::home_dir() { directories.push(path.join(".fonts")); // ~/.fonts is deprecated - directories.push(path.join("local").join("share").join("fonts")); // Flatpak specific + directories.push(path.join(".local").join("share").join("fonts")); } if let Some(mut path) = dirs::data_dir() { path.push("fonts"); diff --git a/tests/tests.rs b/tests/tests.rs index 0b9bc1a..a7bfe55 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -766,7 +766,7 @@ pub fn rasterize_glyph_with_full_hinting() { RasterizationOptions::Bilevel, ) .unwrap(); - let origin = -raster_rect.origin().to_f32(); + let origin: Vector2F = -raster_rect.origin().to_f32(); let mut canvas = Canvas::new(raster_rect.size(), Format::A8); font.rasterize_glyph( &mut canvas, @@ -796,6 +796,60 @@ pub fn rasterize_glyph_with_full_hinting() { } } +// https://github.com/servo/font-kit/issues/252 +// Panic when targeting Canvas larger than glyph with SubpixelAa option in Freetype. +#[cfg(all( + feature = "source", + any( + not(any(target_os = "macos", target_os = "ios", target_family = "windows")), + feature = "loader-freetype-default" + ) +))] +#[test] +pub fn rasterize_glyph_with_full_hinting_subpixel() { + let font = SystemSource::new() + .select_best_match(&[FamilyName::SansSerif], &Properties::new()) + .unwrap() + .load() + .unwrap(); + let glyph_id = font.glyph_for_char('L').unwrap(); + let size = 32.0; + let raster_rect = font + .raster_bounds( + glyph_id, + size, + Transform2F::default(), + HintingOptions::Full(size), + RasterizationOptions::SubpixelAa, + ) + .unwrap(); + let origin: Vector2F = -raster_rect.origin().to_f32(); + let mut canvas = Canvas::new(raster_rect.size(), Format::Rgb24); + font.rasterize_glyph( + &mut canvas, + glyph_id, + size, + Transform2F::from_translation(origin), + HintingOptions::Full(size), + RasterizationOptions::SubpixelAa, + ) + .unwrap(); + check_L_shape(&canvas); + + // Test with larger canvas + let mut canvas = Canvas::new(Vector2I::new(100, 100), Format::Rgb24); + font.rasterize_glyph( + &mut canvas, + glyph_id, + size, + Transform2F::from_translation(origin), + HintingOptions::Full(size), + RasterizationOptions::SubpixelAa, + ) + .unwrap(); + check_L_shape(&canvas); +} + #[cfg(all(feature = "source", target_family = "windows"))] #[test] pub fn rasterize_glyph() {