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]
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.
Example when rendering some text in a wgpu app:
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.
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.