mooman219 / fontdue

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

How to calculate the glyphs layout? #10

Closed TakWolf closed 4 years ago

TakWolf commented 4 years ago

There is a Metrics struct, but have no baseline info.

    /// Inner bounds of the glyph at the offsets specified by the font.
    pub bounds: AABB,

How to use the bounds?

image

================================

font.new_line_width() is a fixed value, This has no scale with it?

mooman219 commented 4 years ago

new_line_width is the advance width of a new line for vertical fonts. Both of the new line offsets being unscaled is a bug (I haven't done any layout work yet, it's a TODO).

If you're looking for how far you need to offset for the next glyph, Metrics.advance_width for the distance you need to advance for the associated character in a horizontal layout. The bounds: AABB for the glyph is its offset in the line. If you're drawing the textures at (x, y), change it to (x + Metrics.xmin, y + Metrics.ymin). For example, g falls belong the baseline and has a negative ymin to represent that.

TakWolf commented 4 years ago

But the metrics.bounds values seems not correct.

I use coordinate origin top left and direction is right down.

0 ----------> +x

+y

vertex order is :

1 --- 2 | / | 3---4

 let vertices = vec![
                            Vertex {
                                position: Position::new(
                                    position.x + metrics.bounds.xmin,
                                    position.y + metrics.bounds.ymin,
                                ),
                                uv: uv.top_left(),
                                color,
                            },
                            Vertex {
                                position: Position::new(
                                    position.x + metrics.bounds.xmin + metrics.width as f32,
                                    position.y + metrics.bounds.ymin,
                                ),
                                uv: uv.top_right(),
                                color,
                            },
                            Vertex {
                                position: Position::new(
                                    position.x + metrics.bounds.xmin,
                                    position.y + metrics.bounds.ymin + metrics.height as f32,
                                ),
                                uv: uv.bottom_left(),
                                color,
                            },
                            Vertex {
                                position: Position::new(
                                    position.x + metrics.bounds.xmin + metrics.width as f32,
                                    position.y + metrics.bounds.ymin + metrics.height as f32,
                                ),
                                uv: uv.bottom_right(),
                                color,
                            },
                        ];

image

mooman219 commented 4 years ago

Metrics assume positive y us up. Subtract ymin in your case

TakWolf commented 4 years ago

font = Roboto-Regular.ttf px = 32.0 text = "AaGg"

A:
Metrics { width: 21, height: 23, advance_width: 20.875, advance_height: 0.0, bounds: AABB { xmin: 0.453125, xmax: 20.46875, ymin: 0.0, ymax: 22.75 } }
a:
Metrics { width: 14, height: 18, advance_width: 17.40625, advance_height: 0.0, bounds: AABB { xmin: 1.703125, xmax: 15.65625, ymin: -0.3125, ymax: 17.21875 } }
G:
Metrics { width: 18, height: 24, advance_width: 21.796875, advance_height: 0.0, bounds: AABB { xmin: 1.90625, xmax: 19.453125, ymin: -0.3125, ymax: 23.0625 } }
g:
Metrics { width: 15, height: 24, advance_width: 17.96875, advance_height: 0.0, bounds: AABB { xmin: 1.515625, xmax: 15.78125, ymin: -6.671875, ymax: 17.21875 } }

(x + Metrics.xmin, y - Metrics.ymin)

image

It's still a little strange

The char a aabb.ymin = -0.3125, not even enough -1px

mooman219 commented 4 years ago

Ah I think that's because you're aligning to the top of the line, all values are relative to the baseline which is below the letters. Xmin and Ymin are the offsets relative to the bottom left of the texture.

TakWolf commented 4 years ago

(x + Metrics.xmin,y + px - metrics.height - metrics.ymin)

image

This seems ok.

But there is no base-line y position. I draw text at y = 0, so the texts is offset at y axis.

mooman219 commented 4 years ago

image Here's an example of what the metrics should represent in ms paint real quick

mooman219 commented 4 years ago

Anyway, I fixed the actual bug for the missing line information in https://github.com/mooman219/fontdue/commit/5703cde10e8f8e609363073c38d5d3a79ba9f618

TakWolf commented 4 years ago

I think there should a baseline y position in the draw region. Now I set baseline y position at the draw region bottom, so it cause like that:

image image

But the text should in the region box.

https://github.com/redox-os/rusttype/blob/master/src/lib.rs#L302

I see rusttype VMetrics has a concept:

pub struct VMetrics {
    /// The highest point that any glyph in the font extends to above the
    /// baseline. Typically positive.
    pub ascent: f32,
    /// The lowest point that any glyph in the font extends to below the
    /// baseline. Typically negative.
    pub descent: f32,
    /// The gap to leave between the descent of one line and the ascent of the
    /// next. This is of course only a guideline given by the font's designers.
    pub line_gap: f32,
}

If so I can set baseline y position in draw region at: line_gap + descent

Can fontdue provide the similar params?

====================

This is I found in Android text draw FontMetrics:

image

mooman219 commented 4 years ago

I don't have layout tools in fontdue yet, but it looks fine to me with a naive implementation I had laying around?

image

Pseudo code:

let caret = Vector2::new(0.0, 0.0);
for each character:
    glpyhs.push(Sprite::new(
        Vector2::new(
            caret.x + metrics.bounds.xmin,
            caret.y + metrics.bounds.ymin,
        ),
    ));
}

// Move the caret forward.
caret.x += metrics.advance_width;
if caret.x > max_width {
    caret.y -= font.advance_height;
    caret.x = 0.0;
}
TakWolf commented 4 years ago

I try to add a header table info to the font.

https://github.com/TakWolf/fontdue/tree/feature/header_table https://github.com/TakWolf/fontdue/blob/96ebc49e5747a18b6f784567bbe271633d6d0c9b/src/font.rs#L137

I can calculate baseline y position in box at -descent + line_gap (The algorithm is similar to https://github.com/redox-os/rusttype/blob/1346e49673127f6698053148da572c5e2e5fc3b9/dev/examples/gpu_cache.rs#L21)

The effect is:

image

image

This way solved my layout problem.