1 ;;; page-layout.scm -- page breaking and page layout
3 ;;;; source file of the GNU LilyPond music typesetter
5 ;;;; (c) 2004 Jan Nieuwenhuizen <janneke@gnu.org>
6 ;;;; Han-Wen Nienhuys <hanwen@cs.uu.nl>
8 (use-modules (oop goops describe)
12 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14 (define-class <optimally-broken-page-node> ()
15 (prev #:init-value '() #:accessor node-prev #:init-keyword #:prev)
16 (page #:init-value 0 #:accessor node-page-number #:init-keyword #:pageno)
17 (force #:init-value 0 #:accessor node-force #:init-keyword #:force)
18 (penalty #:init-value 0 #:accessor node-penalty #:init-keyword #:penalty)
19 (configuration #:init-value '() #:accessor node-configuration #:init-keyword #:configuration)
20 (lines #:init-value 0 #:accessor node-lines #:init-keyword #:lines))
22 (define-method (display (node <optimally-broken-page-node>) port)
23 (map (lambda (x) (display x port))
25 "Page " (node-page-number node)
26 " Lines: " (node-lines node)
27 " Penalty " (node-penalty node)
30 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
33 (string-append "Engraved by LilyPond (version " (lilypond-version) ")"))
35 (define (page-headfoot layout scopes number sym sepsym dir last?)
36 "Create a stencil including separating space."
38 ((header-proc (ly:output-def-lookup layout sym))
39 (sep (ly:output-def-lookup layout sepsym))
40 (stencil (ly:make-stencil "" '(0 . 0) '(0 . 0)))
42 (if (procedure? header-proc)
43 (header-proc layout scopes number last?)
46 (if (and (number? sep)
47 (ly:stencil? head-stencil)
48 (not (ly:stencil-empty? head-stencil)))
50 (ly:stencil-combine-at-edge
51 stencil Y dir head-stencil
56 (define-public (default-page-music-height layout scopes number last?)
57 "Printable area for music and titles; matches default-page-make-stencil."
59 ((h (- (ly:output-def-lookup layout 'vsize)
60 (ly:output-def-lookup layout 'topmargin)
61 (ly:output-def-lookup layout 'bottommargin)))
62 (head (page-headfoot layout scopes number 'make-header 'headsep UP last?))
63 (foot (page-headfoot layout scopes number 'make-footer 'footsep DOWN last?))
65 (- h (if (ly:stencil? head)
66 (interval-length (ly:stencil-extent head Y))
68 (if (ly:stencil? foot)
69 (interval-length (ly:stencil-extent foot Y))
72 ; (display (list "\n available" available head foot))
75 (define-public (default-page-make-stencil
76 lines offsets layout scopes number last? )
77 "Construct a stencil representing the page from LINES. "
79 ((topmargin (ly:output-def-lookup layout 'topmargin))
81 ;; TODO: naming vsize/hsize not analogous to TeX.
83 (vsize (ly:output-def-lookup layout 'vsize))
84 (hsize (ly:output-def-lookup layout 'hsize))
86 (lmargin (ly:output-def-lookup layout 'leftmargin))
87 (leftmargin (if lmargin
90 (ly:output-def-lookup layout 'linewidth)) 2)))
92 (rightmargin (ly:output-def-lookup layout 'rightmargin))
94 (ly:output-def-lookup layout 'bottommargin)))
96 (head (page-headfoot layout scopes number 'make-header 'headsep UP last?))
97 (foot (page-headfoot layout scopes number 'make-footer 'footsep DOWN last?))
99 (head-height (if (ly:stencil? head)
100 (interval-length (ly:stencil-extent head Y))
103 (line-stencils (map ly:paper-system-stencil lines))
104 (height-proc (ly:output-def-lookup layout 'page-music-height))
106 (page-stencil (ly:make-stencil '()
107 (cons leftmargin hsize)
108 (cons (- topmargin) 0)))
110 (add-system (lambda (stencil-position)
113 (ly:stencil-translate-axis
114 (car stencil-position)
117 (cadr stencil-position)
125 "leftmargin" leftmargin "rightmargin" rightmargin
128 (set! page-stencil (ly:stencil-combine-at-edge
129 page-stencil Y DOWN head 0. 0.))
131 (map add-system (zip line-stencils offsets))
132 (if (ly:stencil? foot)
136 (ly:stencil-translate
140 (- (car (ly:stencil-extent foot Y)))))
143 (ly:stencil-translate page-stencil (cons leftmargin 0))
149 ;;; optimal page breaking
151 ;;; This is not optimal page breaking, this is optimal distribution of
152 ;;; lines over pages; line breaks are a given.
157 ; - separate function for word-wrap style breaking?
158 ; - raggedbottom? raggedlastbottom?
160 (define-public (ly:optimal-page-breaks
162 "Return pages as a list starting with 1st page. Each page is a list
166 (define MAXPENALTY 1e9)
167 (define paper (ly:paper-book-paper paper-book))
168 (define scopes (ly:paper-book-scopes paper-book))
170 (define (page-height page-number last?)
172 ((p (ly:output-def-lookup paper 'page-music-height)))
175 (p paper scopes page-number last?)
178 (define (get-path node done)
179 "Follow NODE.PREV, and return as an ascending list of pages. DONE
180 is what have collected so far, and has ascending page numbers."
182 (if (is-a? node <optimally-broken-page-node>)
183 (get-path (node-prev node) (cons node done))
186 (define (combine-penalties force user best-paths)
188 ((prev-force (if (null? best-paths)
190 (node-force (car best-paths))))
191 (prev-penalty (if (null? best-paths)
193 (node-penalty (car best-paths))))
194 (inter-system-space (ly:output-def-lookup paper 'betweensystemspace))
195 (force-equalization-factor 0.3)
196 (relative-force (/ force inter-system-space))
197 (abs-relative-force (abs relative-force))
201 (+ (* abs-relative-force (+ abs-relative-force 1))
203 (* force-equalization-factor (/ (abs (- prev-force force)) inter-system-space))
206 (define (space-systems page-height lines ragged?)
209 (ly:output-def-lookup paper 'betweensystemspace))
210 (system-vector (list->vector
212 (if (= (length lines) 1)
220 ly:paper-system-staff-extents
222 (if (= (length lines) 1)
230 (lambda (sys) (ly:paper-system-extent sys Y)) lines)
231 (if (= (length lines) 1)
235 (no-systems (vector-length real-extents))
236 (topskip (interval-end (vector-ref real-extents 0)))
237 (space-left (- page-height
238 (apply + (map interval-length (vector->list real-extents)))
242 (space (- page-height
244 (- (interval-start (vector-ref real-extents (1- no-systems))))
247 (fixed-dist (ly:output-def-lookup paper 'betweensystempadding))
251 ((this-system-ext (vector-ref staff-extents idx))
252 (next-system-ext (vector-ref staff-extents (1+ idx)))
253 (fixed (max 0 (- (+ (interval-end next-system-ext)
255 (interval-start this-system-ext))))
256 (title1? (and (vector-ref system-vector idx)
257 (ly:paper-system-title? (vector-ref system-vector idx))))
259 (vector-ref system-vector (1+ idx))
260 (ly:paper-system-title? (vector-ref system-vector (1+ idx)))))
263 ((and title2? title1?)
264 (ly:output-def-lookup paper 'betweentitlespace))
266 (ly:output-def-lookup paper 'aftertitlespace))
268 (ly:output-def-lookup paper 'beforetitlespace))
269 (else inter-system-space))
271 (hooke (/ 1 (- ideal fixed)))
276 (springs (map calc-spring (iota (1- no-systems))))
280 ((this-system-ext (vector-ref real-extents idx))
281 (next-system-ext (vector-ref real-extents (1+ idx)))
282 (distance (max (- (+ (interval-end next-system-ext)
284 (interval-start this-system-ext)
286 (entry (list idx (1+ idx) distance)))
288 (rods (map calc-rod (iota (1- no-systems))))
290 ;; we don't set ragged based on amount space left.
291 ;; raggedbottomlast = ##T is much more predictable
292 (result (ly:solve-spring-rod-problem
305 (display (list "\n# systems: " no-systems
306 "\nreal-ext" real-extents "\nstaff-ext" staff-extents
307 "\ninterscore" inter-system-space
308 "\nspace-letf" space-left
309 "\nspring,rod" springs rods
312 "\npage-height" page-height
316 "\npositions" positions "\n"))))
318 (cons force positions)))
320 (define (walk-paths done-lines best-paths current-lines last? current-best)
321 "Return the best optimal-page-break-node that contains
322 CURRENT-LINES. DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
323 ascending range of lines, and BEST-PATHS contains the optimal breaks
324 corresponding to DONE-LINES.
326 CURRENT-BEST is the best result sofar, or #f."
328 (let* ((this-page-num (if (null? best-paths)
329 (ly:output-def-lookup paper 'firstpagenumber)
330 (1+ (node-page-number (car best-paths)))))
332 (ragged-all? (eq? #t (ly:output-def-lookup paper 'raggedbottom)))
333 (ragged-last? (eq? #t (ly:output-def-lookup paper 'raggedlastbottom)))
334 (ragged? (or ragged-all?
337 (page-height (page-height this-page-num last?))
338 (vertical-spacing (space-systems page-height current-lines ragged?))
339 (satisfied-constraints (car vertical-spacing))
340 (force (if satisfied-constraints
341 (if (and last? ragged-last?)
343 satisfied-constraints)
345 (positions (cdr vertical-spacing))
346 (user-nobreak-penalties
348 (apply + (filter negative?
349 (map ly:paper-system-break-before-penalty
350 (cdr current-lines))))))
353 (max (ly:paper-system-break-before-penalty (car current-lines)) 0.0)
354 user-nobreak-penalties))
355 (total-penalty (combine-penalties
362 (< total-penalty (node-penalty current-best))))
363 (new-best (if better?
364 (make <optimally-broken-page-node>
365 #:prev (if (null? best-paths)
368 #:lines current-lines
369 #:pageno this-page-num
371 #:configuration positions
372 #:penalty total-penalty)
378 "\nuser pen " user-penalty
379 "\nsatisfied-constraints" satisfied-constraints
380 "\nlast? " last? "ragged?" ragged?
381 "\nbetter? " better? " total-penalty " total-penalty "\n"
382 "\nconfig " positions
384 "\nlines: " current-lines "\n")))
387 (display (list "\nnew-best is " (node-lines new-best)
389 (if (null? best-paths)
391 (node-lines (car best-paths))))))
393 (if (and (pair? done-lines)
394 ;; if this page is too full, adding another line won't help
395 satisfied-constraints)
396 (walk-paths (cdr done-lines) (cdr best-paths)
397 (cons (car done-lines) current-lines)
402 (define (walk-lines done best-paths todo)
403 "Return the best page breaking as a single
404 <optimal-page-break-node> for optimally breaking TODO ++
405 DONE.reversed. BEST-PATHS is a list of break nodes corresponding to
409 (let* ((this-line (car todo))
410 (last? (null? (cdr todo)))
411 (next (walk-paths done best-paths (list this-line) last? #f)))
413 ; (display "\n***************")
414 (walk-lines (cons this-line done)
415 (cons next best-paths)
418 (define (line-number node)
419 (ly:paper-system-number (car (node-lines node))))
421 (let* ((best-break-node (walk-lines '() '() lines))
422 (break-nodes (get-path best-break-node '())))
424 (if #f; (ly:get-option 'verbose)
427 "\nbreaks: " (map line-number break-nodes))
428 "\nsystems " (map node-lines break-nodes)
429 "\npenalties " (map node-penalty break-nodes)
430 "\nconfigs " (map node-configuration break-nodes))))
436 ((ly:output-def-lookup paper 'page-make-stencil)
438 (node-configuration node)
441 (node-page-number node)
442 (eq? node best-break-node)))