Search code examples
imageimage-processingrustfonts

rusttype get text width for Font


Motive

I'm trying to render a String of dynamic length on an Image using imageproc.

Question

I need a method or function to calculate the width of the text when rendered using a specific Font and Scale to center the text on the image.

Additional Notes

rusttype is the font library used by imageproc.


Solution

  • The rusttype repo contains an example (examples/image.rs), which measures the bounds of a line of text and renders it to an image. Basically what you're searching, besides the centering part.

    // rusttype = "0.9.2"
    use rusttype::{point, Font, Scale};
    
    let v_metrics = font.v_metrics(scale);
    
    let glyphs: Vec<_> = font.layout(text, scale, point(0.0, 0.0)).collect();
    let glyphs_height = (v_metrics.ascent - v_metrics.descent).ceil() as u32;
    let glyphs_width = {
        let min_x = glyphs
            .first()
            .map(|g| g.pixel_bounding_box().unwrap().min.x)
            .unwrap();
        let max_x = glyphs
            .last()
            .map(|g| g.pixel_bounding_box().unwrap().max.x)
            .unwrap();
        (max_x - min_x) as u32
    };
    

    I didn't like always needing to collect into a Vec, while also needing to use non-integer sizes. So in the past I created my own version of what's essentially in the example.

    One important note, is that the position passed to layout doesn't really matter, when you only need the size. However, if you actually need to render the glyphs, then the position is important as otherwise the result will contain aliasing artifacts.

    // rusttype = "0.9.2"
    use rusttype::{point, Font, Scale};
    
    fn measure_line(font: &Font, text: &str, scale: Scale) -> (f32, f32) {
        let width = font
            .layout(text, scale, point(0.0, 0.0))
            .map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
            .last()
            .unwrap_or(0.0);
    
        let v_metrics = font.v_metrics(scale);
        let height = v_metrics.ascent - v_metrics.descent + v_metrics.line_gap;
    
        (width, height)
    }
    

    If the text has multiple lines, then you'd manually need to use .lines() on the string, and then sum the line heights and line spacing.