RAMTT · Tomrum · motoren, anden udgave · 27. september 2026

En motor der tegner Tomrum-motiverne

Anden udgave er bygget som en compiler. En beskrivelse kontrolleres, bliver til en scene af eksakte kurver og går gennem ti trin, der hver lægger en del af reglen på. Reglen er data med versioner, en linter holder øje med den, og samme scene kan skrives som SVG, tegnes på canvas eller blive til appens Tegninger.tsx. Den tegner også tomrummets seks tilstande, læser en vilkårlig SVG ind som beskrivelse, eksporterer PNG, animeret PNG og Lottie uden browser, og kommer ind i appen gennem et Vite-plugin.

–af originalernes streger ramt, median, inden for ⅓ enhed
–hurtigere end første udgave, median pr. motiv
–tests, heraf 300 tilfældige beskrivelser
–linjer TypeScript i strict mode, ingen afhængigheder

Beskrivelsen og tegningen

Til venstre er alt, der skal skrives for kalenderen. Spøgelset, jordskyggen, fyldet, konturens fire vægte, hånden og tidslinjen lægger motoren på. Klik på tegningen for at se den blive tegnet.

Klik for at tegne igen

Kompileren

Første udgave var én funktion, der læste beskrivelsen og skrev SVG-tekst undervejs. Anden udgave deler arbejdet op, som en compiler gør: hvert led har én opgave, og mellem leddene findes en scene, der kan undersøges.

Beskrivelsekrop, dele, prik
Kontrolfejl med sti, fx dele[2].form
Scene gennem ti trinrene funktioner fra scene til scene
SVGtekst, tidslinjen som data
Canvasethvert øjeblik t
ReactTegninger.tsx til appen
PNG, APNG, Lottieuden browser
Vite-pluginkomponenter ved build
Linterreglens regler

Eksakt geometri

Alt er kubiske Bézier-kurver fra start til slut. Buelængde regnes med adaptiv Gauss-Legendre, deling med de Casteljau, yderpunkter og boks ud fra den afledtes roots. Første udgave samplede punkter hver 0,8 enhed og forenklede bagefter.

Uforanderlige scener

Intet trin ændrer den scene det får. Scenerne deler alt, der ikke er ændret, så motoren kan gemme hvert mellemtrin næsten gratis. En test fryser alle scener undervejs, og et trin der prøver at ændre sin indgang, fejler med det samme.

Gentagelig tilfældighed

Hvert trin får sin egen talrække ud fra frøet og sit navn. Et nyt trin flytter derfor ikke de andres tilfældighed, og samme frø giver samme tegning byte for byte på alle maskiner.

Trin for trin

Scenen efter hvert trin. Det trinet har tilføjet eller ændret står fuldt op; resten er dæmpet. Tabellen viser elementerne med de navne motoren giver dem, så en fejl kan føres tilbage til linjen i beskrivelsen.

Reglen som data

Motoren kender ingen tal selv. De står i to versioner af reglen, og hvert af registrets motiver er bundet til den version, det er tegnet efter. Tabellen er skrevet direkte fra reglens data.

Hvert motiv er tegnet med begge love og målt mod originalen, med hånden slået fra og en tolerance på en tredjedel enhed. Alle motiver passer bedst til den lov, de er bundet til. Ved 1,5 enheds tolerance, som første udgave blev målt med, er næsten alt 100 %, og forskellen mellem lovene kan ikke ses.

Registret genskabt

Hver original ved siden af motorens udgave, med den lov motivet er bundet til. Tallet er streger ramt, målt som ovenfor.

Ti nye tomrum til appen

Følger altid den nyeste lov. Sat i Tomrum-komponenten med titel, én linje og præcis to handlinger. Teksterne er forslag.

Tilstandene

Et tomrum er én af seks tilstande. De fem nye har samme hånd og samme lov som resten. Registrets regel om bevægelse er lagt ind i reglen: efter indtegningen står alt stille, undtagen stopurets viser, mens noget indlæses. Linteren melder et motiv, der bryder reglen, og tæller handlingerne: to i alle tilstande undtagen indlæsningen, som ingen har.

Fra SVG til beskrivelse

Importen læser en SVG og skriver en beskrivelse i samme form som motiverne. Det reglen selv lægger på, bliver genkendt og fjernet: spøgelset, skyggen og kroppens kontur. Reglen aflæses af spøgelset. Et ikon fra et andet sæt bliver skaleret ind i rammen og får krop, roller og prik som et udkast, der kan rettes videre.

Tidslinjen er data

Kompileren giver hvert element en start, en varighed og en art: tegnes langs stregen, eller fader frem. Afspilleren i browseren og React-udgangen læser tiderne; ingen af dem regner dem selv. Canvas-udgangen kan tegne ethvert øjeblik, så tegningen kan eksporteres billede for billede.

