mooman219 / fontdue

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

Spanish letters misaligned with font sizes smaller than ~22 #22

Closed john01dav closed 4 years ago

john01dav commented 4 years ago

Spanish uses some accented letters that don't exist in English. These include "í" "ó" and "á". Consider the sample string:

¿Cómo estás?

As can be seen when Github+Firefox render this string, the vertical alignment of the accented letters is fine. Yet, when I render this same string using fontdue, the letters are slightly lower than they should be, but only with smaller font sizes.

Font size 12: image

Font size 17: image

Font size 22: image

This is the code that handles the rendering:

fn draw_string(string: &str, x: i32, y: i32, font_size: f32, buffer: &mut Bitmap){
    let font = include_bytes!("roboto/Roboto-Regular.ttf") as &[u8];
    let mut font = Font::from_bytes(font, FontSettings::default()).unwrap();

    let xf = x as f32;
    let yf = y as f32;

    let mut x_offset: f32 = 0.0;
    for c in string.chars(){
        let (metrics, bitmap) = font.rasterize(c, font_size);
        let xmin = metrics.bounds.xmin;
        let ymax = metrics.bounds.ymax;

        for cx in 0..metrics.width{
            for cy in 0..metrics.height{
                let intensity = bitmap[cy*metrics.width+cx];
                let x_pos = xf+x_offset+(cx as f32)+xmin;
                let y_pos = yf+(cy as f32)- ymax;
                if x_pos >= 0.0 && y_pos >= 0.0 {
                    buffer.set_pixel(x_pos as usize, y_pos as usize, Color::new(intensity, intensity, intensity));
                }
            }
        }
        x_offset += metrics.advance_width;
    }
}
MarimeGui commented 4 years ago

Have you tried rounding your x_pos and y_pos variables before converting them to usize ?

mooman219 commented 4 years ago

I'm aware of the positioning challenges for smaller font sizes. Adding a proper layout utility in fontdue will happen before 1.0 and I'll address this issue.

mooman219 commented 4 years ago

Turns out the issue was I'm aligning to the wrong baseline during rasterization. I'll have a fix out soonish, along with some more subpixel positioning options for people rolling their own layout before Fontdue's is finished.

mooman219 commented 4 years ago

image

Yeah I have it fixed locally. There's some optimization work I need to do because it added 30ns to raster to calculate the alignment.

mooman219 commented 4 years ago

I just pushed it,

john01dav commented 4 years ago

@mooman219 I'm having some trouble using the new fix. Would you be willing to look at my rendering code briefly? I'm not a font expert, so I suspect that there's some error in my code wrt how I'm using your library. Thank you!

Output: image

Code (the second function, render, is the important bit):

use fontdue::{Metrics, Font, FontSettings};
use crate::error::Result;
use crate::bitmap::Bitmap;
use crate::bitmap::Color;

pub struct TextData{
    font: Font,
    chars: Vec<CharData>,
    total_width: usize,
}

struct CharData{
    metrics: Metrics,
    bitmap: Vec<u8>,
}

impl TextData{

    pub fn rasterize_string(string: &str, font_size: f32) -> Result<TextData>{
        let font = include_bytes!("roboto/Roboto-Regular.ttf") as &[u8];
        let font = Font::from_bytes(font, FontSettings::default()).unwrap();

        let mut text_data = TextData{
            font, chars: Vec::new(), total_width: 0
        };

        for c in string.chars(){
            let (metrics, bitmap) = text_data.font.rasterize(c, font_size, 0.0);
            text_data.chars.push(CharData{
                metrics, bitmap
            });
            text_data.total_width += metrics.advance_width as usize;
        }

        Ok(text_data)
    }

    pub fn render(&self, bitmap: &mut Bitmap, x: usize, y: usize){
        let xf = x as f32;
        let yf = y as f32;
        let mut x_offset: f32 = 0.0;
        for char_data in &self.chars{
            let xmin = char_data.metrics.bounds.xmin;
            let ymax = char_data.metrics.bounds.ymax;

            for cx in 0..char_data.metrics.width{
                for cy in 0..char_data.metrics.height{
                    let intensity = char_data.bitmap[cy*char_data.metrics.width+cx];
                    let x_pos = xf+x_offset+(cx as f32)+xmin;
                    let y_pos = yf+(cy as f32)-ymax;
                    if x_pos >= 0.0 && y_pos >= 0.0 {
                        bitmap.set_pixel(x_pos as usize, y_pos as usize, Color::new(intensity, intensity, intensity));
                    }
                }
            }
            x_offset += char_data.metrics.advance_width;
        }
    }

}
mooman219 commented 4 years ago

At a glance, I believe it's to do with not flooring your y positions at the right spot, but the root of the issue is that proper layout is actually just hard (several hundred lines and some state management). I'm implementing a proper layout utility for the next revision so Fontdue will handle it for you.

john01dav commented 4 years ago

Thanks for your help! I have a few things to say/ask in response to what you said: