nx_kp_break.nx
buildroot/runtime/nx_kp_break.nx
about
nx_kp_break.nx -- KNUTH-PLASS OPTIMAL LINE BREAKING, integer-exact.
WHY: every browser, and almost every renderer, breaks lines FIRST-FIT -- it fills the current line until the
next word does not fit, then breaks, and never reconsiders. That is a greedy choice made with no knowledge
of the rest of the paragraph, and it is why justified text on the web has rivers of white and books do not.
Knuth and Plass (1981, the TeX line breaker) instead model the paragraph as BOXES (words), GLUE (spaces that
can stretch and shrink) and PENALTIES (places a break is discouraged or encouraged), then choose the set of
breakpoints that minimises TOTAL badness over the WHOLE paragraph by dynamic programming. Accepting a
slightly worse line early to avoid a terrible line later is a trade a greedy algorithm cannot make.
This is the piece CSS cannot reach. `hyphens:auto` and `text-align:justify` only make the browser's greedy
result less ugly; they do not change the algorithm. Owning the line breaker is what makes a Nishi reading
surface typographically book-grade rather than screen-grade.
INTEGER-EXACT: badness is TeX's 100*r^3 evaluated in fixed point, so a paragraph lays out identically on
every host. Widths are in milli-units (one character = 1000) which keeps the arithmetic exact without float.
nx_kp_break fit <cols> <text> -- optimal layout, one line per output line
nx_kp_break compare <cols> <text> -- Knuth-Plass vs greedy first-fit, with the measured difference
nx_kp_break selftest -- the gate
license_tier: ORIGINAL
dependencies 2 imports · 0 importers
imports: nx_gate_verdict.nxnx_syscalls.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 24 | const KP_MAGIC_4000: i64 = 4000 |
| 25 | const KP_MAGIC_100000: i64 = 100000 |
| 27 | const KP_UNIT: i64 = 1000 // one character cell |
| 28 | const KP_SP_NAT: i64 = 1000 // interword space: natural width |
| 29 | const KP_SP_STRETCH: i64 = 500 // ... may stretch by half a space |
| 30 | const KP_SP_SHRINK: i64 = 333 // ... may shrink by a third (TeX's plain.tex proportions) |
| 31 | const KP_MAX_WORDS: i64 = 512 |
| 32 | const KP_INF: i64 = 1000000000 // "this line cannot be set" -- never a real cost |
| 33 | const KP_BAD_CAP: i64 = 10000 // TeX treats anything past this as equally awful |
| 34 | const KP_R_SCALE: i64 = 1000 |
| 35 | const KP_BAD_K: i64 = 100 // badness = 100 * r^3 |
| 36 | const KP_R_CUBE_DIV: i64 = 1000000000 // (r/1000)^3 == r^3 / 1e9 |
| 37 | const KP_LINE_PENALTY: i64 = 10 // a small per-line cost, so an equal-badness layout prefers fewer lines |
| 234 | const KP_TEST_TEXT: *u8 = "In olden times when wishing still helped one there lived a king whose daughters were all beautiful but the youngest was so beautiful that the sun itself which has seen so much was astonished whenever it shone in her face" |
functions
| 39 | func kp_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 40 | func kp_num(v: i64) -> i64 |
| 50 | func kp_atoi(s: *u8) -> i64 called by 1: main |
| 55 | func kp_seq(a: *u8, b: *u8) -> i64 called by 1: main |
| 62 | func kp_words(text: *u8, wstart: *i64, wlen: *i64, wid: *i64) -> i64 |
| 84 | func kp_badness(r: i64) -> i64 |
| 97 | func kp_ratio(wid: *i64, i: i64, j: i64, target: i64, last: i64) -> i64 |
| 118 | func kp_optimal(wid: *i64, n: i64, target: i64, prev: *i64, best: *i64) -> i64 |
| 145 | func kp_greedy(wid: *i64, n: i64, target: i64, brk: *i64) -> i64 |
| 181 | func kp_backtrack(prev: *i64, n: i64, brk: *i64) -> i64 |
| 195 | func kp_print_lines(text: *u8, wstart: *i64, wlen: *i64, brk: *i64, lines: i64) -> i64 |
| 216 | func kp_worst(wid: *i64, n: i64, target: i64, brk: *i64, lines: i64) -> i64 |
| 236 | func kp_selftest() -> i64 |
| 324 | func main(argc: i64, argv: *i64) -> i64 |