1400 ms

Hånden er ét felt

I første udgave fik hver streg sin egen afvigelse. Konturens fire stykker bøjede derfor hver sin vej, og fyldet blev liggende. Nu flyttes hvert punkt efter hvor det ligger i planen, gennem en sum af fem lange svingninger. Fyld og kontur følges ad, og stykkerne mødes stadig i overlappene.

Til venstre er feltet vist som et gitter, forstørret fire gange sammen med tegningen. Det er det samme felt, der bøjer begge.

Gitter og tegning i samme felt, fire gange forstørret

Pennen med tryk

Stregen bliver til en udfyldt form, der sætter an, holder og løfter. Formen er kubiske kurver forskudt langs normalerne, med halvcirkler i enderne, og ikke længere en polygon af punkter. En tegning med tryk fylder derfor en tredjedel mindre end i første udgave.

Registrets pen
Pen med tryk

Én scene, tre udgange

Skiltet fra appen. SVG og React skrives fra det samme lille træ, så de ikke kan tegne forskelligt. Canvas tegner direkte fra kurverne. React-udgangen bruger appens egne klasser, farver og 70 ms-trin og kan erstatte den håndskrevne Tegninger.tsx.

SVG
Canvas

Eksport uden browser

Motoren rastrerer selv. PNG og animeret PNG laves i ren TypeScript, så en build eller en CLI kan skrive billeder uden Chromium. Billederne herunder er lavet på siden med den samme kode. Lottie bruger trim paths til indtegningen og masker til pennen med tryk, og e-mail-udgaven har faste farver og hverken CSS, masker eller animation.

PNG
Animeret PNG
Lottie
E-mail

Delingsbillede

1200 × 630 med motivet og tomrummets tekst i Inter. CLI'en skriver det som SVG eller, når Playwright findes, som PNG.

Ind i appen

Et Vite-plugin tegner motiverne ved build. De kommer ind som React-komponenter fra et virtuelt modul, med motivernes id'er som typer, og motoren kommer ikke med i appen. En .tomrum-fil i mappen er et nyt motiv. En fejl i den stopper buildet med stien til stedet, og linterens advarsler står i loggen.

De fire håndtegnede motiver i Tegninger.tsx er låst. Pluginnet genererer dem ikke, så de bliver i appen, som de er.

vite.config.ts
import react from "@vitejs/plugin-react"
import { tomrum } from "@ramtt/tomrum-motor/vite"

export default {
  plugins: [
    react(),
    tomrum({ mappe: "src/tomrum", dts: "src/tomrum.d.ts" }),
  ],
}
Tomrum i en felt
import { TOMRUM, TegningStopur } from "virtual:tomrum"
import "virtual:tomrum/lever.css"

const { Tegning, tekst } = TOMRUM.kalender

<Tomrum
  tegning={<Tegning />}
  titel={tekst.titel}
  linje1={tekst.linje}
  primaer={{ label: tekst.primaer, onClick: plan }}
  videre={{ label: tekst.videre, onClick: bibliotek }}
/>
src/tomrum/cykel.tomrum

Tests

Testene kører med Nodes egen testløber mod den kompilerede kode. De visuelle krav måles i Chromium. Resultaterne her er fra kørslen, der byggede siden.

Første og anden udgave

Median over de 22 motiver i Node 22. Størrelsen er SVG uden tidslinje med to decimaler.

Prøv selv

Ret i beskrivelsen, så kontrollerer og tegner motoren igen, og linteren melder hvad den ser. Træk i håndtagene på tegningen, så skrives tallene i beskrivelsen med. Et tal, der er regnet ud, har intet håndtag. Prøv også at stave et felt forkert, sætte en bredde uden for rollens spænd eller slette Z i kroppen.

Begynd fra

Til repoet

Motoren ligger i ~/tomrum/motor/v2 ved siden af første udgave. Kilden er TypeScript uden afhængigheder ved kørsel; esbuild laver modul- og script-udgaver, og ci/tomrum-motor.yml er workflowet til GitHub.

src/

geometri.ts, spec.ts, kompiler.ts, trin/, lov/, render/, eksport/, import.ts, redigering.ts, vite.ts, lint.ts og motiver.ts.

CLI

tomrum byg, tomrum tjek fil.tomrum, tomrum tegn id --png --apng --lottie --epost --og, tomrum importer ikon.svg og tomrum regler.

Tests

npm test for enhed, egenskaber og gyldne aftryk, node test/visuel.mjs for originalerne og rastreringen i Chromium, og node test/bench.mjs mod første udgave.