5.4 KiB
5.4 KiB
valw Phase 5b: editor text, pixelate, numbers, crop (design)
Date: 2026-10-03
Status: approved in brainstorming, pending written-spec review
Builds on: Phase 5a (valw edit), docs/superpowers/specs/2026-10-02-valw-phase-5a-editor-design.md
1. Goal
Finish the editor's tool set from vision 4.5: text, hiding sensitive parts, numbered markers and crop, with the same guarantee as 5a: the saved PNG is what the canvas shows.
Out of scope
- Editing, moving or selecting finished shapes (text included).
- Blur (pixelate only), font choice, text background boxes.
2. Decisions
| Question | Decision | Why |
|---|---|---|
| Text editing | Place and type; Enter or a click elsewhere finishes; no re-editing (undo instead) | No selection model yet |
| Text look | Bold, chosen colour, thin dark outline; S/M/L = 16/24/36 px | Readable on any background |
| Font | DejaVu Sans Bold, vendored in assets/fonts/ with its licence |
Turkish glyphs, permissive licence, works without system fonts |
| Text rendering | Glyph outlines from ab_glyph, filled and stroked by tiny-skia |
One renderer for canvas and export |
| Hiding | Pixelate only | Can't be undone by deblurring |
| Crop | Drag, adjust edges, Enter applies, Esc cancels; an undoable history step | User choice |
3. Behaviour
New tools, after the 5a ones in the toolbar: Text (T), Pixelate (B), Number (N), Crop (C). Ctrl+C stays Copy.
3.1 Text
- A click on the image starts a text at that point (its top-left). Typing inserts characters, Backspace deletes, Shift+Enter starts a new line, Enter or a press anywhere else finishes it. Esc while typing drops the text (and does not trigger the close flow).
- An empty text is discarded. While typing, the single-letter tool keys and Ctrl+Z/Ctrl+Y act on the text box only as text (letters) or not at all (Ctrl shortcuts are ignored until the text finishes).
- Look: the glyphs filled in the chosen colour over an outline in near
black (
#1c1c1eat 80 % opacity) of widthsize / 8, so it reads on light and dark backgrounds. White text gets the same dark outline. - Size from the width control: S 16, M 24, L 36 px (image pixels); line height 1.2 × size; kerning from the font.
- A caret (a thin line after the last glyph) blinks while typing; it is never exported.
3.2 Pixelate
- Drag a rectangle (Shift: square). On release the area turns into blocks of S 8, M 12, L 20 px, aligned to the rectangle's top-left; each block takes the average colour (alpha included) of what is under it at that point in the history, so earlier shapes are pixelated too. Edge blocks are partial.
- A click (under 2 px) is discarded.
3.3 Numbers
- A click places a filled circle in the chosen colour with a white bold number centred in it. Diameter S 24, M 32, L 44 px; the number's size is 0.55 × diameter.
- The number is 1 + the count of numbers already in the document, so undo makes the next number reuse the freed one.
3.4 Crop
- Drag a rectangle: the outside darkens (50 % black over the canvas), the inside stays clear, with a 1 px white border. Dragging within 8 screen px of an edge or corner moves that edge (corners move two); the cursor shows the resize direction.
- Enter applies it, Esc cancels it (Esc then does not start the close flow). Switching tools cancels it.
- An applied crop is one history step: undo restores the previous view. The canvas shows only the cropped area, fitted as in 5a. Shapes keep their original image coordinates; drawing is clamped to the crop.
- A later crop happens inside the current one.
- Export: everything is drawn on the full image, then the last crop's rectangle is cut out. "Unsaved" compares the history including crops.
4. Structure
| File | Change |
|---|---|
assets/fonts/DejaVuSans-Bold.ttf, assets/fonts/LICENSE-DejaVu |
The vendored font |
src/editor/text.rs (new) |
The embedded font, layout (lines, advances, kerning), glyph outlines → tiny-skia paths, text width/height |
src/editor/shape.rs |
Tool::{Text, Pixelate, Number, Crop}; Shape gains the text payload; geometry for numbers |
src/editor/doc.rs |
History entries become Item::{Shape(Shape), Crop(PixelRect)}; crop(); next number |
src/editor/export.rs |
Text, pixelation, number glyphs; final crop |
src/editor/view.rs |
Fitting the crop rectangle instead of the whole image |
src/editor/mod.rs |
Text input, crop handles and keys, toolbar entries |
Dependency: ab_glyph.
5. Testing
Unit tests (cargo nextest):
text: line breaking on\n, width grows with characters, kerning applied, Turkish letters (ğüşıöçİ) have glyphs, an empty string has no size.export: text pixels inside the glyph box use the colour; the outline darkens the edge; pixelation makes each block uniform with the block's average; a pixelate after a red line pixelates the line; a number draws a filled circle with white in its centre; a crop sets the output size and offset.doc: numbers count onlyNumbershapes and follow undo; crops are undoable and nest; dirty includes crops.view: fitting a crop rectangle; screen ↔ image round trip with a crop.mod: the crop edge hit-test (edges, corners, inside, outside).
Manual checks go to the Phase 5b section of docs/test-checklist.md.
6. Definition of done
cargo clippy -- -D warnings, cargo fmt --check, cargo nextest run
and nix flake check pass; the checklist section is written.