From d36cc9786f26690828d8bdc3c3139e96e691d825 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 20 Jun 2026 14:27:59 +0300 Subject: [PATCH] fix(web): render OSNAP menu symbols as SVG instead of font glyphs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The object-snap menu drew each mode's marker as a Unicode geometric glyph (◻ △ ◯ …). The web build bundles only Fira Sans, which lacks them, so they rendered as tofu boxes (#138). Add an SVG per snap mode under assets/icons/osnap, map them via icons::osnap, and draw them with the same tinted-SVG path the rest of the chrome uses — font-independent on every platform. Co-Authored-By: Claude Opus 4.8 --- assets/icons/osnap/apparent.svg | 1 + assets/icons/osnap/center.svg | 1 + assets/icons/osnap/endpoint.svg | 1 + assets/icons/osnap/extension.svg | 1 + assets/icons/osnap/grid.svg | 1 + assets/icons/osnap/insertion.svg | 1 + assets/icons/osnap/intersection.svg | 1 + assets/icons/osnap/midpoint.svg | 1 + assets/icons/osnap/nearest.svg | 1 + assets/icons/osnap/node.svg | 1 + assets/icons/osnap/parallel.svg | 1 + assets/icons/osnap/perpendicular.svg | 1 + assets/icons/osnap/quadrant.svg | 1 + assets/icons/osnap/tangent.svg | 1 + src/ui/icons.rs | 42 ++++++++++++++++++++++++++++ src/ui/snap_popup.rs | 24 ++++++++-------- 16 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 assets/icons/osnap/apparent.svg create mode 100644 assets/icons/osnap/center.svg create mode 100644 assets/icons/osnap/endpoint.svg create mode 100644 assets/icons/osnap/extension.svg create mode 100644 assets/icons/osnap/grid.svg create mode 100644 assets/icons/osnap/insertion.svg create mode 100644 assets/icons/osnap/intersection.svg create mode 100644 assets/icons/osnap/midpoint.svg create mode 100644 assets/icons/osnap/nearest.svg create mode 100644 assets/icons/osnap/node.svg create mode 100644 assets/icons/osnap/parallel.svg create mode 100644 assets/icons/osnap/perpendicular.svg create mode 100644 assets/icons/osnap/quadrant.svg create mode 100644 assets/icons/osnap/tangent.svg diff --git a/assets/icons/osnap/apparent.svg b/assets/icons/osnap/apparent.svg new file mode 100644 index 000000000..66e8c4646 --- /dev/null +++ b/assets/icons/osnap/apparent.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/center.svg b/assets/icons/osnap/center.svg new file mode 100644 index 000000000..4c45bba7b --- /dev/null +++ b/assets/icons/osnap/center.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/endpoint.svg b/assets/icons/osnap/endpoint.svg new file mode 100644 index 000000000..5610ff59f --- /dev/null +++ b/assets/icons/osnap/endpoint.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/extension.svg b/assets/icons/osnap/extension.svg new file mode 100644 index 000000000..9fd1934e3 --- /dev/null +++ b/assets/icons/osnap/extension.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/grid.svg b/assets/icons/osnap/grid.svg new file mode 100644 index 000000000..731603ffb --- /dev/null +++ b/assets/icons/osnap/grid.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/insertion.svg b/assets/icons/osnap/insertion.svg new file mode 100644 index 000000000..b7da7b610 --- /dev/null +++ b/assets/icons/osnap/insertion.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/intersection.svg b/assets/icons/osnap/intersection.svg new file mode 100644 index 000000000..a8eccb0a2 --- /dev/null +++ b/assets/icons/osnap/intersection.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/midpoint.svg b/assets/icons/osnap/midpoint.svg new file mode 100644 index 000000000..eb278968c --- /dev/null +++ b/assets/icons/osnap/midpoint.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/nearest.svg b/assets/icons/osnap/nearest.svg new file mode 100644 index 000000000..96996fa7a --- /dev/null +++ b/assets/icons/osnap/nearest.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/node.svg b/assets/icons/osnap/node.svg new file mode 100644 index 000000000..0c5a3eaa2 --- /dev/null +++ b/assets/icons/osnap/node.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/parallel.svg b/assets/icons/osnap/parallel.svg new file mode 100644 index 000000000..7c887b848 --- /dev/null +++ b/assets/icons/osnap/parallel.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/perpendicular.svg b/assets/icons/osnap/perpendicular.svg new file mode 100644 index 000000000..1db15039f --- /dev/null +++ b/assets/icons/osnap/perpendicular.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/quadrant.svg b/assets/icons/osnap/quadrant.svg new file mode 100644 index 000000000..070c6f9f8 --- /dev/null +++ b/assets/icons/osnap/quadrant.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/osnap/tangent.svg b/assets/icons/osnap/tangent.svg new file mode 100644 index 000000000..ef1e0cca6 --- /dev/null +++ b/assets/icons/osnap/tangent.svg @@ -0,0 +1 @@ + diff --git a/src/ui/icons.rs b/src/ui/icons.rs index 45a6f3fc9..5a95628c5 100644 --- a/src/ui/icons.rs +++ b/src/ui/icons.rs @@ -15,6 +15,25 @@ const TRI_RIGHT: &[u8] = include_bytes!("../../assets/icons/ui/tri_right.svg"); const UNDO: &[u8] = include_bytes!("../../assets/icons/ui/undo.svg"); const REDO: &[u8] = include_bytes!("../../assets/icons/ui/redo.svg"); +// OSNAP marker symbols. Rendered as SVG (not Unicode glyphs) so the snap menu +// shows the right shapes on the web build, whose bundled Fira Sans lacks the +// geometric glyphs and rendered them as tofu boxes. (#138) +const OSNAP_ENDPOINT: &[u8] = include_bytes!("../../assets/icons/osnap/endpoint.svg"); +const OSNAP_MIDPOINT: &[u8] = include_bytes!("../../assets/icons/osnap/midpoint.svg"); +const OSNAP_CENTER: &[u8] = include_bytes!("../../assets/icons/osnap/center.svg"); +const OSNAP_NODE: &[u8] = include_bytes!("../../assets/icons/osnap/node.svg"); +const OSNAP_QUADRANT: &[u8] = include_bytes!("../../assets/icons/osnap/quadrant.svg"); +const OSNAP_INTERSECTION: &[u8] = include_bytes!("../../assets/icons/osnap/intersection.svg"); +const OSNAP_EXTENSION: &[u8] = include_bytes!("../../assets/icons/osnap/extension.svg"); +const OSNAP_INSERTION: &[u8] = include_bytes!("../../assets/icons/osnap/insertion.svg"); +const OSNAP_PERPENDICULAR: &[u8] = + include_bytes!("../../assets/icons/osnap/perpendicular.svg"); +const OSNAP_TANGENT: &[u8] = include_bytes!("../../assets/icons/osnap/tangent.svg"); +const OSNAP_NEAREST: &[u8] = include_bytes!("../../assets/icons/osnap/nearest.svg"); +const OSNAP_APPARENT: &[u8] = include_bytes!("../../assets/icons/osnap/apparent.svg"); +const OSNAP_PARALLEL: &[u8] = include_bytes!("../../assets/icons/osnap/parallel.svg"); +const OSNAP_GRID: &[u8] = include_bytes!("../../assets/icons/osnap/grid.svg"); + const LAY_ON: &[u8] = include_bytes!("../../assets/icons/layers/layon.svg"); const LAY_OFF: &[u8] = include_bytes!("../../assets/icons/layers/layoff.svg"); const LAY_FRZ: &[u8] = include_bytes!("../../assets/icons/layers/layfrz.svg"); @@ -82,6 +101,29 @@ pub fn raw<'a, M: 'a>(bytes: &'static [u8], size: f32) -> Element<'a, M> { .into() } +/// SVG bytes for an OSNAP mode's marker symbol, for the snap menu. (#138) +pub fn osnap(snap: crate::snap::SnapType) -> &'static [u8] { + use crate::snap::SnapType as S; + match snap { + S::Endpoint => OSNAP_ENDPOINT, + S::Midpoint => OSNAP_MIDPOINT, + S::Center => OSNAP_CENTER, + S::Node => OSNAP_NODE, + S::Quadrant => OSNAP_QUADRANT, + S::Intersection => OSNAP_INTERSECTION, + S::Extension => OSNAP_EXTENSION, + S::Insertion => OSNAP_INSERTION, + S::Perpendicular => OSNAP_PERPENDICULAR, + S::Tangent => OSNAP_TANGENT, + S::Nearest => OSNAP_NEAREST, + S::ApparentIntersection => OSNAP_APPARENT, + S::Parallel => OSNAP_PARALLEL, + S::Grid => OSNAP_GRID, + // Not shown in the snap menu; fall back to a neutral marker. + S::ObjectPick => OSNAP_NEAREST, + } +} + /// Layer visibility icon bytes (on / off). pub fn layer_visible(visible: bool) -> &'static [u8] { if visible { diff --git a/src/ui/snap_popup.rs b/src/ui/snap_popup.rs index d048010fa..700a8eef3 100644 --- a/src/ui/snap_popup.rs +++ b/src/ui/snap_popup.rs @@ -36,8 +36,8 @@ pub fn snap_popup_overlay<'a>(snapper: &'a Snapper, right_offset: f32) -> Elemen // Snap mode rows let mut rows: Vec> = Vec::new(); - for &(snap_type, icon, label) in ALL_SNAP_MODES { - rows.push(snap_row(snap_type, icon, label, snapper.is_on(snap_type))); + for &(snap_type, _glyph, label) in ALL_SNAP_MODES { + rows.push(snap_row(snap_type, label, snapper.is_on(snap_type))); } // "Object Snap Settings…" footer @@ -101,18 +101,18 @@ pub fn snap_popup_overlay<'a>(snapper: &'a Snapper, right_offset: f32) -> Elemen // ── Individual snap row ─────────────────────────────────────────────────── -fn snap_row<'a>( - snap_type: SnapType, - icon: &'a str, - label: &'a str, - active: bool, -) -> Element<'a, Message> { +fn snap_row<'a>(snap_type: SnapType, label: &'a str, active: bool) -> Element<'a, Message> { let checkmark = crate::ui::icons::check_cell(active, CHECK_COLOR); - let icon_el = text(icon) - .size(11) - .color(ICON_COLOR) - .width(Length::Fixed(16.0)); + // SVG marker (not a Unicode glyph) so the symbols render on the web build, + // whose bundled font lacks them and showed tofu boxes. (#138) + let icon_el = container(crate::ui::icons::tinted::( + crate::ui::icons::osnap(snap_type), + 13.0, + ICON_COLOR, + )) + .width(Length::Fixed(16.0)) + .align_x(iced::Center); let label_el = text(label) .size(11)