1 (use-modules (oop goops describe)
5 ;;; optimal page breaking
7 ;;; This is not optimal page breaking, this is optimal distribution of
8 ;;; lines over pages; line breaks are a given.
12 ;;; + \pagebreak, \nopagebreak
14 ;;; - short circut SCORE=-1 (dismiss path)
21 (define-class <optimally-broken-page-node> ()
22 (prev #:init-value '() #:accessor node-prev #:init-keyword #:prev)
23 (page #:init-value 0 #:accessor node-page-number #:init-keyword #:pageno)
24 (penalty #:init-value 0 #:accessor node-penalty #:init-keyword #:penalty)
25 (height #:init-value 0 #:accessor node-height #:init-keyword #:height)
26 (lines #:init-value 0 #:accessor node-lines #:init-keyword #:lines)
30 (define-method (display (node <optimally-broken-page-node>) port)
36 "Page " (node-page-number node)
37 " Lines: " (node-lines node)
38 " Penalty " (node-penalty node)
48 ;; TODO: first-diff and last-diff are arbitrary.
51 (define-public (ly:optimal-page-breaks lines
55 "Return pages as a list starting with 1st page. Each page is a list of lines. "
57 (define (make-node prev lines page-num penalty)
58 (make <optimally-broken-page-node>
67 (define (line-height line)
68 (ly:paper-line-extent line Y))
70 ;; FIXME: may need some tweaking: square, cubic
71 (define (height-penalty available used)
74 ((left (- available used))
77 (relative-empty (/ left available)))
79 ;; Convexity: two half-empty pages is better than 1 completely
81 (* (1+ relative-empty) relative-empty)))
85 ;; this should take the
86 (define (page-height page-number last?)
87 (let ((h text-height))
89 (set! h (+ h first-diff)))
91 (set! h (+ h last-diff)))
94 (define (cumulative-height lines)
95 (apply + (map line-height lines)))
97 (define (get-path node done)
98 "Follow NODE.PREV, and return as an ascending list of pages. DONE is what have
99 collected so far, and has ascending page numbers."
101 (if (is-a? node <optimally-broken-page-node>)
102 (get-path (node-prev node) (cons node done))
106 (define (add-penalties . lst)
107 (if (find negative? lst)
111 (define (walk-paths done-lines best-paths current-lines last? current-best)
112 "Return the best optimal-page-break-node that contains
113 CURRENT-LINES. DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
114 ascending range of lines, and BEST-PATHS contains the optimal breaks
115 corresponding to DONE-LINES.
117 CURRENT-BEST is the best result sofar, or #f."
121 ((this-page-num (if (null? best-paths)
123 (1+ (node-page-number (car best-paths)))))
124 (prev-penalty (if (null? best-paths)
126 (node-penalty (car best-paths))))
128 (page-height (page-height this-page-num last?))
129 (space-used (cumulative-height current-lines))
131 (this-page-penalty (height-penalty page-height space-used))
132 (user-penalty (ly:paper-line-break-penalty (car current-lines)))
133 (total-penalty (add-penalties
139 (< total-penalty (node-penalty current-best))))
140 (new-best (if better?
141 (make-node (if (null? best-paths)
145 this-page-num total-penalty)
149 "user pen " user-penalty " prev-penalty " prev-penalty "\n"
150 "better? " better? " total-penalty " total-penalty "\n"
151 "height " page-height " spc used: " space-used "\n"
152 "pen " this-page-penalty " lines: " current-lines "\n"))
154 (foo (display debug-info))
157 (if (and (pair? done-lines)
159 ;; if this page is too full, adding another line won't help
160 (positive? this-page-penalty))
161 (walk-paths (cdr done-lines) (cdr best-paths) (cons (car done-lines) current-lines)
165 (define (walk-lines done best-paths todo)
166 "Return the best page breaking as a single
167 <optimal-page-break-node> for optimally breaking TODO ++
168 DONE.reversed. BEST-PATHS is a list of break nodes corresponding to
174 ((this-line (car todo))
175 (last? (null? (cdr todo)))
183 (walk-lines (cons this-line done)
184 (cons next best-paths)
188 (define (line-number node)
189 (ly:paper-line-number (car (node-lines node))))
194 (walk-lines '() '() lines))
195 (break-nodes (get-path best-break-node '()))
196 (break-lines (map node-lines break-nodes))
197 (break-numbers (map line-number break-nodes))
200 (display break-lines)
202 (if (ly:get-option 'verbose)
204 (format (current-error-port) "breaks: ~S\n" break-numbers)
205 (force-output (current-error-port))))
207 ;; TODO: if solution is bad return no breaks and revert to