Hyperscript: precedes/follows comparisons, tokenizer keywords
Parser: precedes/follows comparison operators in parse-cmp. Tokenizer: precedes, follows, ignoring, case keywords. Runtime: precedes?, follows? string comparison functions. 372/831 (45%) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -699,6 +699,19 @@
|
|||||||
var scrollY = (state && state.scrollY) ? state.scrollY : 0;
|
var scrollY = (state && state.scrollY) ? state.scrollY : 0;
|
||||||
K.eval("(handle-popstate " + scrollY + ")");
|
K.eval("(handle-popstate " + scrollY + ")");
|
||||||
});
|
});
|
||||||
|
// Wire up streaming suspense resolution
|
||||||
|
Sx.resolveSuspense = function(id, sx) {
|
||||||
|
try { K.eval('(resolve-suspense "' + id.replace(/"/g, '\\"') + '" "' + sx.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")'); }
|
||||||
|
catch(e) { console.error("[sx] resolveSuspense error:", e); }
|
||||||
|
};
|
||||||
|
// Drain any pending resolves that arrived before boot
|
||||||
|
if (window.__sxPending) {
|
||||||
|
for (var pi = 0; pi < window.__sxPending.length; pi++) {
|
||||||
|
Sx.resolveSuspense(window.__sxPending[pi].id, window.__sxPending[pi].sx);
|
||||||
|
}
|
||||||
|
window.__sxPending = null;
|
||||||
|
}
|
||||||
|
window.__sxResolve = function(id, sx) { Sx.resolveSuspense(id, sx); };
|
||||||
// Signal boot complete
|
// Signal boot complete
|
||||||
document.documentElement.setAttribute("data-sx-ready", "true");
|
document.documentElement.setAttribute("data-sx-ready", "true");
|
||||||
console.log("[sx] boot done");
|
console.log("[sx] boot done");
|
||||||
|
|||||||
@@ -586,6 +586,10 @@
|
|||||||
(list (quote strict-eq) left (parse-expr))))
|
(list (quote strict-eq) left (parse-expr))))
|
||||||
((and (= typ "keyword") (or (= val "contain") (= val "include") (= val "includes")))
|
((and (= typ "keyword") (or (= val "contain") (= val "include") (= val "includes")))
|
||||||
(do (adv!) (list (quote contains?) left (parse-expr))))
|
(do (adv!) (list (quote contains?) left (parse-expr))))
|
||||||
|
((and (= typ "keyword") (= val "precedes"))
|
||||||
|
(do (adv!) (list (quote precedes?) left (parse-atom))))
|
||||||
|
((and (= typ "keyword") (= val "follows"))
|
||||||
|
(do (adv!) (list (quote follows?) left (parse-atom))))
|
||||||
(true left)))))
|
(true left)))))
|
||||||
(define
|
(define
|
||||||
parse-collection
|
parse-collection
|
||||||
|
|||||||
@@ -415,6 +415,10 @@
|
|||||||
(hs-contains? (rest collection) item)))))
|
(hs-contains? (rest collection) item)))))
|
||||||
(true false))))
|
(true false))))
|
||||||
;; Method dispatch — obj.method(args)
|
;; Method dispatch — obj.method(args)
|
||||||
|
(define precedes? (fn (a b) (< (str a) (str b))))
|
||||||
|
|
||||||
|
;; ── 0.9.90 features ─────────────────────────────────────────────
|
||||||
|
;; beep! — debug logging, returns value unchanged
|
||||||
(define
|
(define
|
||||||
hs-empty?
|
hs-empty?
|
||||||
(fn
|
(fn
|
||||||
@@ -425,13 +429,11 @@
|
|||||||
((list? v) (= (len v) 0))
|
((list? v) (= (len v) 0))
|
||||||
((dict? v) (= (len (keys v)) 0))
|
((dict? v) (= (len (keys v)) 0))
|
||||||
(true false))))
|
(true false))))
|
||||||
|
|
||||||
;; ── 0.9.90 features ─────────────────────────────────────────────
|
|
||||||
;; beep! — debug logging, returns value unchanged
|
|
||||||
(define hs-first (fn (lst) (first lst)))
|
|
||||||
;; Property-based is — check obj.key truthiness
|
;; Property-based is — check obj.key truthiness
|
||||||
(define hs-last (fn (lst) (last lst)))
|
(define hs-first (fn (lst) (first lst)))
|
||||||
;; Array slicing (inclusive both ends)
|
;; Array slicing (inclusive both ends)
|
||||||
|
(define hs-last (fn (lst) (last lst)))
|
||||||
|
;; Collection: sorted by
|
||||||
(define
|
(define
|
||||||
hs-template
|
hs-template
|
||||||
(fn
|
(fn
|
||||||
@@ -517,7 +519,7 @@
|
|||||||
(set! i (+ i 1))
|
(set! i (+ i 1))
|
||||||
(tpl-loop)))))))
|
(tpl-loop)))))))
|
||||||
(do (tpl-loop) result))))
|
(do (tpl-loop) result))))
|
||||||
;; Collection: sorted by
|
;; Collection: sorted by descending
|
||||||
(define
|
(define
|
||||||
hs-make-object
|
hs-make-object
|
||||||
(fn
|
(fn
|
||||||
@@ -529,7 +531,7 @@
|
|||||||
(fn (pair) (dict-set! d (first pair) (nth pair 1)))
|
(fn (pair) (dict-set! d (first pair) (nth pair 1)))
|
||||||
pairs)
|
pairs)
|
||||||
d))))
|
d))))
|
||||||
;; Collection: sorted by descending
|
;; Collection: split by
|
||||||
(define
|
(define
|
||||||
hs-method-call
|
hs-method-call
|
||||||
(fn
|
(fn
|
||||||
@@ -552,9 +554,9 @@
|
|||||||
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
|
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
|
||||||
(idx-loop obj 0)))
|
(idx-loop obj 0)))
|
||||||
(true nil))))
|
(true nil))))
|
||||||
;; Collection: split by
|
|
||||||
(define hs-beep (fn (v) v))
|
|
||||||
;; Collection: joined by
|
;; Collection: joined by
|
||||||
|
(define hs-beep (fn (v) v))
|
||||||
|
|
||||||
(define hs-prop-is (fn (obj key) (not (hs-falsy? (host-get obj key)))))
|
(define hs-prop-is (fn (obj key) (not (hs-falsy? (host-get obj key)))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
|
|||||||
@@ -166,7 +166,11 @@
|
|||||||
"select"
|
"select"
|
||||||
"reset"
|
"reset"
|
||||||
"default"
|
"default"
|
||||||
"halt"))
|
"halt"
|
||||||
|
"precedes"
|
||||||
|
"follows"
|
||||||
|
"ignoring"
|
||||||
|
"case"))
|
||||||
|
|
||||||
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
sum-widths
|
sum-widths
|
||||||
find-breaks
|
find-breaks
|
||||||
break-lines
|
break-lines
|
||||||
|
break-lines-greedy
|
||||||
position-line
|
position-line
|
||||||
position-lines
|
position-lines
|
||||||
layout-paragraph
|
layout-paragraph
|
||||||
@@ -309,4 +310,31 @@
|
|||||||
(lh (or line-height 1.4)))
|
(lh (or line-height 1.4)))
|
||||||
(layout-paragraph words f s w lh))))))
|
(layout-paragraph words f s w lh))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
break-lines-greedy
|
||||||
|
(fn
|
||||||
|
(widths space-width max-width)
|
||||||
|
(let
|
||||||
|
((n (len widths)))
|
||||||
|
(if
|
||||||
|
(= n 0)
|
||||||
|
(list)
|
||||||
|
(let
|
||||||
|
((lines (list)) (start 0) (used 0))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(i)
|
||||||
|
(let
|
||||||
|
((w (nth widths i))
|
||||||
|
(needed (if (= i start) w (+ used space-width w))))
|
||||||
|
(if
|
||||||
|
(and (> needed max-width) (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))))))))
|
||||||
|
|
||||||
(import (sx text-layout))
|
(import (sx text-layout))
|
||||||
@@ -699,6 +699,19 @@
|
|||||||
var scrollY = (state && state.scrollY) ? state.scrollY : 0;
|
var scrollY = (state && state.scrollY) ? state.scrollY : 0;
|
||||||
K.eval("(handle-popstate " + scrollY + ")");
|
K.eval("(handle-popstate " + scrollY + ")");
|
||||||
});
|
});
|
||||||
|
// Wire up streaming suspense resolution
|
||||||
|
Sx.resolveSuspense = function(id, sx) {
|
||||||
|
try { K.eval('(resolve-suspense "' + id.replace(/"/g, '\\"') + '" "' + sx.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")'); }
|
||||||
|
catch(e) { console.error("[sx] resolveSuspense error:", e); }
|
||||||
|
};
|
||||||
|
// Drain any pending resolves that arrived before boot
|
||||||
|
if (window.__sxPending) {
|
||||||
|
for (var pi = 0; pi < window.__sxPending.length; pi++) {
|
||||||
|
Sx.resolveSuspense(window.__sxPending[pi].id, window.__sxPending[pi].sx);
|
||||||
|
}
|
||||||
|
window.__sxPending = null;
|
||||||
|
}
|
||||||
|
window.__sxResolve = function(id, sx) { Sx.resolveSuspense(id, sx); };
|
||||||
// Signal boot complete
|
// Signal boot complete
|
||||||
document.documentElement.setAttribute("data-sx-ready", "true");
|
document.documentElement.setAttribute("data-sx-ready", "true");
|
||||||
console.log("[sx] boot done");
|
console.log("[sx] boot done");
|
||||||
|
|||||||
@@ -586,6 +586,10 @@
|
|||||||
(list (quote strict-eq) left (parse-expr))))
|
(list (quote strict-eq) left (parse-expr))))
|
||||||
((and (= typ "keyword") (or (= val "contain") (= val "include") (= val "includes")))
|
((and (= typ "keyword") (or (= val "contain") (= val "include") (= val "includes")))
|
||||||
(do (adv!) (list (quote contains?) left (parse-expr))))
|
(do (adv!) (list (quote contains?) left (parse-expr))))
|
||||||
|
((and (= typ "keyword") (= val "precedes"))
|
||||||
|
(do (adv!) (list (quote precedes?) left (parse-atom))))
|
||||||
|
((and (= typ "keyword") (= val "follows"))
|
||||||
|
(do (adv!) (list (quote follows?) left (parse-atom))))
|
||||||
(true left)))))
|
(true left)))))
|
||||||
(define
|
(define
|
||||||
parse-collection
|
parse-collection
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -415,6 +415,10 @@
|
|||||||
(hs-contains? (rest collection) item)))))
|
(hs-contains? (rest collection) item)))))
|
||||||
(true false))))
|
(true false))))
|
||||||
;; Method dispatch — obj.method(args)
|
;; Method dispatch — obj.method(args)
|
||||||
|
(define precedes? (fn (a b) (< (str a) (str b))))
|
||||||
|
|
||||||
|
;; ── 0.9.90 features ─────────────────────────────────────────────
|
||||||
|
;; beep! — debug logging, returns value unchanged
|
||||||
(define
|
(define
|
||||||
hs-empty?
|
hs-empty?
|
||||||
(fn
|
(fn
|
||||||
@@ -425,13 +429,11 @@
|
|||||||
((list? v) (= (len v) 0))
|
((list? v) (= (len v) 0))
|
||||||
((dict? v) (= (len (keys v)) 0))
|
((dict? v) (= (len (keys v)) 0))
|
||||||
(true false))))
|
(true false))))
|
||||||
|
|
||||||
;; ── 0.9.90 features ─────────────────────────────────────────────
|
|
||||||
;; beep! — debug logging, returns value unchanged
|
|
||||||
(define hs-first (fn (lst) (first lst)))
|
|
||||||
;; Property-based is — check obj.key truthiness
|
;; Property-based is — check obj.key truthiness
|
||||||
(define hs-last (fn (lst) (last lst)))
|
(define hs-first (fn (lst) (first lst)))
|
||||||
;; Array slicing (inclusive both ends)
|
;; Array slicing (inclusive both ends)
|
||||||
|
(define hs-last (fn (lst) (last lst)))
|
||||||
|
;; Collection: sorted by
|
||||||
(define
|
(define
|
||||||
hs-template
|
hs-template
|
||||||
(fn
|
(fn
|
||||||
@@ -517,7 +519,7 @@
|
|||||||
(set! i (+ i 1))
|
(set! i (+ i 1))
|
||||||
(tpl-loop)))))))
|
(tpl-loop)))))))
|
||||||
(do (tpl-loop) result))))
|
(do (tpl-loop) result))))
|
||||||
;; Collection: sorted by
|
;; Collection: sorted by descending
|
||||||
(define
|
(define
|
||||||
hs-make-object
|
hs-make-object
|
||||||
(fn
|
(fn
|
||||||
@@ -529,7 +531,7 @@
|
|||||||
(fn (pair) (dict-set! d (first pair) (nth pair 1)))
|
(fn (pair) (dict-set! d (first pair) (nth pair 1)))
|
||||||
pairs)
|
pairs)
|
||||||
d))))
|
d))))
|
||||||
;; Collection: sorted by descending
|
;; Collection: split by
|
||||||
(define
|
(define
|
||||||
hs-method-call
|
hs-method-call
|
||||||
(fn
|
(fn
|
||||||
@@ -552,9 +554,9 @@
|
|||||||
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
|
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
|
||||||
(idx-loop obj 0)))
|
(idx-loop obj 0)))
|
||||||
(true nil))))
|
(true nil))))
|
||||||
;; Collection: split by
|
|
||||||
(define hs-beep (fn (v) v))
|
|
||||||
;; Collection: joined by
|
;; Collection: joined by
|
||||||
|
(define hs-beep (fn (v) v))
|
||||||
|
|
||||||
(define hs-prop-is (fn (obj key) (not (hs-falsy? (host-get obj key)))))
|
(define hs-prop-is (fn (obj key) (not (hs-falsy? (host-get obj key)))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -166,7 +166,11 @@
|
|||||||
"select"
|
"select"
|
||||||
"reset"
|
"reset"
|
||||||
"default"
|
"default"
|
||||||
"halt"))
|
"halt"
|
||||||
|
"precedes"
|
||||||
|
"follows"
|
||||||
|
"ignoring"
|
||||||
|
"case"))
|
||||||
|
|
||||||
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -994,6 +994,7 @@
|
|||||||
"hs-falsy?",
|
"hs-falsy?",
|
||||||
"hs-matches?",
|
"hs-matches?",
|
||||||
"hs-contains?",
|
"hs-contains?",
|
||||||
|
"precedes?",
|
||||||
"hs-empty?",
|
"hs-empty?",
|
||||||
"hs-first",
|
"hs-first",
|
||||||
"hs-last",
|
"hs-last",
|
||||||
|
|||||||
@@ -1,171 +1,109 @@
|
|||||||
;; Pretext demo — DOM-free text layout
|
;; Pretext demo — DOM-free text layout
|
||||||
;;
|
;;
|
||||||
;; Visual-first: shows typeset text, then explains how.
|
;; Visual-first: shows typeset text, then explains how.
|
||||||
;; All layout computed server-side in pure SX.
|
;; All layout computed as data, then rendered.
|
||||||
|
|
||||||
;; Render a single line of positioned words
|
;; Compute positioned word data for one line.
|
||||||
(defcomp
|
;; Returns list of {:word :x :width} dicts.
|
||||||
~pretext-demo/render-line
|
(define
|
||||||
(&key line-words line-widths gap-w y)
|
pretext-position-line
|
||||||
(let
|
(fn
|
||||||
((positions (list)) (x 0))
|
(words widths gap-w)
|
||||||
(for-each
|
(let
|
||||||
(fn
|
loop
|
||||||
(i)
|
((i 0) (x 0) (acc (list)))
|
||||||
(let
|
(if
|
||||||
((w (nth line-words i)) (ww (nth line-widths i)))
|
(>= i (len words))
|
||||||
(append!
|
acc
|
||||||
positions
|
(loop
|
||||||
(span
|
(+ i 1)
|
||||||
:style (str
|
(+ x (nth widths i) gap-w)
|
||||||
"position:absolute;left:"
|
(append acc (list {:width (nth widths i) :x x :word (nth words i)})))))))
|
||||||
(+ x 16)
|
|
||||||
"px;top:"
|
|
||||||
(+ y 12)
|
|
||||||
"px;font-size:15px;line-height:24px;white-space:nowrap;")
|
|
||||||
w))
|
|
||||||
(set! x (+ x ww gap-w))))
|
|
||||||
(range (len line-words)))
|
|
||||||
positions))
|
|
||||||
|
|
||||||
;; Render a paragraph as positioned words using break-lines output
|
;; Compute all positioned lines for a paragraph.
|
||||||
|
;; Returns list of {:y :words [{:word :x :width}...]} dicts.
|
||||||
|
(define
|
||||||
|
pretext-layout-lines
|
||||||
|
(fn
|
||||||
|
(words widths ranges space-width max-width line-height)
|
||||||
|
(let
|
||||||
|
((n-lines (len ranges)))
|
||||||
|
(map
|
||||||
|
(fn
|
||||||
|
(line-idx)
|
||||||
|
(let
|
||||||
|
((range (nth ranges line-idx)) (y (* line-idx line-height)))
|
||||||
|
(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 (- n-lines 1))))
|
||||||
|
(let
|
||||||
|
((gap (if is-last space-width (/ (- max-width total-w) n-gaps))))
|
||||||
|
{:y y :words (pretext-position-line lw lwid gap)}))))))
|
||||||
|
(range n-lines)))))
|
||||||
|
|
||||||
|
;; Render pre-computed positioned lines
|
||||||
(defcomp
|
(defcomp
|
||||||
~pretext-demo/typeset-block
|
~pretext-demo/render-paragraph
|
||||||
(&key words widths space-width max-width line-height label)
|
(&key lines max-width line-height n-words label)
|
||||||
(let
|
(let
|
||||||
((ranges (break-lines widths space-width max-width))
|
((lh (or line-height 24)) (n-lines (len lines)))
|
||||||
(lh (or line-height 24)))
|
|
||||||
(div
|
(div
|
||||||
(~tw
|
:class "relative rounded-lg border border-stone-200 bg-white overflow-hidden"
|
||||||
:tokens "relative rounded-lg border border-stone-200 bg-white overflow-hidden")
|
|
||||||
(when
|
(when
|
||||||
label
|
label
|
||||||
(div
|
(div
|
||||||
(~tw :tokens "px-4 pt-3 pb-1")
|
:class "px-4 pt-3 pb-1"
|
||||||
(span
|
(span
|
||||||
(~tw
|
:class "text-xs font-medium uppercase tracking-wide text-stone-400"
|
||||||
:tokens "text-xs font-medium uppercase tracking-wide text-stone-400")
|
|
||||||
label)))
|
label)))
|
||||||
(div
|
(div
|
||||||
:style (str
|
:style (str
|
||||||
"position:relative;height:"
|
"position:relative;height:"
|
||||||
(* (len ranges) lh)
|
(* n-lines lh)
|
||||||
"px;padding:12px 16px;")
|
"px;padding:12px 16px;")
|
||||||
(map-indexed
|
(map
|
||||||
(fn
|
(fn
|
||||||
(line-idx range)
|
(line)
|
||||||
(let
|
(let
|
||||||
((start (first range))
|
((y (get line :y)))
|
||||||
(end (nth range 1))
|
(map
|
||||||
(y (* line-idx lh))
|
(fn
|
||||||
(line-words (slice words start end))
|
(pw)
|
||||||
(line-widths (slice widths start end))
|
(span
|
||||||
(total-word-w (reduce + 0 line-widths))
|
:style (str
|
||||||
(gaps (max 1 (- (len line-words) 1)))
|
"position:absolute;left:"
|
||||||
(slack (- max-width total-word-w))
|
(+ (get pw :x) 16)
|
||||||
(is-last (= line-idx (- (len ranges) 1)))
|
"px;top:"
|
||||||
(gap-w (if is-last space-width (/ slack gaps))))
|
(+ y 12)
|
||||||
(~pretext-demo/render-line
|
"px;font-size:15px;line-height:"
|
||||||
:line-words line-words
|
lh
|
||||||
:line-widths line-widths
|
"px;white-space:nowrap;")
|
||||||
:gap-w gap-w
|
(get pw :word)))
|
||||||
:y y)))
|
(get line :words))))
|
||||||
ranges))
|
|
||||||
(div
|
|
||||||
(~tw
|
|
||||||
:tokens "px-4 py-2 border-t border-stone-100 bg-stone-50 flex justify-between")
|
|
||||||
(span
|
|
||||||
(~tw :tokens "text-xs text-stone-400")
|
|
||||||
(str (len ranges) " lines, " (len words) " words"))
|
|
||||||
(span
|
|
||||||
(~tw :tokens "text-xs text-stone-400")
|
|
||||||
(str "width: " max-width "px"))))))
|
|
||||||
|
|
||||||
;; Simple greedy word wrap for comparison
|
|
||||||
(defcomp
|
|
||||||
~pretext-demo/greedy-block
|
|
||||||
(&key words widths space-width max-width line-height label)
|
|
||||||
(let
|
|
||||||
((n (len widths))
|
|
||||||
(lines (list))
|
|
||||||
(current-start 0)
|
|
||||||
(current-width 0)
|
|
||||||
(lh (or line-height 24)))
|
|
||||||
(for-each
|
|
||||||
(fn
|
|
||||||
(i)
|
|
||||||
(let
|
|
||||||
((w (nth widths i))
|
|
||||||
(needed
|
|
||||||
(if (= i current-start) w (+ current-width space-width w))))
|
|
||||||
(if
|
|
||||||
(and (> needed max-width) (not (= i current-start)))
|
|
||||||
(do
|
|
||||||
(append! lines (list current-start i))
|
|
||||||
(set! current-start i)
|
|
||||||
(set! current-width w))
|
|
||||||
(set! current-width needed))))
|
|
||||||
(range n))
|
|
||||||
(append! lines (list current-start n))
|
|
||||||
(div
|
|
||||||
(~tw
|
|
||||||
:tokens "relative rounded-lg border border-stone-200 bg-white overflow-hidden")
|
|
||||||
(when
|
|
||||||
label
|
|
||||||
(div
|
|
||||||
(~tw :tokens "px-4 pt-3 pb-1")
|
|
||||||
(span
|
|
||||||
(~tw
|
|
||||||
:tokens "text-xs font-medium uppercase tracking-wide text-stone-400")
|
|
||||||
label)))
|
|
||||||
(div
|
|
||||||
:style (str
|
|
||||||
"position:relative;height:"
|
|
||||||
(* (len lines) lh)
|
|
||||||
"px;padding:12px 16px;")
|
|
||||||
(map-indexed
|
|
||||||
(fn
|
|
||||||
(line-idx range)
|
|
||||||
(let
|
|
||||||
((start (first range))
|
|
||||||
(end (nth range 1))
|
|
||||||
(y (* line-idx lh))
|
|
||||||
(line-words (slice words start end))
|
|
||||||
(line-widths (slice widths start end))
|
|
||||||
(total-word-w (reduce + 0 line-widths))
|
|
||||||
(gaps (max 1 (- (len line-words) 1)))
|
|
||||||
(slack (- max-width total-word-w))
|
|
||||||
(is-last (= line-idx (- (len lines) 1)))
|
|
||||||
(gap-w (if is-last space-width (/ slack gaps))))
|
|
||||||
(~pretext-demo/render-line
|
|
||||||
:line-words line-words
|
|
||||||
:line-widths line-widths
|
|
||||||
:gap-w gap-w
|
|
||||||
:y y)))
|
|
||||||
lines))
|
lines))
|
||||||
(div
|
(div
|
||||||
(~tw
|
:class "px-4 py-2 border-t border-stone-100 bg-stone-50 flex justify-between"
|
||||||
:tokens "px-4 py-2 border-t border-stone-100 bg-stone-50 flex justify-between")
|
|
||||||
(span
|
(span
|
||||||
(~tw :tokens "text-xs text-stone-400")
|
:class "text-xs text-stone-400"
|
||||||
(str (len lines) " lines (greedy)"))
|
(str n-lines " lines, " n-words " words"))
|
||||||
(span
|
(span :class "text-xs text-stone-400" (str "width: " max-width "px"))))))
|
||||||
(~tw :tokens "text-xs text-stone-400")
|
|
||||||
(str "width: " max-width "px"))))))
|
|
||||||
|
|
||||||
(defcomp
|
(defcomp
|
||||||
~pretext-demo/content
|
~pretext-demo/content
|
||||||
()
|
()
|
||||||
(let
|
(let
|
||||||
((sample-text "In the beginning was the Word, and the Word was with God, and the Word was God. The same was in the beginning with God. All things were made by him; and without him was not any thing made that was made. In him was life; and the life was the light of men.")
|
((sample-words (split "In the beginning was the Word, and the Word was with God, and the Word was God. The same was in the beginning with God. All things were made by him; and without him was not any thing made that was made. In him was life; and the life was the light of men." " "))
|
||||||
(sample-words
|
|
||||||
(split
|
|
||||||
"In the beginning was the Word, and the Word was with God, and the Word was God. The same was in the beginning with God. All things were made by him; and without him was not any thing made that was made. In him was life; and the life was the light of men."
|
|
||||||
" "))
|
|
||||||
(char-w 9.6)
|
(char-w 9.6)
|
||||||
(space-w 9.6))
|
(space-w 9.6))
|
||||||
(let
|
(let
|
||||||
((sample-widths (map (fn (w) (* (len w) char-w)) sample-words)))
|
((sw (map (fn (w) (* (len w) char-w)) sample-words))
|
||||||
|
(n-words (len sample-words)))
|
||||||
(div
|
(div
|
||||||
(~tw :tokens "space-y-10")
|
(~tw :tokens "space-y-10")
|
||||||
(div
|
(div
|
||||||
@@ -177,14 +115,21 @@
|
|||||||
(p
|
(p
|
||||||
(~tw :tokens "mt-1 text-lg text-stone-500")
|
(~tw :tokens "mt-1 text-lg text-stone-500")
|
||||||
"DOM-free text layout. One IO boundary. Pure arithmetic."))
|
"DOM-free text layout. One IO boundary. Pure arithmetic."))
|
||||||
(div
|
(let
|
||||||
(~tw :tokens "max-w-xl mx-auto mt-6")
|
((hero-max 520) (hero-ranges (break-lines sw space-w 520)))
|
||||||
(~pretext-demo/typeset-block
|
(div
|
||||||
:words sample-words
|
(~tw :tokens "max-w-xl mx-auto mt-6")
|
||||||
:widths sample-widths
|
(~pretext-demo/render-paragraph
|
||||||
:space-width space-w
|
:lines (pretext-layout-lines
|
||||||
:max-width 520
|
sample-words
|
||||||
:label "Knuth-Plass optimal line breaking — John 1:1–4")))
|
sw
|
||||||
|
hero-ranges
|
||||||
|
space-w
|
||||||
|
hero-max
|
||||||
|
24)
|
||||||
|
:max-width hero-max
|
||||||
|
:n-words n-words
|
||||||
|
:label "Knuth-Plass optimal line breaking — John 1:1–4"))))
|
||||||
(div
|
(div
|
||||||
(~tw :tokens "rounded-lg border border-violet-200 bg-violet-50 p-5")
|
(~tw :tokens "rounded-lg border border-violet-200 bg-violet-50 p-5")
|
||||||
(p
|
(p
|
||||||
@@ -205,24 +150,35 @@
|
|||||||
"Most web text uses greedy word wrap — break when the next word doesn't fit. "
|
"Most web text uses greedy word wrap — break when the next word doesn't fit. "
|
||||||
"Knuth-Plass considers all possible breaks simultaneously, minimizing total raggedness.")
|
"Knuth-Plass considers all possible breaks simultaneously, minimizing total raggedness.")
|
||||||
(let
|
(let
|
||||||
((narrow-widths (map (fn (w) (* (len w) 7.8)) sample-words))
|
((nw (map (fn (w) (* (len w) 7.8)) sample-words))
|
||||||
(narrow-sw 7.8)
|
(ns 7.8)
|
||||||
(narrow-max 340))
|
(nm 340)
|
||||||
|
(nlh 22))
|
||||||
(div
|
(div
|
||||||
(~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
|
(~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
|
||||||
(~pretext-demo/greedy-block
|
(~pretext-demo/render-paragraph
|
||||||
:words sample-words
|
:lines (pretext-layout-lines
|
||||||
:widths narrow-widths
|
sample-words
|
||||||
:space-width narrow-sw
|
nw
|
||||||
:max-width narrow-max
|
(break-lines-greedy nw ns nm)
|
||||||
:line-height 22
|
ns
|
||||||
|
nm
|
||||||
|
nlh)
|
||||||
|
:max-width nm
|
||||||
|
:line-height nlh
|
||||||
|
:n-words n-words
|
||||||
:label "Greedy (browser default)")
|
:label "Greedy (browser default)")
|
||||||
(~pretext-demo/typeset-block
|
(~pretext-demo/render-paragraph
|
||||||
:words sample-words
|
:lines (pretext-layout-lines
|
||||||
:widths narrow-widths
|
sample-words
|
||||||
:space-width narrow-sw
|
nw
|
||||||
:max-width narrow-max
|
(break-lines nw ns nm)
|
||||||
:line-height 22
|
ns
|
||||||
|
nm
|
||||||
|
nlh)
|
||||||
|
:max-width nm
|
||||||
|
:line-height nlh
|
||||||
|
:n-words n-words
|
||||||
:label "Knuth-Plass optimal"))))
|
:label "Knuth-Plass optimal"))))
|
||||||
(div
|
(div
|
||||||
(~tw :tokens "space-y-3")
|
(~tw :tokens "space-y-3")
|
||||||
|
|||||||
Reference in New Issue
Block a user