Text layers can follow a path (start offset, flip, alignment; edited in place; canvas, PNG, SVG, PDF; SVG textPath imports as real text on a path). A new pattern paint tiles a source layer in grid or hex rows with scale, spacing and alignment, live as the source changes (SVG pattern, PDF vector tiling pattern).
- Frame and section name labels follow a rotated frame's top edge as shown (turned with it,
cut to the edge's length; a mirrored frame's label stays upright, a frame turned past 90
degrees takes its label along). Component icons, section pills, eyes and status badges turn too.
- The canvas publishes each label's area to the engine (ToolState::labels): with the Move and
Scale tools, hovering a label highlights its frame and pressing it selects and drags the frame
(one undo step); labels of locked, hidden, deleted or other pages' frames do nothing.
- Right-to-left paragraphs mirror lists and indents: bullets and numbers (".1") on the right,
wrapped lines hanging from the right, the paragraph indent on the right; trailing spaces of a
right-to-left line hang outside it instead of pushing its text out of a right-aligned box.
Canvas, PNG, PDF and SVG all draw the shared layout.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Right-to-left and mixed-direction names in the panels: layers, pages,
Assets, tab titles, the file header and canvas frame titles show in
visual order (Unicode bidi, joined Arabic letters, mirrored brackets),
elided at their end (the left end of a right-to-left name). egui shapes
each font run itself, so shown names are laid out a cluster at a time.
Rename fields keep egui's editing and draw the shown text over it: the
caret, selection, clicks, drags and arrow keys follow what's on screen.
System fonts are added for scripts the built-in UI fonts lack.
The Typography section's text style browser: list and grid views with
each style in its own font, search, folders, this file's and enabled
libraries' styles, apply, detach, edit the typography, create from the
selected chars, hover to preview on the canvas, and "Mixed" when the
chars use several styles. Text styles now link chars (style_id on text
styles), so a range can take a style; editing what a style sets
unlinks the chars edited; files from before link on open.
Live text previews (text.preview / text.endPreview): a hovered text
style, font style or named instance and a dragged axis slider show on
the canvas without an undo step; release commits one step, Escape
cancels. ui.input sends raw window input over the control channel.
Visual QA: list markers keep the paragraph's font, renames start with
the name selected and centred, Max lines shows while truncating.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On lines with right-to-left runs, Left and Right now move the caret one
position left or right on screen (TextLayout::visual_step) instead of one
char in logical order, so the caret no longer jumps backwards inside
Arabic or Hebrew. At a line's edge it continues to the neighbouring line
in reading order. Plain left-to-right lines are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Large documents stay interactive: the canvas re-renders only what a pan uncovers or an edit
touches, the layers panel lays out only the rows in view, and per-frame work no longer walks the
whole page.
- Benchmark: `cargo xtask bench [SIZES]` builds synthetic pages (`figcraft_engine::synthetic`:
screens with an auto layout header, a card group, button instances and shapes/text) at
1k/10k/50k layers and prints engine/render timings (`figcraft-cli perf --layers 1000,10000`) and
headless whole-UI frame timings (an ignored ui-egui test).
- Retained canvas rendering (`figcraft_render::Retained`, used by the render worker): views snap
to whole device pixels, so a pan shifts the previous image in place and renders only the
uncovered strips; an edit renders only where changed layers were and are
(`Document::changed_ids` diffs the two versions skipping shared structure; ancestors that paint
from their children's pixels - booleans, effects, masks - count whole; background blur reach
and noise are allowed for). Zoom, page, options, font or page-level changes, or large changes,
render the whole view as before. `ui.inspect` reports `canvas.renderWork`.
- The texture image is converted on the render thread, not the UI thread.
- Layers panel: only visible rows are laid out (stable ids while scrolling); rows are cached per
document version, page, expanded set and filter.
- Frame labels and layout grids of off-screen top-level frames are skipped; the Prototype tab's
connection sources are cached per document version.
- Text: the layout cache keys by a cheap fingerprint confirmed by `TextData` equality (no Debug
formatting per lookup, 32k entries) and hands out shared layouts; a layout generation marks
renders stale when fonts arrive. Overflowing text is culled by its glyphs, so it no longer
vanishes while its box is off screen.
Before -> after (release, Windows, 1440x900 view, ms; 10k / 50k layers):
- move one layer, re-render @100%: 7.65 / 6.49 -> 0.18 / 0.81
- move one layer, re-render zoomed out: 11.95 / 24.10 -> 0.16 / 0.77
- pan 40 px @100%: 7.53 / 6.82 -> 0.69 / 0.81
- pan 40 px zoomed out: 12.07 / 24.30 -> 1.62 / 4.17
- full render @100% / zoomed to fit: 7.78 / 44.22 -> 6.56 / 38.83 (10k @100%, 50k fit)
- UI frame, every screen expanded in layers: 6.16 / 39.07 -> 0.24 / 0.57
- UI frame, scrolling that panel: 6.11 / 40.18 -> 0.43 / 0.40
- UI frame, layers filtered by name: 1.03 / 6.92 -> 0.17 / 0.36
- UI frame, Prototype tab: 0.89 / 6.45 -> 0.18 / 0.80
- UI frame, idle: 0.31 / 1.43 -> 0.11 / 0.39
Unchanged and within budget: single-layer edits through the engine ~0.01 ms, normalize after a
one-node edit < 0.01 ms, hit test 26 / 93 us. Left: save/load at 50k (~0.7 s each), zoomed-out
renders at 50k (~40 ms).
Tests: retained renders match full renders after pans, moves, recolours, hide/remove/add, undo,
masks, overflowing text, edits under a background blur and an inner-shadow/noise layer, a drop-
shadowed group, and a blur panned in from the view's edge; zoom/page/options/page background
render full; junk views don't panic. Layer-row and prototype-source caches follow edits, undo,
page switches, expansion and the filter. `changed_ids`, the text cache, and the perf CLI.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Text runs get bidi embedding levels from the Unicode bidi algorithm
(unicode-bidi, MIT/Apache-2.0). Right-to-left runs are shaped right to
left, kept in logical order for line breaking and placed in visual order
per line (rule L2, trailing spaces at the paragraph level). Glyphs and
lines know their direction, so the caret, clicks and selection
highlights follow right-to-left runs. Text without right-to-left
characters skips the analysis.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Inline IME composition: egui Preedit events become a composition held in the text edit
session (`TextEdit.preedit`), shown inline at the caret with an underline and the IME cursor,
reflowing the text; the document only gets the committed text (`text.edit.insert`). Composing
over a selection replaces it; moving the caret or ending the edit drops an uncommitted
composition. The IME candidate window follows the cursor inside the composition.
New command `text.edit.preedit {text, cursor?}`; `text.edit.selection` reports it.
- Variable fonts: faces read their fvar axes (tag, name, range, default, hidden) and named
instances. Named instances are offered as styles and resolve in `find`; a style name with no
instance implies weight/italic/slant on the axes and optical size follows the font size.
Per-run custom axis values (`TextStyle.variations`, serde default, omitted when empty) are applied
in HarfRust shaping (ShaperInstance) and skrifa outlines/advances (cached per coordinates).
PDF export outlines non-default instances instead of embedding the default one.
- Commands/props: `fontVariations` / `variationSettings` props (whole layer or start..end),
`text.setVariations {variations, replace?}` (merges; edited range while editing),
`text.fonts {family, style?, axes: true}`; `node.get` and copy/paste properties carry the axes.
Picking a named style (Bold/Italic toggles, weight steps, fontStyle/fontWeight) drops custom
wght/ital/slnt values and keeps the others.
- Type settings ▸ Variable tab for variable fonts: a slider (default tick, double-click to reset)
and a field per visible axis, "Mixed" across differing styles, live drag as one undo step,
"Reset to style". Long style/family names stay on one line with an ellipsis and a tooltip.
- Tests: variable font shaping/outlines/clamping/instances (skipped without a variable font),
serde backward compatibility, preedit lifecycle and undo, variation commands/props/undo/junk;
junk sweeps extended with variation and preedit params.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Edit-mode toolbar (Move V, Pen P, Bend hold Cmd, Paint bucket Shift+B, Lasso Q, Cut X, Done)
replaces the tools while a vector is edited.
- Pen in edit mode continues open ends, branches from any point (vector networks), adds points on
segments and closes paths into fill regions; dragging pulls out mirrored handles.
- Paint bucket fills or unfills any closed area: faces of the network (half-edge walk, nested
pieces become holes), with a hatched preview of the area under the pointer.
- Bend tool / Cmd: bend segments, pull handles out of a point, click a smooth point to sharpen it.
- Lasso and marquee point selection, segment selection (shift adds), cut at a segment or a point.
- Per-point handle mirroring (none / angle / angle and length, inferred for existing files),
Alt-drag breaks it; smooth/sharp points; join and merge selected points (Cmd+J, Shift+Cmd+J).
- Delete opens the path, Shift+Delete heals it; arrows nudge points; Cmd+A selects all points.
- Design panel shows the selected points' position, corner radius and mirroring.
- Every gesture is one undo step; edit mode ends when its layer is undone away.
- Paths that end on their start without a ClosePath (ellipse outlines) stay closed and filled.
- New commands (vector.edit.tool/pen/paint/cut/lasso/mirroring/smooth/position/nudge/connect/...)
for the CLI, control channel and MCP; docs updated.
- Clippy: simplify a few boolean expressions flagged by the current toolchain.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>