mooman219 / fontdue

The fastest font renderer in the world, written in pure rust.
Apache License 2.0
1.44k stars 72 forks source link

Adding an example for calculating the bounding box #124

Open incetarik opened 2 years ago

incetarik commented 2 years ago

Hi, first of all thank you for your work.

I tried this library to measure a string in a given font. To test, I also had my SVG editor open and I saw that the characters bounds are 100% correct. Yet, I could not find a way to calculate the overall text bounding box. I mean the box I have in the SVG editor for the text itself, not the characters.

Could you please add an example for calculating the expected string bounding box, if there is a way to calculate that properly?

incetarik commented 2 years ago

Any update?

leecbaker commented 2 years ago

You can probably get what you need with this:

let mut layout = Layout::new(CoordinateSystem::PositiveYUp);
layout.append(
    &[&self.font],
    &TextStyle::new(string, self.font_size, 0),
);

for glyph in layout.glyphs() {
    let left = glyph.x;
    let top = glyph.y;
    let right = glyph.x + glyph.width;
    let bottom = glyph.y + glyph.height; // may need to flip the sign, or flip top and bottom depending on CoordinateSystem

    // find the max of each of these, and that will be your bounding box.
}
toyboot4e commented 2 years ago

@leecbaker Hi :wave: You're calculating each character's bounds, but I think the author wants the whole text bounds. I'm wrong, I'm just showing a hopefully more efficient version.

@incetarik I have the following code for calculating the whole text bounds:

// after making your text `Layout`:
let glyphs = layout.glyphs();

let text_width = match layout.lines() {
    Some(lines) => lines
        .iter()
        .map(|ln| {
            let glyph = &glyphs[ln.glyph_end];
            glyph.x + glyph.width as f32
        })
        // same as `.max()`, but for the non-Ord `f32`
        .fold(0.0 / 0.0, |m, v| v.max(m)),
    None => 0.0,
};

let text_height = layout.height();

Window rendering with the calculated with/height (with no padding):

text-bounds

The width is measured correctly. The height might have to be adjusted if I make mistake. @leecbaker 's answer would be more accurate about the height. Combining the two might be the best answer.

incetarik commented 2 years ago

You can probably get what you need with this:

  • Assuming you already have a font
  • Create one or more TextStyle for your text
  • Create a Layout with Layout::new, and append the TextStyles to it
  • Iterate through the glyphs in the layout with layout.glyphs(). You can use the x, y, width, and height to figure out individual bounds for each letter; the min and max of each of those values should give you the 4 bounds of your bounding box. Something like this:
let mut layout = Layout::new(CoordinateSystem::PositiveYUp);
layout.append(
    &[&self.font],
    &TextStyle::new(string, self.font_size, 0),
);

for glyph in layout.glyphs() {
    let left = glyph.x;
    let top = glyph.y;
    let right = glyph.x + glyph.width;
    let bottom = glyph.y + glyph.height; // may need to flip the sign, or flip top and bottom depending on CoordinateSystem

    // find the max of each of these, and that will be your bounding box.
}

@leecbaker Hello, I have done the following, yet the results are not matching:


let mut layout = fontdue::layout::Layout::new(fontdue::layout::CoordinateSystem::PositiveYUp);
let style = fontdue::layout::TextStyle::new(input, opts.font_size, 0);

 layout.append(&[&font], &style);

let (width, height) = layout.glyphs()
  .iter()
  .map(|g| {
    let left = g.x;
    let top = g.y;
    let right = left + g.width as f32;
    let bottom = top + g.height as f32;

    let height = bottom - top;
    (right, height)
  })
  .fold((0.0f32, layout.height()), |l, r| (l.0.max(r.0), l.1.max(r.1)));

Here are the tests:

Test 1

Screen Shot 2022-10-02 at 13 11 19
Caption Value
Rust Result W: 284, H: 38
Font Size 32px

Test 2

Screen Shot 2022-10-02 at 13 13 55
Caption Value
Rust Result W: 67, H: 38
Font Size 32px

Screen Shot 2022-10-02 at 13 15 56
Caption Value
Rust Result W: 281, H: 32
Font Size 27px

The font being tested is: Raleway-Medium The font file:

Raleway-Medium.zip

mooman219 commented 2 years ago

Sorry about that! Fontdue may add more padding than is typically required between some glyphs, so the results will not match output as seen in the browser, or another tool.

incetarik commented 2 years ago

Sorry about that! Fontdue may add more padding than is typically required between some glyphs, so the results will not match output as seen in the browser, or another tool.

@mooman219 will there be any action for this then? Or maybe providing another function that does not add extra padding? At least, which method would be more accurate? I may also iterate through the letters and use font.metrics and then utilizeadvance_width for example.

mooman219 commented 2 years ago

The extra padding is because I don't expose fractional offsets, so fontdue is making some quality judgments for you at font creation time. This was an opinion I baked in because people using fractional offsets had the following issues:

The goal was to give you decent glyphs out of the box. Right now you'll get whole pixel glyph alignment without overlap,

Fractional offsets are actually already supported internally, but will need some work to expose in a friendly way. Coincidentally someone else just asked for this so it's probably about time for me to figure out what's ergonomic for both the average user of this library and for someone that's willing to dig into the spicy parts of layout.

incetarik commented 2 years ago

Any improvements on this?