17cupsofcoffee / tetra

🎮 A simple 2D game framework written in Rust
MIT License
920 stars 63 forks source link

Font rendering sizes are not correct #223

Closed fossegutten closed 3 years ago

fossegutten commented 3 years ago

Summary: I used a font that is made for pixel perfect text, at size 9. In tetra, it looks very blurry with size 9, but looks fine in size 10. Tried the font in godot engine, where it works fine at size 9, but not 10. The font is "Vector", from Chevy Ray's font pack. I am sure it is reproducible with other (free) fonts as well.

Edit: I tried with another font that should be pixel perfect at size 10, but I could't get it to work at any whole pixel size.

How it looks at size 9: image

How it looks at size 10 (expected this at size 9): image

17cupsofcoffee commented 3 years ago

Tetra currently uses ab-glyph (a fork/rewrite of rusttype) for text rendering, and we just pass the size directly to their code. So I'm not really sure where this could be going wrong on our end.

Could you see if you can replicate this issue with GGEZ? I believe it uses the same underlying text rendering library, so that would determine if this is an upstream issue.

I tried with another font that should be pixel perfect at size 10, but I could't get it to work at any whole pixel size.

ab-glyph/rusttype tend to not handle pixel fonts very well, and it's a massive source of frustration to me as someone who almost exclusively makes games with pixel art 😢 The Chevy Ray ones are the first I've seen that haven't ended up with horrible anti-aliasing - perhaps Chevy has done something in the TTF to disable it?

I'm currently working on adding bitmap font support (#80) to support these kinds of fonts better.

fossegutten commented 3 years ago

I think the fonts are made specifically for pixel art by placing the font coordinates at exact positions. I am not sure how he did it, as I am not a font expert. They work perfectly in unity (I think) and godot, as truetype fonts. I can test it with ggez another day :)

17cupsofcoffee commented 3 years ago

Yeah, I tried to tweak some other pixel fonts in FontForge to get them to have hard edges, but I wasn't able to get it to work. I just assumed ab-glyph didn't support it - will have to revisit now you've shown me it can be done :)

And yeah, no problem if you can't try it straight away! I just didn't want to assume this is an upstream issue without verifying that first 😄

fossegutten commented 3 years ago

Results are the same in ggez 0.5.1, which seems to use rusttype. Macroquad 0.3.0.alpha14 uses fontdue and it draws correct sized fonts! Might be worth changing dependencies? It is supposedly faster aswell.

17cupsofcoffee commented 3 years ago

I'd be open to switching to a different library, but Fontdue doesn't support subpixel positioning yet as far as I can tell (at least on the Crates.io version), which would potentially be a regression to text quality.

This issue on the ab_glyph repo is interesting, though - it looks like the exact same thing you're running into: https://github.com/alexheretic/ab-glyph/issues/15

Someone in the comments seems to have got the expected results by using the units_per_em in the size calculation, and this is apparently the metric that Fontdue uses too, so I think that's the way forward.

I'd say it's a breaking change to modify how font sizing works (since it'd change how existing games get rendered), but I'll make this change in a 0.6 version soon.

fossegutten commented 3 years ago

Ok. I am happy to help test it out, as I have some pixel art fonts. However, I think you can download some pixel art .ttf fonts from here: https://www.kenney.nl/

17cupsofcoffee commented 3 years ago

:+1: I'm having some computer troubles at the moment, but once I'm back up and running I'll try to get this change made on a branch for us to try out.

17cupsofcoffee commented 3 years ago

My computer is still out of action, but I was able to push a (completely untested, possibly broken 😅) change from my work laptop. It's on the font-size-fix branch if you want to test it out. Will be able to give it a proper look myself once my new motherboard gets here, hopefully...

fossegutten commented 3 years ago

Didn't really work. the font.units_per_em() returns an option. I fixed that, but it returned 4096 with one font and 1024 with another font. The font obviously turned microscopic / invisible.

Edit: I tried this also, from the commented out function in Font.rs from ab-glyph: It seems to create a more reasonable value, but it's still blurry.

pub(crate) struct VectorRasterizer<F> {
    font: Rc<F>,
    scale: PxScale,
}

impl<F> VectorRasterizer<F>
where
    F: AbFont,
{
    pub fn new(font: Rc<F>, size: f32) -> VectorRasterizer<F> {
        let px_per_em = size * (96.0 / 72.0);
        let units_per_em = font.units_per_em().unwrap();
        let height = font.height_unscaled();
        let px_size = px_per_em * height / units_per_em;
        let scale = PxScale::from(px_size);

        VectorRasterizer {
            font,
            scale,
        }
    }
}
fossegutten commented 3 years ago

Removing that first line did the trick

17cupsofcoffee commented 3 years ago

Fixed on the 0.6 branch.