forked from 1b-agent/opencadstudio-upstream
fix(text): render TTF glyphs as solid fills, fix font fallback, correct Z-order
Text elements previously rendered as hollow wireframe outlines and sometimes fell back to the wrong system font due to case-sensitive exact matching. Also, the viewport background grid drew on top of solid entities. Case-insensitive and partial-name fallback matching (e.g., mapping "arialn" to "Arial Narrow") is now added to `sysfont::canonical_family_name`, ensuring text resolves to the correct installed system font. TrueType font contours are now triangulated into solid fills (`fill_tris`) using `lyon_tessellation` in `ttf_glyph.rs`. To prevent the GPU from misclassifying this 2D text as a 3D mesh (which broke flat shading), text tessellation splits the output into separate outline and fill `WireModel`s. The background grid is extracted from `SelectionCanvas` into `GridCanvas` and moved beneath the 3D viewport to ensure proper layering. Finally, `DepthBiasState` is enabled on all face/fill pipelines to apply a polygon offset, perfectly resolving Z-fighting between coplanar wireframe lines and solid faces.
This commit is contained in:
1 parent
c599c2ba09
commit
627761b629
20 files changed
+624
-191
No files matched your search
+61
-17
@@ -65,6 +65,7 @@ impl TruckConvertible for Table {
|
||||
let total_h = *row_offsets.last().unwrap_or(&0.0);
|
||||
|
||||
let mut pts: Vec<[f32; 3]> = Vec::new();
|
||||
let mut tris_pts: Vec<[f32; 3]> = Vec::new();
|
||||
|
||||
// Per-cell borders. When a cell carries a CellStyle, honour the
|
||||
// visibility / `invisible` flag of each of its four borders so
|
||||
@@ -161,15 +162,28 @@ impl TruckConvertible for Table {
|
||||
|
||||
let font_for_handle = |handle: Option<acadrust::Handle>| -> Option<String> {
|
||||
handle.and_then(|h| lookup_style(h)).and_then(|s| {
|
||||
let file = s.font_file.trim();
|
||||
if !file.is_empty() {
|
||||
let basename = file.rsplit(['/', '\\']).next().unwrap_or(file);
|
||||
let stem = basename.split('.').next().unwrap_or(basename).trim();
|
||||
if !stem.is_empty() {
|
||||
return Some(stem.to_string());
|
||||
let mut font_name = if !s.true_type_font.trim().is_empty() {
|
||||
s.true_type_font.trim().to_string()
|
||||
} else {
|
||||
let file = s.font_file.trim();
|
||||
if !file.is_empty() {
|
||||
let basename = file.rsplit(['/', '\\']).next().unwrap_or(file);
|
||||
let stem = basename.split('.').next().unwrap_or(basename).trim();
|
||||
if !stem.is_empty() {
|
||||
stem.to_string()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if !crate::scene::text::lff::is_builtin(&font_name) {
|
||||
if let Some(canonical) = crate::scene::text::sysfont::canonical_family_name(&font_name) {
|
||||
font_name = canonical;
|
||||
}
|
||||
}
|
||||
None
|
||||
Some(font_name)
|
||||
})
|
||||
};
|
||||
// Build a ResolvedTextStyle for the cell — needed by the shared MText
|
||||
@@ -304,6 +318,9 @@ impl TruckConvertible for Table {
|
||||
pts.push([x + ox, y + oy, origin.z]);
|
||||
}
|
||||
}
|
||||
for &[x, y] in &ts.fill_tris {
|
||||
tris_pts.push([x + ox, y + oy, origin.z]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -321,12 +338,18 @@ impl TruckConvertible for Table {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let fill_tris_f64: Vec<[f64; 3]> = tris_pts
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| {
|
||||
[x as f64 + base[0], y as f64 + base[1], z as f64 + base[2]]
|
||||
})
|
||||
.collect();
|
||||
Some(TruckEntity {
|
||||
object: TruckObject::Lines(pts_f64),
|
||||
snap_pts: vec![(glam::DVec3::new(self.insertion_point.x, self.insertion_point.y, self.insertion_point.z), SnapHint::Insertion)],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
fill_tris: vec![],
|
||||
fill_tris: fill_tris_f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -435,10 +458,24 @@ pub fn tessellate_table(
|
||||
};
|
||||
let font_for_handle = |handle: Option<acadrust::Handle>| -> Option<String> {
|
||||
handle.and_then(lookup_style).and_then(|s| {
|
||||
let file = s.font_file.trim();
|
||||
let basename = file.rsplit(['/', '\\']).next().unwrap_or(file);
|
||||
let stem = basename.split('.').next().unwrap_or(basename).trim();
|
||||
(!stem.is_empty()).then(|| stem.to_string())
|
||||
let mut font_name = if !s.true_type_font.trim().is_empty() {
|
||||
s.true_type_font.trim().to_string()
|
||||
} else {
|
||||
let file = s.font_file.trim();
|
||||
let basename = file.rsplit(['/', '\\']).next().unwrap_or(file);
|
||||
let stem = basename.split('.').next().unwrap_or(basename).trim();
|
||||
if !stem.is_empty() {
|
||||
stem.to_string()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if !crate::scene::text::lff::is_builtin(&font_name) {
|
||||
if let Some(canonical) = crate::scene::text::sysfont::canonical_family_name(&font_name) {
|
||||
font_name = canonical;
|
||||
}
|
||||
}
|
||||
Some(font_name)
|
||||
})
|
||||
};
|
||||
let resolved_style_for_handle =
|
||||
@@ -455,7 +492,7 @@ pub fn tessellate_table(
|
||||
|
||||
// Accumulators keyed by quantised colour (+ weight for borders).
|
||||
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
|
||||
let mut texts: 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 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 sel_col = WireModel::SELECTED;
|
||||
@@ -672,10 +709,11 @@ pub fn tessellate_table(
|
||||
} else {
|
||||
entity_color
|
||||
};
|
||||
let buf = &mut texts
|
||||
let entry = texts
|
||||
.entry(key4(tcol))
|
||||
.or_insert_with(|| (tcol, Vec::new()))
|
||||
.1;
|
||||
.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;
|
||||
@@ -690,6 +728,9 @@ pub fn tessellate_table(
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -730,10 +771,13 @@ pub fn tessellate_table(
|
||||
out.push(mk(color, pts, vec![], lw));
|
||||
}
|
||||
}
|
||||
for (_, (color, pts)) in texts {
|
||||
for (_, (color, pts, tris)) in texts {
|
||||
if !pts.is_empty() {
|
||||
out.push(mk(color, pts, vec![], line_weight_px));
|
||||
}
|
||||
if !tris.is_empty() {
|
||||
out.push(mk(color, vec![], tris, line_weight_px));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user