forked from 1b-agent/opencadstudio-upstream
fix(mleader): render shaped cursive glyphs for arabic and complex scripts
- Emit shaped fill triangles and outline strokes instead of unshaped SDF quads - Update bounding box to include fill triangles - Add unit test for multileader arabic text tessellation
This commit is contained in:
1 parent
f1d5284fb9
commit
e3d01e92e2
2 files changed
+109
-8
No files matched your search
@@ -2293,11 +2293,18 @@ impl MultiLeaderTess for MultiLeader {
|
|||||||
// Run-less stroke groups are decoration geometry (underline /
|
// Run-less stroke groups are decoration geometry (underline /
|
||||||
// overline / strike-through, stacked-fraction bars) — they have
|
// overline / strike-through, stacked-fraction bars) — they have
|
||||||
// no glyphs to replace them, so they must be drawn as lines.
|
// no glyphs to replace them, so they must be drawn as lines.
|
||||||
|
// Web runs that require bidi or joined-script shaping keep their
|
||||||
|
// already shaped vector geometry because the per-glyph SDF path has no
|
||||||
|
// cluster-position data.
|
||||||
let mut deco_pts: Vec<[f32; 3]> = Vec::new();
|
let mut deco_pts: Vec<[f32; 3]> = Vec::new();
|
||||||
let mut deco_fill: Vec<[f32; 3]> = Vec::new();
|
let mut deco_fill: Vec<[f32; 3]> = Vec::new();
|
||||||
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
|
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
|
||||||
for ts in &layout.strokes {
|
for ts in &layout.strokes {
|
||||||
let Some(run) = &ts.run else {
|
let is_shaped = ts
|
||||||
|
.run
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|r| crate::scene::text::web_font::requires_shaping(&r.text));
|
||||||
|
if ts.run.is_none() || is_shaped {
|
||||||
for stroke in &ts.strokes {
|
for stroke in &ts.strokes {
|
||||||
if stroke.len() < 2 {
|
if stroke.len() < 2 {
|
||||||
continue;
|
continue;
|
||||||
@@ -2321,7 +2328,8 @@ impl MultiLeaderTess for MultiLeader {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
};
|
}
|
||||||
|
let run = ts.run.as_ref().unwrap();
|
||||||
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||||
&mut atlas,
|
&mut atlas,
|
||||||
run.height,
|
run.height,
|
||||||
@@ -2349,7 +2357,7 @@ impl MultiLeaderTess for MultiLeader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !sdf_verts.is_empty() || !deco_pts.is_empty() {
|
if !sdf_verts.is_empty() || !deco_pts.is_empty() || !deco_fill.is_empty() {
|
||||||
// Pick box from the glyph quads (f64 accumulate → f32).
|
// Pick box from the glyph quads (f64 accumulate → f32).
|
||||||
let (mut nx, mut ny, mut xx, mut xy) =
|
let (mut nx, mut ny, mut xx, mut xy) =
|
||||||
(f64::MAX, f64::MAX, f64::MIN, f64::MIN);
|
(f64::MAX, f64::MAX, f64::MIN, f64::MIN);
|
||||||
@@ -2367,6 +2375,12 @@ impl MultiLeaderTess for MultiLeader {
|
|||||||
ny = ny.min(p[1] as f64);
|
ny = ny.min(p[1] as f64);
|
||||||
xy = xy.max(p[1] as f64);
|
xy = xy.max(p[1] as f64);
|
||||||
}
|
}
|
||||||
|
for p in deco_fill.iter().filter(|p| p[0].is_finite()) {
|
||||||
|
nx = nx.min(p[0] as f64);
|
||||||
|
xx = xx.max(p[0] as f64);
|
||||||
|
ny = ny.min(p[1] as f64);
|
||||||
|
xy = xy.max(p[1] as f64);
|
||||||
|
}
|
||||||
wires.push(WireModel {
|
wires.push(WireModel {
|
||||||
bg_adapt: None,
|
bg_adapt: None,
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
@@ -2618,3 +2632,51 @@ impl MultiLeaderTess for MultiLeader {
|
|||||||
wires
|
wires
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use acadrust::CadDocument;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mleader_arabic_text_tessellation() {
|
||||||
|
let doc = CadDocument::default();
|
||||||
|
let mut ml = MultiLeader::default();
|
||||||
|
ml.content_type = LeaderContentType::MText;
|
||||||
|
ml.context.text_string = "عربي".to_string();
|
||||||
|
ml.context.text_height = 10.0;
|
||||||
|
ml.path_type = MultiLeaderPathType::StraightLineSegments;
|
||||||
|
|
||||||
|
let wires = ml.tessellate(
|
||||||
|
&doc,
|
||||||
|
acadrust::Handle::default(),
|
||||||
|
false,
|
||||||
|
[1.0, 1.0, 1.0, 1.0],
|
||||||
|
1.0,
|
||||||
|
1.0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
[0.0, 0.0, 0.0, 1.0],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find the text wire model:
|
||||||
|
// Arabic text requires shaping, so it should emit vector fill_tris
|
||||||
|
// instead of unshaped SDF glyph quads (text_verts).
|
||||||
|
let text_wire = wires
|
||||||
|
.iter()
|
||||||
|
.find(|w| !w.fill_tris.is_empty() || !w.text_verts.is_empty())
|
||||||
|
.expect("Should produce a text wire for mleader");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
text_wire.text_verts.is_empty(),
|
||||||
|
"Arabic text must not be emitted as unshaped SDF glyph quads"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!text_wire.fill_tris.is_empty(),
|
||||||
|
"Arabic text must emit shaped vector fill triangles"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-5
@@ -1476,7 +1476,11 @@ pub fn tessellate_table(
|
|||||||
rustc_hash::FxHashSet::default();
|
rustc_hash::FxHashSet::default();
|
||||||
let sel_col = WireModel::SELECTED;
|
let sel_col = WireModel::SELECTED;
|
||||||
|
|
||||||
let mut add_edge = |a: Vec3, b: Vec3, col: [f32; 4], lw: f32| {
|
let mut add_edge = |a: Vec3,
|
||||||
|
b: Vec3,
|
||||||
|
col: [f32; 4],
|
||||||
|
lw: f32,
|
||||||
|
borders: &mut HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)>| {
|
||||||
let k = (
|
let k = (
|
||||||
(a.x * 1000.0) as i32,
|
(a.x * 1000.0) as i32,
|
||||||
(a.y * 1000.0) as i32,
|
(a.y * 1000.0) as i32,
|
||||||
@@ -1627,19 +1631,19 @@ pub fn tessellate_table(
|
|||||||
};
|
};
|
||||||
let (tv, tc, tw) = edge(0);
|
let (tv, tc, tw) = edge(0);
|
||||||
if tv {
|
if tv {
|
||||||
add_edge(tl, tr, tc, tw);
|
add_edge(tl, tr, tc, tw, &mut borders);
|
||||||
}
|
}
|
||||||
let (rv, rc, rw) = edge(1);
|
let (rv, rc, rw) = edge(1);
|
||||||
if rv {
|
if rv {
|
||||||
add_edge(tr, br_, rc, rw);
|
add_edge(tr, br_, rc, rw, &mut borders);
|
||||||
}
|
}
|
||||||
let (bv, bc, bw) = edge(2);
|
let (bv, bc, bw) = edge(2);
|
||||||
if bv {
|
if bv {
|
||||||
add_edge(bl, br_, bc, bw);
|
add_edge(bl, br_, bc, bw, &mut borders);
|
||||||
}
|
}
|
||||||
let (lv, lc, lw) = edge(3);
|
let (lv, lc, lw) = edge(3);
|
||||||
if lv {
|
if lv {
|
||||||
add_edge(tl, bl, lc, lw);
|
add_edge(tl, bl, lc, lw, &mut borders);
|
||||||
}
|
}
|
||||||
|
|
||||||
let value_contents: Vec<_> = cell
|
let value_contents: Vec<_> = cell
|
||||||
@@ -1960,6 +1964,41 @@ pub fn tessellate_table(
|
|||||||
let Some(run) = &stroke.run else {
|
let Some(run) = &stroke.run else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
if crate::scene::text::web_font::requires_shaping(&run.text) {
|
||||||
|
if !stroke.fill_tris.is_empty() {
|
||||||
|
let entry = fills
|
||||||
|
.entry(key4(tcol))
|
||||||
|
.or_insert_with(|| (tcol, Vec::new()));
|
||||||
|
for &[x, y] in &stroke.fill_tris {
|
||||||
|
entry.1.push([
|
||||||
|
stroke.origin[0] as f32 + x,
|
||||||
|
stroke.origin[1] as f32 + y,
|
||||||
|
to.z as f32,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !stroke.strokes.is_empty() {
|
||||||
|
let entry = borders
|
||||||
|
.entry((key4(tcol), (line_weight_px * 100.0) as u32))
|
||||||
|
.or_insert_with(|| (tcol, line_weight_px, Vec::new()));
|
||||||
|
for s in &stroke.strokes {
|
||||||
|
if s.len() < 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !entry.2.is_empty() {
|
||||||
|
entry.2.push([f32::NAN; 3]);
|
||||||
|
}
|
||||||
|
for &[x, y] in s {
|
||||||
|
entry.2.push([
|
||||||
|
stroke.origin[0] as f32 + x,
|
||||||
|
stroke.origin[1] as f32 + y,
|
||||||
|
to.z as f32,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
let quads = crate::scene::text::glyph_quads::layout_glyph_quads(
|
||||||
&mut atlas,
|
&mut atlas,
|
||||||
run.height,
|
run.height,
|
||||||
|
|||||||
Reference in new issue
Block a user