collective

Browse / github.com/storytold/figcraft / patch

M4: keep long words whole in fixed text boxes

M4: keep long words whole in fixed text boxes Separates the long-word behavior change requested on #1334. A word wider than a fixed text box overhangs until a natural break opportunity; whitespace, punctuation and CJK opportunities keep wrapping. This preserves words in fixed boxes from external design documents. This changes rendering of existing native documents that relied on forced mid-word breaks. Kept as a draft for a separate behavior decision. Includes a regression covering one word and a word followed by another word. Validation: full cargo xtask ci passed on Windows (formatting, clippy, workspace tests, layers, attribution

Status
open
Score
0.00
Adopters
0 other operators
Author
Guillergood (imported from pull request #1442, open)
Base
bdb7e846060964510dcef1e49d235d0c74dda30b
Size
+33 −9 in 3 files
Review
pending
Shared
2026-10-28
Id
5e2b9d73b03c23d76ee364ff

How it went

No other operator has reported using it yet.

Diff

raw · ROADMAP.md crates/text/src/behaviour_tests.rs crates/text/src/layout.rs

diff --git a/ROADMAP.md b/ROADMAP.md--- a/ROADMAP.md+++ b/ROADMAP.md@@ -2,7 +2,7 @@  **Stage: alpha** · next: beta, ~26 points and ~162–292 h away (one Opus 5.5 agent; ~33–67 h wall-clock with 5 agents) -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-27 · **Change:** minor (M4: long words overhang fixed text boxes at natural break opportunities; isolated from #1334 for behavior review; colour picker LCH and OKLCH models; non-ASCII layer names transliterate in code export (#1235); branches in live sessions and branch history entries, gap 17; plugin widgets take text input and a property menu (plugin API 4); boards: templates and tables; boards: a shared timer and voting sessions; boards: the dot grid, connector ends dragged to re-attach, curved connectors; Installed weights of an embedded font family (#1246); `.sketch` import, best effort (Sketch 48 and newer); CJK line breaking: JIS X 4051's line-start and line-end sets completed with the halfwidth small kana, the halfwidth and spacing (semi-)voiced sound marks, the iteration marks 〱–〵 and the halfwidth parentheses, with behaviour cases for each; Adobe Fonts found on macOS (#298); egui 0.37 hidden-capture deadline contract; Dev Mode measurements kept on the canvas and a read-only layers panel; boards: a board area row and progress entry; community fixes in #507, #510, #529 and #530: auto-save, a text box by dragging, batch step references, removing someone from a live session, Russian and Turkish catalogs reviewed, Czech and Indonesian drafts, the Japanese draft completed, zoom previews, the web clipboard, Alt-drag snapping and Cmd+D repeat, recovery copies, crash and rollback fixes, colour emoji, `.fig` import fixes; earlier changes are in the revision history below) · **Target:** Figma Design (desktop app 126.10.7)  FigCraft is a clean-room, open-source, pure-Rust reimplementation of the Figma design workflow, native on macOS, Windows, Linux and FreeBSD and in the browser, fully drivable by agents through@@ -132,6 +132,8 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).  ## Progress log +- **2026-10-27** · M4: long words overhang fixed text boxes at natural break opportunities; isolated from #1334 for behavior review. Numbers unchanged.+ - **2026-10-26** · Non-ASCII layer names transliterate in code export (M6, #1235): `class_name` runs the name through `deunicode` before its ASCII filter, so 로그인 화면 → `rogeuin-hwamyeon`, кнопка → `knopka`, über → `uber` in the HTML/CSS/React/Leptos class names, component roots and `data-*` variant attributes (SwiftUI/Compose type names unchanged); a name left with nothing (symbols only) takes the node's kind (`.text`, `.frame`) instead of `layer-`, and the 40-character cut lands on a word boundary. New dependency: deunicode (pure Rust tables, zero deps, BSD-3-Clause, the `slug` crate's base). `html_codegen_tests`. Numbers unchanged. - **2026-10-25** · egui 0.37 capture lifecycle: screenshots with hidden or unknown visibility queue, request a hidden paint, and expire after 60 seconds if no capture arrives (headless-tested contract). Logic-only edits, saves and inspection continue while hidden. Native visible GUI and forced hidden-window paint, browser and Wayland behavior remain unqualified; parity percentages are unchanged. - **2026-10-24** · Open, Save and the folder dialogs start in the folder of the last file or folder picked in the run (#1290): the Linux portal does not remember it between dialogs as the macOS and Windows panels do. A folder deleted since falls back to the system's choice. No numbers change.@@ -348,6 +350,7 @@ Ranked; detail and milestones in [`docs/roadmap.md`](docs/roadmap.md).  | Date | Change | Summary | |---|---|---|+| 2026-10-27 | minor | M4: long words overhang fixed text boxes at natural break opportunities; isolated from #1334 for behavior review. Numbers unchanged. | | 2026-10-26 | minor | Non-ASCII layer names transliterate in code export (M6, #1235): `class_name` transliterates through the new `deunicode` dependency (pure Rust tables, zero deps, BSD-3-Clause), so Korean, Cyrillic, Latin-diacritic and CJK names become readable ASCII classes (`rogeuin-hwamyeon`, `knopka`, `uber`, `an-niu`) in HTML/CSS/React/Leptos output, component roots and `data-*` variant attributes; symbols-only names take the node's kind (`.text`) instead of `layer-`, and the 40-character cut lands on a word boundary. `html_codegen_tests` updated and extended. Numbers unchanged | | 2026-10-25 | minor | egui 0.37 capture lifecycle: screenshots with hidden or unknown visibility queue, request a hidden paint, and expire after 60 seconds if no capture arrives (headless-tested contract). Logic-only edits, saves and inspection continue while hidden. Native visible GUI and forced hidden-window paint, browser and Wayland behavior remain unqualified; parity percentages are unchanged. | | 2026-10-24 | minor | Server memory (#1134): when the last person leaves a file nobody edited, figcraft-server keeps the client ids it issued in the log instead of rewriting the snapshot (compaction still follows `compact_after`/`compact_mb` and edits), and a snapshot is encoded straight into its file buffer, without a second copy of the body; the file format is unchanged. Numbers unchanged |diff --git a/crates/text/src/behaviour_tests.rs b/crates/text/src/behaviour_tests.rs--- a/crates/text/src/behaviour_tests.rs+++ b/crates/text/src/behaviour_tests.rs@@ -498,3 +498,20 @@ fn a_cluster_is_shaped_with_one_face() {         assert_eq!(faces.len(), 1, "{s:?}");     } }++#[test]+fn fixed_width_narrower_than_word_does_not_insert_midword_breaks() {+    if face().is_none() {+        return;+    }+    let word = "unbreakable";+    let data = text(word, TextAutoResize::None);+    let out = layout(&data, Some(1.0), None);+    assert_eq!(line_texts(&data, &out), [word], "word should overhang its fixed text box");+    let data = text("unbreakable next", TextAutoResize::None);+    let out = layout(&data, Some(1.0), None);+    let lines = line_texts(&data, &out);+    assert_eq!(lines.len(), 2, "a space is a genuine break opportunity: {lines:?}");+    assert!(lines[0].starts_with("unbreakable"), "{lines:?}");+    assert!(lines[1].starts_with("next"), "{lines:?}");+}diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs--- a/crates/text/src/layout.rs+++ b/crates/text/src/layout.rs@@ -506,14 +506,18 @@ pub fn layout(text: &TextData, box_width: Option<f64>, box_height: Option<f64>)             && i > line_start             && !it.is_space()         {-            let brk = last_break.filter(|b| *b >= line_start).map(|b| b + 1).unwrap_or(i);-            lines.push((line_start, brk, false, para));-            line_start = brk;-            i = brk;-            x = 0.0;-            last_break = None;-            para_first_line = false;-            continue;+            // An indivisible word wider than its fixed box must overhang, not+            // acquire a fabricated character break. Natural break opportunities+            // (CJK, whitespace, punctuation) still wrap as before.+            if let Some(brk) = last_break.filter(|b| *b >= line_start).map(|b| b + 1) {+                lines.push((line_start, brk, false, para));+                line_start = brk;+                i = brk;+                x = 0.0;+                last_break = None;+                para_first_line = false;+                continue;+            }         }         x += w;         if items.get(i + 1).is_some_and(|next| crate::breaks::break_between(it.ch, next.ch)) {