feat(text): SDF for synthesized-table cell text + guard table pick box
An in-app-created table (no baked *T### block) synthesised its cell text via layout_mtext as stroke polylines — the last text path still rendering strokes while SDF is the default (imported/baked tables already render SDF via the recursive Text arm). - tessellate_table: when SDF is on, build each cell's glyph quads from the run-carrying layout and collect them into one text-carrying wire (per-vertex cell colour, glyph-bounds AABB); the stroke/fill accumulation is the SDF-off fallback. Mirrors the MLEADER SDF branch. - tess.rs synthesized-table path: guard the whole-table AABB clobber on (!points.is_empty() || !fill_tris.is_empty()) — matching the dim / mleader / baked-block paths — so the empty SDF-text wire keeps its tight glyph-box AABB and the pick box hugs the text instead of the whole table. Found by the adversarial review pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
d9a2c8245c
commit
67ce45d40b
2 changed files with 74 additions and 19 deletions
|
|
@ -493,6 +493,10 @@ pub fn tessellate_table(
|
||||||
// Accumulators keyed by quantised colour (+ weight for borders).
|
// Accumulators keyed by quantised colour (+ weight for borders).
|
||||||
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
|
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
|
||||||
let mut texts: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>, Vec<[f32; 3]>)> = HashMap::default();
|
let mut texts: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>, Vec<[f32; 3]>)> = HashMap::default();
|
||||||
|
// SDF cell text: glyph quads (per-vertex coloured) collected across all
|
||||||
|
// cells; emitted as one text-carrying wire at the end. Empty when SDF off.
|
||||||
|
let sdf_on = crate::scene::text::sdf_atlas::sdf_text_enabled();
|
||||||
|
let mut text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
||||||
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::default();
|
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::default();
|
||||||
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32)> = rustc_hash::FxHashSet::default();
|
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32)> = rustc_hash::FxHashSet::default();
|
||||||
let sel_col = WireModel::SELECTED;
|
let sel_col = WireModel::SELECTED;
|
||||||
|
|
@ -709,27 +713,55 @@ pub fn tessellate_table(
|
||||||
} else {
|
} else {
|
||||||
entity_color
|
entity_color
|
||||||
};
|
};
|
||||||
let entry = texts
|
if sdf_on {
|
||||||
.entry(key4(tcol))
|
// SDF: glyph quads per run at its placed origin (rotation baked
|
||||||
.or_insert_with(|| (tcol, Vec::new(), Vec::new()));
|
// in by layout_glyph_quads), coloured by the cell's text colour.
|
||||||
let buf = &mut entry.1;
|
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
|
||||||
let tris_buf = &mut entry.2;
|
for ts in &layout.strokes {
|
||||||
for ts in &layout.strokes {
|
let Some(run) = &ts.run else { continue };
|
||||||
let sx = ts.origin[0] as f32;
|
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||||
let sy = ts.origin[1] as f32;
|
&mut atlas,
|
||||||
for stroke in &ts.strokes {
|
run.height,
|
||||||
if stroke.len() < 2 {
|
run.rotation,
|
||||||
continue;
|
run.width_factor,
|
||||||
}
|
run.oblique,
|
||||||
if !buf.is_empty() {
|
run.tracking,
|
||||||
buf.push([f32::NAN; 3]);
|
&run.font,
|
||||||
}
|
&run.text,
|
||||||
for &[x, y] in stroke {
|
);
|
||||||
buf.push([x + sx, y + sy, (to.z as f64) as f32]);
|
crate::scene::pipeline::text_gpu::push_glyph_vertices(
|
||||||
|
&mut text_verts,
|
||||||
|
&quads,
|
||||||
|
[ts.origin[0], ts.origin[1], to.z as f64],
|
||||||
|
1.0,
|
||||||
|
tcol,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for &[x, y] in &ts.fill_tris {
|
} else {
|
||||||
tris_buf.push([x + sx, y + sy, (to.z as f64) as f32]);
|
let entry = texts
|
||||||
|
.entry(key4(tcol))
|
||||||
|
.or_insert_with(|| (tcol, Vec::new(), Vec::new()));
|
||||||
|
let buf = &mut entry.1;
|
||||||
|
let tris_buf = &mut entry.2;
|
||||||
|
for ts in &layout.strokes {
|
||||||
|
let sx = ts.origin[0] as f32;
|
||||||
|
let sy = ts.origin[1] as f32;
|
||||||
|
for stroke in &ts.strokes {
|
||||||
|
if stroke.len() < 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !buf.is_empty() {
|
||||||
|
buf.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
for &[x, y] in stroke {
|
||||||
|
buf.push([x + sx, y + sy, (to.z as f64) as f32]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &[x, y] in &ts.fill_tris {
|
||||||
|
tris_buf.push([x + sx, y + sy, (to.z as f64) as f32]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -785,6 +817,24 @@ pub fn tessellate_table(
|
||||||
out.push(mk(color, vec![], tris, line_weight_px));
|
out.push(mk(color, vec![], tris, line_weight_px));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// SDF cell text: one wire carrying the glyph quads (per-vertex coloured) +
|
||||||
|
// a glyph-bounds AABB (f64 accumulate → f32) so the text draws + picks;
|
||||||
|
// empty points so it adds no stroke geometry.
|
||||||
|
if !text_verts.is_empty() {
|
||||||
|
let (mut nx, mut ny, mut xx, mut xy) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
|
||||||
|
for v in &text_verts {
|
||||||
|
let x = v.pos[0] as f64 + v.pos_low[0] as f64;
|
||||||
|
let y = v.pos[1] as f64 + v.pos_low[1] as f64;
|
||||||
|
nx = nx.min(x);
|
||||||
|
xx = xx.max(x);
|
||||||
|
ny = ny.min(y);
|
||||||
|
xy = xy.max(y);
|
||||||
|
}
|
||||||
|
let mut w = mk(entity_color, vec![], vec![], line_weight_px);
|
||||||
|
w.aabb = [nx as f32, ny as f32, xx as f32, xy as f32];
|
||||||
|
w.text_verts = text_verts;
|
||||||
|
out.push(w);
|
||||||
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -437,7 +437,12 @@ pub(crate) fn tessellate_entity(
|
||||||
let aabb = entity_aabb(e);
|
let aabb = entity_aabb(e);
|
||||||
for w in &mut wires {
|
for w in &mut wires {
|
||||||
w.aci = aci;
|
w.aci = aci;
|
||||||
w.aabb = aabb;
|
// Empty SDF-text cells keep their tight glyph-box AABB; only
|
||||||
|
// stroke/fill wires take the whole-table box as a broad-phase
|
||||||
|
// pick hint (matches the dim / mleader / baked-block paths).
|
||||||
|
if !w.points.is_empty() || !w.fill_tris.is_empty() {
|
||||||
|
w.aabb = aabb;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return wires;
|
return wires;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue