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:
Karim Jerbi committed 2026-06-25 18:17:01 +01:00
1 parent c599c2ba09
commit 627761b629
20 files changed
+624 -191

No files matched your search

+50 -29
View File
@@ -119,11 +119,60 @@ pub struct OstTrackPoint {
pub screen: Point,
}
pub fn grid_overlay<'a>(
grid: Vec<GridParams>,
) -> Element<'a, Message> {
canvas(GridCanvas { grid })
.width(Length::Fill)
.height(Length::Fill)
.into()
}
struct GridCanvas {
grid: Vec<GridParams>,
}
impl canvas::Program<Message> for GridCanvas {
type State = ();
fn draw(
&self,
_state: &(),
renderer: &iced::Renderer,
_theme: &Theme,
bounds: iced::Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry> {
let mut frame = canvas::Frame::new(renderer, bounds.size());
for g in &self.grid {
let gb = g.bounds;
let cx0 = gb.x.max(0.0);
let cy0 = gb.y.max(0.0);
let cx1 = (gb.x + gb.width).min(bounds.width);
let cy1 = (gb.y + gb.height).min(bounds.height);
if cx1 <= cx0 || cy1 <= cy0 {
continue;
}
let clip = iced::Rectangle {
x: cx0,
y: cy0,
width: cx1 - cx0,
height: cy1 - cy0,
};
frame.with_clip(clip, |f| {
draw_grid(f, g.view_rot, g.eye, g.plane, gb, g.origin, g.axes)
});
}
vec![frame.into_geometry()]
}
}
pub fn selection_overlay<'a>(
selection: SelectionState,
snap: Option<(Point, SnapType)>,
grips: Vec<GripMarker>,
grid: Vec<GridParams>,
ucs_icon: Option<UcsIconParams>,
ost_points: Vec<OstTrackPoint>,
cursor_screen: Point,
@@ -135,7 +184,6 @@ pub fn selection_overlay<'a>(
selection,
snap,
grips,
grid,
ucs_icon,
ost_points,
cursor_screen,
@@ -152,7 +200,6 @@ struct SelectionCanvas {
selection: SelectionState,
snap: Option<(Point, SnapType)>,
grips: Vec<GripMarker>,
grid: Vec<GridParams>,
ucs_icon: Option<UcsIconParams>,
ost_points: Vec<OstTrackPoint>,
cursor_screen: Point,
@@ -313,32 +360,6 @@ impl canvas::Program<Message> for SelectionCanvas {
}
}
// ── Grid display ──────────────────────────────────────────────────
// One entry per pane with its grid on. Each is clipped to its own
// rectangle so lines never spill into neighbouring panes — and the clip
// is intersected with the widget so a zoomed-in viewport whose rectangle
// overflows the canvas never paints grid outside the 3-D view.
for g in &self.grid {
let gb = g.bounds;
let cx0 = gb.x.max(0.0);
let cy0 = gb.y.max(0.0);
let cx1 = (gb.x + gb.width).min(bounds.width);
let cy1 = (gb.y + gb.height).min(bounds.height);
if cx1 <= cx0 || cy1 <= cy0 {
continue;
}
let clip = iced::Rectangle {
x: cx0,
y: cy0,
width: cx1 - cx0,
height: cy1 - cy0,
};
// Project with the full viewport rect `gb` so the grid stays aligned;
// only the clip is clamped.
frame.with_clip(clip, |f| {
draw_grid(f, g.view_rot, g.eye, g.plane, gb, g.origin, g.axes)
});
}
if let (Some(a), Some(b)) = (self.selection.box_anchor, self.selection.box_current) {
let (fill, stroke) = if self.selection.box_crossing {