Skip to content

Commit

Permalink
fix text_size (#689)
Browse files Browse the repository at this point in the history
  • Loading branch information
cospectrum authored Oct 19, 2024
1 parent a6fe217 commit e4d93e2
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 26 deletions.
85 changes: 71 additions & 14 deletions src/drawing/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,36 @@ fn layout_glyphs(
text: &str,
mut f: impl FnMut(OutlinedGlyph, Rect),
) -> (u32, u32) {
let (mut w, mut h) = (0f32, 0f32);

if text.is_empty() {
return (0, 0);
}
let font = font.as_scaled(scale);
let mut last: Option<GlyphId> = None;

let mut w = 0.0;
let mut prev: Option<GlyphId> = None;

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()));
w += font.h_advance(glyph_id);
if let Some(g) = font.outline_glyph(glyph) {
if let Some(last) = last {
w += font.kern(glyph_id, last);
if let Some(prev) = prev {
w += font.kern(glyph_id, prev);
}
last = Some(glyph_id);
prev = Some(glyph_id);
let bb = g.px_bounds();
h = h.max(bb.height());
f(g, bb);
}
}

(w as u32, h as u32)
let w = w.ceil();
let h = font.height().ceil();
assert!(w >= 0.0);
assert!(h >= 0.0);
(1 + w as u32, h as u32)
}

/// Get the width and height of the given text, rendered with the given font and scale.
///
/// Note that this function *does not* support newlines, you must do this manually.
pub fn text_size(scale: impl Into<PxScale> + Copy, font: &impl Font, text: &str) -> (u32, u32) {
layout_glyphs(scale, font, text, |_, _| {})
}
Expand Down Expand Up @@ -67,6 +71,7 @@ where
draw_text_mut(&mut out, color, x, y, scale, font, text);
out
}

#[doc=generate_mut_doc_comment!("draw_text")]
pub fn draw_text_mut<C>(
canvas: &mut C,
Expand All @@ -84,11 +89,11 @@ pub fn draw_text_mut<C>(
let image_height = canvas.height() as i32;

layout_glyphs(scale, font, text, |g, bb| {
let bbx = x + bb.min.x.round() as i32;
let bby = y + bb.min.y.round() as i32;
let x_shift = x + bb.min.x.round() as i32;
let y_shift = y + bb.min.y.round() as i32;
g.draw(|gx, gy, gv| {
let image_x = gx as i32 + bbx;
let image_y = gy as i32 + bby;
let image_x = gx as i32 + x_shift;
let image_y = gy as i32 + y_shift;

if (0..image_width).contains(&image_x) && (0..image_height).contains(&image_y) {
let image_x = image_x as u32;
Expand All @@ -101,3 +106,55 @@ pub fn draw_text_mut<C>(
})
});
}

#[cfg(not(miri))]
#[cfg(test)]
mod proptests {
use super::*;
use crate::{
proptest_utils::arbitrary_image_with,
rect::{Rect, Region},
};
use ab_glyph::FontRef;
use image::Luma;
use proptest::prelude::*;

const FONT_BYTES: &[u8] = include_bytes!("../../tests/data/fonts/DejaVuSans.ttf");

proptest! {
#[test]
fn proptest_text_size(
img in arbitrary_image_with::<Luma<u8>>(Just(0), 0..=100, 0..=100),
x in 0..100,
y in 0..100,
scale in 0.0..100f32,
ref text in "[0-9a-zA-Z]*",
) {
let font = FontRef::try_from_slice(FONT_BYTES).unwrap();
let background = Luma([0]);
let text_color = Luma([255u8]);

let img = draw_text(&img, text_color, x, y, scale, &font, text);

let (text_w, text_h) = text_size(scale, &font, text);
if text.is_empty() {
return Ok(());
}

let first_char = text.chars().next().unwrap();
let first_x_bearing =
font.as_scaled(scale).h_side_bearing(font.glyph_id(first_char));
let rect = if first_x_bearing < 0.0 {
let x_shift = first_x_bearing.abs().ceil() as i32;
Rect::at(x - x_shift, y).of_size(text_w + x_shift as u32, text_h)
} else {
Rect::at(x, y).of_size(text_w, text_h)
};
for (px, py, &p) in img.enumerate_pixels() {
if !rect.contains(px as i32, py as i32) {
assert_eq!(p, background, "pixel_position: {:?}, rect: {:?}", (px, py), rect);
}
}
}
}
}
30 changes: 18 additions & 12 deletions tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ use image::{

use imageproc::contrast::ThresholdType;
use imageproc::definitions::Image;
use imageproc::drawing::text_size;
use imageproc::filter::bilateral::GaussianEuclideanColorDistance;
use imageproc::filter::bilateral_filter;
use imageproc::kernel::{self};
use imageproc::rect::{Rect, Region};
use imageproc::{
definitions::{Clamp, HasBlack, HasWhite},
edges::canny,
Expand Down Expand Up @@ -826,19 +828,23 @@ fn test_bilateral_filter() {

#[test]
fn test_draw_text() {
let mut image = GrayImage::from_pixel(300, 300, Luma::black());
let font_bytes = include_bytes!("data/fonts/DejaVuSans.ttf");
let font = ab_glyph::FontRef::try_from_slice(font_bytes).unwrap();

imageproc::drawing::draw_text_mut(
&mut image,
Luma::white(),
50,
100,
30.0f32,
&font,
"Hello world!",
);

compare_to_truth_image(&image, "text.png");
let background = Luma::black();
let mut img = GrayImage::from_pixel(300, 300, background);

let text = "Hello world!";
let scale = 30.0;
let (x, y) = (50, 100);
imageproc::drawing::draw_text_mut(&mut img, Luma::white(), x, y, scale, &font, text);
compare_to_truth_image(&img, "text.png");

let (text_w, text_h) = text_size(scale, &font, text);
let rect = Rect::at(x, y).of_size(text_w, text_h);
for (px, py, &p) in img.enumerate_pixels() {
if !rect.contains(px as i32, py as i32) {
assert_eq!(p, background);
}
}
}

0 comments on commit e4d93e2

Please sign in to comment.