You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
612 lines
30 KiB
Racket
612 lines
30 KiB
Racket
#lang debug racket
|
|
(require racket/list racket/match sugar/debug sugar/list racket/generator
|
|
"param.rkt" "quad.rkt" "atomize.rkt" "position.rkt" "ocm.rkt" "log.rkt")
|
|
(provide wrap)
|
|
|
|
(define-syntax (debug-report stx)
|
|
(with-syntax ([DEBUG (datum->syntax stx 'debug)]
|
|
[STRING-EXPR (syntax-case stx ()
|
|
[(_ SYM)
|
|
#'(format "~a" SYM)]
|
|
[(_ VAL SYM)
|
|
#'(format "~a: ~a" SYM VAL)])])
|
|
#'(when DEBUG (log-quad-debug STRING-EXPR))))
|
|
|
|
(define (nonprinting-at-start? x) (not (printable? x 'start)))
|
|
(define (nonprinting-at-end? x) (not (printable? x 'end)))
|
|
|
|
(define (default-finish-wrap-func wrap-qs q0 ending-q idx) (list wrap-qs))
|
|
(define (finalize-reversed-wraps wraps)
|
|
; append* because `finish-wrap-proc` returns a spliceable list
|
|
; reverse because wraps accumulated in reverse
|
|
(match (append* (reverse wraps))
|
|
['(()) '()] ; special case
|
|
[wraps wraps]))
|
|
|
|
(define (arg->proc arg [arity 1])
|
|
(match arg
|
|
[(? procedure? proc) proc]
|
|
[val #:when (eq? arity 2) (λ (q idx) val)]
|
|
[val (λ (q) val)]))
|
|
|
|
(define (wrap qs
|
|
[max-distance (current-wrap-distance)]
|
|
[debug #f]
|
|
;; hard break: must wrap
|
|
#:hard-break [hard-break-func-arg #false]
|
|
;; soft break: can wrap
|
|
#:soft-break [soft-break-func-arg #false]
|
|
;; no break: must not wrap (exception to hard / soft predicates)
|
|
#:no-break [no-break-func-arg #false]
|
|
;; size of potential wrap.
|
|
;; simple: measure q and add it to last-dist
|
|
;; sophisticated: process all wrap-qs and measure resulting
|
|
;; wrap-qs are reversed from typographic order
|
|
#:distance [distance-func (λ (q last-dist wrap-qs)
|
|
(+ last-dist (if (printable? q) (distance q) 0)))]
|
|
;; called when wrap counter increments.
|
|
;; perhaps should reset after paragraph breaks, etc.
|
|
#:wrap-count [wrap-count (λ (idx wrap-triggering-q) (add1 idx))]
|
|
;; starting value when wrap counter resets.
|
|
;; could use an arbitrary data structure (then incremented with `wrap-count`
|
|
#:initial-wrap-count [initial-wrap-idx 1]
|
|
;; called when wrap is done.
|
|
;; takes as input list of qs in wrap,
|
|
;; q0 that caused the previous wrap, or #f at beginning.
|
|
;; q that caused this one, or #f at end.
|
|
;; (q0 is not part of this wrap, but q is)
|
|
;; idx is current wrap-count value.
|
|
#:finish-wrap [finish-wrap-func default-finish-wrap-func]
|
|
#:nicely [nicely? #f]
|
|
#:footnote-qs [footnote-qs null]
|
|
#:footnote-leftover-proc [footnote-leftover-proc (λ _ (error 'no-footnote-leftover-proc))]
|
|
#:footnote-new-proc [footnote-new-proc (λ _ (error 'no-footnote-new-proc))])
|
|
(define wrap-proc (if nicely? wrap-best wrap-first))
|
|
(define hard-break-func (arg->proc hard-break-func-arg))
|
|
(define soft-break-func (arg->proc soft-break-func-arg))
|
|
(define no-break-func (arg->proc no-break-func-arg))
|
|
(define (hard-break? x) (and (hard-break-func x) (not (no-break-func x))))
|
|
(define (soft-break? x) (and (soft-break-func x) (not (no-break-func x))))
|
|
; takes quads in wrap, triggering quad, and wrap idx; returns list containing wrap (and maybe other things)
|
|
(define (finish-wrap qs previous-wrap-ender wrap-idx [wrap-triggering-q (car qs)] [fn-qs null])
|
|
;; reverse because quads accumulated in reverse
|
|
;; wrap-triggering-q is ordinarily the last accumulated q
|
|
;; unless it's the last wrap, in which case it's #f
|
|
;; but we capture it separately because it's likely to get trimmed away by `nonprinting-at-end?`
|
|
;; note: we don't trim `soft-break?` or `hard-break?` because that's an orthogonal consideration
|
|
;; for instance, a hyphen is `soft-break?` but shouldn't be trimmed.
|
|
(define has-footnotes? (pair? footnote-qs))
|
|
(apply finish-wrap-func
|
|
(reverse (dropf qs nonprinting-at-end?)) previous-wrap-ender wrap-triggering-q wrap-idx
|
|
(if has-footnotes? (list fn-qs) null)))
|
|
(wrap-proc qs
|
|
max-distance
|
|
debug
|
|
hard-break?
|
|
soft-break?
|
|
finish-wrap
|
|
wrap-count
|
|
distance-func
|
|
initial-wrap-idx
|
|
|
|
footnote-qs
|
|
footnote-leftover-proc
|
|
footnote-new-proc))
|
|
|
|
(define (wrap-first qs
|
|
max-distance
|
|
debug
|
|
hard-break?
|
|
soft-break?
|
|
finish-wrap
|
|
wrap-count
|
|
distance-func
|
|
initial-wrap-idx
|
|
|
|
footnote-qs-in
|
|
footnote-leftover-proc
|
|
footnote-new-proc)
|
|
|
|
(define has-footnotes? (pair? footnote-qs-in))
|
|
|
|
(let loop ([wraps null] ; list of (list of quads)
|
|
[wrap-idx initial-wrap-idx] ; wrap count (could be (length wraps) but we'd rather avoid `length`)
|
|
[next-wrap-head null] ; list of quads ending in previous `soft-break?` or `hard-break?`
|
|
[next-wrap-tail null] ; list of unbreakable quads
|
|
[current-dist #false] ; #false (to indicate start) or integer
|
|
[previous-wrap-ender #f]
|
|
[qs qs] ; list of quads
|
|
[footnote-qs footnote-qs-in] ; list of footnote quads
|
|
[footnote-next-wrap null]
|
|
[footnote-wraps null] ; list of footnote lines wrapped into footnote area for this col
|
|
[footnote-dist 0] ; dist consumed by footnotes in current footnote wrap
|
|
; this needs to be tracked separately from current-dist because #false is used to detect start
|
|
[max-distance max-distance] ; might be reduced by footnotes
|
|
)
|
|
#|
|
|
1) If there are lines left over from a previous footnote, set as many of those lines on the current page as space allows. If the footnote zone is empty, this is a footnote continuation, so start with a continuation break. Loop without making a new column break.
|
|
|#
|
|
(let-values ([(max-distance footnote-next-wrap footnote-qs)
|
|
((if has-footnotes?
|
|
footnote-leftover-proc
|
|
values) max-distance footnote-next-wrap footnote-qs)])
|
|
|
|
(match qs
|
|
[(? null?)
|
|
(define last-wrap
|
|
(finish-wrap (append next-wrap-tail next-wrap-head) previous-wrap-ender wrap-idx #f))
|
|
(finalize-reversed-wraps (cons last-wrap wraps))]
|
|
[(cons q other-qs)
|
|
(debug-report q 'next-q)
|
|
(debug-report (quad-elems q) 'next-q-elems)
|
|
(define would-be-wrap-qs (append (cons q next-wrap-tail) next-wrap-head))
|
|
(define (handle-hard-overflow)
|
|
(cond
|
|
[(empty? next-wrap-head)
|
|
(define-values (next-wrap-qs other-qs)
|
|
(cond
|
|
[(empty? next-wrap-tail)
|
|
;; degenerate case where q is big enough to trigger a wrap on its own,
|
|
;; but nothing left in next-wrap-head or next-wrap-tail.
|
|
;; so we put it in its own wrap and recur, because otherwise we can't proceed
|
|
;; though it will look screwy
|
|
(debug-report 'making-the-best-of-a-bad-situation)
|
|
(values (list q) (cdr qs))]
|
|
[else
|
|
(debug-report 'would-overflow-hard-without-captured-break)
|
|
(values next-wrap-tail qs)]))
|
|
(loop (cons (finish-wrap next-wrap-qs previous-wrap-ender wrap-idx (car next-wrap-qs) footnote-next-wrap) wraps)
|
|
(wrap-count wrap-idx q)
|
|
null
|
|
null
|
|
#false
|
|
(car next-wrap-qs)
|
|
other-qs
|
|
footnote-qs
|
|
null ; reset footnote-next-wrap
|
|
footnote-wraps
|
|
0 ; reset footnote-dist
|
|
max-distance)]
|
|
[else ; finish the wrap & reset the line without consuming a quad
|
|
(loop (cons (finish-wrap next-wrap-head previous-wrap-ender wrap-idx (car next-wrap-head) footnote-next-wrap) wraps)
|
|
(wrap-count wrap-idx q)
|
|
null
|
|
next-wrap-tail
|
|
(for/sum ([item (in-list next-wrap-tail)]) (distance item))
|
|
(car next-wrap-head)
|
|
qs
|
|
footnote-qs
|
|
null ; reset footnote-next-wrap
|
|
footnote-wraps
|
|
0 ; reset footnote-dist
|
|
max-distance)]))
|
|
(with-handlers ([symbol? (λ (exn) (handle-hard-overflow))])
|
|
(let-values ([(max-distance footnote-next-wrap footnote-qs)
|
|
(if has-footnotes?
|
|
(footnote-new-proc max-distance footnote-next-wrap footnote-qs q)
|
|
(values max-distance footnote-next-wrap footnote-qs))])
|
|
(cond
|
|
[(hard-break? q)
|
|
(debug-report 'found-hard-break)
|
|
;; must break. finish the wrap and consume the hard break
|
|
(loop (cons (finish-wrap would-be-wrap-qs previous-wrap-ender wrap-idx) wraps)
|
|
(wrap-count wrap-idx q)
|
|
null
|
|
null
|
|
#false
|
|
q
|
|
other-qs
|
|
footnote-qs
|
|
footnote-next-wrap
|
|
footnote-wraps
|
|
footnote-dist
|
|
max-distance)]
|
|
[(let ([at-start? (not current-dist)]) at-start?)
|
|
(match q
|
|
[(and (? soft-break?) (? nonprinting-at-start?))
|
|
(debug-report q 'skipping-soft-break-at-beginning)
|
|
(loop wraps
|
|
wrap-idx
|
|
next-wrap-head
|
|
next-wrap-tail
|
|
current-dist
|
|
previous-wrap-ender
|
|
other-qs
|
|
footnote-qs
|
|
footnote-next-wrap
|
|
footnote-wraps
|
|
footnote-dist
|
|
max-distance)]
|
|
[_ (debug-report 'hard-quad-at-start)
|
|
(loop wraps
|
|
wrap-idx
|
|
next-wrap-head
|
|
(list q)
|
|
(distance-func q 0 would-be-wrap-qs)
|
|
previous-wrap-ender
|
|
other-qs
|
|
footnote-qs
|
|
footnote-next-wrap
|
|
footnote-wraps
|
|
footnote-dist
|
|
max-distance)])]
|
|
[else ; cases that require computing distance
|
|
(define wrap-distance (distance-func q current-dist would-be-wrap-qs))
|
|
(define would-overflow? (> wrap-distance max-distance))
|
|
(cond
|
|
[would-overflow?
|
|
(cond
|
|
[(and (soft-break? q) (nonprinting-at-end? q))
|
|
(debug-report 'would-overflow-soft-nonprinting)
|
|
;; a break is inevitable but we want to wait to finish the wrap until we see a hard quad
|
|
;; but we can move the current-partial into the current-wrap
|
|
(loop wraps
|
|
wrap-idx
|
|
(append (cons q next-wrap-tail) next-wrap-head)
|
|
null
|
|
wrap-distance
|
|
previous-wrap-ender
|
|
other-qs
|
|
footnote-qs
|
|
footnote-next-wrap
|
|
footnote-wraps
|
|
footnote-dist
|
|
max-distance)]
|
|
[else (handle-hard-overflow)])]
|
|
[(soft-break? q)
|
|
(debug-report 'would-not-overflow-soft)
|
|
;; a soft break that fits, so move it on top of the next-wrap-head with the next-wrap-tail
|
|
(loop wraps
|
|
wrap-idx
|
|
(append (cons q next-wrap-tail) next-wrap-head)
|
|
null
|
|
wrap-distance
|
|
previous-wrap-ender
|
|
other-qs
|
|
footnote-qs
|
|
footnote-next-wrap
|
|
footnote-wraps
|
|
footnote-dist
|
|
max-distance)]
|
|
[else
|
|
(debug-report 'would-not-overflow)
|
|
;; add to partial
|
|
(loop wraps
|
|
wrap-idx
|
|
next-wrap-head
|
|
(cons q next-wrap-tail)
|
|
wrap-distance
|
|
previous-wrap-ender
|
|
other-qs
|
|
footnote-qs
|
|
footnote-next-wrap
|
|
footnote-wraps
|
|
footnote-dist
|
|
max-distance)])])))]))))
|
|
|
|
(define last-line-can-be-short? #t)
|
|
(define mega-penalty 1e8)
|
|
(define hyphen-penalty +inf.0)
|
|
(define max-consecutive-hyphens 1)
|
|
(define (pieces-sublist pieces i j)
|
|
(reverse (apply append (for/list ([n (in-range i j)])
|
|
(vector-ref pieces n)))))
|
|
|
|
(define (wrap-best qs
|
|
max-distance
|
|
debug
|
|
hard-break?
|
|
soft-break?
|
|
finish-wrap
|
|
wrap-count
|
|
distance-func
|
|
initial-wrap-idx . _)
|
|
(for*/fold ([wrapss null]
|
|
[wrap-idx initial-wrap-idx]
|
|
[previous-wrap-ender #f]
|
|
#:result (finalize-reversed-wraps (apply append wrapss)))
|
|
([pieces-maybe-ending-in-hard-break (in-list (slicef-after qs hard-break?))])
|
|
(define-values (pieces last-ender)
|
|
(match pieces-maybe-ending-in-hard-break
|
|
[(list pieces ... (? hard-break? hbr)) (values pieces hbr)]
|
|
[pieces (values pieces #f)]))
|
|
(define pieces-vec (list->vector (slicef-after pieces soft-break?)))
|
|
(define-values (wraps idx ender)
|
|
(wrap-pieces-best pieces-vec wrap-idx previous-wrap-ender last-ender wrap-count distance-func max-distance finish-wrap))
|
|
(values (cons wraps wrapss) idx last-ender)))
|
|
|
|
(struct penalty-rec (val idx hyphen-count) #:transparent)
|
|
(define (wrap-pieces-best pieces-vec starting-wrap-idx previous-last-q last-ender wrap-count distance-func max-distance finish-wrap)
|
|
(define (penalty i j)
|
|
(cond
|
|
[(or (eq? i j) (> j (vector-length pieces-vec)))
|
|
(define out-of-bounds-signal (- i))
|
|
(penalty-rec out-of-bounds-signal #f 0)]
|
|
[else
|
|
(match-define (penalty-rec last-val starting-idx hyphen-count) (ocm-min-value ocm i))
|
|
(define would-be-wrap-qs (pieces-sublist pieces-vec i j)) ; `reverse` to track ordinary wrap logic
|
|
(define wrap-distance (for/fold ([last-dist 0])
|
|
([q (in-list would-be-wrap-qs)])
|
|
(distance-func q last-dist would-be-wrap-qs)))
|
|
(define underflow (- max-distance wrap-distance))
|
|
(define new-consecutive-hyphen-count
|
|
(if (equal? (quad-elems (car would-be-wrap-qs)) '("\u00AD")) (add1 hyphen-count) 0))
|
|
(penalty-rec
|
|
(+ last-val ; include penalty so far
|
|
mega-penalty ; new line penalty
|
|
(if (> new-consecutive-hyphen-count max-consecutive-hyphens)
|
|
(* hyphen-penalty (- new-consecutive-hyphen-count max-consecutive-hyphens))
|
|
0)
|
|
(cond
|
|
[(negative? underflow)
|
|
;; overfull line: huge penalty prevents break; multiplier is essential for monotonicity.
|
|
(* mega-penalty (- underflow))]
|
|
[(let ([on-last-line? (eq? j (vector-length pieces-vec))])
|
|
(or (not on-last-line?)
|
|
(and on-last-line? (not last-line-can-be-short?))))
|
|
;; standard penalty
|
|
(expt underflow 2)]
|
|
[else 0]))
|
|
(wrap-count starting-idx (car would-be-wrap-qs))
|
|
new-consecutive-hyphen-count)]))
|
|
|
|
;; starting from last position, ask ocm for position of row minimum (= new-pos)
|
|
;; collect this value, and use it as the input next time
|
|
;; until you reach first position.
|
|
(define ocm (make-ocm penalty (penalty-rec 0 starting-wrap-idx 0) penalty-rec-val))
|
|
(define last-j (vector-length pieces-vec))
|
|
(define breakpoints
|
|
(if (zero? last-j)
|
|
(list 0 0)
|
|
(let loop ([bps (list last-j)]) ; start from end
|
|
(match (ocm-min-index ocm (car bps)) ; look to the previous line
|
|
[0 (cons 0 bps)]; zero means we're at first position, and therefore done
|
|
[min-i (loop (cons min-i bps))]))))
|
|
(for/fold ([wraps null]
|
|
[wrap-idx starting-wrap-idx]
|
|
[previous-wrap-ender previous-last-q])
|
|
([i (in-list breakpoints)]
|
|
[j (in-list (cdr breakpoints))])
|
|
(define wrap-qs (pieces-sublist pieces-vec i j)) ; first-fit gets wrap-qs in reverse, so be consistent
|
|
;; last wrap-ender must be #false
|
|
(define this-wrap-ender (if (eq? j last-j) last-ender (car wrap-qs)))
|
|
(values (cons (finish-wrap wrap-qs previous-wrap-ender wrap-idx this-wrap-ender) wraps)
|
|
(wrap-count wrap-idx this-wrap-ender)
|
|
this-wrap-ender)))
|
|
|
|
|
|
(module+ test
|
|
(define q-zero (q #:size (pt 0 0)))
|
|
(define q-one (q #:size (pt 1 1) #:printable #t))
|
|
(define x (struct-copy quad q-one [elems '(#\x)]))
|
|
(define zwx (struct-copy quad q-zero
|
|
[printable (λ _ #t)]
|
|
[elems '(#\z)]))
|
|
(define hyph (struct-copy quad q-one [elems '(#\-)]))
|
|
(define shy (struct-copy quad q-one
|
|
[printable (λ (q [sig #f])
|
|
(case sig
|
|
[(end) #t]
|
|
[else #f]))]
|
|
[elems '(#\-)]))
|
|
(define a (struct-copy quad q-one [elems '(#\a)]))
|
|
(define b (struct-copy quad q-one [elems '(#\b)]))
|
|
(define c (struct-copy quad q-one [elems '(#\c)]))
|
|
(define d (struct-copy quad q-one [elems '(#\d)]))
|
|
(define sp (struct-copy quad q-one
|
|
[printable (λ (q [sig #f])
|
|
(case sig
|
|
[(start end) #f]
|
|
[else #t]))]
|
|
[elems '(#\space)]))
|
|
(define lbr (struct-copy quad q-one
|
|
[printable (λ _ #f)]
|
|
[elems '(#\newline)]))
|
|
|
|
(define (soft-break? q) (memv (car (quad-elems q)) '(#\space #\-)))
|
|
|
|
(define (linewrap xs size [debug #f] #:nicely [nicely? #f])
|
|
(add-between (wrap xs size debug
|
|
#:nicely nicely?
|
|
#:finish-wrap (λ (xs . _) (list xs))
|
|
#:hard-break (λ (q) (char=? (car (quad-elems q)) #\newline))
|
|
#:soft-break soft-break?) lbr))
|
|
|
|
|
|
(define (visual-wrap str int [debug #f] #:nicely [nicely? #f])
|
|
(string-join
|
|
(for/list ([x (in-list (linewrap (for/list ([c (in-string str)])
|
|
(define atom (q c))
|
|
(if (equal? (quad-elems atom) '(#\space))
|
|
(struct-copy quad sp)
|
|
(struct-copy quad q-one
|
|
[attrs (quad-attrs atom)]
|
|
[elems (quad-elems atom)]))) int debug
|
|
#:nicely nicely?))]
|
|
#:when (and (list? x) (andmap quad? x)))
|
|
(list->string (map car (map quad-elems x))))
|
|
"|"))
|
|
|
|
(define (pagewrap xs size [debug #f])
|
|
(add-between
|
|
(wrap (flatten xs) size debug
|
|
#:hard-break (λ (x) (and (quad? x) (memv (car (quad-elems x)) '(#\page))))
|
|
#:soft-break (λ (x) (and (quad? x) (eq? x lbr)))) pbr))
|
|
(define pbr (q #:size #false
|
|
#:printable #false
|
|
#:elems '(#\page)))
|
|
|
|
(define (linewrap2 xs size [debug #f])
|
|
(add-between
|
|
(wrap xs size debug
|
|
#:hard-break (λ (q) (memv (car (quad-elems q)) '(#\newline)))
|
|
#:soft-break soft-break?
|
|
#:finish-wrap (λ (pcs . _) (list (apply q pcs))))
|
|
lbr)))
|
|
|
|
|
|
(module+ test (require rackunit))
|
|
|
|
#;(module+ test
|
|
(test-case
|
|
"kp linebreaking"
|
|
(define meg-is-an-ally (list a b c sp a b sp c d sp a b c d x)) ; "Meg is an ally."
|
|
(check-equal? (linewrap meg-is-an-ally 6)
|
|
;; Meg is
|
|
;; an
|
|
;; ally.
|
|
(list (list a b c sp a b) lbr (list c d) lbr (list a b c d x)))
|
|
(check-equal? (linewrap meg-is-an-ally 6 #:nicely #t)
|
|
;; Meg
|
|
;; is an
|
|
;; ally.
|
|
(list (list a b c) lbr (list a b sp c d) lbr (list a b c d x)))))
|
|
|
|
(module+ test
|
|
(test-begin
|
|
(test-case
|
|
"chars"
|
|
(check-equal? (linewrap (list) 1) (list))
|
|
(check-equal? (linewrap (list a) 1) (list (list a)))
|
|
(check-equal? (linewrap (list a b) 1) (list (list a) lbr (list b)))
|
|
(check-equal? (linewrap (list a b c) 1) (list (list a) lbr (list b) lbr (list c)))
|
|
(check-equal? (linewrap (list a b c) 2) (list (list a b) lbr (list c)))
|
|
(check-equal? (linewrap (list x x x x) 2) (list (list x x) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x x x x) 3) (list (list x x x) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x x x x) 1)
|
|
(list (list x) lbr (list x) lbr (list x) lbr (list x) lbr (list x)))
|
|
(check-equal? (linewrap (list x x x x x) 10) (list (list x x x x x))))
|
|
|
|
(test-case
|
|
"chars and spaces"
|
|
(check-equal? (linewrap (list a sp b) 1) (list (list a) lbr (list b)))
|
|
(check-equal? (linewrap (list a b sp c) 2) (list (list a b) lbr (list c)))
|
|
(check-equal? (linewrap (list a sp b) 3) (list (list a sp b)))
|
|
(check-equal? (linewrap (list a sp b c) 3) (list (list a) lbr (list b c))))
|
|
|
|
(test-case
|
|
"leading & trailing spaces"
|
|
(check-equal? (linewrap (list sp x) 2) (list (list x)))
|
|
(check-equal? (linewrap (list x sp) 2) (list (list x)))
|
|
(check-equal? (linewrap (list sp x sp) 2) (list (list x)))
|
|
(check-equal? (linewrap (list sp sp x sp sp) 2) (list (list x)))
|
|
(check-equal? (linewrap (list sp sp x sp sp x sp) 1) (list (list x) lbr (list x))))
|
|
|
|
(test-case
|
|
"hard hyphens"
|
|
(check-equal? (linewrap (list hyph) 1) (list (list hyph)))
|
|
(check-equal? (linewrap (list hyph hyph) 1) (list (list hyph) lbr (list hyph)))
|
|
(check-equal? (linewrap (list hyph hyph) 2) (list (list hyph hyph)))
|
|
(check-equal? (linewrap (list hyph hyph hyph) 2) (list (list hyph hyph) lbr (list hyph)))
|
|
(check-equal? (linewrap (list x hyph) 1) (list (list x) lbr (list hyph)))
|
|
(check-equal? (linewrap (list a b hyph c d) 1)
|
|
(list (list a) lbr (list b) lbr (list hyph) lbr (list c) lbr (list d)))
|
|
(check-equal? (linewrap (list a b hyph c d) 2) (list (list a b) lbr (list hyph c) lbr (list d)))
|
|
(check-equal? (linewrap (list a b hyph c d) 3) (list (list a b hyph) lbr (list c d)))
|
|
(check-equal? (linewrap (list x x hyph x x) 4) (list (list x x hyph) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x hyph x x) 5) (list (list x x hyph x x))))
|
|
|
|
;; todo: fix soft hyphens
|
|
#;(test-case
|
|
"soft hyphens"
|
|
(check-equal? (linewrap (list shy) 1) (list))
|
|
(check-equal? (linewrap (list shy shy) 2) (list))
|
|
(check-equal? (linewrap (list shy shy shy) 2) (list))
|
|
(check-equal? (linewrap (list x shy) 1) (list (list x)))
|
|
(check-equal? (linewrap (list x shy shy shy shy) 1) (list (list x)))
|
|
;; todo: degenerate cases that don't work without continuations
|
|
;(check-equal? (linewrap (list x x shy x x) 1) (list x br x br x br x))
|
|
;(check-equal? (linewrap (list x x shy x x) 2) (list x x br x x))
|
|
(check-equal? (linewrap (list x x shy x x) 3) (list (list x x shy) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x shy x x) 4) (list (list x x x x)))
|
|
(check-equal? (linewrap (list x x shy x x) 5) (list (list x x x x)))
|
|
(check-equal? (linewrap (list x x shy x sp x) 4) (list (list x x x) lbr (list x))))
|
|
|
|
(test-case
|
|
"zero width nonbreakers"
|
|
(check-equal? (linewrap (list sp zwx) 2) (list (list zwx)))
|
|
(check-equal? (linewrap (list zwx sp) 2) (list (list zwx)))
|
|
(check-equal? (linewrap (list sp zwx sp) 2) (list (list zwx)))
|
|
(check-equal? (linewrap (list sp sp zwx sp sp) 2) (list (list zwx)))
|
|
(check-equal? (linewrap (list sp sp zwx sp sp zwx sp) 2) (list (list zwx sp sp zwx))))
|
|
|
|
(test-case
|
|
"hard breaks"
|
|
(check-equal? (linewrap (list lbr) 2) (list (list) lbr (list)))
|
|
(check-equal? (linewrap (list a lbr b) 2) (list (list a) lbr (list b)))
|
|
(check-equal? (linewrap (list a b lbr) 2) (list (list a b) lbr (list)))
|
|
(check-equal? (linewrap (list a b lbr lbr) 2) (list (list a b) lbr (list) lbr (list)))
|
|
(check-equal? (linewrap (list x lbr x x) 3) (list (list x) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x lbr x) 3) (list (list x x) lbr (list x)))
|
|
(check-equal? (linewrap (list x x x x) 3) (list (list x x x) lbr (list x)))
|
|
(check-equal? (linewrap (list x x x sp x x) 2) (list (list x x) lbr (list x) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x x sp x x) 3) (list (list x x x) lbr (list x x))))
|
|
|
|
|
|
(test-case
|
|
"hard breaks and spurious spaces"
|
|
(check-equal? (linewrap (list a sp sp sp lbr b) 2) (list (list a) lbr (list b)))
|
|
(check-equal? (linewrap (list a sp lbr sp sp b c sp) 3) (list (list a) lbr (list b c)))
|
|
(check-equal? (linewrap (list sp sp x x sp sp lbr sp sp sp x) 3) (list (list x x) lbr (list x)))
|
|
(check-equal? (linewrap (list a sp b sp sp lbr sp c) 3) (list (list a sp b) lbr (list c)))
|
|
(check-equal? (linewrap (list x x x x) 3) (list (list x x x) lbr (list x)))
|
|
(check-equal? (linewrap (list x x x sp x x) 2) (list (list x x) lbr (list x) lbr (list x x)))
|
|
(check-equal? (linewrap (list x x x sp x x) 3) (list (list x x x) lbr (list x x))))
|
|
|
|
(test-case
|
|
"visual breaks"
|
|
(check-equal? (visual-wrap "My dog has fleas" 1) "M|y|d|o|g|h|a|s|f|l|e|a|s")
|
|
(check-equal? (visual-wrap "My dog has fleas" 2) "My|do|g|ha|s|fl|ea|s")
|
|
(check-equal? (visual-wrap "My dog has fleas" 3) "My|dog|has|fle|as")
|
|
(check-equal? (visual-wrap "My dog has fleas" 4) "My|dog|has|flea|s")
|
|
(check-equal? (visual-wrap "My dog has fleas" 5) "My|dog|has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 6) "My dog|has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 7) "My dog|has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 8) "My dog|has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 9) "My dog|has fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 10) "My dog has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 11) "My dog has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 12) "My dog has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 13) "My dog has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 14) "My dog has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 15) "My dog has|fleas")
|
|
(check-equal? (visual-wrap "My dog has fleas" 16) "My dog has fleas"))
|
|
|
|
(test-case
|
|
"soft page breaks"
|
|
(check-equal? (pagewrap null 2) (list))
|
|
(check-equal? (pagewrap (list x) 2) (list (list x)))
|
|
(check-equal? (pagewrap (list x x) 2) (list (list x x)))
|
|
(check-equal? (pagewrap (list x x x) 1) (list (list x) pbr (list x) pbr (list x)))
|
|
(check-equal? (pagewrap (list x x x) 2) (list (list x x) pbr (list x)))
|
|
(check-equal? (pagewrap (list x x x) 3) (list (list x x x)))
|
|
(check-equal? (pagewrap (list x x x) 4) (list (list x x x)))
|
|
(check-equal? (pagewrap (list x lbr x x) 2) (list (list x) pbr (list x x))))
|
|
|
|
(test-case
|
|
"hard page breaks"
|
|
(check-equal? (pagewrap (list a pbr b c) 2) (list (list a) pbr (list b c)))
|
|
(check-equal? (pagewrap (list x pbr x x) 1) (list (list x) pbr (list x) pbr (list x)))
|
|
(check-equal? (pagewrap (list x pbr pbr x x) 1) (list (list x) pbr (list) pbr (list x) pbr (list x)))
|
|
(check-equal? (pagewrap (list x pbr pbr x x) 2) (list (list x) pbr (list) pbr (list x x)))
|
|
(check-equal? (pagewrap (list lbr x lbr lbr pbr lbr x x lbr) 2) (list (list x) pbr (list x x))))
|
|
|
|
(test-case
|
|
"composed line breaks and page breaks"
|
|
(check-equal? (pagewrap (linewrap null 1) 2) (list))
|
|
(check-equal? (pagewrap (linewrap (list x) 1) 2) (list (list x)))
|
|
(check-equal? (pagewrap (linewrap (list x x x) 1) 2) (list (list x lbr x) pbr (list x)))
|
|
(check-equal? (pagewrap (linewrap (list x x x) 2) 2) (list (list x x) pbr (list x)))
|
|
(check-equal? (pagewrap (linewrap (list x x x) 2) 1) (list (list x) pbr (list x) pbr (list x))))
|
|
|
|
(test-case
|
|
"hard breaks and spurious spaces with slugs"
|
|
(check-equal? (linewrap2 (list a sp sp sp lbr b) 2) (list (q a) lbr (q b)))
|
|
(check-equal? (linewrap2 (list x sp lbr sp sp x x sp) 3) (list (q x) lbr (q x x)))
|
|
(check-equal? (linewrap2 (list sp sp x x sp sp lbr sp sp sp x) 3) (list (q x x) lbr (q x)))
|
|
(check-equal? (linewrap2 (list a sp b sp sp lbr sp c) 3) (list (q a sp b) lbr (q c)))
|
|
(check-equal? (linewrap2 (list x x x x) 3) (list (q x x x) lbr (q x)))
|
|
(check-equal? (linewrap2 (list x x x sp x x) 2) (list (q x x) lbr (q x) lbr (q x x)))
|
|
(check-equal? (linewrap2 (list x x x sp x x) 3) (list (q x x x) lbr (q x x))))))
|
|
|