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)
14 (define (write-page-breaks pages)
20 (define (record when property-pairs)
22 (acons when property-pairs
25 (define (moment->skip mom)
27 (ly:moment-main-numerator mom)
28 (ly:moment-main-denominator mom)))
30 (define (dump-tweaks out-port tweak-list last-moment)
31 (if (not (null? tweak-list))
33 ((now (caar tweak-list))
34 (diff (ly:moment-sub now last-moment))
35 (these-tweaks (cdar tweak-list))
36 (skip (moment->skip diff))
38 (base (format "\\overrideProperty
39 #\"Score.NonMusicalPaperColumn\"
40 #'line-break-system-details
44 (format out-port "\\skip ~a\n~a\n" skip base)
45 (dump-tweaks out-port (cdr tweak-list) now)
50 (define (handle-page page)
51 (define (handle-system sys)
53 ((props '((line-break . #t))))
55 (if (equal? (car (page-lines page)) sys)
56 (set! props (cons '(page-break . #t)
59 (if (not (ly:prob-property? sys 'is-title))
60 (record (ly:grob-property (ly:spanner-bound (ly:prob-property sys 'system-grob) LEFT) 'when)
63 (for-each handle-system (page-lines page)))
66 (for-each handle-page pages)
69 ((out-port (open-output-file "breaks.ly")))
71 (display "{" out-port)
72 (dump-tweaks out-port (reverse tweaks) (ly:make-moment 0 1))
73 (display "}" out-port)
78 (define (post-process-pages layout pages)
79 (if (ly:get-option 'write-page-layout)
80 (write-page-breaks pages)))
84 ;;; optimal page breaking
86 ;;; This is not optimal page breaking, this is optimal distribution of
87 ;;; lines over pages; line breaks are a given.
92 ;; - separate function for word-wrap style breaking?
93 ;; - raggedbottom? raggedlastbottom?
95 (define-public (optimal-page-breaks lines paper-book)
96 "Return pages as a list starting with 1st page. Each page is a 'page Prob."
98 (define MAXPENALTY 1e9)
99 (define paper (ly:paper-book-paper paper-book))
102 (define page-alist (layout->page-init (ly:paper-book-paper paper-book)))
103 (define scopes (ly:paper-book-scopes paper-book))
104 (define force-equalization-factor #f)
105 (define (get-path node done)
106 "Follow NODE.PREV, and return as an ascending list of pages. DONE
107 is what have collected so far, and has ascending page numbers."
110 (get-path (page-prev node) (cons node done))
113 (define (combine-penalties force user best-paths)
114 (let* ((prev-force (if (null? best-paths)
116 (page-force (car best-paths))))
117 (prev-penalty (if (null? best-paths)
119 (page-penalty (car best-paths))))
120 (inter-system-space (ly:output-def-lookup paper 'betweensystemspace))
121 (relative-force (/ force inter-system-space))
122 (abs-relative-force (abs relative-force)))
124 (+ (* abs-relative-force (+ abs-relative-force 1))
126 (* force-equalization-factor (/ (abs (- prev-force force))
130 (define (space-systems page-height lines ragged?)
131 (let* ((global-inter-system-space
132 (ly:output-def-lookup paper 'betweensystemspace))
134 (ly:output-def-lookup paper 'pagetopspace))
135 (global-fixed-dist (ly:output-def-lookup paper 'betweensystempadding))
137 (system-vector (list->vector
139 (if (= (length lines) 1)
144 (append (map paper-system-staff-extents lines)
145 (if (= (length lines) 1)
153 (lambda (sys) (paper-system-extent sys Y)) lines)
154 (if (= (length lines) 1)
158 (system-count (vector-length real-extents))
162 (interval-end (vector-ref staff-extents 0)))
163 (interval-end (vector-ref real-extents 0))
165 (last-system (vector-ref system-vector (1- system-count)))
166 (bottom-space (if (ly:prob? last-system)
167 (ly:prob-property last-system 'bottom-space 0.0)
169 (space-left (- page-height
171 (apply + (map interval-length
172 (vector->list real-extents)))))
174 (space (- page-height
178 (vector-ref real-extents (1- system-count))))))
183 (upper-system (vector-ref system-vector idx))
184 (between-space (ly:prob-property upper-system 'next-space
185 global-inter-system-space))
186 (fixed-dist (ly:prob-property upper-system 'next-padding
189 (this-system-ext (vector-ref staff-extents idx))
190 (next-system-ext (vector-ref staff-extents (1+ idx)))
191 (fixed (max 0 (- (+ (interval-end next-system-ext)
193 (interval-start this-system-ext))))
194 (title1? (and (vector-ref system-vector idx)
195 (paper-system-title? (vector-ref system-vector idx)
198 (vector-ref system-vector (1+ idx))
199 (paper-system-title? (vector-ref system-vector (1+ idx)))))
202 ((and title2? title1?)
203 (ly:output-def-lookup paper 'betweentitlespace))
205 (ly:output-def-lookup paper 'aftertitlespace))
207 (ly:output-def-lookup paper 'beforetitlespace))
208 (else between-space))
210 (hooke (/ 1 (- ideal fixed))))
211 (list ideal hooke))))
213 (springs (map calc-spring (iota (1- system-count))))
217 (upper-system (vector-ref system-vector idx))
218 (fixed-dist (ly:prob-property upper-system 'next-padding
220 (this-system-ext (vector-ref real-extents idx))
221 (next-system-ext (vector-ref real-extents (1+ idx)))
223 (distance (max (- (+ (interval-end next-system-ext)
225 (interval-start this-system-ext)
227 (entry (list idx (1+ idx) distance)))
229 (rods (map calc-rod (iota (1- system-count))))
231 ;; we don't set ragged based on amount space left.
232 ;; raggedbottomlast = ##T is much more predictable
233 (result (ly:solve-spring-rod-problem
245 (display (list "\n# systems: " system-count
246 "\nreal-ext" real-extents "\nstaff-ext" staff-extents
247 "\ninterscore" global-inter-system-space
248 "\nspace-left" space-left
249 "\nspring,rod" springs rods
252 "\npage-height" page-height
256 "\npositions" positions "\n"))))
258 (cons force positions)))
260 (define (walk-paths done-lines best-paths current-lines last? current-best)
261 "Return the best optimal-page-break-node that contains
262 CURRENT-LINES. DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
263 ascending range of lines, and BEST-PATHS contains the optimal breaks
264 corresponding to DONE-LINES.
266 CURRENT-BEST is the best result sofar, or #f."
269 (let* ((this-page-num (if (null? best-paths)
270 (ly:output-def-lookup paper 'firstpagenumber)
271 (1+ (page-page-number (car best-paths)))))
273 (this-page (make-page
275 'paper-book paper-book
277 'page-number this-page-num))
279 (ragged-all? (eq? #t (ly:output-def-lookup paper 'raggedbottom)))
280 (ragged-last? (eq? #t (ly:output-def-lookup paper 'raggedlastbottom)))
281 (ragged? (or ragged-all?
284 (height (page-printable-height this-page))
285 (vertical-spacing (space-systems height current-lines ragged?))
286 (satisfied-constraints (car vertical-spacing))
287 (force (if satisfied-constraints
288 (if (and last? ragged-last?)
290 satisfied-constraints)
292 (positions (cdr vertical-spacing))
293 (get-break-penalty (lambda (sys)
294 (ly:prob-property sys 'penalty 0.0)))
295 (user-nobreak-penalties
297 (apply + (filter negative?
298 (map get-break-penalty
299 (cdr current-lines))))))
302 (max (get-break-penalty (car current-lines)) 0.0)
303 user-nobreak-penalties))
305 (total-penalty (combine-penalties
311 (< total-penalty (page-penalty current-best))))
312 (new-best (if better?
316 (page-set-property! this-page
320 (cons 'prev (if (null? best-paths)
323 (cons 'lines current-lines)
325 (cons 'configuration positions)
326 (cons 'penalty total-penalty)))
330 ;; (display total-penalty) (newline)
334 "\nuser pen " user-penalty
335 "\nsatisfied-constraints" satisfied-constraints
336 "\nlast? " last? "ragged?" ragged?
337 "\nbetter? " better? " total-penalty " total-penalty "\n"
338 "\nconfig " positions
340 "\nlines: " current-lines "\n")))
343 (display (list "\nnew-best is " (page-lines new-best)
345 (if (null? best-paths)
347 (page-lines (car best-paths))))))
349 (if (and (pair? done-lines)
350 ;; if this page is too full, adding another line won't help
351 satisfied-constraints)
352 (walk-paths (cdr done-lines) (cdr best-paths)
353 (cons (car done-lines) current-lines)
357 (define (walk-lines done best-paths todo)
358 "Return the best page breaking as a single
359 page node for optimally breaking TODO ++
360 DONE.reversed. BEST-PATHS is a list of break nodes corresponding to
365 (let* ((this-line (car todo))
366 (last? (null? (cdr todo)))
367 (next (walk-paths done best-paths (list this-line) last? #f)))
369 ;; (display "\n***************")
370 (walk-lines (cons this-line done)
371 (cons next best-paths)
374 (define (line-number node)
375 (ly:prob-property (car (page-lines node)) 'number))
377 (ly:message (_ "Calculating page breaks..."))
378 (set! force-equalization-factor
379 (ly:output-def-lookup paper 'verticalequalizationfactor 0.3))
381 (let* ((best-break-node (walk-lines '() '() lines))
382 (break-nodes (get-path best-break-node '()))
385 (page-set-property! (car (last-pair break-nodes)) 'is-last #t)
386 (if #f; (ly:get-option 'verbose)
389 "\nbreaks: " (map line-number break-nodes))
390 "\nsystems " (map page-lines break-nodes)
391 "\npenalties " (map page-penalty break-nodes)
392 "\nconfigs " (map page-configuration break-nodes))))
394 ;; construct page stencils.
395 (for-each page-stencil break-nodes)
396 (post-process-pages paper break-nodes)