Files
rose-ash/shared/sx/templates/font.sx
giles 1eadefd0c1 Step 17b: Pretext — DOM-free text layout with otfm font measurement
Pure SX text layout library with one IO boundary (text-measure perform).
Knuth-Plass optimal line breaking, Liang's hyphenation, position calculation.

Library (lib/text-layout.sx):
- break-lines: Knuth-Plass DP over word widths
- break-lines-greedy: simple word-wrap for comparison
- hyphenate-word: Liang's trie algorithm
- position-line/position-lines: running x/y sums
- measure-text: single perform (text-measure IO)

Server font measurement (otfm):
- Reads OpenType cmap + hmtx tables from .ttf files
- DejaVu Serif/Sans bundled in shared/static/fonts/
- _cek_io_resolver hook: perform works inside aser/eval_expr
- JIT VM suspension inline resolution for IO in compiled code

~font component (shared/sx/templates/font.sx):
- Works like ~tw: emits @font-face CSS via cssx scope
- Sets font-family on parent via spread
- Deduplicates font declarations

Infrastructure fixes:
- stdin load command: per-expression error handling (was aborting on first error)
- cek_run IO hook: _cek_io_resolver in sx_types.ml
- JIT VmSuspended: inline IO resolution when resolver installed
- ListRef handling in IO resolver (perform creates ListRef, not List)

Demo page at /sx/(applications.(pretext)):
- Hero: justified paragraph with otfm-measured proportional widths
- Greedy vs Knuth-Plass side-by-side comparison
- Badness scoring visualization
- Hyphenation syllable decomposition

25 new tests (spec/tests/test-text-layout.sx), 3201/3201 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:13:00 +00:00

41 lines
1.1 KiB
Plaintext

;; ~font — web font component
;;
;; Works like ~tw: emits @font-face CSS into the cssx scope,
;; sets font-family on the parent element via spread.
;;
;; Usage:
;; (div (~font :family "Pretext Serif" :src "/static/fonts/DejaVuSerif.ttf")
;; "This text uses DejaVu Serif")
;;
;; (p (~font :family "Pretext Serif") ;; reuse already-declared font
;; "No :src needed after first declaration")
;; Track which font families have already emitted @font-face rules
(define *font-declared* (dict))
(defcomp
~font
(&key family src weight style format)
(let
((fam (or family "serif"))
(wt (or weight "normal"))
(st (or style "normal"))
(fmt (or format "truetype")))
(when
(and src (not (has-key? *font-declared* fam)))
(dict-set! *font-declared* fam true)
(collect!
"cssx"
(str
"@font-face{font-family:'"
fam
"';src:url('"
src
"') format('"
fmt
"');font-weight:"
wt
";font-style:"
st
";}")))
(make-spread {:style (str "font-family:'" fam "',serif;")})))