pop-os / cosmic-text

Pure Rust multi-line text handling

Home Page:https://pop-os.github.io/cosmic-text/cosmic_text/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Line wrapping/text reflow does not respect buffer size

ten3roberts opened this issue · comments

I have noticed that the buffer size set by Buffer.set_size is not always respected by the line wrapping, but overruns slightly, usually by the half end of the word.

I can reproduce this with varying texts and fonts, and discovered this when writing my GUI library which needs to limit text sizes to fit.

I have managed to find the minimal repro.

The code has the same effect if you measure the distance between the first and last glyph, so line_w is correct. Rendering the text also shows that it overflows despite having vertical space to wrap.

use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, Wrap};

fn main() {
    let mut font_system = FontSystem::new();
    let metrics = Metrics::new(14.0, 20.0);

    let mut buffer = Buffer::new(&mut font_system, metrics);

    let mut buffer = buffer.borrow_with(&mut font_system);

    // Add some text!
    buffer.set_wrap(Wrap::Word);
    buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced);

    // Set a size for the text buffer, in pixels
    buffer.set_size(50.0, 1000.0);

    // Perform shaping as desired
    buffer.shape_until_scroll();

    let measured_size = measure(&buffer);

    assert!(
        measured_size <= buffer.size().0,
        "Measured width is larger than buffer width\n{} <= {}",
        measured_size,
        buffer.size().0
    );
}

fn measure(buffer: &Buffer) -> f32 {
    buffer
        .layout_runs()
        .fold(0.0f32, |width, run| width.max(run.line_w))
}
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/cosmic-test`
thread 'main' panicked at src/main.rs:23:5:
Measured width is larger than buffer width
79.24716 <= 50
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

[Process exited 101]

Example when rendering some text in a wgpu app:

image

commented

Did a little investigation here, It looks like this example uncovers two possible issues with layout:

It looks like cosmic-text should treat wrapping mode as 'best case' like the web. When Word is selected, it should fall back to Glyph to make a best effort attempt.

Second, I have no idea why your example has an empty run.

(Latest master branch)

0 39.04004 ['L', 'o', 'r', 'e', 'm']
0 37.54297 ['i', 'p', 's', 'u', 'm']
0 32.908203 ['d', 'o', 'l', 'o', 'r']
0 14.075195 ['s', 'i', 't']
0 34.282227 ['a', 'm', 'e', 't', ',']
0 19.557617 ['q', 'u', 'i']
0 38.82129 ['m', 'i', 'n', 'i', 'm']
0 39.135742 ['l', 'a', 'b', 'o', 'r', 'e']
0 65.734375 ['a', 'd', 'i', 'p', 'i', 's', 'i', 'c', 'i', 'n', 'g']
0 38.82129 ['m', 'i', 'n', 'i', 'm']
0 21.998047 ['s', 'i', 'n', 't']
0 36.620117 ['c', 'i', 'l', 'l', 'u', 'm']
0 21.998047 ['s', 'i', 'n', 't']
0 71.9209 ['c', 'o', 'n', 's', 'e', 'c', 't', 'e', 't', 'u', 'r']
0 21.998047 []
0 61.024414 ['c', 'u', 'p', 'i', 'd', 'a', 't', 'a', 't', '.']

Awesome, I also discovered the empty run (due to unwrapping the first and last glyphs to measure width).

fontdue handles wrapping as best case too, and falls back to glyph breaking when there does not seem to be enough space.

However, the wrapping that I've seen overruns on are not due to insufficient space for a word, visibly, there is a solution where no overruns occur as there is plenty of horizontal space for multiple words, and large enough vertical space for it to wrap more lines.

commented

Example when rendering some text in a wgpu app:

image

Any chance you can post the cosmic-text code that replicates this on your end? Would make it easier than fiddling with it until it breaks :D

Example when rendering some text in a wgpu app:

image

Any chance you can post the cosmic-text code that replicates this on your end? Would make it easier than fiddling with it until it breaks :D

Absolutely, I am developing a UI framework https://github.com/ten3roberts/violet, which is where I discovered it.

I can push out a branch with an isolating example of a text block being wrapped. The renderer that consumes the text is rather primitive at the moment, and contained in src/wgpu/text_renderer.rs, along with some layout in a TextBufferState to share between the renderer and layout systems.

commented

Edit: Glyph fallback and empty run fix is merged into master now. I'll look into this overflow one as soon as I can repro it.