printpdf icon indicating copy to clipboard operation
printpdf copied to clipboard

Potential Way get length of text at certain fontsize

Open Charles-Schleich opened this issue 4 years ago • 10 comments

I have been wondering if it would be possible to get the length of a line of text for a certain font and at a certain font size. Something along the lines of

get_length( text: String, font_size: i64 , font: &IndirectFontRef) -> Mm;

Any idea how I might go about implementing something like this ? Or where I should start ? I'm newish to rust, but I'm keen to give it a shot.

Charles-Schleich avatar May 12 '20 12:05 Charles-Schleich

Wrong crate, this is a PDF library. I've written azul_text_layout for this: https://crates.io/crates/azul-text-layout

See here:

use azul_text_layout::{
    text_layout::{split_text_into_words, words_to_scaled_words},
    text_shaping::get_font_metrics_freetype, 
};

let font = include_bytes!("Helvetica.ttf");
let font_index = 0; // only for fonts with font collections
let font_metrics = get_font_metrics_freetype(&font, font_index);
let words = split_text_into_words("hello");
let scaled_words = words_to_scaled_words(&words, font, font_index, font_metrics, font_size);

let total_width = scaled_words.items.iter().map(|i| i.word_width).sum();

fschutt avatar May 12 '20 18:05 fschutt

Note: this doesn't take into account the width of the spaces between the words.

If you want to do a full text layout (including more advanced things like line breaking, line offset, custom character / word spacing) azul-text-layout does support that too, see here: https://github.com/fschutt/printpdf/issues/39#issuecomment-559118743

fschutt avatar May 12 '20 18:05 fschutt

Fantastic stuff, if want to account for the width of spaces as well It looks like I can also use scaled_words.space_advance_px,

These are some great libraries. Thank you for pointing me in the right direction !

Charles-Schleich avatar May 14 '20 09:05 Charles-Schleich

Potentially related, is there a way to set the local origin on a line of text to be the top left, as opposed to bottom left ? (i.e. such that the text draws downwards from the origin) Alternatively, I've been trying to offset the y coordinate on the .use_text(text, fontsize, x, y, &font); method by the height of the font in the following manner:

let font_metrics = get_font_metrics_freetype(font, font_index as i32);
let font_height = font_metrics.get_height(fontsize);

Unfortunately this font_height does not correspond nicely to the Mm() scale expected by .use_text(), and even after tinkering to scale the value used to offset, I haven't managed to find a good way to do this that is consistent for many different fonts.

Charles-Schleich avatar May 19 '20 16:05 Charles-Schleich

@fschutt You wrote printpdf is the wrong crate. I am curious how would you implement something where you have to "right align" numeric values and so on. From my naive point of view I would see the need to calculate the width of a text with specific parameters (font etc.). Is this a use case where printpdf is not the right tool for the job?

maku avatar Nov 16 '20 16:11 maku

@maku

Is this a use case where printpdf is not the right tool for the job?

Yes. printpdf does not do text layout. I've written another crate azul-text-layout with which you can use to calculate the per-line offset. PDF does not know what "right align" means, it only knows left-align. So you have to calculate an "offset" for each line of text that you want to right-align.

Text layout / text shaping is more complicated that most people realize. Which is why it's the job of a text layout library, not a PDF library.

fschutt avatar Nov 16 '20 21:11 fschutt

@fschutt thanks for answering, but I still don't understand correctly, azul-text-layout is not a PDF document creation lib, right? What would be the way when I have to generate a PDF with Rust that should display numbers right-aligned? You mean use azul-text-layout only for calculation purposes and printpdf for the rest?. Somehow this has to be possible since it is also possible in libraries in other programming languages. Even if it might be complicated ... can you give me some advice?

maku avatar Nov 17 '20 06:11 maku

You mean use azul-text-layout only for calculation purposes and printpdf for the rest?

Yes. There is some code here to get you started: https://github.com/fschutt/printpdf/issues/39#issuecomment-559118743 and on the docs.rs page

Essentially you'll want to set horizontal_alignment = StyleTextAlignmentHorz::Right and set text_layout_options.max_horizontal_width = None. Then just copy the rest of the code - the "right aligning" is done by calling set_text_cursor, which places the text relative to the page. So if you want to right align a text block of numbers, all you'll have to do is to add the coordinate of the line to the coordinate of the text block.

fschutt avatar Nov 17 '20 10:11 fschutt

For anyone else stumbling upon this, I went for an alternative route and used rusttype and extracted this function from genpdf-rs to do the text measurements

 pub fn str_width(&self, font_cache: &FontCache, s: &str, font_size: u8) -> Mm {
        let str_width: Mm = font_cache
            .get_rt_font(*self)
            .glyphs_for(s.chars())
            .map(|g| g.scaled(self.scale).h_metrics().advance_width)
            .map(|w| Mm::from(printpdf::Pt(f64::from(w * f32::from(font_size)))))
            .sum();
        let kerning_width: Mm = self
            .kerning(font_cache, s.chars())
            .into_iter()
            .map(|val| val * f32::from(font_size))
            .map(|val| Mm::from(printpdf::Pt(f64::from(val))))
            .sum();
        str_width + kerning_width
    }

And converted the width of the text to MM in 72dpi like (str_width + kerning_width) * (25.4 / 72.0)

ninjacato avatar Apr 27 '22 10:04 ninjacato

@ninjacato You just have to be aware that rusttype has very rudimentary font shaping and no WOFF2 file support (while azul-text-layout uses the allsorts font shaping engine and does have these features).

fschutt avatar Apr 27 '22 19:04 fschutt