oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

1// canvas.mjs's maths: boxes as frame fractions, clamped to the frame,
2// snapped to the thirds and to the other blocks' edges within one per
3// cent, moved and resized by handle, nudged by key, and written with
4// four decimals.
5import test from 'node:test'
6import assert from 'node:assert/strict'
7import {
8 clampBox, fixBox, guides, snapMove, snapResize, moveBox, resizeBox, nudge, rectBox,
9 MIN_SIZE, NUDGE, NUDGE_BIG
10} from './canvas.mjs'
11
12const near = (a, b, eps = 1e-9) => Math.abs(a - b) < eps
13const boxNear = (got, want) => {
14 for (const k of ['x', 'y', 'w', 'h']) assert.ok(near(got[k], want[k]), `${k}: ${got[k]} vs ${want[k]}`)
15}
16
17test('clampBox keeps the box inside the frame and no smaller than the minimum', () => {
18 assert.deepEqual(clampBox({ x: -0.2, y: 0.5, w: 0.3, h: 0.2 }), { x: 0, y: 0.5, w: 0.3, h: 0.2 })
19 assert.deepEqual(clampBox({ x: 0.9, y: 0.95, w: 0.3, h: 0.2 }), { x: 0.7, y: 0.8, w: 0.3, h: 0.2 })
20 assert.deepEqual(clampBox({ x: 0.1, y: 0.1, w: 0, h: 5 }), { x: 0.1, y: 0, w: MIN_SIZE, h: 1 })
21})
22
23test('fixBox writes four decimals', () => {
24 assert.deepEqual(fixBox({ x: 0.123456, y: 0.1, w: 0.33333333, h: 0.2 }), { x: 0.1235, y: 0.1, w: 0.3333, h: 0.2 })
25 assert.deepEqual(fixBox({ x: 1.2, y: 0, w: 0.5, h: 0.5 }), { x: 0.5, y: 0, w: 0.5, h: 0.5 })
26})
27
28test('guides are the thirds plus every other block edge', () => {
29 const g = guides([{ x: 0.1, y: 0.2, w: 0.3, h: 0.4 }])
30 assert.deepEqual(g.x.sort(), [0, 1 / 3, 2 / 3, 1, 0.1, 0.4].sort())
31 assert.deepEqual(g.y.sort(), [0, 1 / 3, 2 / 3, 1, 0.2, 0.6000000000000001].sort())
32 assert.equal(guides([]).x.length, 4)
33})
34
35test('snapMove pulls the nearest edge onto a guide within one per cent, and leaves a far one', () => {
36 const g = guides([{ x: 0.5, y: 0.5, w: 0.2, h: 0.2 }])
37 // left edge 0.008 from the frame's left → 0
38 boxNear(snapMove({ x: 0.008, y: 0.4, w: 0.2, h: 0.1 }, g), { x: 0, y: 0.4, w: 0.2, h: 0.1 })
39 // right edge 0.295 → the other block's left at 0.5 is too far; the third at 1/3 is 0.038 away — no snap
40 boxNear(snapMove({ x: 0.095, y: 0.4, w: 0.2, h: 0.1 }, g), { x: 0.095, y: 0.4, w: 0.2, h: 0.1 })
41 // right edge at 0.495 → snaps to the other block's left edge, moving the whole box
42 boxNear(snapMove({ x: 0.295, y: 0.4, w: 0.2, h: 0.1 }, g), { x: 0.3, y: 0.4, w: 0.2, h: 0.1 })
43 // top edge 0.505 → the other block's top at 0.5
44 boxNear(snapMove({ x: 0.1, y: 0.505, w: 0.2, h: 0.1 }, g), { x: 0.1, y: 0.5, w: 0.2, h: 0.1 })
45 // the closer of two candidate edges wins: left at 0.009 from 0, right at 0.004 from 1/3
46 const both = snapMove({ x: 0.009, y: 0, w: 1 / 3 - 0.005, h: 0.1 }, g)
47 boxNear(both, { x: 1 / 3 - (1 / 3 - 0.005), y: 0, w: 1 / 3 - 0.005, h: 0.1 })
48})
49
50test('snapResize moves only the edges the handle drags', () => {
51 const g = guides([])
52 // east handle: right edge 0.66 → 2/3, left stays
53 boxNear(snapResize({ x: 0.2, y: 0.2, w: 0.46, h: 0.2 }, 'e', g), { x: 0.2, y: 0.2, w: 2 / 3 - 0.2, h: 0.2 })
54 // west handle: left edge 0.338 → 1/3, right edge stays at 0.8
55 boxNear(snapResize({ x: 0.338, y: 0.2, w: 0.462, h: 0.2 }, 'w', g), { x: 1 / 3, y: 0.2, w: 0.8 - 1 / 3, h: 0.2 })
56 // north-west: both leading edges
57 boxNear(snapResize({ x: 0.005, y: 0.338, w: 0.3, h: 0.5 }, 'nw', g), { x: 0, y: 1 / 3, w: 0.305, h: 0.5 + 0.338 - 1 / 3 })
58 // south handle never touches x
59 boxNear(snapResize({ x: 0.005, y: 0.2, w: 0.3, h: 0.795 }, 's', g), { x: 0.005, y: 0.2, w: 0.3, h: 0.8 })
60})
61
62test('moveBox and resizeBox by handle', () => {
63 assert.deepEqual(moveBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 0.05, -0.05), { x: 0.15000000000000002, y: 0.05, w: 0.2, h: 0.2 })
64 boxNear(resizeBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 'se', 0.1, 0.1), { x: 0.1, y: 0.1, w: 0.3, h: 0.3 })
65 boxNear(resizeBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 'nw', 0.05, 0.05), { x: 0.15, y: 0.15, w: 0.15, h: 0.15 })
66 boxNear(resizeBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 'n', 0, -0.1), { x: 0.1, y: 0, w: 0.2, h: 0.3 })
67 boxNear(resizeBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 'w', 0.1, 0), { x: 0.2, y: 0.1, w: 0.1, h: 0.2 })
68 // A handle dragged past the opposite edge stops at the minimum size.
69 boxNear(resizeBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 'e', -0.5, 0), { x: 0.1, y: 0.1, w: MIN_SIZE, h: 0.2 })
70 boxNear(resizeBox({ x: 0.1, y: 0.1, w: 0.2, h: 0.2 }, 'w', 0.5, 0), { x: 0.1 + 0.2 - MIN_SIZE, y: 0.1, w: MIN_SIZE, h: 0.2 })
71})
72
73test('nudge: arrows move by half a per cent, Shift by two, Alt resizes', () => {
74 const b = { x: 0.1, y: 0.1, w: 0.2, h: 0.2 }
75 boxNear(nudge(b, 'ArrowRight'), { ...b, x: 0.1 + NUDGE })
76 boxNear(nudge(b, 'ArrowUp', { shift: true }), { ...b, y: 0.1 - NUDGE_BIG })
77 boxNear(nudge(b, 'ArrowDown', { alt: true }), { ...b, h: 0.2 + NUDGE })
78 boxNear(nudge(b, 'ArrowLeft', { alt: true, shift: true }), { ...b, w: 0.2 - NUDGE_BIG })
79 assert.equal(nudge(b, 'Enter'), null)
80})
81
82test('rectBox reads a block rectangle against its frame', () => {
83 const frame = { left: 100, top: 50, width: 800, height: 450 }
84 boxNear(rectBox(frame, { left: 180, top: 95, width: 400, height: 90 }), { x: 0.1, y: 0.1, w: 0.5, h: 0.2 })
85 assert.deepEqual(rectBox({ left: 0, top: 0, width: 0, height: 0 }, { left: 0, top: 0, width: 1, height: 1 }), { x: 0, y: 0, w: 0, h: 0 })
86})
87