Step 17b: bytecode-compiled text-layout, WASM library import fix

- text-layout.sx added to WASM bytecode pipeline (9K compiled)
- Fix multi-list map calls (map-indexed + nth instead of map fn list1 list2)
- pretext-layout-lines and pretext-position-line moved to library exports
- Browser load-sxbc: handle VmSuspended for import, copy library exports
  to global_env after module load (define-library export fix)
- compile-modules.js: text-layout in SOURCE_MAP, FILES, and entry deps
- Island uses library functions (break-lines, pretext-layout-lines)
  instead of inlining — runs on bytecode VM when exports resolve

Known issue: define-library exports don't propagate to browser global env
yet. The load-sxbc import suspension handler resumes correctly but
bind_import_set doesn't fire. Needs deeper investigation into how the
WASM kernel's define-library registers exports vs how other libraries
(adapter-html, tw) make their exports available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 16:37:04 +00:00
parent 676ec6dd2b
commit 699dd5ad69
17 changed files with 2270 additions and 1662 deletions

View File

@@ -1,7 +1,7 @@
;; Pretext island — client-side text layout with live controls
;;
;; Uses canvas.measureText for pixel-perfect browser font measurement.
;; All layout functions inside the island — self-contained.
;; Calls break-lines/break-lines-greedy from lib/text-layout.sx (bytecode-compiled).
(defisland
~pretext-demo/live
@@ -16,91 +16,9 @@
(canvas (host-call doc "createElement" "canvas"))
(ctx (host-call canvas "getContext" "2d")))
(let
((measure-word (fn (word sz) (do (host-set! ctx "font" (str sz "px 'Pretext Serif', DejaVu Serif, serif")) (host-get (host-call ctx "measureText" word) "width"))))
(sum-w
(fn
(widths sw from to)
(let
loop
((k from) (total 0))
(if
(>= k to)
(+ total (* (max 0 (- (- to from) 1)) sw))
(loop (+ k 1) (+ total (nth widths k)))))))
(brk-optimal
(fn
(widths sw mx)
(let
((n (len widths)))
(if
(<= n 0)
(list)
(let
((dp (map (fn (i) (if (= i 0) (list 0 -1) (list 999999999 -1))) (range (+ n 1)))))
(for-each
(fn
(i)
(let
((prev-cost (first (nth dp i))))
(let
try-j
((j i))
(when
(< j n)
(let
((used (sum-w widths sw i (+ j 1))))
(when
(<= used (* mx 1.15))
(let
((slack (- mx used)))
(let
((bad (if (< slack 0) 100000 (let ((r (/ slack mx))) (* (* r r) (* r 1000))))))
(let
((cost (+ prev-cost (let ((b (+ 1 bad))) (* b b))))
(entry (nth dp (+ j 1))))
(when
(< cost (first entry))
(set!
dp
(map-indexed
(fn
(idx e)
(if
(= idx (+ j 1))
(list cost i)
e))
dp)))
(try-j (+ j 1)))))))))))
(range n))
(let
((breaks (let trace ((pos n) (acc (list))) (let ((prev (nth (nth dp pos) 1))) (if (<= prev 0) acc (trace prev (cons prev acc)))))))
(let
((starts (cons 0 breaks)))
(let
((ends (append (rest starts) (list n))))
(map (fn (s e) (list s e)) starts ends)))))))))
(brk-greedy
(fn
(widths sw mx)
(let
((n (len widths)) (lines (list)) (start 0) (used 0))
(for-each
(fn
(i)
(let
((w (nth widths i))
(needed (if (= i start) w (+ used sw w))))
(if
(and (> needed mx) (not (= i start)))
(do
(set! lines (append lines (list (list start i))))
(set! start i)
(set! used w))
(set! used needed))))
(range n))
(append lines (list (list start n)))))))
((measure-word (fn (word sz) (do (host-set! ctx "font" (str sz "px 'Pretext Serif', DejaVu Serif, serif")) (host-get (host-call ctx "measureText" word) "width")))))
(let
((layout (computed (fn () (let ((sz (deref font-size)) (mw (deref max-w)) (optimal (deref use-optimal))) (let ((widths (map (fn (w) (measure-word w sz)) words)) (sw (measure-word " " sz))) (let ((ranges (if optimal (brk-optimal widths sw mw) (brk-greedy widths sw mw))) (lh (* sz 1.5))) (map (fn (line-idx) (let ((range (nth ranges line-idx)) (y (* line-idx lh))) (let ((start (first range)) (end (nth range 1))) (let ((lw (slice words start end)) (lwid (slice widths start end))) (let ((total-w (reduce + 0 lwid)) (n-gaps (max 1 (- (len lw) 1))) (is-last (= line-idx (- (len ranges) 1)))) (let ((gap (if is-last sw (/ (- mw total-w) n-gaps)))) (let pos ((i 0) (x 0) (acc (list))) (if (>= i (len lw)) {:y y :words acc} (pos (+ i 1) (+ x (nth lwid i) gap) (append acc (list {:x x :word (nth lw i)}))))))))))) (range (len ranges))))))))))
((layout (computed (fn () (let ((sz (deref font-size)) (mw (deref max-w)) (optimal (deref use-optimal))) (let ((widths (map (fn (w) (measure-word w sz)) words)) (sw (measure-word " " sz)) (lh (* sz 1.5))) (let ((ranges (if optimal (break-lines widths sw mw) (break-lines-greedy widths sw mw)))) (pretext-layout-lines words widths ranges sw mw lh))))))))
(div
(~tw :tokens "space-y-4")
(div