Skip to content

Draw: Lasso Select, Add Pen, Ink to Shape and Ink Replay (#395) - #408

Merged
echelon merged 3 commits into
storytold:mainfrom
vangel76:feat/395-draw-tools
Oct 11, 2026
Merged

echelon merged 3 commits into
storytold:mainfrom
vangel76:feat/395-draw-tools

Conversation

@vangel76

Copy link
Copy Markdown
Contributor

Fixes #395

What

  • Lasso Select (draw.lasso): no points picks up the lasso; {"page", "points"} selects the strokes mostly (≥60% of sampled points) inside the loop and the drawings whose centre is inside. draw.lassoMove {dx, dy}, draw.lassoColor {color} and draw.lassoDelete act on the selection, one undo step each. The selection lasts until the document changes some other way. In the app you drag a loop and get a dashed frame around the selection. Dragging the frame moves the selection, Delete/Backspace deletes it, the Color menu recolours it, and Esc lets it go.

  • Add Pen (draw.addPen {kind, color, width}, draw.removePen {index}): a pen gallery after the built-in pens. Pens are validated and capped at 24. The gallery is saved in ui.json (inkPens), and on reading, entries that aren't pens are dropped without losing the other settings. Each gallery pen shows its kind's icon over a bar of its colour and width. Clicking a pen picks it up, and right-clicking it gives Remove Pen. When a gallery pen is in use, the Color and Thickness menus update it.

  • Ink to Shape (draw.inkToShape): toggles the mode, or {"value": bool} sets it. While it is on, a pen or pencil stroke that looks like a shape is replaced by that shape in the same undo step, keeping its colour and width:

    • a line
    • a rectangle or square (upright Rectangle)
    • an ellipse or circle (Ellipse)
    • a triangle or a polygon of up to 8 corners (a closed non-ink freeform)

    Other strokes stay ink. With ink lasso-selected, the button converts the selection instead (draw.convertInk). The recogniser is our own code in crates/engine/src/cmd/ink_shape.rs:

    • chord deviation test for lines
    • closure test
    • axis-aligned ellipse fit by RMS of the normalised radius
    • closed Ramer–Douglas–Peucker (iterative), then merging near-straight or crowded corners and checking that the polygon fits every point
  • Ink Replay (draw.replay): returns the strokes in drawing order and turns the replay on or off. While it runs, the pages are drawn without ink and the strokes are drawn again progressively in an overlay, timed by their length. A bottom bar has Rewind / Play-Pause / Forward (to the end of the current stroke) / "Stroke n of m" / Close, and Esc closes it.

    • Each new stroke records its drawing order in the new Freeform::order.
    • DOCX keeps it in the drawing name (Ink Pen 3 #12) and reads it back.
    • Ink without an order (from other files) replays first, in document order.
  • New icons are drawn in code (addPen, replay controls). New strings are in all 9 catalogs. Catalog goes from 402/434 to 406/434 (93.5%). Progress docs are updated.

Tests

  • ink_shape: rectangle (started mid-side), square, ellipse, circle, a shaky line, a triangle and a pentagon are recognised. An open zigzag, a hook, a tap, a tiny tick, a 24-spike closed star, NaN/∞ points and a flood of points stay ink.
  • draw tests:
    • lasso selects only the stroke inside the loop; move, recolour and delete are one undo step each; hostile loops (two points, 20k points) don't panic
    • Ink to Shape mode converts a drawn rectangle in the stroke's undo step, keeps a scribble as ink, and converts a lasso-selected ellipse
    • replay order follows drawing order, not document order
    • addPen validation, the gallery cap and removePen; a saved gallery drops junk entries
  • The DOCX roundtrip test checks that the drawing order survives.
  • hostile_params_never_panic covers the new commands.
  • cargo test passes for doc, docx, engine and ui-egui. cargo clippy -D warnings is clean on those crates. cargo xtask wasm, layers and parity pass.
  • Checked headlessly with ui_shot:
    • the gallery pens show in the ribbon
    • the lasso frame draws around the selection
    • a drawn rectangle and ellipse are converted to crisp shapes
    • the replay bar shows

Not done

  • Arrows in Ink to Shape; rotated ellipses are recognised as polygons or stay ink.
  • Lasso doesn't rotate or resize the selection. Moving a selection doesn't re-anchor drawings to the paragraph they land on.
  • Gallery pens can't be reordered (add and remove only). Word's own InkML (w14:contentPart) and per-point timestamps aren't read, so a replay is paced by stroke length, not real time.
  • Ink to Math (needs handwriting recognition).

🤖 Generated with Claude Code

@vangel76
vangel76 force-pushed the feat/395-draw-tools branch from 9c4c460 to 024d4ab Compare October 11, 2026 06:36
@vangel76

Copy link
Copy Markdown
Contributor Author

Rebased onto main after #474. Merge fix: ink_on now returns each stroke's turn (PlacedInk.spin, from #445), so the eraser keeps hitting turned ink where it is drawn, and Lasso Select and Ink Replay also use the turned stroke (PlacedInk::paths applies the turn). New test: the_lasso_selects_a_rotated_stroke_where_it_is_drawn.

@vangel76
vangel76 force-pushed the feat/395-draw-tools branch 2 times, most recently from a9d6792 to 0a634a8 Compare October 11, 2026 17:58
vangel76 and others added 2 commits October 11, 2026 21:16
- draw.lasso: with no points picks up the lasso; with a loop on a page it
  selects the strokes mostly inside it and drawings whose centre is inside.
  draw.lassoMove / draw.lassoColor / draw.lassoDelete move, recolour and
  delete the selection (one undo step each); it lasts until the document
  changes otherwise. In the app: drag a loop, drag the selection to move it,
  Delete deletes, Esc lets go, the Color menu recolours it.
- draw.addPen / draw.removePen: a pen gallery (kind, colour, width; capped,
  validated) shown after the built-in pens, kept in ui.json; damaged entries
  are dropped on reading. Right-click a gallery pen to remove it.
- draw.inkToShape: toggles Ink to Shape (pen and pencil strokes that look
  like a line, rectangle/square, ellipse/circle, triangle or polygon of up to
  eight corners become that shape, in the same undo step); with ink
  lasso-selected it converts the selection (draw.convertInk). Our own
  recogniser (cmd/ink_shape.rs): chord test for lines, closure test,
  ellipse RMS fit, closed RDP simplification with corner merging.
- draw.replay: Ink Replay plays the strokes back in drawing order with a
  Rewind / Play-Pause / Forward / Close bar; pages are drawn without ink
  meanwhile. Each new stroke records its drawing order (Freeform::order),
  written to DOCX in the drawing name (`Ink Pen 3 storytold#12`) and read back.

Catalog 402/434 -> 406/434 (93.5%). New strings in all nine catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vangel76
vangel76 force-pushed the feat/395-draw-tools branch from 0a634a8 to 81ade96 Compare October 11, 2026 19:34
# Conflicts:
#	ROADMAP.md
#	docs/parity-checklist.md
#	docs/target-app-parity.md
@echelon
echelon merged commit 4ca58d6 into storytold:main Oct 11, 2026
@echelon

echelon commented Oct 11, 2026

Copy link
Copy Markdown
Member

Thanks for the contribution, @vangel76! This is now merged into main. If you haven't already, please come join us on Discord: https://discord.gg/artcraft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parity: Draw — Lasso Select, Add Pen, Ink to Shape, Ink Replay (draw.lasso, draw.addPen, draw.inkToShape, draw.replay)

2 participants