]> git.donarmstrong.com Git - lilypond.git/blob - lily/page-breaking.cc
Merge branch 'master' of git+ssh://jneem@git.sv.gnu.org/srv/git/lilypond into jneeman2
[lilypond.git] / lily / page-breaking.cc
1 /*
2   page-breaking.cc -- implement a superclass and utility
3   functions shared by various page-breaking algorithms
4
5   source file of the GNU LilyPond music typesetter
6
7   (c) 2006--2007 Joe Neeman <joeneeman@gmail.com>
8 */
9
10 #include "page-breaking.hh"
11
12 #include "international.hh"
13 #include "item.hh"
14 #include "output-def.hh"
15 #include "page-spacing.hh"
16 #include "paper-book.hh"
17 #include "paper-score.hh"
18 #include "paper-system.hh"
19 #include "system.hh"
20 #include "warn.hh"
21
22 /* for each forbidden page break, merge the systems around it into one system. */
23 static vector<Line_details>
24 compress_lines (const vector<Line_details> &orig)
25 {
26   vector<Line_details> ret;
27
28   for (vsize i = 0; i < orig.size (); i++)
29     {
30       if (ret.size () && !scm_is_symbol (ret.back ().page_permission_))
31         {
32           Line_details const &old = ret.back ();
33           Line_details compressed = orig[i];
34           compressed.extent_[DOWN] = old.extent_[DOWN];
35           compressed.extent_[UP] = old.extent_[UP] + orig[i].extent_.length () + old.padding_;
36           compressed.space_ += old.space_;
37           compressed.inverse_hooke_ += old.inverse_hooke_;
38
39           /* we don't need the force_ field for the vertical spacing,
40              so we use force_ = n to signal that the line was compressed,
41              reducing the number of lines by n (and force_ = 0 otherwise).
42              This makes uncompression much easier. */
43           compressed.force_ = old.force_ + 1;
44           ret.back () = compressed;
45         }
46       else
47         {
48           ret.push_back (orig[i]);
49           ret.back ().force_ = 0;
50         }
51     }
52   return ret;
53 }
54
55 /* translate the number of systems-per-page into something meaningful for
56    the uncompressed lines.
57 */
58 static vector<vsize>
59 uncompress_solution (vector<vsize> const &systems_per_page,
60                      vector<Line_details> const &compressed)
61 {
62   vector<vsize> ret;
63   vsize start_sys = 0;
64
65   for (vsize i = 0; i < systems_per_page.size (); i++)
66     {
67       int compressed_count = 0;
68       for (vsize j = start_sys; j < start_sys + systems_per_page[i]; j++)
69         compressed_count += (int)compressed[j].force_;
70
71       ret.push_back (systems_per_page[i] + compressed_count);
72       start_sys += systems_per_page[i];
73     }
74   return ret;
75 }
76
77 /* for Page_breaking, the start index (when we are dealing with the stuff
78    between a pair of breakpoints) refers to the break_ index of the end of
79    the previous page. So the system index of the start of the current page
80    could either be the same as the end of the previous page or one more than
81    it. */
82
83 /* Turn a break index into the sys index that starts the next page */
84 vsize
85 Page_breaking::next_system (Break_position const &break_pos) const
86 {
87   vsize sys = break_pos.sys_;
88
89   if (sys == VPOS) /* beginning of the book */
90     return 0;
91   if (all_[sys].pscore_ && !break_pos.score_ender_)
92     return sys; /* the score overflows the previous page */
93   return sys + 1; /* this page starts with a new sys */
94 }
95
96 Page_breaking::Page_breaking (Paper_book *pb, Break_predicate is_break)
97 {
98   book_ = pb;
99   ragged_ = to_boolean (pb->paper_->c_variable ("ragged"));
100   ragged_last_ = to_boolean (pb->paper_->c_variable ("ragged-last"));
101   create_system_list ();
102   find_chunks_and_breaks (is_break);
103 }
104
105 Page_breaking::~Page_breaking ()
106 {
107 }
108
109 bool
110 Page_breaking::ragged () const
111 {
112   return ragged_;
113 }
114
115 bool
116 Page_breaking::ragged_last () const
117 {
118   return ragged_last_;
119 }
120
121 /* translate indices into breaks_ into start-end parameters for the line breaker */
122 void
123 Page_breaking::line_breaker_args (vsize sys,
124                                   Break_position const &start,
125                                   Break_position const &end,
126                                   vsize *line_breaker_start,
127                                   vsize *line_breaker_end)
128 {
129   assert (all_[sys].pscore_);
130   assert (next_system (start) <= sys && sys <= end.sys_);
131
132   if (start.sys_ == sys)
133     *line_breaker_start = start.score_break_;
134   else
135     *line_breaker_start = 0;
136
137   if (end.sys_ == sys)
138     *line_breaker_end = end.score_break_;
139   else
140     *line_breaker_end = VPOS;
141 }
142
143 void
144 Page_breaking::break_into_pieces (vsize start_break, vsize end_break, Line_division const &div)
145 {
146   vector<Break_position> chunks = chunk_list (start_break, end_break);
147   bool ignore_div = false;
148   if (chunks.size () != div.size () + 1)
149     {
150       programming_error ("did not find a valid page breaking configuration");
151       ignore_div = true;
152     }
153
154   for (vsize i = 0; i + 1 < chunks.size (); i++)
155     {
156       vsize sys = next_system (chunks[i]);
157       if (all_[sys].pscore_)
158         {
159           vsize start;
160           vsize end;
161           line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end);
162
163           vector<Column_x_positions> pos = ignore_div
164             ? line_breaking_[sys].best_solution (start, end)
165             : line_breaking_[sys].solve (start, end, div[i]);
166           all_[sys].pscore_->root_system ()->break_into_pieces (pos);
167         }
168     }
169 }
170
171 SCM
172 Page_breaking::systems ()
173 {
174   SCM ret = SCM_EOL;
175   for (vsize sys = 0; sys < all_.size (); sys++)
176     {
177       if (all_[sys].pscore_)
178         {
179           all_[sys].pscore_->root_system ()->do_break_substitution_and_fixup_refpoints ();
180           SCM lines = all_[sys].pscore_->root_system ()->get_broken_system_grobs ();
181           ret = scm_cons (lines, ret);
182         }
183       else
184         {
185           Prob *pb = all_[sys].prob_;
186           ret = scm_cons (scm_list_1 (pb->self_scm ()), ret);
187           pb->unprotect ();
188         }
189     }
190   return scm_append (scm_reverse (ret));
191 }
192
193 Real
194 Page_breaking::page_height (int page_num, bool last) const
195 {
196   SCM mod = scm_c_resolve_module ("scm page");
197   SCM calc_height = scm_c_module_lookup (mod, "calc-printable-height");
198   SCM make_page = scm_c_module_lookup (mod, "make-page");
199
200   calc_height = scm_variable_ref (calc_height);
201   make_page = scm_variable_ref (make_page);
202
203   SCM page = scm_apply_0 (make_page, scm_list_n (
204                   book_->self_scm (),
205                   ly_symbol2scm ("page-number"), scm_from_int (page_num),
206                   ly_symbol2scm ("is-last"), scm_from_bool (last),
207                   SCM_UNDEFINED));
208   SCM height = scm_apply_1 (calc_height, page, SCM_EOL);
209   return scm_to_double (height) - scm_to_double (book_->paper_->c_variable ("page-top-space"));
210 }
211
212 SCM
213 Page_breaking::breakpoint_property (vsize breakpoint, char const *str)
214 {
215   Break_position const &pos = breaks_[breakpoint];
216
217   if (pos.sys_ == VPOS)
218     return SCM_EOL;
219   if (all_[pos.sys_].pscore_)
220     return pos.col_->get_property (str);
221   return all_[pos.sys_].prob_->get_property (str);
222 }
223
224 SCM
225 Page_breaking::make_pages (vector<vsize> lines_per_page, SCM systems)
226 {
227   SCM layout_module = scm_c_resolve_module ("scm layout-page-layout");
228   SCM page_module = scm_c_resolve_module ("scm page");
229
230   SCM make_page = scm_c_module_lookup (layout_module, "stretch-and-draw-page");
231   SCM page_stencil = scm_c_module_lookup (page_module, "page-stencil");
232   make_page = scm_variable_ref (make_page);
233   page_stencil = scm_variable_ref (page_stencil);
234
235   SCM book = book_->self_scm ();
236   bool ragged_all = to_boolean (book_->paper_->c_variable ("ragged-bottom"));
237   bool ragged_last = to_boolean (book_->paper_->c_variable ("ragged-last-bottom"));
238   int first_page_number = robust_scm2int (book_->paper_->c_variable ("first-page-number"), 1);
239   SCM ret = SCM_EOL;
240
241   for (vsize i = 0; i < lines_per_page.size (); i++)
242     {
243       SCM page_num = scm_from_int (i + first_page_number);
244       SCM last = scm_from_bool (i == lines_per_page.size () - 1);
245       SCM ragged = scm_from_bool (ragged_all || (to_boolean (last) && ragged_last));
246       SCM line_count = scm_from_int (lines_per_page[i]);
247       SCM lines = scm_list_head (systems, line_count);
248       SCM page = scm_apply_0 (make_page,
249                               scm_list_n (book, lines, page_num, ragged, last, SCM_UNDEFINED));
250
251       scm_apply_1 (page_stencil, page, SCM_EOL);
252       ret = scm_cons (page, ret);
253       systems = scm_list_tail (systems, line_count);
254     }
255   ret = scm_reverse (ret);
256   return ret;
257 }
258
259 /* The page-turn-page-breaker needs to have a line-breaker between any two
260    columns with non-NULL page-turn-permission.
261
262    The optimal-breaker needs to have a line-breaker between any two columns
263    with page-break-permission = 'force.
264
265    By using a grob predicate, we can accommodate both of these uses.
266 */
267 void
268 Page_breaking::create_system_list ()
269 {
270   SCM specs = book_->get_system_specs ();
271   for (SCM s = specs; scm_is_pair (s); s = scm_cdr (s))
272     {
273       if (Paper_score *ps = dynamic_cast<Paper_score*> (unsmob_music_output (scm_car (s))))
274         {
275           SCM system_count = ps->layout ()->c_variable ("system-count");
276
277           if (scm_is_number (system_count))
278             s = scm_append (scm_list_3 (scm_list_1 (scm_car (s)),
279                                         scm_vector_to_list (ps->get_paper_systems ()),
280                                         scm_cdr (s)));
281           else
282             all_.push_back (System_spec (ps));
283         }
284       else
285         {
286           Prob *pb = unsmob_prob (scm_car (s));
287           assert (pb);
288
289           pb->protect ();
290           all_.push_back (System_spec (pb));
291         }
292     }
293 }
294
295 void
296 Page_breaking::find_chunks_and_breaks (Break_predicate is_break)
297 {
298   SCM force_sym = ly_symbol2scm ("force");
299
300   chunks_.push_back (Break_position ());
301   breaks_.push_back (Break_position ());
302
303   for (vsize i = 0; i < all_.size (); i++)
304     {
305       if (all_[i].pscore_)
306         {
307           vector<Grob*> cols = all_[i].pscore_->root_system ()->used_columns ();
308           vector<vsize> line_breaker_columns;
309           line_breaker_columns.push_back (0);
310
311           for (vsize j = 1; j < cols.size (); j++)
312             {
313               bool last = j == cols.size () - 1;
314               bool break_point = is_break (cols[j]);
315               bool chunk_end = cols[j]->get_property ("page-break-permission") == force_sym;
316               Break_position cur_pos = Break_position (i,
317                                                        line_breaker_columns.size (),
318                                                        cols[j],
319                                                        last);
320
321               if (break_point || (i == all_.size () - 1 && last))
322                 breaks_.push_back (cur_pos);
323               if (chunk_end || last)
324                 chunks_.push_back (cur_pos);
325
326               if ((break_point || chunk_end) && !last)
327                 line_breaker_columns.push_back (j);
328             }
329           line_breaking_.push_back (Constrained_breaking (all_[i].pscore_, line_breaker_columns));
330         }
331       else
332         {
333           /* TODO: we want some way of applying Break_p to a prob? */
334           if (i == all_.size () - 1)
335             breaks_.push_back (Break_position (i));
336
337           chunks_.push_back (Break_position (i));
338           line_breaking_.push_back (Constrained_breaking (NULL));
339         }
340     }
341 }
342
343 vector<Break_position>
344 Page_breaking::chunk_list (vsize start_index, vsize end_index)
345 {
346   Break_position start = breaks_[start_index];
347   Break_position end = breaks_[end_index];
348
349   vsize i;
350   for (i = 0; i < chunks_.size () && chunks_[i] <= start; i++)
351     ;
352
353   vector<Break_position> ret;
354   ret.push_back (start);
355   for (; i < chunks_.size () && chunks_[i] < end; i++)
356     ret.push_back (chunks_[i]);
357   ret.push_back (end);
358   return ret;
359 }
360
361 vsize
362 Page_breaking::min_system_count (vsize start, vsize end)
363 {
364   vector<Break_position> chunks = chunk_list (start, end);
365   Line_division div = system_count_bounds (chunks, true);
366   vsize ret = 0;
367
368   for (vsize i = 0; i < div.size (); i++)
369     ret += div[i];
370   return ret;
371 }
372
373 vsize
374 Page_breaking::max_system_count (vsize start, vsize end)
375 {
376   vector<Break_position> chunks = chunk_list (start, end);
377   Line_division div = system_count_bounds (chunks, false);
378   vsize ret = 0;
379
380   for (vsize i = 0; i < div.size (); i++)
381     ret += div[i];
382   return ret;
383 }
384
385 Page_breaking::Line_division
386 Page_breaking::system_count_bounds (vector<Break_position> const &chunks, bool min)
387 {
388   assert (chunks.size () >= 2);
389
390   Line_division ret;
391   ret.resize (chunks.size () - 1, 1);
392
393   for (vsize i = 0; i + 1 < chunks.size (); i++)
394     {
395       vsize sys = next_system (chunks[i]);
396       if (all_[sys].pscore_)
397         {
398           vsize start;
399           vsize end;
400           line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end);
401           ret[i] = min
402             ? line_breaking_[sys].min_system_count (start, end)
403             : line_breaking_[sys].max_system_count (start, end);
404         }
405     }
406
407   return ret;
408 }
409
410 void
411 Page_breaking::set_current_breakpoints (vsize start,
412                                         vsize end,
413                                         vsize system_count,
414                                         Line_division lower_bound,
415                                         Line_division upper_bound)
416 {
417   current_chunks_ = chunk_list (start, end);
418   current_start_breakpoint_ = start;
419   current_end_breakpoint_ = end;
420   clear_line_details_cache ();
421
422   if (!lower_bound.size ())
423     lower_bound = system_count_bounds (current_chunks_, true);
424   if (!upper_bound.size ())
425     upper_bound = system_count_bounds (current_chunks_, false);
426
427   assert (lower_bound.size () == current_chunks_.size () - 1);
428   assert (upper_bound.size () == current_chunks_.size () - 1);
429
430   Line_division work_in_progress;
431   current_configurations_.clear ();
432   line_divisions_rec (system_count,
433                       lower_bound,
434                       upper_bound,
435                       &work_in_progress);
436 }
437
438 vsize
439 Page_breaking::current_configuration_count () const
440 {
441   return current_configurations_.size ();
442 }
443
444 void
445 Page_breaking::cache_line_details (vsize configuration_index)
446 {
447   if (cached_configuration_index_ != configuration_index)
448     {
449       SCM padding_scm = book_->paper_->c_variable ("page-breaking-between-system-padding");
450       if (!scm_is_number (padding_scm))
451         padding_scm = book_->paper_->c_variable ("between-system-padding");
452       Real padding = robust_scm2double (padding_scm, 0.0);
453
454       Line_division &div = current_configurations_[configuration_index];
455       uncompressed_line_details_.clear ();
456       for (vsize i = 0; i + 1 < current_chunks_.size (); i++)
457         {
458           vsize sys = next_system (current_chunks_[i]);
459           if (all_[sys].pscore_)
460             {
461               vsize start;
462               vsize end;
463               line_breaker_args (sys, current_chunks_[i], current_chunks_[i+1], &start, &end);
464
465               vector<Line_details> details = line_breaking_[sys].line_details (start, end, div[i]);
466               uncompressed_line_details_.insert (uncompressed_line_details_.end (), details.begin (), details.end ());
467             }
468           else
469             {
470               assert (div[i] == 1);
471               uncompressed_line_details_.push_back (Line_details (all_[sys].prob_));
472               uncompressed_line_details_.back ().padding_ = padding;
473             }
474         }
475       cached_line_details_ = compress_lines (uncompressed_line_details_);
476     }
477 }
478
479 void
480 Page_breaking::clear_line_details_cache ()
481 {
482   cached_configuration_index_ = VPOS;
483   cached_line_details_.clear ();
484   uncompressed_line_details_.clear ();
485 }
486
487 void
488 Page_breaking::line_divisions_rec (vsize system_count,
489                                    Line_division const &min_sys,
490                                    Line_division const &max_sys,
491                                    Line_division *cur_division)
492 {
493   vsize my_index = cur_division->size ();
494   vsize others_min = 0;
495   vsize others_max = 0;
496
497   for (vsize i = my_index + 1; i < min_sys.size (); i++)
498     {
499       others_min += min_sys[i];
500       others_max += max_sys[i];
501     }
502   others_max = min (others_max, system_count);
503   vsize real_min = max (min_sys[my_index], system_count - others_max);
504   vsize real_max = min (max_sys[my_index], system_count - others_min);
505
506   if (real_min > real_max || real_min <= 0)
507     {
508       /* this should never happen within a recursive call. If it happens
509          at all, it means that we were called with an unsolvable problem
510          and we should return an empty result */
511       assert (my_index == 0);
512       return;
513     }
514
515   for (vsize i = real_min; i <= real_max; i++)
516     {
517       cur_division->push_back (i);
518       if (my_index == min_sys.size () - 1)
519         current_configurations_.push_back (*cur_division);
520       else
521         line_divisions_rec (system_count - i, min_sys, max_sys, cur_division);
522       cur_division->pop_back ();
523     }
524 }
525
526 vsize
527 Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
528 {
529   vsize ret = 1;
530   Real cur_rod_height = 0;
531   Real cur_spring_height = 0;
532   Real cur_page_height = page_height (first_page_num, false);
533
534   cache_line_details (configuration);
535   for (vsize i = 0; i < cached_line_details_.size (); i++)
536     {
537       Real ext_len = cached_line_details_[i].extent_.length ();
538       Real next_rod_height = cur_rod_height + ext_len
539         + ((cur_rod_height > 0) ? cached_line_details_[i].padding_: 0);
540       Real next_spring_height = cur_spring_height + line_space (cached_line_details_[i]);
541       Real next_height = next_rod_height + (ragged () ? next_spring_height : 0);
542
543
544       if ((next_height > cur_page_height && cur_rod_height > 0)
545           || (i > 0
546               && cached_line_details_[i-1].page_permission_ == ly_symbol2scm ("force")))
547         {
548           cur_rod_height = ext_len;
549           cur_spring_height = line_space (cached_line_details_[i]);
550           cur_page_height = page_height (first_page_num + ret, false);
551           ret++;
552         }
553       else
554         {
555           cur_rod_height = next_rod_height;
556           cur_spring_height = next_spring_height;
557         }
558     }
559
560   /* there are two potential problems with the last page (because we didn't know
561      it was the last page until after we managed to fit all the systems to it):
562      - we are ragged-last but the last page has a compressed spring
563      - the value returned by page_height (num, true) is smaller than the
564        value returned by page_height (num, false) and it causes the page not to
565        fit.
566
567      In either case, we just need to add one more page. This is because the last
568      line will always fit on the extra page and by adding one more page to the
569      end, the previous page is no longer the last page, so our previous
570      calculations that treated it as a non-last page were ok.
571   */
572
573   cur_page_height = page_height (first_page_num + ret - 1, true);
574   Real cur_height = cur_rod_height + ((ragged_last () || ragged ()) ? cur_spring_height : 0);
575   if (cur_height > cur_page_height
576       /* don't increase the page count if the last page had only one system */
577       && cur_rod_height > cached_line_details_.back ().extent_.length ())
578     ret++;
579
580   assert (ret <= cached_line_details_.size ());
581   return ret;
582 }
583
584 Spacing_result
585 Page_breaking::space_systems_on_n_pages (vsize configuration, vsize n, vsize first_page_num)
586 {
587   Spacing_result ret;
588   assert (n >= min_page_count (configuration, first_page_num));
589
590   cache_line_details (configuration);
591   if (n > cached_line_details_.size ())
592     return Spacing_result ();
593   if (n == 1)
594     ret = space_systems_on_1_page (cached_line_details_,
595                                    page_height (first_page_num, last ()),
596                                    ragged () || (last () && ragged_last ()));
597   else if (n == 2)
598     ret = space_systems_on_2_pages (configuration, first_page_num);
599   else
600     {
601       Page_spacer ps (cached_line_details_, first_page_num, this);
602       ret = ps.solve (n);
603     }
604
605   return finalize_spacing_result (configuration, ret);
606 }
607
608 Real
609 Page_breaking::blank_page_penalty () const
610 {
611   SCM penalty_sym = last () ? ly_symbol2scm ("blank-last-page-force") : ly_symbol2scm ("blank-page-force");
612   return robust_scm2double (book_->paper_->lookup_variable (penalty_sym), 0.0);
613 }
614
615 Spacing_result
616 Page_breaking::space_systems_on_n_or_one_more_pages (vsize configuration, vsize n, vsize first_page_num)
617 {
618   Spacing_result n_res = space_systems_on_n_pages (configuration, n, first_page_num);
619   Spacing_result m_res = space_systems_on_n_pages (configuration, n+1, first_page_num);
620   Real penalty = blank_page_penalty ();
621   n_res.demerits_ += penalty;
622   n_res.force_.back () += penalty;
623
624   if (m_res.demerits_ < n_res.demerits_)
625     return m_res;
626   return n_res;
627 }
628
629 Spacing_result
630 Page_breaking::space_systems_on_best_pages (vsize configuration, vsize first_page_num)
631 {
632   vsize min_p_count = min_page_count (configuration, first_page_num);
633   Real odd_pages_penalty = blank_page_penalty ();
634
635   cache_line_details (configuration);
636   Page_spacer ps (cached_line_details_, first_page_num, this);
637   Spacing_result best = ps.solve (min_p_count);
638   best.force_.back () += (min_p_count % 2) ? odd_pages_penalty : 0;
639   best.demerits_ += (min_p_count % 2) ? odd_pages_penalty : 0;
640
641   for (vsize i = min_p_count+1; i <= cached_line_details_.size (); i++)
642     {
643       Spacing_result cur = ps.solve (i);
644       cur.demerits_ += (i % 2) ? odd_pages_penalty : 0;
645       if (cur.demerits_ < best.demerits_)
646         best = cur;
647     }
648
649   return finalize_spacing_result (configuration, best);
650 }
651
652 /* Calculate demerits and fix res.systems_per_page_ so that
653    it refers to the original line numbers, not the ones given by compress_lines (). */
654 Spacing_result
655 Page_breaking::finalize_spacing_result (vsize configuration, Spacing_result res)
656 {
657   cache_line_details (configuration);
658   res.systems_per_page_ = uncompress_solution (res.systems_per_page_, cached_line_details_);
659
660   Real line_force = 0;
661   Real line_penalty = 0;
662   Real page_force = 0;
663   Real page_weighting = robust_scm2double (book_->paper_->c_variable ("page-spacing-weight"), 10);
664
665   for (vsize i = 0; i < uncompressed_line_details_.size (); i++)
666     {
667       line_force += uncompressed_line_details_[i].force_ * uncompressed_line_details_[i].force_;
668       line_penalty += uncompressed_line_details_[i].break_penalty_;
669     }
670
671   for (vsize i = 0; i < res.force_.size (); i++)
672     {
673       Real f = res.force_[i];
674       if (isinf (f) && res.systems_per_page_[i] == 1)
675         f = 20000;
676
677       page_force += f * f;
678     }
679
680   /* for a while we tried averaging page and line forces across pages instead
681      of summing them, but it caused a problem: if there is a single page
682      with a very bad page force (for example because of a forced page break),
683      the page breaker will put in a _lot_ of pages so that the bad force
684      becomes averaged out over many pages. */
685   res.demerits_ = line_force + line_penalty + (page_force + res.penalty_) * page_weighting;
686   return res;
687
688 }
689
690 /* the cases for page_count = 1 or 2 can be done in O (n) time. Since they
691    are by far the most common cases, we have special functions for them.
692
693    space_systems_on_1_page has a different calling convention than most of the
694    space_systems functions. This is because space_systems_on_1_page is (unlike
695    the other space_systems functions) sometimes called on subsets of a full
696    configuration. */
697 Spacing_result
698 Page_breaking::space_systems_on_1_page (vector<Line_details> const &lines, Real page_height, bool ragged)
699 {
700   Page_spacing space (page_height);
701   Spacing_result ret;
702
703   for (vsize i = 0; i < lines.size (); i++)
704     space.append_system (lines[i]);
705
706   ret.systems_per_page_.push_back (lines.size ());
707   ret.force_.push_back (ragged ? min (space.force_, 0.0) : space.force_);
708   ret.penalty_ = lines.back ().page_penalty_ + lines.back ().turn_penalty_;
709
710   /* don't do finalize_spacing_result () because we are only an internal function */
711   return ret;
712 }
713
714 Spacing_result
715 Page_breaking::space_systems_on_2_pages (vsize configuration, vsize first_page_num)
716 {
717   Real page1_height = page_height (first_page_num, false);
718   Real page2_height = page_height (first_page_num+1, last ());
719   bool ragged1 = ragged ();
720   bool ragged2 = ragged () || (last () && ragged_last ());
721
722   /* if there is a forced break, this reduces to 2 1-page problems */
723   cache_line_details (configuration);
724   for (vsize i = 0; i + 1 < cached_line_details_.size (); i++)
725     if (cached_line_details_[i].page_permission_ == ly_symbol2scm ("force"))
726       {
727         vector<Line_details> lines1 (cached_line_details_.begin (), cached_line_details_.begin () + i + 1);
728         vector<Line_details> lines2 (cached_line_details_.begin () + i + 1, cached_line_details_.end ());
729         Spacing_result p1 = space_systems_on_1_page (lines1, page1_height, ragged1);
730         Spacing_result p2 = space_systems_on_1_page (lines2, page2_height, ragged2);
731
732         p1.systems_per_page_.push_back (p2.systems_per_page_[0]);
733         p1.force_.push_back (p2.force_[0]);
734         p1.penalty_ += p2.penalty_ - cached_line_details_[i].turn_penalty_;
735         return p1;
736       }
737
738   vector<Real> page1_force;
739   vector<Real> page2_force;
740   Page_spacing page1 (page1_height);
741   Page_spacing page2 (page2_height);
742
743   page1_force.resize (cached_line_details_.size () - 1, infinity_f);
744   page2_force.resize (cached_line_details_.size () - 1, infinity_f);
745
746   /* find the page 1 and page 2 forces for each page-breaking position */
747   for (vsize i = 0; i < page1_force.size (); i++)
748     {
749       page1.append_system (cached_line_details_[i]);
750       page2.prepend_system (cached_line_details_[cached_line_details_.size () - 1 - i]);
751       page1_force[i] = (ragged1 && page1.force_ < 0 && i > 0) ? infinity_f : page1.force_;
752
753       if (ragged2)
754         page2_force[page2_force.size () - 1 - i] =
755           (page2.force_ < 0 && i + 1 < page1_force.size ()) ? infinity_f : 0;
756       else
757         page2_force[page2_force.size () - 1 - i] = page2.force_;
758     }
759
760   /* find the position that minimises the sum of the page forces */
761   vsize best_sys_count = 1;
762   Real best_demerits = infinity_f;
763   for (vsize i = 0; i < page1_force.size (); i++)
764     {
765       Real dem = page1_force[i] * page1_force[i]
766         + page2_force[i] * page2_force[i]
767         + cached_line_details_[i+1].page_penalty_
768         + cached_line_details_.back ().page_penalty_ + cached_line_details_.back ().turn_penalty_;
769       if (dem < best_demerits)
770         {
771           best_demerits = dem;
772           best_sys_count = i+1;
773         }
774     }
775
776   Spacing_result ret;
777   ret.systems_per_page_.push_back (best_sys_count);
778   ret.systems_per_page_.push_back (cached_line_details_.size () - best_sys_count);
779   ret.force_.push_back (page1_force[best_sys_count-1]);
780   ret.force_.push_back (page2_force[best_sys_count-1]);
781   ret.penalty_ = cached_line_details_[best_sys_count-1].page_penalty_
782     + cached_line_details_.back ().page_penalty_
783     + cached_line_details_.back ().turn_penalty_;
784
785   /* don't do finalize_spacing_result () because we are only an internal function */
786   return ret;
787 }
788
789 bool
790 Page_breaking::all_lines_stretched (vsize configuration)
791 {
792   cache_line_details (configuration);
793   for (vsize i = 0; i < cached_line_details_.size (); i++)
794     if (cached_line_details_[i].force_ < 0)
795       return false;
796
797   return true;
798 }
799
800 Page_breaking::Line_division
801 Page_breaking::current_configuration (vsize configuration_index) const
802 {
803   return current_configurations_[configuration_index];
804 }
805
806 bool Page_breaking::last () const
807 {
808   return current_end_breakpoint_ == breaks_.size () - 1;
809 }