1 ;;;; page-layout.scm -- page breaking and page layout
3 ;;;; source file of the GNU LilyPond music typesetter
5 ;;;; (c) 2004--2006 Jan Nieuwenhuizen <janneke@gnu.org>
6 ;;;; Han-Wen Nienhuys <hanwen@cs.uu.nl>
8 (use-modules (oop goops describe)
19 ;;; optimal page breaking
21 ;;; This is not optimal page breaking, this is optimal distribution of
22 ;;; lines over pages; line breaks are a given.
27 ;; - separate function for word-wrap style breaking?
28 ;; - raggedbottom? raggedlastbottom?
30 (define-public (optimal-page-breaks lines paper-book)
31 "Return pages as a list starting with 1st page. Each page is a 'page prob.
35 (define MAXPENALTY 1e9)
36 (define paper (ly:paper-book-paper paper-book))
37 (define scopes (ly:paper-book-scopes paper-book))
38 (define force-equalization-factor #f)
39 (define (get-path node done)
40 "Follow NODE.PREV, and return as an ascending list of pages. DONE
41 is what have collected so far, and has ascending page numbers."
44 (get-path (page-prev node) (cons node done))
47 (define (combine-penalties force user best-paths)
48 (let* ((prev-force (if (null? best-paths)
50 (page-force (car best-paths))))
51 (prev-penalty (if (null? best-paths)
53 (page-penalty (car best-paths))))
54 (inter-system-space (ly:output-def-lookup paper 'betweensystemspace))
55 (relative-force (/ force inter-system-space))
56 (abs-relative-force (abs relative-force)))
59 (+ (* abs-relative-force (+ abs-relative-force 1))
61 (* force-equalization-factor (/ (abs (- prev-force force))
65 (define (space-systems page-height lines ragged?)
66 (let* ((global-inter-system-space
67 (ly:output-def-lookup paper 'betweensystemspace))
69 (ly:output-def-lookup paper 'pagetopspace))
70 (global-fixed-dist (ly:output-def-lookup paper 'betweensystempadding))
72 (system-vector (list->vector
74 (if (= (length lines) 1)
79 (append (map paper-system-staff-extents lines)
80 (if (= (length lines) 1)
88 (lambda (sys) (paper-system-extent sys Y)) lines)
89 (if (= (length lines) 1)
93 (system-count (vector-length real-extents))
97 (interval-end (vector-ref staff-extents 0)))
98 (interval-end (vector-ref real-extents 0))
100 (last-system (vector-ref system-vector (1- system-count)))
101 (bottom-space (if (ly:prob? last-system)
102 (ly:prob-property last-system 'bottom-space 0.0)
104 (space-left (- page-height
106 (apply + (map interval-length
107 (vector->list real-extents)))))
109 (space (- page-height
113 (vector-ref real-extents (1- system-count))))))
118 (upper-system (vector-ref system-vector idx))
119 (between-space (ly:prob-property upper-system 'next-space
120 global-inter-system-space))
121 (fixed-dist (ly:prob-property upper-system 'next-padding
124 (this-system-ext (vector-ref staff-extents idx))
125 (next-system-ext (vector-ref staff-extents (1+ idx)))
126 (fixed (max 0 (- (+ (interval-end next-system-ext)
128 (interval-start this-system-ext))))
129 (title1? (and (vector-ref system-vector idx)
130 (paper-system-title? (vector-ref system-vector idx)
133 (vector-ref system-vector (1+ idx))
134 (paper-system-title? (vector-ref system-vector (1+ idx)))))
137 ((and title2? title1?)
138 (ly:output-def-lookup paper 'betweentitlespace))
140 (ly:output-def-lookup paper 'aftertitlespace))
142 (ly:output-def-lookup paper 'beforetitlespace))
143 (else between-space))
145 (hooke (/ 1 (- ideal fixed))))
146 (list ideal hooke))))
148 (springs (map calc-spring (iota (1- system-count))))
152 (upper-system (vector-ref system-vector idx))
153 (fixed-dist (ly:prob-property upper-system 'next-padding
155 (this-system-ext (vector-ref real-extents idx))
156 (next-system-ext (vector-ref real-extents (1+ idx)))
158 (distance (max (- (+ (interval-end next-system-ext)
160 (interval-start this-system-ext)
162 (entry (list idx (1+ idx) distance)))
164 (rods (map calc-rod (iota (1- system-count))))
166 ;; we don't set ragged based on amount space left.
167 ;; raggedbottomlast = ##T is much more predictable
168 (result (ly:solve-spring-rod-problem
180 (display (list "\n# systems: " system-count
181 "\nreal-ext" real-extents "\nstaff-ext" staff-extents
182 "\ninterscore" global-inter-system-space
183 "\nspace-left" space-left
184 "\nspring,rod" springs rods
187 "\npage-height" page-height
191 "\npositions" positions "\n"))))
193 (cons force positions)))
195 (define (walk-paths done-lines best-paths current-lines last? current-best)
196 "Return the best optimal-page-break-node that contains
197 CURRENT-LINES. DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
198 ascending range of lines, and BEST-PATHS contains the optimal breaks
199 corresponding to DONE-LINES.
201 CURRENT-BEST is the best result sofar, or #f."
204 (let* ((this-page-num (if (null? best-paths)
205 (ly:output-def-lookup paper 'firstpagenumber)
206 (1+ (page-page-number (car best-paths)))))
208 (this-page (make-page
209 'paper-book paper-book
211 'page-number this-page-num))
213 (ragged-all? (eq? #t (ly:output-def-lookup paper 'raggedbottom)))
214 (ragged-last? (eq? #t (ly:output-def-lookup paper 'raggedlastbottom)))
215 (ragged? (or ragged-all?
218 (height (page-height this-page))
219 (vertical-spacing (space-systems height current-lines ragged?))
220 (satisfied-constraints (car vertical-spacing))
221 (force (if satisfied-constraints
222 (if (and last? ragged-last?)
224 satisfied-constraints)
226 (positions (cdr vertical-spacing))
227 (get-break-penalty (lambda (sys)
228 (ly:prob-property sys 'penalty 0.0)))
229 (user-nobreak-penalties
231 (apply + (filter negative?
232 (map get-break-penalty
233 (cdr current-lines))))))
236 (max (get-break-penalty (car current-lines)) 0.0)
237 user-nobreak-penalties))
239 (total-penalty (combine-penalties
245 (< total-penalty (page-penalty current-best))))
246 (new-best (if better?
250 (page-set-property! this-page
254 (cons 'prev (if (null? best-paths)
257 (cons 'lines current-lines)
259 (cons 'configuration positions)
260 (cons 'penalty total-penalty)))
264 ;; (display total-penalty) (newline)
268 "\nuser pen " user-penalty
269 "\nsatisfied-constraints" satisfied-constraints
270 "\nlast? " last? "ragged?" ragged?
271 "\nbetter? " better? " total-penalty " total-penalty "\n"
272 "\nconfig " positions
274 "\nlines: " current-lines "\n")))
277 (display (list "\nnew-best is " (page-lines new-best)
279 (if (null? best-paths)
281 (page-lines (car best-paths))))))
283 (if (and (pair? done-lines)
284 ;; if this page is too full, adding another line won't help
285 satisfied-constraints)
286 (walk-paths (cdr done-lines) (cdr best-paths)
287 (cons (car done-lines) current-lines)
291 (define (walk-lines done best-paths todo)
292 "Return the best page breaking as a single
293 page node for optimally breaking TODO ++
294 DONE.reversed. BEST-PATHS is a list of break nodes corresponding to
299 (let* ((this-line (car todo))
300 (last? (null? (cdr todo)))
301 (next (walk-paths done best-paths (list this-line) last? #f)))
303 ;; (display "\n***************")
304 (walk-lines (cons this-line done)
305 (cons next best-paths)
308 (define (line-number node)
309 (ly:prob-property (car (page-lines node)) 'number))
311 (ly:message (_ "Calculating page breaks..."))
312 (set! force-equalization-factor
313 (ly:output-def-lookup paper 'verticalequalizationfactor 0.3))
315 (let* ((best-break-node (walk-lines '() '() lines))
316 (break-nodes (get-path best-break-node '()))
320 (set! (page-property (car (last-pair break-nodes)) 'is-last) #t)
322 (if #f; (ly:get-option 'verbose)
325 "\nbreaks: " (map line-number break-nodes))
326 "\nsystems " (map page-lines break-nodes)
327 "\npenalties " (map page-penalty break-nodes)
328 "\nconfigs " (map page-configuration break-nodes))))
330 (let ((stencils (map page-stencil break-nodes)))