]> git.donarmstrong.com Git - lilypond.git/blob - scm/layout-page-layout.scm
* scm/layout-page-layout.scm: Define module, tidy code, use more
[lilypond.git] / scm / layout-page-layout.scm
1 ;;;; layout-page-layout.scm -- page breaking and page layout
2 ;;;;
3 ;;;;  source file of the GNU LilyPond music typesetter
4 ;;;;
5 ;;;; (c) 2004--2006 Jan Nieuwenhuizen <janneke@gnu.org>
6 ;;;;          Han-Wen Nienhuys <hanwen@cs.uu.nl>
7
8 (define-module (scm layout-page-layout)
9   #:use-module (srfi srfi-1)
10   #:use-module (oop goops describe)
11   #:use-module (oop goops)
12   #:use-module (scm paper-system)
13   #:use-module (scm page)
14   #:use-module (scm layout-page-dump)
15   #:use-module (lily)
16   #:export (post-process-pages optimal-page-breaks))
17
18 (define (post-process-pages layout pages)
19   (if (ly:output-def-lookup layout 'write-page-layout #f)
20       (write-page-breaks pages)))
21
22 ;;;
23 ;;; Utilities for computing line distances and positions
24 ;;;
25 (define (line-next-space line next-line layout)
26   "Return space to use between `line' and `next-line'.
27   `next-line' can be #f, meaning that `line' is the last line."
28   (let* ((title? (paper-system-title? line))
29          (next-title? (and next-line (paper-system-title? next-line))))
30     (cond ((and title? next-title?)
31            (ly:output-def-lookup layout 'between-title-space))
32           (title?
33            (ly:output-def-lookup layout 'after-title-space))
34           (next-title?
35            (ly:output-def-lookup layout 'before-title-space))
36           (else
37            (ly:prob-property
38             line 'next-space
39             (ly:output-def-lookup layout 'between-system-space))))))
40
41 (define (line-next-padding line next-line layout)
42   "Return padding to use between `line' and `next-line'.
43   `next-line' can be #f, meaning that `line' is the last line."
44   (ly:prob-property
45    line 'next-padding
46    (ly:output-def-lookup layout 'between-system-padding)))
47
48
49 (define (line-minimum-distance line next-line layout)
50   "Minimum distance between `line' reference position and `next-line'
51  reference position. If next-line is #f, return #f."
52   (and next-line
53        (max 0 (- (+ (interval-end (paper-system-extent next-line Y))
54                     (line-next-padding line next-line layout))
55                  (interval-start (paper-system-extent line Y))))))
56
57 (define (line-ideal-distance line next-line layout)
58   "Ideal distance between `line' reference position and `next-line'
59  reference position. If next-line is #f, return #f."
60   (and next-line
61        (+ (max 0 (- (+ (interval-end (paper-system-staff-extents next-line))
62                        (line-next-padding line next-line layout))
63                     (interval-start (paper-system-staff-extents line))))
64           (line-next-space line next-line layout))))
65
66 (define (first-line-position line layout)
67   "Position of the first line on page"
68   (max (+ (ly:output-def-lookup layout 'page-top-space)
69           (interval-end (paper-system-staff-extents line)))
70        (interval-end (paper-system-extent line Y))))
71
72 (define (line-ideal-relative-position line prev-line layout)
73   "Return ideal position of `line', relative to `prev-line' position.
74   `prev-line' can be #f, meaning that `line' is the first line."
75   (if (not prev-line)
76       ;; first line on page
77       (first-line-position line layout)
78       ;; not the first line on page
79       (max (line-minimum-distance prev-line line layout)
80            (line-ideal-distance prev-line line layout))))
81
82 (define (line-minimum-relative-position line prev-line layout)
83   "Return position of `line', relative to `prev-line' position.
84   `prev-line' can be #f, meaning that `line' is the first line."
85   (if (not prev-line)
86       ;; first line on page
87       (first-line-position line layout)
88       ;; not the first line on page
89       (line-minimum-distance prev-line line layout)))
90
91 ;;;
92 ;;; Page breaking functions
93 ;;;
94
95 ;; Optimal distribution of
96 ;; lines over pages; line breaks are a given.
97
98 ;; TODO:
99 ;;
100 ;; - density scoring
101 ;; - separate function for word-wrap style breaking?
102 ;; - ragged-bottom? ragged-last-bottom?
103
104 (define (get-path node done)
105   "Follow NODE.PREV, and return as an ascending list of pages. DONE
106 is what have collected so far, and has ascending page numbers."
107   (if (page? node)
108       (get-path (page-prev node) (cons node done))
109       done))
110
111 (define (combine-penalties force user best-paths
112                            inter-system-space force-equalization-factor)
113   (let* ((prev-force (if (null? best-paths)
114                          0.0
115                          (page-force (car best-paths))))
116          (prev-penalty (if (null? best-paths)
117                            0.0
118                            (page-penalty (car best-paths))))
119          (relative-force (/ force inter-system-space))
120          (abs-relative-force (abs relative-force)))
121     (+ (* abs-relative-force (+ abs-relative-force 1))
122        prev-penalty
123        (* force-equalization-factor (/ (abs (- prev-force force))
124                                        inter-system-space))
125        user)))
126
127 (define (space-systems page-height lines ragged? paper)
128   "Compute lines positions on page: return force and line positions as a pair.
129  force is #f if lines do not fit on page."
130   (let* ((springs (map (lambda (prev-line line)
131                          (list (line-ideal-distance prev-line line paper)
132                                (/ 1.0 (line-next-space prev-line line paper))))
133                        lines
134                        (cdr lines)))
135          (rods (map (let ((i -1))
136                       (lambda (prev-line line)
137                         (set! i (1+ i))
138                         (list i (1+ i)
139                               (line-minimum-distance prev-line line paper))))
140                        lines
141                        (cdr lines)))
142          (last-line (car (last-pair lines)))
143          (topskip (first-line-position (first lines) paper))
144          (space-to-fill (- page-height
145                            topskip
146                            (ly:prob-property last-line
147                                              'bottom-space 0.0)
148                            (- (interval-start (paper-system-extent
149                                                last-line Y)))))
150          (space-result
151           (ly:solve-spring-rod-problem springs rods space-to-fill ragged?)))
152     (cons (car space-result)
153           (map (lambda (y)
154                  (+ y topskip))
155                (cdr space-result)))))
156
157 (define (walk-paths done-lines best-paths current-lines last? current-best
158                     paper-book page-alist)
159   "Return the best optimal-page-break-node that contains
160 CURRENT-LINES.  DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
161 ascending range of lines, and BEST-PATHS contains the optimal breaks
162 corresponding to DONE-LINES.
163
164 CURRENT-BEST is the best result sofar, or #f."
165   (let* ((paper (ly:paper-book-paper paper-book))
166          (this-page (make-page
167                      page-alist
168                      'paper-book paper-book
169                      'is-last last?
170                      'page-number (if (null? best-paths)
171                                       (ly:output-def-lookup paper 'first-page-number)
172                                       (1+ (page-page-number (first best-paths))))))
173          (ragged-all? (eq? #t (ly:output-def-lookup paper 'ragged-bottom)))
174          (ragged-last? (eq? #t (ly:output-def-lookup paper 'ragged-last-bottom)))
175          (ragged? (or ragged-all? (and ragged-last? last?)))
176          (height (page-printable-height this-page))
177          (vertical-spacing (space-systems height current-lines ragged? paper))
178          (satisfied-constraints (car vertical-spacing))
179          (force (if satisfied-constraints
180                     (if (and last? ragged-last?)
181                         0.0
182                         satisfied-constraints)
183                     10000))
184          (positions (cdr vertical-spacing))
185          (get-break-penalty (lambda (sys)
186                               (ly:prob-property sys 'penalty 0.0)))
187          (user-nobreak-penalties (- (apply + (filter negative?
188                                                      (map get-break-penalty
189                                                           (cdr current-lines))))))
190          (user-penalty (+ (max (get-break-penalty (car current-lines)) 0.0)
191                           user-nobreak-penalties))
192          (total-penalty (combine-penalties
193                          force user-penalty best-paths
194                          (ly:output-def-lookup paper 'between-system-space)
195                          (ly:output-def-lookup paper 'verticalequalizationfactor 0.3)))
196          (new-best (if (or (not current-best)
197                            (and satisfied-constraints
198                                 (< total-penalty (page-penalty current-best))))
199                        (begin
200                          (map (lambda (x)
201                                 (page-set-property! this-page
202                                                     (car x)
203                                                     (cdr x)))
204                               (list (cons 'prev (if (null? best-paths)
205                                                     #f
206                                                     (car best-paths)))
207                                     (cons 'lines current-lines)
208                                     (cons 'force force)
209                                     (cons 'configuration positions)
210                                     (cons 'penalty total-penalty)))
211                          this-page)
212                        current-best)))
213     (if #f ;; debug
214         (display
215          (list
216           "\nuser pen " user-penalty
217           "\nsatisfied-constraints" satisfied-constraints
218           "\nlast? " last? "ragged?" ragged?
219           "\nis-better " is-better " total-penalty " total-penalty "\n"
220           "\nconfig " positions
221           "\nforce " force
222           "\nlines: " current-lines "\n")))
223     (if #f ; debug
224         (display (list "\nnew-best is " (page-lines new-best)
225                        "\ncontinuation of "
226                        (if (null? best-paths)
227                            "start"
228                            (page-lines (car best-paths))))))
229     (if (and (pair? done-lines)
230              ;; if this page is too full, adding another line won't help
231              satisfied-constraints)
232         (walk-paths (cdr done-lines) (cdr best-paths)
233                     (cons (car done-lines) current-lines)
234                     last? new-best
235                     paper-book page-alist)
236         new-best)))
237
238 (define (walk-lines done best-paths todo paper-book page-alist)
239   "Return the best page breaking as a single
240 page node for optimally breaking TODO ++
241 DONE.reversed. BEST-PATHS is a list of break nodes corresponding to
242 DONE."
243   (if (null? todo)
244       (car best-paths)
245       (let* ((this-line (car todo))
246              (last? (null? (cdr todo)))
247              (next (walk-paths done best-paths (list this-line) last? #f
248                                paper-book page-alist)))
249         (walk-lines (cons this-line done)
250                     (cons next best-paths)
251                     (cdr todo)
252                     paper-book
253                     page-alist))))
254
255 (define-public (optimal-page-breaks lines paper-book)
256   "Return pages as a list starting with 1st page. Each page is a 'page Prob."
257   (let* ((paper (ly:paper-book-paper paper-book))
258          (page-alist (layout->page-init paper)) 
259          (force-equalization-factor (ly:output-def-lookup
260                                      paper 'verticalequalizationfactor 0.3)))
261     (ly:message (_ "Calculating page breaks..."))
262     (let* ((best-break-node (walk-lines '() '() lines paper-book page-alist))
263            (break-nodes (get-path best-break-node '())))
264       (page-set-property! (car (last-pair break-nodes)) 'is-last #t)
265       (if #f; (ly:get-option 'verbose)
266           (begin
267             (display (list
268                       "\nbreaks: " (map (lambda (node)
269                                           (ly:prob-property (car (page-lines node))
270                                                             'number))
271                                         break-nodes)
272                       "\nsystems " (map page-lines break-nodes)
273                       "\npenalties " (map page-penalty break-nodes)
274                       "\nconfigs " (map page-configuration break-nodes)))))
275       ;; construct page stencils.
276       (for-each page-stencil break-nodes)
277       (post-process-pages paper break-nodes)
278       break-nodes)))