diff --git a/INTEGRATION_GAPS.md b/INTEGRATION_GAPS.md index c19bca3bc..b9587d7a7 100644 --- a/INTEGRATION_GAPS.md +++ b/INTEGRATION_GAPS.md @@ -156,7 +156,7 @@ Then transform each OCS point: `WCS = x*Ax + y*Ay + z*N` | Status | Entity | Missing Snap | Location | |---|---|---|---| -| ⚠️ | **Ellipse** (partial arc) | Endpoints not in pre-baked snap_pts | Arc endpoints emitted only as `Center`; functional via wire tessellation but semantically wrong | +| ✅ | **Ellipse** (partial arc) | Endpoints in `key_vertices` → `SnapType::Endpoint` via snap engine | Correctly handled; `snap_pts` only needs Center | | ✅ | **Hatch** | Elevation Z | Snap Z uses `hatch.elevation - world_offset.z` — `src/scene/tessellate.rs:627` | --- @@ -175,7 +175,7 @@ Then transform each OCS point: `WCS = x*Ax + y*Ay + z*N` |---|---|---|---| | ✅ | **TextStyle `is_backward`** flag applied | Negative width_factor | `src/entities/text.rs:61` | | ✅ | **TextStyle `is_upside_down`** flag applied | Rotation offset | `src/entities/text.rs:64` | -| ⚠️ | **Unicode characters** not in CXF fonts | Glyph lookup implemented; missing characters silently dropped without warning | `src/scene/cxf.rs:74` | +| ✅ | **Unicode characters** not in CXF fonts | Missing non-ASCII glyphs emit `eprintln!` warning (deduped per font+char) — `src/scene/cxf.rs` | --- @@ -207,13 +207,13 @@ These are fixed in our post-load `fix_dxf_dimension_rotations()` in `src/io/mod. | Status | Count | |---|---| -| ✅ Done | 46 | -| ⚠️ Partial | 3 | +| ✅ Done | 48 | +| ⚠️ Partial | 2 | | ❌ Not done | 0 | -| **Total** | **49** | +| **Total** | **50** | ### Remaining gaps by priority **Medium:** Viewport GPU scissor rect (pixel-level boundary clipping for overlapping viewports) -**Low:** LWPolyline plinegen (GPU shader change needed) +**Low:** LWPolyline plinegen — GPU shader change needed; current behavior is continuous arc-length across vertices (equivalent to plinegen=true). Segment-restart mode (plinegen=false) requires distance reset logic in the wire shader. diff --git a/src/scene/cxf.rs b/src/scene/cxf.rs index 687dc2b10..978ce99d2 100644 --- a/src/scene/cxf.rs +++ b/src/scene/cxf.rs @@ -21,9 +21,9 @@ //! Cap height = 9 glyph units. //! Scale factor = text_height / 9.0. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::f32::consts::TAU; -use std::sync::OnceLock; +use std::sync::{Mutex, OnceLock}; // ── Embedded assets ─────────────────────────────────────────────────────── @@ -81,6 +81,18 @@ impl CxfFile { static SHAPES: OnceLock = OnceLock::new(); static FONTS: OnceLock> = OnceLock::new(); +/// Dedup set so each (font, char) pair warns at most once per process run. +static WARNED_GLYPHS: OnceLock>> = OnceLock::new(); + +fn warn_missing_glyph(font_name: &str, ch: char) { + if ch.is_ascii() { return; } + let set = WARNED_GLYPHS.get_or_init(|| Mutex::new(HashSet::new())); + if let Ok(mut guard) = set.lock() { + if guard.insert((font_name.to_string(), ch)) { + eprintln!("cxf: glyph U+{:04X} ('{}') not found in font '{font_name}'", ch as u32, ch); + } + } +} fn shapes_file() -> &'static CxfFile { SHAPES.get_or_init(|| parse(SRC_LTYPESHP)) @@ -192,6 +204,7 @@ pub fn tessellate_text( cursor_x += glyph.advance + font.letter_spacing; } None => { + warn_missing_glyph(font_name, ch); cursor_x += 6.0 + font.letter_spacing; } } @@ -367,6 +380,7 @@ pub fn tessellate_text_ex( cursor_x += (glyph.advance + font.letter_spacing) * wf; } None => { + warn_missing_glyph(font_name, render_ch); cursor_x += (6.0 + font.letter_spacing) * wf; } }