diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 104df0a7..ad633ecb 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -1,4 +1,5 @@ use image::{GenericImage, Pixel}; +use num::Zero; use std::f32; use crate::definitions::{Clamp, Image}; @@ -21,6 +22,39 @@ fn layout_glyphs( let mut w = 0.0; let mut prev: Option = None; + let mut y_max = 0.0_f32; + let mut y_min = 0.0_f32; + let mut y_above_ascent = 0.0_f32; + let mut y_below_ascent = f32::MAX; + + // Find the peak `y_above_ascent` value which above ascent across all glyphs + // And the minimum `y_below_ascent` value which below ascent across all glyphs + for c in text.chars() { + let glyph_id = font.glyph_id(c); + let glyph = glyph_id.with_scale_and_position(scale, point(w, font.ascent())); + if let Some(g) = font.outline_glyph(glyph) { + let bb = g.px_bounds(); + + // for correct offset + y_above_ascent = y_above_ascent.min(bb.min.y); + y_below_ascent = y_below_ascent.min(bb.min.y).max(0.0); + + // for correct height + y_min = y_min.min(bb.min.y); + y_max = y_max.max(bb.max.y); + } + } + + // height and remove overflow ascent + let h = y_max - y_min - y_below_ascent; + + // select offset + let offset_y = if y_above_ascent.is_zero() { + y_below_ascent + } else { + y_above_ascent + }; + for c in text.chars() { let glyph_id = font.glyph_id(c); let glyph = glyph_id.with_scale_and_position(scale, point(w, font.ascent())); @@ -30,13 +64,15 @@ fn layout_glyphs( w += font.kern(glyph_id, prev); } prev = Some(glyph_id); - let bb = g.px_bounds(); + let mut bb = g.px_bounds(); + bb.min.y -= offset_y; + bb.max.y -= offset_y; f(g, bb); } } let w = w.ceil(); - let h = font.height().ceil(); + let h = h.ceil(); assert!(w >= 0.0); assert!(h >= 0.0); (1 + w as u32, h as u32) diff --git a/tests/data/truth/overflow_glyph_number.png b/tests/data/truth/overflow_glyph_number.png new file mode 100644 index 00000000..e46310a2 Binary files /dev/null and b/tests/data/truth/overflow_glyph_number.png differ diff --git a/tests/data/truth/overflow_glyph_out.png b/tests/data/truth/overflow_glyph_out.png new file mode 100644 index 00000000..8b24fbd6 Binary files /dev/null and b/tests/data/truth/overflow_glyph_out.png differ diff --git a/tests/data/truth/overflow_glyph_symbol.png b/tests/data/truth/overflow_glyph_symbol.png new file mode 100644 index 00000000..f2c90a6b Binary files /dev/null and b/tests/data/truth/overflow_glyph_symbol.png differ diff --git a/tests/data/truth/overflow_glyph_symbols.png b/tests/data/truth/overflow_glyph_symbols.png new file mode 100644 index 00000000..6474b32b Binary files /dev/null and b/tests/data/truth/overflow_glyph_symbols.png differ diff --git a/tests/data/truth/overflow_glyph_symbols_little.png b/tests/data/truth/overflow_glyph_symbols_little.png new file mode 100644 index 00000000..d9e176f1 Binary files /dev/null and b/tests/data/truth/overflow_glyph_symbols_little.png differ diff --git a/tests/regression.rs b/tests/regression.rs index 556cbe8a..f58f4b0f 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -1021,3 +1021,44 @@ fn test_draw_text_with_alpha() { compare_to_truth("elephant.png", "text_alpha.png", draw_text_with_alpha); } + +#[test] +#[cfg(feature = "text")] +fn test_draw_text_without_overflow_glyphs() { + use image::RgbaImage; + + fn txt_img(word: &str) -> RgbaImage { + let mplantin = include_bytes!("data/fonts/DejaVuSans.ttf"); + let mplantin = ab_glyph::FontArc::try_from_slice(mplantin).unwrap(); + + let font_size = 128.0; + + let (w, h) = imageproc::drawing::text_size(font_size, &mplantin, word); + let mut image: DynamicImage = DynamicImage::new_rgba8(w, h); + + imageproc::drawing::draw_text_mut( + &mut image, + image::Rgba([100u8, 100u8, 100u8, 255u8]), + 0, + 0, + font_size, + &mplantin, + word, + ); + + image.to_rgba8() + } + + let words = [ + ("ÁÄAaaa", "out"), + ("A", "symbol"), + ("Äabcpghky", "symbols"), + ("abcpghky", "symbols_little"), + ("1.", "number"), + ]; + + for (word, filename) in words { + let img = txt_img(word); + compare_to_truth_image(&img, &format!("overflow_glyph_{}.png", filename)); + } +}