Text paragraphs, styling, fontconfig

This commit is contained in:
2026-03-20 20:15:27 -04:00
parent 00fe1daa0c
commit f71e03317d
9 changed files with 687 additions and 43 deletions

View File

@@ -22,6 +22,7 @@ struct Vertex {
struct TextVertex {
position: [f32; 2],
uv: [f32; 2],
color: [f32; 4],
}
#[derive(Clone, Copy, Debug)]
@@ -53,6 +54,7 @@ struct GlyphBitmap {
struct PreparedGlyphBitmap {
rect: PixelRect,
cache_key: CacheKey,
color: Color,
}
struct RasterizedText {
@@ -124,13 +126,14 @@ struct TextTextureGlyph {
local_x_bits: u32,
local_y_bits: u32,
advance_bits: u32,
color: (u8, u8, u8, u8),
cache_key: Option<CacheKey>,
}
const VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4];
const TEXT_VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
const TEXT_VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 3] =
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Float32x4];
impl Vertex {
const LAYOUT: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
@@ -258,11 +261,13 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
struct VertexIn {
@location(0) position: vec2<f32>,
@location(1) uv: vec2<f32>,
@location(2) color: vec4<f32>,
};
struct VertexOut {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
};
@group(0) @binding(0) var text_texture: texture_2d<f32>;
@@ -273,12 +278,13 @@ fn vs_main(input: VertexIn) -> VertexOut {
var out: VertexOut;
out.position = vec4(input.position, 0.0, 1.0);
out.uv = input.uv;
out.color = input.color;
return out;
}
@fragment
fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
return textureSample(text_texture, text_sampler, input.uv);
return textureSample(text_texture, text_sampler, input.uv) * input.color;
}
"#
.into(),
@@ -561,7 +567,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
else {
continue;
};
blit_cached_image(&mut pixels, clip, glyph.rect, image, text.color);
blit_cached_image(&mut pixels, clip, glyph.rect, image, glyph.color);
}
Some(RasterizedText {
@@ -600,6 +606,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
bottom: local_y - image.placement.top + height,
},
cache_key,
color: glyph.color,
});
}
glyphs
@@ -670,7 +677,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
let Some(cache_key) = glyph.cache_key else {
continue;
};
let Some(atlas_glyph) = self.ensure_atlas_glyph(cache_key, text.color) else {
let Some(atlas_glyph) = self.ensure_atlas_glyph(cache_key, glyph.color) else {
continue;
};
@@ -689,6 +696,7 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
atlas_glyph.atlas_rect,
clip_rect,
scene.logical_size,
glyph.color,
);
}
}
@@ -1190,31 +1198,38 @@ fn build_text_vertices(origin: Point, size: UiSize, logical_size: UiSize) -> [Te
let right = to_ndc_x(origin.x + size.width, logical_size.width.max(1.0));
let top = to_ndc_y(origin.y, logical_size.height.max(1.0));
let bottom = to_ndc_y(origin.y + size.height, logical_size.height.max(1.0));
let color = [1.0, 1.0, 1.0, 1.0];
[
TextVertex {
position: [left, top],
uv: [0.0, 0.0],
color,
},
TextVertex {
position: [left, bottom],
uv: [0.0, 1.0],
color,
},
TextVertex {
position: [right, top],
uv: [1.0, 0.0],
color,
},
TextVertex {
position: [right, top],
uv: [1.0, 0.0],
color,
},
TextVertex {
position: [left, bottom],
uv: [0.0, 1.0],
color,
},
TextVertex {
position: [right, bottom],
uv: [1.0, 1.0],
color,
},
]
}
@@ -1225,6 +1240,7 @@ fn push_glyph_vertices(
atlas_rect: AtlasRect,
clip_rect: Option<PixelRect>,
logical_size: UiSize,
color: Color,
) {
let Some((dest_rect, uv_rect)) = clipped_glyph_quad(glyph_rect, atlas_rect, clip_rect) else {
return;
@@ -1235,34 +1251,50 @@ fn push_glyph_vertices(
let top = to_ndc_y(dest_rect.top as f32, logical_size.height.max(1.0));
let bottom = to_ndc_y(dest_rect.bottom as f32, logical_size.height.max(1.0));
let color = color_to_f32(color);
vertices.extend_from_slice(&[
TextVertex {
position: [left, top],
uv: [uv_rect.0, uv_rect.1],
color,
},
TextVertex {
position: [left, bottom],
uv: [uv_rect.0, uv_rect.3],
color,
},
TextVertex {
position: [right, top],
uv: [uv_rect.2, uv_rect.1],
color,
},
TextVertex {
position: [right, top],
uv: [uv_rect.2, uv_rect.1],
color,
},
TextVertex {
position: [left, bottom],
uv: [uv_rect.0, uv_rect.3],
color,
},
TextVertex {
position: [right, bottom],
uv: [uv_rect.2, uv_rect.3],
color,
},
]);
}
fn color_to_f32(color: Color) -> [f32; 4] {
[
color.r as f32 / 255.0,
color.g as f32 / 255.0,
color.b as f32 / 255.0,
color.a as f32 / 255.0,
]
}
fn build_vertices(scene: &SceneSnapshot) -> Vec<Vertex> {
let width = scene.logical_size.width.max(1.0);
let height = scene.logical_size.height.max(1.0);
@@ -1385,6 +1417,7 @@ fn text_texture_key(text: &PreparedText) -> TextTextureKey {
local_x_bits: (glyph.position.x - text.origin.x).to_bits(),
local_y_bits: (glyph.position.y - text.origin.y).to_bits(),
advance_bits: glyph.advance.to_bits(),
color: (glyph.color.r, glyph.color.g, glyph.color.b, glyph.color.a),
cache_key: glyph.cache_key,
})
.collect(),
@@ -1443,6 +1476,7 @@ mod tests {
glyph: "c".into(),
position: Point::new(24.0, 44.0),
advance: 8.0,
color: Color::rgb(0xEE, 0xEE, 0xEE),
cache_key: None,
}],
};