Handle text overflow
Cull glyphs fully outside the bounds, and clip glyphs intersecting the bounds. This is all done on the CPU for now. Fixes #2
This commit is contained in:
parent
4e9e10061f
commit
afab881559
3 changed files with 110 additions and 14 deletions
|
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0 OR Zlib"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wgpu = "0.13.1"
|
wgpu = "0.13.1"
|
||||||
fontdue = "0.7.2"
|
fontdue = { git = "https://github.com/mooman219/fontdue", rev = "828b4f4" }
|
||||||
etagere = "0.2.6"
|
etagere = "0.2.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use fontdue::layout::{HorizontalAlign, VerticalAlign};
|
||||||
use glyphon::{
|
use glyphon::{
|
||||||
fontdue::{
|
fontdue::{
|
||||||
layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle},
|
layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle},
|
||||||
Font, FontSettings,
|
Font, FontSettings,
|
||||||
},
|
},
|
||||||
Color, HasColor, Resolution, TextAtlas, TextRenderer,
|
Color, HasColor, Resolution, TextAtlas, TextOverflow, TextRenderer,
|
||||||
};
|
};
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
Backends, CommandEncoderDescriptor, DeviceDescriptor, Features, Instance, Limits, LoadOp,
|
Backends, CommandEncoderDescriptor, DeviceDescriptor, Features, Instance, Limits, LoadOp,
|
||||||
|
@ -88,15 +89,15 @@ async fn run() {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
Event::RedrawRequested(_) => {
|
Event::RedrawRequested(_) => {
|
||||||
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
|
let mut layout1 = Layout::new(CoordinateSystem::PositiveYDown);
|
||||||
|
|
||||||
layout.reset(&LayoutSettings {
|
layout1.reset(&LayoutSettings {
|
||||||
x: 0.0,
|
x: 0.0,
|
||||||
y: 0.0,
|
y: 0.0,
|
||||||
..LayoutSettings::default()
|
..LayoutSettings::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
layout.append(
|
layout1.append(
|
||||||
fonts.as_slice(),
|
fonts.as_slice(),
|
||||||
&TextStyle::with_user_data(
|
&TextStyle::with_user_data(
|
||||||
"Hello world!\nI'm on a new line!",
|
"Hello world!\nI'm on a new line!",
|
||||||
|
@ -106,6 +107,28 @@ async fn run() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut layout2 = Layout::new(CoordinateSystem::PositiveYDown);
|
||||||
|
|
||||||
|
layout2.reset(&LayoutSettings {
|
||||||
|
x: 0.0,
|
||||||
|
y: 200.0,
|
||||||
|
max_width: Some(200.0),
|
||||||
|
max_height: Some(190.0),
|
||||||
|
horizontal_align: HorizontalAlign::Center,
|
||||||
|
vertical_align: VerticalAlign::Middle,
|
||||||
|
..LayoutSettings::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
layout2.append(
|
||||||
|
fonts.as_slice(),
|
||||||
|
&TextStyle::with_user_data(
|
||||||
|
"abcdefghijklmnopqrstuvwxyz\nThis should be partially clipped!\nabcdefghijklmnopqrstuvwxyz",
|
||||||
|
25.0,
|
||||||
|
0,
|
||||||
|
GlyphUserData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
text_renderer
|
text_renderer
|
||||||
.prepare(
|
.prepare(
|
||||||
&device,
|
&device,
|
||||||
|
@ -116,7 +139,7 @@ async fn run() {
|
||||||
height: config.height,
|
height: config.height,
|
||||||
},
|
},
|
||||||
&fonts,
|
&fonts,
|
||||||
&[layout],
|
&[(layout1, TextOverflow::Hide), (layout2, TextOverflow::Hide)],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
89
src/lib.rs
89
src/lib.rs
|
@ -316,6 +316,15 @@ impl TextAtlas {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Controls the overflow behavior of any glyphs that are outside of the layout bounds.
|
||||||
|
pub enum TextOverflow {
|
||||||
|
/// Glyphs can overflow the bounds.
|
||||||
|
Overflow,
|
||||||
|
/// Hide any glyphs outside the bounds. If a glyph is partially outside the bounds, it will be
|
||||||
|
/// clipped to the bounds.
|
||||||
|
Hide,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TextRenderer {
|
pub struct TextRenderer {
|
||||||
vertex_buffer: Buffer,
|
vertex_buffer: Buffer,
|
||||||
vertex_buffer_size: u64,
|
vertex_buffer_size: u64,
|
||||||
|
@ -365,7 +374,7 @@ impl TextRenderer {
|
||||||
atlas: &mut TextAtlas,
|
atlas: &mut TextAtlas,
|
||||||
screen_resolution: Resolution,
|
screen_resolution: Resolution,
|
||||||
fonts: &[Font],
|
fonts: &[Font],
|
||||||
layouts: &[Layout<impl HasColor>],
|
layouts: &[(Layout<impl HasColor>, TextOverflow)],
|
||||||
) -> Result<(), PrepareError> {
|
) -> Result<(), PrepareError> {
|
||||||
self.screen_resolution = screen_resolution;
|
self.screen_resolution = screen_resolution;
|
||||||
|
|
||||||
|
@ -391,7 +400,7 @@ impl TextRenderer {
|
||||||
|
|
||||||
self.glyphs_in_use.clear();
|
self.glyphs_in_use.clear();
|
||||||
|
|
||||||
for layout in layouts.iter() {
|
for (layout, _) in layouts.iter() {
|
||||||
for glyph in layout.glyphs() {
|
for glyph in layout.glyphs() {
|
||||||
self.glyphs_in_use.insert(glyph.key);
|
self.glyphs_in_use.insert(glyph.key);
|
||||||
|
|
||||||
|
@ -494,22 +503,86 @@ impl TextRenderer {
|
||||||
let mut glyph_indices = Vec::new();
|
let mut glyph_indices = Vec::new();
|
||||||
let mut glyphs_added = 0;
|
let mut glyphs_added = 0;
|
||||||
|
|
||||||
for layout in layouts.iter() {
|
for (layout, overflow) in layouts.iter() {
|
||||||
|
let settings = layout.settings();
|
||||||
|
|
||||||
|
// Note: subpixel positioning is not currently handled, so we always use the nearest
|
||||||
|
// pixel.
|
||||||
|
let bounds_min_x = settings.x.round() as u32;
|
||||||
|
let bounds_max_x = settings
|
||||||
|
.max_width
|
||||||
|
.map(|w| bounds_min_x + w.round() as u32)
|
||||||
|
.unwrap_or(u32::MAX);
|
||||||
|
let bounds_min_y = settings.y.round() as u32;
|
||||||
|
let bounds_max_y = settings
|
||||||
|
.max_height
|
||||||
|
.map(|h| bounds_min_y + h.round() as u32)
|
||||||
|
.unwrap_or(u32::MAX);
|
||||||
|
|
||||||
for glyph in layout.glyphs() {
|
for glyph in layout.glyphs() {
|
||||||
|
let mut x = glyph.x.round() as u32;
|
||||||
|
let mut y = glyph.y.round() as u32;
|
||||||
|
|
||||||
let details = atlas.glyph_cache.get(&glyph.key).unwrap();
|
let details = atlas.glyph_cache.get(&glyph.key).unwrap();
|
||||||
let (atlas_x, atlas_y) = match details.gpu_cache {
|
let (mut atlas_x, mut atlas_y) = match details.gpu_cache {
|
||||||
GpuCache::InAtlas { x, y } => (x, y),
|
GpuCache::InAtlas { x, y } => (x, y),
|
||||||
GpuCache::SkipRasterization => continue,
|
GpuCache::SkipRasterization => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut width = details.width as u32;
|
||||||
|
let mut height = details.height as u32;
|
||||||
|
|
||||||
|
match overflow {
|
||||||
|
TextOverflow::Overflow => {}
|
||||||
|
TextOverflow::Hide => {
|
||||||
|
// Starts beyond right edge or ends beyond left edge
|
||||||
|
let max_x = x + width;
|
||||||
|
if x > bounds_max_x || max_x < bounds_min_x {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts beyond bottom edge or ends beyond top edge
|
||||||
|
let max_y = y + height;
|
||||||
|
if y > bounds_max_y || max_y < bounds_min_y {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip left ege
|
||||||
|
if x < bounds_min_x {
|
||||||
|
let right_shift = bounds_min_x - x;
|
||||||
|
|
||||||
|
x = bounds_min_x;
|
||||||
|
width = max_x - bounds_min_x;
|
||||||
|
atlas_x += right_shift as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip right edge
|
||||||
|
if x + width > bounds_max_x {
|
||||||
|
width = bounds_max_x - x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip top edge
|
||||||
|
if y < bounds_min_y {
|
||||||
|
let bottom_shift = bounds_min_y - y;
|
||||||
|
|
||||||
|
y = bounds_min_y;
|
||||||
|
height = max_y - bounds_min_y;
|
||||||
|
atlas_y += bottom_shift as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip bottom edge
|
||||||
|
if y + height > bounds_max_y {
|
||||||
|
height = bounds_max_y - y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let color = glyph.user_data.color();
|
let color = glyph.user_data.color();
|
||||||
|
|
||||||
glyph_vertices.extend(
|
glyph_vertices.extend(
|
||||||
iter::repeat(GlyphToRender {
|
iter::repeat(GlyphToRender {
|
||||||
// Note: subpixel positioning is not currently handled, so we always use
|
pos: [x, y],
|
||||||
// the nearest pixel.
|
dim: [width as u16, height as u16],
|
||||||
pos: [glyph.x.round() as u32, glyph.y.round() as u32],
|
|
||||||
dim: [details.width, details.height],
|
|
||||||
uv: [atlas_x, atlas_y],
|
uv: [atlas_x, atlas_y],
|
||||||
color: [color.r, color.g, color.b, color.a],
|
color: [color.r, color.g, color.b, color.a],
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue