2 page-breaking.cc -- implement a superclass and utility
3 functions shared by various page-breaking algorithms
5 source file of the GNU LilyPond music typesetter
7 (c) 2006--2009 Joe Neeman <joeneeman@gmail.com>
11 This is a utility class for page-breaking algorithms. There are some complex
12 parts of this class, some of which are useful to understand if you intend
13 to write a page breaking algorithm (ie. a subclass of Page_breaking). Most
14 of these complexities were introduced in order to break the problem of
15 page-breaking into simpler subproblems and to hide some of the bookkeeping
16 complexities of page breaking from the page breaking algorithms.
19 There are several functions that actually distribute systems across pages
20 (for example, the space_systems_XXX and pack_systems_XXX functions). If
21 each of these functions had to handle \noPageBreak, it would be a mess.
22 Therefore, we handle \noPageBreak by "compressing" the list of systems
23 before doing any layout: we concatenate any two systems separated by a
24 \noPageBreak into a single system. The page-breaking functions can do their
25 magic without encountering a \noPageBreak; we then "uncompress" the systems
26 at the end. We almost always work with cached_line_details_, which are
30 The basic operation of a page breaking algorithm is to repeatedly request
31 some systems from the line-breaker and place those systems on some pages.
32 With each repetition, the page breaking algorithm asks the line-breaker for
33 some systems that it thinks will help it achieve a better layout. The
34 Page_breaking class provides functionality to facilitate this in the case
35 that the page breaking algorithm only cares about the number of systems.
37 Even if a page breaking algorithm only cares number of systems, there may
38 be many ways to satisfy its request. For example, in a piece with 2 scores
39 and a request for 10 systems, we could return 5 systems from each score or
40 4 from the first and 6 from the second. Even within a score, we might
41 want to try several different line breaking configurations with a fixed
42 system count; if there is a forced \pageBreak, for example, we might wish
43 to tweak the number of systems on both sides of the \pageBreak independently.
45 The Page_breaking class takes care of finding these configurations. It
46 divides the piece into "chunks" and sets up the line-breaker in such a way
47 that the number of systems in each chunk can be modified independently.
48 Chunks never cross score boundaries; each title and markup is its own chunk.
49 When a page breaking algorithm requests a number of systems, the Page_breaker
50 stores an array of potential configurations, which the page breaking
51 algorithm can iterate over using current_configuration(vsize).
54 A Line_division is simply a way of storing the exact way in which the
55 total number of systems is distributed among chunks. Note that a
56 Line_division may not (in fact, usually will not) describe all of the chunks
57 in the entire book. Rather, it will describe the subset of chunks that lie
58 between some fixed starting and ending point. This subset of chunks changes
59 whenever a page breaking algorithm asks to consider a different pair of
60 starting and ending breakpoints. In particular, a Line_division should be
61 discarded after a call to set_current_breakpoints, since that Line_division
62 refers to a subset of chunks which might be different from the current
63 subset of chunks under consideration.
66 #include "page-breaking.hh"
68 #include "international.hh"
70 #include "output-def.hh"
71 #include "page-layout-problem.hh"
72 #include "page-spacing.hh"
73 #include "paper-book.hh"
74 #include "paper-score.hh"
75 #include "paper-system.hh"
79 /* for each forbidden page break, merge the systems around it into one
81 static vector
<Line_details
>
82 compress_lines (const vector
<Line_details
> &orig
)
84 vector
<Line_details
> ret
;
86 for (vsize i
= 0; i
< orig
.size (); i
++)
88 if (ret
.size () && !scm_is_symbol (ret
.back ().page_permission_
))
90 Line_details
const &old
= ret
.back ();
91 Line_details compressed
= orig
[i
];
92 Real padding
= orig
[i
].title_
? old
.title_padding_
: old
.padding_
;
94 compressed
.extent_
[DOWN
] = old
.extent_
[DOWN
];
95 compressed
.extent_
[UP
] = old
.extent_
[UP
] + orig
[i
].extent_
.length () + padding
;
96 compressed
.space_
+= old
.space_
;
97 compressed
.inverse_hooke_
+= old
.inverse_hooke_
;
99 compressed
.compressed_lines_count_
= old
.compressed_lines_count_
+ 1;
100 compressed
.compressed_nontitle_lines_count_
=
101 old
.compressed_nontitle_lines_count_
+ (compressed
.title_
? 0 : 1);
103 // compressed.title_ is true if and only if the first of its
104 // compressed lines was a title.
105 compressed
.title_
= old
.title_
;
106 ret
.back () = compressed
;
110 ret
.push_back (orig
[i
]);
111 ret
.back ().force_
= 0;
117 /* translate the number of systems-per-page into something meaningful for
118 the uncompressed lines.
121 uncompress_solution (vector
<vsize
> const &systems_per_page
,
122 vector
<Line_details
> const &compressed
)
127 for (vsize i
= 0; i
< systems_per_page
.size (); i
++)
129 int compressed_count
= 0;
130 for (vsize j
= start_sys
; j
< start_sys
+ systems_per_page
[i
]; j
++)
131 compressed_count
+= compressed
[j
].compressed_lines_count_
- 1;
133 ret
.push_back (systems_per_page
[i
] + compressed_count
);
134 start_sys
+= systems_per_page
[i
];
139 /* for Page_breaking, the start index (when we are dealing with the stuff
140 between a pair of breakpoints) refers to the break_ index of the end of
141 the previous page. So the system index of the start of the current page
142 could either be the same as the end of the previous page or one more than
145 /* Turn a break index into the sys index that starts the next page */
147 Page_breaking::next_system (Break_position
const &break_pos
) const
149 vsize sys
= break_pos
.system_spec_index_
;
151 if (sys
== VPOS
) /* beginning of the book */
153 if (system_specs_
[sys
].pscore_
&& !break_pos
.score_ender_
)
154 return sys
; /* the score overflows the previous page */
155 return sys
+ 1; /* this page starts with a new System_spec */
158 Page_breaking::Page_breaking (Paper_book
*pb
, Break_predicate is_break
)
162 ragged_
= to_boolean (pb
->paper_
->c_variable ("ragged-bottom"));
163 ragged_last_
= to_boolean (pb
->paper_
->c_variable ("ragged-last-bottom"));
164 systems_per_page_
= max (0, robust_scm2int (pb
->paper_
->c_variable ("systems-per-page"), 0));
165 max_systems_per_page_
= max (0, robust_scm2int (pb
->paper_
->c_variable ("max-systems-per-page"), 0));
166 min_systems_per_page_
= max (0, robust_scm2int (pb
->paper_
->c_variable ("min-systems-per-page"), 0));
168 if (systems_per_page_
&& (max_systems_per_page_
|| min_systems_per_page_
))
170 warning (_f ("ignoring min-systems-per-page and max-systems-per-page because systems-per-page was set"));
171 min_systems_per_page_
= max_systems_per_page_
= 0;
173 if (max_systems_per_page_
&& min_systems_per_page_
> max_systems_per_page_
)
175 warning (_f ("min-systems-per-page is larger than max-systems-per-page, ignoring both values"));
176 min_systems_per_page_
= max_systems_per_page_
= 0;
179 create_system_list ();
180 find_chunks_and_breaks (is_break
);
183 Page_breaking::~Page_breaking ()
188 Page_breaking::ragged () const
194 Page_breaking::ragged_last () const
200 Page_breaking::systems_per_page () const
202 return systems_per_page_
;
206 Page_breaking::max_systems_per_page () const
208 return max_systems_per_page_
;
212 Page_breaking::min_systems_per_page () const
214 return min_systems_per_page_
;
218 Page_breaking::system_count () const
220 return system_count_
;
224 Page_breaking::too_many_lines (int line_count
) const
226 return max_systems_per_page () > 0 && line_count
> max_systems_per_page ();
230 Page_breaking::too_few_lines (int line_count
) const
232 return line_count
< min_systems_per_page ();
236 Page_breaking::line_count_penalty (int line_count
) const
238 if (too_many_lines (line_count
))
239 return (line_count
- max_systems_per_page ()) * TERRIBLE_SPACING_PENALTY
;
240 if (too_few_lines (line_count
))
241 return (min_systems_per_page () - line_count
) * TERRIBLE_SPACING_PENALTY
;
247 Page_breaking::line_count_status (int line_count
) const
249 if (too_many_lines (line_count
))
250 return SYSTEM_COUNT_TOO_MANY
;
251 if (too_few_lines (line_count
))
252 return SYSTEM_COUNT_TOO_FEW
;
254 return SYSTEM_COUNT_OK
;
257 /* translate indices into breaks_ into start-end parameters for the line breaker */
259 Page_breaking::line_breaker_args (vsize sys
,
260 Break_position
const &start
,
261 Break_position
const &end
,
262 vsize
*line_breaker_start
,
263 vsize
*line_breaker_end
)
265 assert (system_specs_
[sys
].pscore_
);
266 assert (next_system (start
) <= sys
&& sys
<= end
.system_spec_index_
);
268 if (start
.system_spec_index_
== sys
)
269 *line_breaker_start
= start
.score_break_
;
271 *line_breaker_start
= 0;
273 if (end
.system_spec_index_
== sys
)
274 *line_breaker_end
= end
.score_break_
;
276 *line_breaker_end
= VPOS
;
280 Page_breaking::break_into_pieces (vsize start_break
, vsize end_break
,
281 Line_division
const &div
)
283 vector
<Break_position
> chunks
= chunk_list (start_break
, end_break
);
284 bool ignore_div
= false;
285 if (chunks
.size () != div
.size () + 1)
287 programming_error ("did not find a valid page breaking configuration");
291 for (vsize i
= 0; i
+ 1 < chunks
.size (); i
++)
293 vsize sys
= next_system (chunks
[i
]);
294 if (system_specs_
[sys
].pscore_
)
298 line_breaker_args (sys
, chunks
[i
], chunks
[i
+1], &start
, &end
);
300 vector
<Column_x_positions
> pos
= ignore_div
301 ? line_breaking_
[sys
].best_solution (start
, end
)
302 : line_breaking_
[sys
].solve (start
, end
, div
[i
]);
303 system_specs_
[sys
].pscore_
->root_system ()->break_into_pieces (pos
);
309 Page_breaking::systems ()
312 for (vsize sys
= 0; sys
< system_specs_
.size (); sys
++)
314 if (system_specs_
[sys
].pscore_
)
316 system_specs_
[sys
].pscore_
->root_system ()
317 ->do_break_substitution_and_fixup_refpoints ();
318 SCM lines
= system_specs_
[sys
].pscore_
->root_system ()
319 ->get_broken_system_grobs ();
320 ret
= scm_cons (lines
, ret
);
324 Prob
*pb
= system_specs_
[sys
].prob_
;
325 ret
= scm_cons (scm_list_1 (pb
->self_scm ()), ret
);
329 return scm_append (scm_reverse (ret
));
333 Page_breaking::make_page (int page_num
, bool last
) const
335 bool last_part
= ly_scm2bool (book_
->paper_
->c_variable ("is-last-bookpart"));
336 SCM mod
= scm_c_resolve_module ("scm page");
337 SCM make_page_scm
= scm_c_module_lookup (mod
, "make-page");
339 make_page_scm
= scm_variable_ref (make_page_scm
);
341 return scm_apply_0 (make_page_scm
,
342 scm_list_n (book_
->self_scm (),
343 ly_symbol2scm ("page-number"), scm_from_int (page_num
),
344 ly_symbol2scm ("is-last-bookpart"), scm_from_bool (last_part
),
345 ly_symbol2scm ("is-bookpart-last-page"), scm_from_bool (last
),
350 Page_breaking::page_height (int page_num
, bool last
) const
352 SCM mod
= scm_c_resolve_module ("scm page");
353 SCM page
= make_page (page_num
, last
);
354 SCM calc_height
= scm_c_module_lookup (mod
, "calc-printable-height");
355 calc_height
= scm_variable_ref (calc_height
);
357 SCM height
= scm_apply_1 (calc_height
, page
, SCM_EOL
);
358 return scm_to_double (height
);
362 Page_breaking::breakpoint_property (vsize breakpoint
, char const *str
)
364 Break_position
const &pos
= breaks_
[breakpoint
];
366 if (pos
.system_spec_index_
== VPOS
)
368 if (system_specs_
[pos
.system_spec_index_
].pscore_
)
369 return pos
.col_
->get_property (str
);
370 return system_specs_
[pos
.system_spec_index_
].prob_
->get_property (str
);
374 Page_breaking::get_page_configuration (SCM systems
, int page_num
, bool ragged
, bool last
)
376 SCM dummy_page
= make_page (page_num
, last
);
377 Page_layout_problem
layout (book_
, dummy_page
, systems
);
378 return scm_is_pair (systems
) ? layout
.solution (ragged
) : SCM_EOL
;
382 Page_breaking::draw_page (SCM systems
, SCM configuration
, int page_num
, bool last
)
384 // Create a stencil for each system.
385 SCM paper_systems
= SCM_EOL
;
386 for (SCM s
= scm_reverse (systems
); scm_is_pair (s
); s
= scm_cdr (s
))
388 SCM paper_system
= scm_car (s
);
389 if (Grob
*g
= unsmob_grob (scm_car (s
)))
391 System
*sys
= dynamic_cast<System
*> (g
);
392 paper_system
= sys
->get_paper_system ();
395 paper_systems
= scm_cons (paper_system
, paper_systems
);
398 // Create the page and draw it.
399 SCM page
= make_page (page_num
, last
);
400 SCM page_module
= scm_c_resolve_module ("scm page");
401 SCM page_stencil
= scm_c_module_lookup (page_module
, "page-stencil");
402 page_stencil
= scm_variable_ref (page_stencil
);
404 Prob
*p
= unsmob_prob (page
);
405 p
->set_property ("lines", paper_systems
);
406 p
->set_property ("configuration", configuration
);
407 scm_apply_1 (page_stencil
, page
, SCM_EOL
);
413 Page_breaking::make_pages (vector
<vsize
> lines_per_page
, SCM systems
)
415 int first_page_number
416 = robust_scm2int (book_
->paper_
->c_variable ("first-page-number"), 1);
418 SCM label_page_table
= book_
->top_paper ()->c_variable ("label-page-table");
419 if (label_page_table
== SCM_UNDEFINED
)
420 label_page_table
= SCM_EOL
;
422 // Build a list of (systems . configuration) pairs. Note that we lay out
423 // the staves and find the configurations before drawing anything. Some
424 // grobs (like tuplet brackets) look at their neighbours while drawing
425 // themselves. If this happens before the neighbouring staves have
426 // been laid out, bad side-effects could happen (in particular,
427 // Align_interface::align_to_ideal_distances might be called).
428 SCM systems_and_configs
= SCM_EOL
;
430 for (vsize i
= 0; i
< lines_per_page
.size (); i
++)
432 int page_num
= i
+ first_page_number
;
433 bool bookpart_last_page
= (i
== lines_per_page
.size () - 1);
434 bool rag
= ragged () || (bookpart_last_page
&& ragged_last ());
435 SCM line_count
= scm_from_int (lines_per_page
[i
]);
436 SCM lines
= scm_list_head (systems
, line_count
);
437 SCM config
= get_page_configuration (lines
, page_num
, rag
, bookpart_last_page
);
439 systems_and_configs
= scm_cons (scm_cons (lines
, config
), systems_and_configs
);
440 systems
= scm_list_tail (systems
, line_count
);
443 // Now it's safe to make the pages.
444 int page_num
= first_page_number
+ lines_per_page
.size () - 1;
445 for (SCM s
= systems_and_configs
; scm_is_pair (s
); s
= scm_cdr (s
))
447 SCM lines
= scm_caar (s
);
448 SCM config
= scm_cdar (s
);
449 bool bookpart_last_page
= (s
== systems_and_configs
);
450 SCM page
= draw_page (lines
, config
, page_num
, bookpart_last_page
);
453 SCM page_num_scm
= scm_from_int (page_num
);
454 for (SCM l
= lines
; scm_is_pair (l
) ; l
= scm_cdr (l
))
456 SCM labels
= SCM_EOL
;
457 if (Grob
* line
= unsmob_grob (scm_car (l
)))
459 System
*system
= dynamic_cast<System
*> (line
);
460 labels
= system
->get_property ("labels");
462 else if (Prob
*prob
= unsmob_prob (scm_car (l
)))
463 labels
= prob
->get_property ("labels");
465 for (SCM lbls
= labels
; scm_is_pair (lbls
) ; lbls
= scm_cdr (lbls
))
466 label_page_table
= scm_cons (scm_cons (scm_car (lbls
), page_num_scm
),
470 ret
= scm_cons (page
, ret
);
473 book_
->top_paper ()->set_variable (ly_symbol2scm ("label-page-table"), label_page_table
);
477 /* The page-turn-page-breaker needs to have a line-breaker between any two
478 columns with non-NULL page-turn-permission.
480 The optimal-breaker needs to have a line-breaker between any two columns
481 with page-break-permission = 'force.
483 By using a grob predicate, we can accommodate both of these uses.
486 Page_breaking::create_system_list ()
488 SCM specs
= book_
->get_system_specs ();
489 for (SCM s
= specs
; scm_is_pair (s
); s
= scm_cdr (s
))
491 if (Paper_score
*ps
= dynamic_cast<Paper_score
*> (unsmob_music_output (scm_car (s
))))
493 system_specs_
.push_back (System_spec (ps
));
497 Prob
*pb
= unsmob_prob (scm_car (s
));
501 system_specs_
.push_back (System_spec (pb
));
507 Page_breaking::find_chunks_and_breaks (Break_predicate is_break
)
509 SCM force_sym
= ly_symbol2scm ("force");
511 chunks_
.push_back (Break_position ());
512 breaks_
.push_back (Break_position ());
514 for (vsize i
= 0; i
< system_specs_
.size (); i
++)
516 if (system_specs_
[i
].pscore_
)
520 SCM system_count
= system_specs_
[i
].pscore_
->layout ()->c_variable ("system-count");
521 if (scm_is_number (system_count
))
523 // With system-count given, the line configuration for
524 // this score is fixed. We need to ensure that chunk
525 // boundaries only occur at line breaks.
526 Constrained_breaking
breaking (system_specs_
[i
].pscore_
);
527 vector
<Line_details
> details
= breaking
.line_details (0, VPOS
, scm_to_int (system_count
));
529 cols
.push_back (system_specs_
[i
].pscore_
->root_system ()->used_columns ()[0]);
530 for (vsize j
= 0; j
< details
.size (); j
++)
531 cols
.push_back (details
[j
].last_column_
);
534 cols
= system_specs_
[i
].pscore_
->root_system ()->used_columns ();
536 int last_chunk_idx
= 0;
537 vector
<vsize
> line_breaker_columns
;
538 line_breaker_columns
.push_back (0);
540 for (vsize j
= 1; j
< cols
.size (); j
++)
542 bool last
= (j
== cols
.size () - 1);
543 bool break_point
= is_break (cols
[j
]);
544 bool chunk_end
= cols
[j
]->get_property ("page-break-permission") == force_sym
;
545 Break_position cur_pos
= Break_position (i
,
546 line_breaker_columns
.size (),
550 // NOTE: even in the breaks_ list, forced_line_count_
551 // refers to the number of lines between a
552 // Break_position and the start of that /chunk/. This
553 // is needed for system_count_bounds to work correctly,
554 // since it mixes Break_positions from breaks_ and
556 if (scm_is_number (system_count
))
557 cur_pos
.forced_line_count_
= j
- last_chunk_idx
;
559 if (break_point
|| (i
== system_specs_
.size () - 1 && last
))
560 breaks_
.push_back (cur_pos
);
561 if (chunk_end
|| last
)
563 chunks_
.push_back (cur_pos
);
567 if ((break_point
|| chunk_end
) && !last
)
568 line_breaker_columns
.push_back (j
);
570 line_breaking_
.push_back (Constrained_breaking (system_specs_
[i
].pscore_
, line_breaker_columns
));
574 /* TODO: we want some way of applying Break_p to a prob? */
575 if (i
== system_specs_
.size () - 1)
576 breaks_
.push_back (Break_position (i
));
578 chunks_
.push_back (Break_position (i
));
580 /* FIXME: shouldn't we push a Null_breaker or similar dummy
582 line_breaking_
.push_back (Constrained_breaking (NULL
));
587 vector
<Break_position
>
588 Page_breaking::chunk_list (vsize start_index
, vsize end_index
)
590 Break_position start
= breaks_
[start_index
];
591 Break_position end
= breaks_
[end_index
];
594 for (; i
< chunks_
.size () && chunks_
[i
] <= start
; i
++)
597 vector
<Break_position
> ret
;
598 ret
.push_back (start
);
599 for (; i
< chunks_
.size () && chunks_
[i
] < end
; i
++)
600 ret
.push_back (chunks_
[i
]);
606 Page_breaking::min_system_count (vsize start
, vsize end
)
608 vector
<Break_position
> chunks
= chunk_list (start
, end
);
609 Line_division div
= system_count_bounds (chunks
, true);
612 for (vsize i
= 0; i
< div
.size (); i
++)
618 Page_breaking::max_system_count (vsize start
, vsize end
)
620 vector
<Break_position
> chunks
= chunk_list (start
, end
);
621 Line_division div
= system_count_bounds (chunks
, false);
624 for (vsize i
= 0; i
< div
.size (); i
++)
629 Page_breaking::Line_division
630 Page_breaking::system_count_bounds (vector
<Break_position
> const &chunks
,
633 assert (chunks
.size () >= 2);
636 ret
.resize (chunks
.size () - 1, 1);
638 for (vsize i
= 0; i
+ 1 < chunks
.size (); i
++)
640 vsize sys
= next_system (chunks
[i
]);
642 if (chunks
[i
+1].forced_line_count_
)
643 ret
[i
] = chunks
[i
+1].forced_line_count_
;
644 else if (system_specs_
[sys
].pscore_
)
648 line_breaker_args (sys
, chunks
[i
], chunks
[i
+1], &start
, &end
);
650 ? line_breaking_
[sys
].min_system_count (start
, end
)
651 : line_breaking_
[sys
].max_system_count (start
, end
);
659 Page_breaking::set_current_breakpoints (vsize start
,
662 Line_division lower_bound
,
663 Line_division upper_bound
)
665 system_count_
= system_count
;
666 current_chunks_
= chunk_list (start
, end
);
667 current_start_breakpoint_
= start
;
668 current_end_breakpoint_
= end
;
669 clear_line_details_cache ();
671 if (!lower_bound
.size ())
672 lower_bound
= system_count_bounds (current_chunks_
, true);
673 if (!upper_bound
.size ())
674 upper_bound
= system_count_bounds (current_chunks_
, false);
676 assert (lower_bound
.size () == current_chunks_
.size () - 1);
677 assert (upper_bound
.size () == current_chunks_
.size () - 1);
679 Line_division work_in_progress
;
680 current_configurations_
.clear ();
681 line_divisions_rec (system_count
,
686 /* we only consider a constant number of configurations. Otherwise,
687 this becomes slow when there are many small scores. The constant
688 5 is somewhat arbitrary. */
689 if (current_configurations_
.size () > 5)
691 vector
<pair
<Real
,vsize
> > dems_and_indices
;
693 for (vsize i
= 0; i
< current_configurations_
.size (); i
++)
695 cache_line_details (i
);
697 for (vsize j
= 0; j
< cached_line_details_
.size (); j
++)
698 dem
+= cached_line_details_
[j
].force_
* cached_line_details_
[j
].force_
699 + cached_line_details_
[j
].break_penalty_
;
701 dems_and_indices
.push_back (pair
<Real
,vsize
> (dem
, i
));
703 vector_sort (dems_and_indices
, less
<pair
<Real
,vsize
> > ());
705 vector
<Line_division
> best_5_configurations
;
706 for (vsize i
= 0; i
< 5; i
++)
707 best_5_configurations
.push_back (current_configurations_
[dems_and_indices
[i
].second
]);
709 clear_line_details_cache ();
710 current_configurations_
= best_5_configurations
;
715 Page_breaking::set_to_ideal_line_configuration (vsize start
, vsize end
)
717 current_chunks_
= chunk_list (start
, end
);
718 current_start_breakpoint_
= start
;
719 current_end_breakpoint_
= end
;
720 clear_line_details_cache ();
724 for (vsize i
= 0; i
+1 < current_chunks_
.size (); i
++)
726 vsize sys
= next_system (current_chunks_
[i
]);
728 if (current_chunks_
[i
+1].forced_line_count_
)
729 div
.push_back (current_chunks_
[i
+1].forced_line_count_
);
730 else if (system_specs_
[sys
].pscore_
)
732 line_breaker_args (sys
, current_chunks_
[i
], current_chunks_
[i
+1], &start
, &end
);
733 div
.push_back (line_breaking_
[sys
].best_solution (start
, end
).size ());
738 system_count_
+= div
.back ();
740 current_configurations_
.clear ();
741 current_configurations_
.push_back (div
);
745 Page_breaking::current_configuration_count () const
747 return current_configurations_
.size ();
751 Page_breaking::cache_line_details (vsize configuration_index
)
753 if (cached_configuration_index_
!= configuration_index
)
755 cached_configuration_index_
= configuration_index
;
757 Line_division
&div
= current_configurations_
[configuration_index
];
758 uncompressed_line_details_
.clear ();
759 for (vsize i
= 0; i
+ 1 < current_chunks_
.size (); i
++)
761 vsize sys
= next_system (current_chunks_
[i
]);
762 if (system_specs_
[sys
].pscore_
)
766 line_breaker_args (sys
, current_chunks_
[i
], current_chunks_
[i
+1], &start
, &end
);
768 vector
<Line_details
> details
= line_breaking_
[sys
].line_details (start
, end
, div
[i
]);
769 uncompressed_line_details_
.insert (uncompressed_line_details_
.end (), details
.begin (), details
.end ());
773 assert (div
[i
] == 1);
774 uncompressed_line_details_
.push_back (Line_details (system_specs_
[sys
].prob_
, book_
->paper_
));
777 cached_line_details_
= compress_lines (uncompressed_line_details_
);
782 Page_breaking::clear_line_details_cache ()
784 cached_configuration_index_
= VPOS
;
785 cached_line_details_
.clear ();
786 uncompressed_line_details_
.clear ();
790 Page_breaking::line_divisions_rec (vsize system_count
,
791 Line_division
const &min_sys
,
792 Line_division
const &max_sys
,
793 Line_division
*cur_division
)
795 vsize my_index
= cur_division
->size ();
796 vsize others_min
= 0;
797 vsize others_max
= 0;
799 for (vsize i
= my_index
+ 1; i
< min_sys
.size (); i
++)
801 others_min
+= min_sys
[i
];
802 others_max
+= max_sys
[i
];
804 others_max
= min (others_max
, system_count
);
805 vsize real_min
= max (min_sys
[my_index
], system_count
- others_max
);
806 vsize real_max
= min (max_sys
[my_index
], system_count
- others_min
);
808 if (real_min
> real_max
|| real_min
<= 0)
810 /* this should never happen within a recursive call. If it happens
811 at all, it means that we were called with an unsolvable problem
812 and we should return an empty result */
813 assert (my_index
== 0);
817 for (vsize i
= real_min
; i
<= real_max
; i
++)
819 cur_division
->push_back (i
);
820 if (my_index
== min_sys
.size () - 1)
821 current_configurations_
.push_back (*cur_division
);
823 line_divisions_rec (system_count
- i
, min_sys
, max_sys
, cur_division
);
824 cur_division
->pop_back ();
829 Page_breaking::min_page_count (vsize configuration
, vsize first_page_num
)
832 vsize page_starter
= 0;
833 Real cur_rod_height
= 0;
834 Real cur_spring_height
= 0;
835 Real cur_page_height
= page_height (first_page_num
, false);
838 cache_line_details (configuration
);
840 if (cached_line_details_
.size ())
841 cur_page_height
-= min_whitespace_at_top_of_page (cached_line_details_
[0]);
843 for (vsize i
= 0; i
< cached_line_details_
.size (); i
++)
845 Real ext_len
= cached_line_details_
[i
].extent_
.length ();
847 if (cur_rod_height
> 0)
848 padding
= cached_line_details_
[i
].title_
?
849 cached_line_details_
[i
-1].title_padding_
: cached_line_details_
[i
-1].padding_
;
851 Real next_rod_height
= cur_rod_height
+ ext_len
+ padding
;
852 Real next_spring_height
= cur_spring_height
+ cached_line_details_
[i
].space_
;
853 Real next_height
= next_rod_height
+ (ragged () ? next_spring_height
: 0)
854 + min_whitespace_at_bottom_of_page (cached_line_details_
[i
]);
855 int next_line_count
= line_count
+ cached_line_details_
[i
].compressed_nontitle_lines_count_
;
857 if ((!too_few_lines (line_count
) && (next_height
> cur_page_height
&& cur_rod_height
> 0))
858 || too_many_lines (next_line_count
)
860 && cached_line_details_
[i
-1].page_permission_
== ly_symbol2scm ("force")))
862 line_count
= cached_line_details_
[i
].compressed_nontitle_lines_count_
;
863 cur_rod_height
= ext_len
;
864 cur_spring_height
= cached_line_details_
[i
].space_
;
867 cur_page_height
= page_height (first_page_num
+ ret
, false);
868 cur_page_height
-= min_whitespace_at_top_of_page (cached_line_details_
[i
]);
874 cur_rod_height
= next_rod_height
;
875 cur_spring_height
= next_spring_height
;
876 line_count
= next_line_count
;
880 /* there are two potential problems with the last page (because we didn't know
881 it was the last page until after we managed to fit all the systems to it):
882 - we are ragged-last but the last page has a compressed spring
883 - the value returned by page_height (num, true) is smaller than the
884 value returned by page_height (num, false) and it causes the page not to
887 In either case, we just need to add one more page. This is because the last
888 line will always fit on the extra page and by adding one more page to the
889 end, the previous page is no longer the last page, so our previous
890 calculations that treated it as a non-last page were ok.
893 cur_page_height
= page_height (first_page_num
+ ret
- 1, true);
894 cur_page_height
-= min_whitespace_at_top_of_page (cached_line_details_
[page_starter
]);
895 cur_page_height
-= min_whitespace_at_bottom_of_page (cached_line_details_
.back ());
897 Real cur_height
= cur_rod_height
+ ((ragged_last () || ragged ()) ? cur_spring_height
: 0);
898 if (!too_few_lines (line_count
- cached_line_details_
.back ().compressed_nontitle_lines_count_
)
899 && cur_height
> cur_page_height
900 /* don't increase the page count if the last page had only one system */
901 && cur_rod_height
> cached_line_details_
.back ().extent_
.length ())
904 assert (ret
<= cached_line_details_
.size ());
908 // If systems_per_page_ is positive, we don't really try to space on N pages;
909 // we just put the requested number of systems on each page and penalize
910 // if the result doesn't have N pages.
912 Page_breaking::space_systems_on_n_pages (vsize configuration
, vsize n
, vsize first_page_num
)
914 Page_spacing_result ret
;
916 if (systems_per_page_
> 0)
918 Page_spacing_result ret
= space_systems_with_fixed_number_per_page (configuration
, first_page_num
);
919 ret
.demerits_
+= (ret
.force_
.size () == n
) ? 0 : BAD_SPACING_PENALTY
;
923 cache_line_details (configuration
);
924 bool valid_n
= (n
>= min_page_count (configuration
, first_page_num
)
925 && n
<= cached_line_details_
.size ());
928 programming_error ("number of pages is out of bounds");
930 if (n
== 1 && valid_n
)
931 ret
= space_systems_on_1_page (cached_line_details_
,
932 page_height (first_page_num
, is_last ()),
933 ragged () || (is_last () && ragged_last ()));
934 else if (n
== 2 && valid_n
)
935 ret
= space_systems_on_2_pages (configuration
, first_page_num
);
938 Page_spacer
ps (cached_line_details_
, first_page_num
, this);
942 return finalize_spacing_result (configuration
, ret
);
946 Page_breaking::blank_page_penalty () const
951 penalty_sym
= ly_symbol2scm ("blank-last-page-force");
952 else if (ends_score ())
953 penalty_sym
= ly_symbol2scm ("blank-after-score-page-force");
955 penalty_sym
= ly_symbol2scm ("blank-page-force");
957 Break_position
const &pos
= breaks_
[current_end_breakpoint_
];
958 if (Paper_score
*ps
= system_specs_
[pos
.system_spec_index_
].pscore_
)
959 return robust_scm2double (ps
->layout ()->lookup_variable (penalty_sym
), 0.0);
961 return robust_scm2double (book_
->paper_
->lookup_variable (penalty_sym
), 0.0);
964 // If systems_per_page_ is positive, we don't really try to space on N
965 // or N+1 pages; see the comment to space_systems_on_n_pages.
967 Page_breaking::space_systems_on_n_or_one_more_pages (vsize configuration
, vsize n
, vsize first_page_num
)
969 Page_spacing_result n_res
;
970 Page_spacing_result m_res
;
972 if (systems_per_page_
> 0)
974 Page_spacing_result ret
= space_systems_with_fixed_number_per_page (configuration
, first_page_num
);
975 ret
.demerits_
+= (ret
.force_
.size () == n
|| ret
.force_
.size () == (n
-1)) ? 0 : BAD_SPACING_PENALTY
;
979 cache_line_details (configuration
);
980 vsize min_p_count
= min_page_count (configuration
, first_page_num
);
981 bool valid_n
= n
>= min_p_count
|| n
<= cached_line_details_
.size ();
984 programming_error ("both page counts are out of bounds");
986 if (n
== 1 && valid_n
)
988 bool rag
= ragged () || (is_last () && ragged_last ());
989 Real height
= page_height (first_page_num
, is_last ());
991 if (1 >= min_p_count
)
992 n_res
= space_systems_on_1_page (cached_line_details_
, height
, rag
);
993 if (1 < cached_line_details_
.size ())
994 m_res
= space_systems_on_2_pages (configuration
, first_page_num
);
998 Page_spacer
ps (cached_line_details_
, first_page_num
, this);
1000 if (n
>= min_p_count
|| !valid_n
)
1001 n_res
= ps
.solve (n
);
1002 if (n
< cached_line_details_
.size () || !valid_n
)
1003 m_res
= ps
.solve (n
+1);
1006 m_res
= finalize_spacing_result (configuration
, m_res
);
1007 n_res
= finalize_spacing_result (configuration
, n_res
);
1009 Real penalty
= blank_page_penalty ();
1010 n_res
.demerits_
+= penalty
;
1012 if (n_res
.force_
.size ())
1013 n_res
.force_
.back () += penalty
;
1015 return (m_res
.demerits_
< n_res
.demerits_
) ? m_res
: n_res
;
1019 Page_breaking::space_systems_on_best_pages (vsize configuration
, vsize first_page_num
)
1021 vsize min_p_count
= min_page_count (configuration
, first_page_num
);
1022 Real odd_pages_penalty
= blank_page_penalty ();
1024 cache_line_details (configuration
);
1025 Page_spacer
ps (cached_line_details_
, first_page_num
, this);
1026 Page_spacing_result best
= ps
.solve (min_p_count
);
1027 best
.force_
.back () += (min_p_count
% 2) ? odd_pages_penalty
: 0;
1028 best
.demerits_
+= (min_p_count
% 2) ? odd_pages_penalty
: 0;
1030 for (vsize i
= min_p_count
+1; i
<= cached_line_details_
.size (); i
++)
1032 Page_spacing_result cur
= ps
.solve (i
);
1033 cur
.demerits_
+= (i
% 2) ? odd_pages_penalty
: 0;
1034 if (cur
.demerits_
< best
.demerits_
)
1038 return finalize_spacing_result (configuration
, best
);
1042 Page_breaking::space_systems_with_fixed_number_per_page (vsize configuration
,
1043 vsize first_page_num
)
1045 Page_spacing_result res
;
1046 Page_spacing
space (page_height (first_page_num
, false), this);
1049 vsize page_first_line
= 0;
1051 cache_line_details (configuration
);
1052 while (line
< cached_line_details_
.size ())
1056 space
.resize (page_height (first_page_num
+ page
, false));
1058 int system_count_on_this_page
= 0;
1059 while (system_count_on_this_page
< systems_per_page_
1060 && line
< cached_line_details_
.size ())
1062 Line_details
const &cur_line
= cached_line_details_
[line
];
1063 space
.append_system (cur_line
);
1064 system_count_on_this_page
+= cur_line
.compressed_nontitle_lines_count_
;
1067 if (cur_line
.page_permission_
== ly_symbol2scm ("force"))
1071 res
.systems_per_page_
.push_back (line
- page_first_line
);
1073 res
.force_
.push_back (space
.force_
);
1074 res
.penalty_
+= cached_line_details_
[line
-1].page_penalty_
;
1075 if (system_count_on_this_page
!= systems_per_page_
)
1077 res
.penalty_
+= TERRIBLE_SPACING_PENALTY
;
1078 res
.system_count_status_
|= ((system_count_on_this_page
< systems_per_page_
))
1079 ? SYSTEM_COUNT_TOO_FEW
: SYSTEM_COUNT_TOO_MANY
;
1082 page_first_line
= line
;
1085 /* Recalculate forces for the last page because we know now that is
1086 really the last page. */
1087 space
.resize (page_height (first_page_num
+ page
, true));
1088 res
.force_
.back () = space
.force_
;
1089 return finalize_spacing_result (configuration
, res
);
1093 Page_breaking::pack_systems_on_least_pages (vsize configuration
, vsize first_page_num
)
1095 // TODO: add support for min/max-systems-per-page.
1096 Page_spacing_result res
;
1098 vsize page_first_line
= 0;
1099 Page_spacing
space (page_height (first_page_num
, false), this);
1101 cache_line_details (configuration
);
1102 for (vsize line
= 0; line
< cached_line_details_
.size (); line
++)
1104 Real prev_force
= space
.force_
;
1105 space
.append_system (cached_line_details_
[line
]);
1106 if ((line
> page_first_line
)
1107 && (isinf (space
.force_
)
1109 && (cached_line_details_
[line
-1].page_permission_
== ly_symbol2scm ("force")))))
1111 res
.systems_per_page_
.push_back (line
- page_first_line
);
1112 res
.force_
.push_back (prev_force
);
1113 res
.penalty_
+= cached_line_details_
[line
-1].page_penalty_
;
1115 space
.resize (page_height (first_page_num
+ page
, false));
1117 space
.append_system (cached_line_details_
[line
]);
1118 page_first_line
= line
;
1121 if (line
== cached_line_details_
.size () - 1)
1123 /* This is the last line */
1124 /* When the last page height was computed, we did not know yet that it
1125 * was the last one. If the systems put on it don't fit anymore, the last
1126 * system is moved to a new page */
1127 space
.resize (page_height (first_page_num
+ page
, true));
1128 if ((line
> page_first_line
) && (isinf (space
.force_
)))
1130 res
.systems_per_page_
.push_back (line
- page_first_line
);
1131 res
.force_
.push_back (prev_force
);
1132 /* the last page containing the last line */
1133 space
.resize (page_height (first_page_num
+ page
+ 1, true));
1135 space
.append_system (cached_line_details_
[line
]);
1136 res
.systems_per_page_
.push_back (1);
1137 res
.force_
.push_back (space
.force_
);
1138 res
.penalty_
+= cached_line_details_
[line
-1].page_penalty_
;
1139 res
.penalty_
+= cached_line_details_
[line
].page_penalty_
;
1143 res
.systems_per_page_
.push_back (line
+ 1 - page_first_line
);
1144 res
.force_
.push_back (space
.force_
);
1145 res
.penalty_
+= cached_line_details_
[line
].page_penalty_
;
1149 return finalize_spacing_result (configuration
, res
);
1152 /* Calculate demerits and fix res.systems_per_page_ so that
1153 it refers to the original line numbers, not the ones given by compress_lines (). */
1155 Page_breaking::finalize_spacing_result (vsize configuration
, Page_spacing_result res
)
1157 if (res
.force_
.empty ())
1160 cache_line_details (configuration
);
1161 res
.systems_per_page_
= uncompress_solution (res
.systems_per_page_
, cached_line_details_
);
1163 Real line_force
= 0;
1164 Real line_penalty
= 0;
1165 Real page_demerits
= res
.penalty_
;
1166 Real page_weighting
= robust_scm2double (book_
->paper_
->c_variable ("page-spacing-weight"), 10);
1168 for (vsize i
= 0; i
< uncompressed_line_details_
.size (); i
++)
1170 line_force
+= uncompressed_line_details_
[i
].force_
* uncompressed_line_details_
[i
].force_
;
1171 line_penalty
+= uncompressed_line_details_
[i
].break_penalty_
;
1174 for (vsize i
= 0; i
< res
.force_
.size (); i
++)
1176 Real f
= res
.force_
[i
];
1178 page_demerits
+= min(f
*f
, BAD_SPACING_PENALTY
);
1181 /* for a while we tried averaging page and line forces across pages instead
1182 of summing them, but it caused a problem: if there is a single page
1183 with a very bad page force (for example because of a forced page break),
1184 the page breaker will put in a _lot_ of pages so that the bad force
1185 becomes averaged out over many pages. */
1186 res
.demerits_
= line_force
+ line_penalty
+ page_demerits
* page_weighting
;
1191 /* the cases for page_count = 1 or 2 can be done in O (n) time. Since they
1192 are by far the most common cases, we have special functions for them.
1194 space_systems_on_1_page has a different calling convention than most of the
1195 space_systems functions. This is because space_systems_on_1_page is (unlike
1196 the other space_systems functions) sometimes called on subsets of a full
1199 Page_breaking::space_systems_on_1_page (vector
<Line_details
> const &lines
, Real page_height
, bool ragged
)
1201 Page_spacing
space (page_height
, this);
1202 Page_spacing_result ret
;
1205 for (vsize i
= 0; i
< lines
.size (); i
++)
1207 space
.append_system (lines
[i
]);
1208 line_count
+= lines
[i
].compressed_nontitle_lines_count_
;
1211 ret
.systems_per_page_
.push_back (lines
.size ());
1212 ret
.force_
.push_back (ragged
? min (space
.force_
, 0.0) : space
.force_
);
1213 ret
.penalty_
= line_count_penalty (line_count
) + lines
.back ().page_penalty_
+ lines
.back ().turn_penalty_
;
1214 ret
.system_count_status_
|= line_count_status (line_count
);
1216 /* don't do finalize_spacing_result () because we are only an internal function */
1221 Page_breaking::space_systems_on_2_pages (vsize configuration
, vsize first_page_num
)
1223 Real page1_height
= page_height (first_page_num
, false);
1224 Real page2_height
= page_height (first_page_num
+ 1, is_last ());
1225 bool ragged1
= ragged ();
1226 bool ragged2
= ragged () || (is_last () && ragged_last ());
1228 /* if there is a forced break, this reduces to 2 1-page problems */
1229 cache_line_details (configuration
);
1230 for (vsize i
= 0; i
+ 1 < cached_line_details_
.size (); i
++)
1231 if (cached_line_details_
[i
].page_permission_
== ly_symbol2scm ("force"))
1233 vector
<Line_details
> lines1 (cached_line_details_
.begin (), cached_line_details_
.begin () + i
+ 1);
1234 vector
<Line_details
> lines2 (cached_line_details_
.begin () + i
+ 1, cached_line_details_
.end ());
1235 Page_spacing_result p1
= space_systems_on_1_page (lines1
, page1_height
, ragged1
);
1236 Page_spacing_result p2
= space_systems_on_1_page (lines2
, page2_height
, ragged2
);
1238 p1
.systems_per_page_
.push_back (p2
.systems_per_page_
[0]);
1239 p1
.force_
.push_back (p2
.force_
[0]);
1240 p1
.penalty_
+= p2
.penalty_
- cached_line_details_
[i
].turn_penalty_
;
1244 vector
<Real
> page1_force
;
1245 vector
<Real
> page2_force
;
1247 // page1_penalty and page2_penalty store the penalties based
1248 // on min-systems-per-page and max-systems-per-page.
1249 vector
<Real
> page1_penalty
;
1250 vector
<Real
> page2_penalty
;
1252 // page1_status and page2_status keep track of whether the min-systems-per-page
1253 // and max-systems-per-page constraints are satisfied.
1254 vector
<int> page1_status
;
1255 vector
<int> page2_status
;
1257 Page_spacing
page1 (page1_height
, this);
1258 Page_spacing
page2 (page2_height
, this);
1259 int page1_line_count
= 0;
1260 int page2_line_count
= 0;
1262 page1_force
.resize (cached_line_details_
.size () - 1, infinity_f
);
1263 page2_force
.resize (cached_line_details_
.size () - 1, infinity_f
);
1264 page1_penalty
.resize (cached_line_details_
.size () - 1, infinity_f
);
1265 page2_penalty
.resize (cached_line_details_
.size () - 1, infinity_f
);
1266 page1_status
.resize (cached_line_details_
.size () - 1, 0);
1267 page2_status
.resize (cached_line_details_
.size () - 1, 0);
1269 /* find the page 1 and page 2 forces for each page-breaking position */
1270 for (vsize i
= 0; i
< page1_force
.size (); i
++)
1272 page1
.append_system (cached_line_details_
[i
]);
1273 page2
.prepend_system (cached_line_details_
[cached_line_details_
.size () - 1 - i
]);
1274 page1_line_count
+= cached_line_details_
[i
].compressed_nontitle_lines_count_
;
1275 page2_line_count
+= cached_line_details_
[cached_line_details_
.size () - 1 - i
].compressed_nontitle_lines_count_
;
1277 page1_force
[i
] = (ragged1
&& page1
.force_
< 0 && i
> 0) ? infinity_f
: page1
.force_
;
1278 page1_penalty
[i
] = line_count_penalty (page1_line_count
);
1279 page1_status
[i
] = line_count_status (page1_line_count
);
1282 page2_force
[page2_force
.size () - 1 - i
] =
1283 (page2
.force_
< 0 && i
+ 1 < page1_force
.size ()) ? infinity_f
: 0;
1285 page2_force
[page2_force
.size () - 1 - i
] = page2
.force_
;
1286 page2_penalty
[page2_penalty
.size () - 1 - i
] = line_count_penalty (page2_line_count
);
1287 page2_status
[page2_penalty
.size () - 1 - i
] = line_count_status (page2_line_count
);
1290 /* find the position that minimises the sum of the page forces */
1291 vsize best_sys_count
= 1;
1292 Real best_demerits
= infinity_f
;
1293 for (vsize i
= 0; i
< page1_force
.size (); i
++)
1295 Real f
= page1_force
[i
] * page1_force
[i
] + page2_force
[i
] * page2_force
[i
];
1297 // NOTE: we treat max-systems-per-page and min-systems-per-page as soft
1298 // constraints. That is, we penalize harshly when they don't happen
1299 // but we don't completely reject.
1301 + page1_penalty
[i
] + page2_penalty
[i
]
1302 + cached_line_details_
[i
+1].page_penalty_
1303 + cached_line_details_
.back ().page_penalty_
+ cached_line_details_
.back ().turn_penalty_
;
1304 if (dem
< best_demerits
)
1306 best_demerits
= dem
;
1307 best_sys_count
= i
+1;
1311 Page_spacing_result ret
;
1312 ret
.systems_per_page_
.push_back (best_sys_count
);
1313 ret
.systems_per_page_
.push_back (cached_line_details_
.size () - best_sys_count
);
1314 ret
.force_
.push_back (page1_force
[best_sys_count
-1]);
1315 ret
.force_
.push_back (page2_force
[best_sys_count
-1]);
1316 ret
.system_count_status_
= page1_status
[best_sys_count
-1] | page2_status
[best_sys_count
-1];
1317 ret
.penalty_
= cached_line_details_
[best_sys_count
-1].page_penalty_
1318 + cached_line_details_
.back ().page_penalty_
1319 + cached_line_details_
.back ().turn_penalty_
1320 + page1_penalty
[best_sys_count
-1] + page2_penalty
[best_sys_count
-1];
1322 /* don't do finalize_spacing_result () because we are only an internal function */
1327 Page_breaking::all_lines_stretched (vsize configuration
)
1329 cache_line_details (configuration
);
1330 for (vsize i
= 0; i
< cached_line_details_
.size (); i
++)
1331 if (cached_line_details_
[i
].force_
< 0)
1337 Page_breaking::Line_division
1338 Page_breaking::current_configuration (vsize configuration_index
) const
1340 return current_configurations_
[configuration_index
];
1344 Page_breaking::is_last () const
1346 return current_end_breakpoint_
== last_break_position ();
1350 Page_breaking::ends_score () const
1352 return breaks_
[current_end_breakpoint_
].score_ender_
;
1356 Page_breaking::last_break_position () const
1358 return breaks_
.size () - 1;
1361 // This gives the minimum distance between the top of the
1362 // printable area (ie. the bottom of the top-margin) and
1363 // the extent box of the topmost system.
1365 Page_breaking::min_whitespace_at_top_of_page (Line_details
const &line
) const
1367 SCM first_system_spacing
= book_
->paper_
->c_variable ("top-system-spacing");
1369 first_system_spacing
= book_
->paper_
->c_variable ("top-title-spacing");
1371 Real min_distance
= -infinity_f
;
1374 Page_layout_problem::read_spacing_spec (first_system_spacing
,
1376 ly_symbol2scm ("minimum-distance"));
1377 Page_layout_problem::read_spacing_spec (first_system_spacing
,
1379 ly_symbol2scm ("padding"));
1381 // FIXME: take into account the height of the header
1382 return max (0.0, max (padding
, min_distance
- line
.extent_
[UP
]));
1386 Page_breaking::min_whitespace_at_bottom_of_page (Line_details
const &line
) const
1388 SCM last_system_spacing
= book_
->paper_
->c_variable ("bottom-system-spacing");
1389 Real min_distance
= -infinity_f
;
1392 Page_layout_problem::read_spacing_spec (last_system_spacing
,
1394 ly_symbol2scm ("minimum-distance"));
1395 Page_layout_problem::read_spacing_spec (last_system_spacing
,
1397 ly_symbol2scm ("padding"));
1399 // FIXME: take into account the height of the footer
1400 return max (0.0, max (padding
, min_distance
+ line
.extent_
[DOWN
]));