]> git.donarmstrong.com Git - lilypond.git/blob - lily/page-layout-problem.cc
Looser spacing for ragged-last-bottom; issue 1377
[lilypond.git] / lily / page-layout-problem.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2009--2011 Joe Neeman <joeneeman@gmail.com>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "page-layout-problem.hh"
21
22 #include "align-interface.hh"
23 #include "axis-group-interface.hh"
24 #include "hara-kiri-group-spanner.hh"
25 #include "international.hh"
26 #include "item.hh"
27 #include "output-def.hh"
28 #include "paper-book.hh"
29 #include "paper-column.hh"
30 #include "paper-score.hh"
31 #include "pointer-group-interface.hh"
32 #include "prob.hh"
33 #include "skyline-pair.hh"
34 #include "system.hh"
35 #include "text-interface.hh"
36
37 /*
38  Returns the number of footntoes associated with a given line.
39 */
40
41 vector<Grob *>
42 Page_layout_problem::get_footnote_grobs (SCM lines)
43 {
44   vector<Grob *> footnotes;
45   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
46     {
47       if (Grob *g = unsmob_grob (scm_car (s)))
48         {
49           System *sys = dynamic_cast<System *> (g);
50           if (!sys)
51             {
52               programming_error ("got a grob for footnotes that wasn't a System");
53               continue;
54             }
55           extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
56           footnotes.insert (footnotes.end (), footnote_grobs.begin (), footnote_grobs.end ());
57         }
58       else if (Prob *p = unsmob_prob (scm_car (s)))
59         {
60           SCM stencils = p->get_property ("footnotes");
61           if (stencils == SCM_EOL)
62             continue;
63           for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
64             footnotes.push_back (0);
65         }
66     }
67
68   return footnotes;
69 }
70
71 vsize
72 Page_layout_problem::get_footnote_count (SCM lines)
73 {
74   vector<Grob *> notes = get_footnote_grobs (lines);
75   return notes.size ();
76 }
77
78 SCM
79 Page_layout_problem::get_footnotes_from_lines (SCM lines)
80 {
81   if (!scm_is_pair (lines))
82     return SCM_EOL;
83
84   bool footnotes_added;
85   if (Grob *g = unsmob_grob (scm_car (lines)))
86     footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
87   else if (Prob *p = unsmob_prob (scm_car (lines)))
88     footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
89   else
90     {
91       programming_error ("Systems on a page must be a prob or grob.");
92       return SCM_EOL;
93     }
94   if (!footnotes_added)
95     {
96       programming_error ("Footnotes must be added to lines before they are retrieved.");
97       return SCM_EOL;
98     }
99
100   SCM out = SCM_EOL;
101   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
102     {
103       if (Grob *g = unsmob_grob (scm_car (s)))
104         out = scm_cons (g->get_property ("footnote-stencil"), out);
105       else if (Prob *p = unsmob_prob (scm_car (s)))
106         out = scm_cons (p->get_property ("footnote-stencil"), out);
107       else
108         programming_error ("Systems on a page must be a prob or grob.");
109     }
110
111   return scm_reverse (out);
112 }
113
114 /*
115    Adds a footnote stencil to each system.  This stencil may
116    itself be comprised of several footnotes.
117
118    This is a long function, but it seems better to keep it intact rather than
119    splitting it into parts.
120 */
121
122 void
123 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
124 {
125   /*
126     first, we have to see how many footnotes are on this page.
127     we need to do this first so that we can line them up
128   */
129
130   Output_def *paper = pb->paper_;
131
132   if (!paper)
133     {
134       programming_error ("Cannot get footnotes because there is no valid paper block.");
135       return;
136     }
137
138   SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
139   if (!scm_is_pair (number_footnote_table))
140     number_footnote_table = SCM_EOL;
141   SCM numbering_function = paper->c_variable ("footnote-numbering-function");
142   SCM layout = paper->self_scm ();
143   SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
144                           paper->self_scm ());
145   Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
146   Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
147
148   vector<Grob *> fn_grobs = get_footnote_grobs (lines);
149   vsize fn_count = fn_grobs.size ();
150
151   // now, make the footnote stencils with the numbering function
152   SCM numbers = SCM_EOL;
153   SCM in_text_numbers = SCM_EOL;
154   /*
155     TODO: This recalculates numbering every time this function is called, including once
156     after the balloon prints are called.  Although it is not a huge computational drain,
157     it'd be more elegant to turn this calculation off when it is no longer needed.
158
159     In a separate commit, it'd be nice to streamline the way that page layout property
160     is handled so that the process of building `config's in page-breaking does result
161     in duplicated work, either by making this process less complicated or (preferably)
162     by passing its results downstream.
163   */
164   vector<SCM> footnote_number_markups; // Holds the numbering markups.
165   vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
166   for (vsize i = 0; i < fn_count; i++)
167     {
168       if (fn_grobs[i])
169         {
170           SCM assertion_function = fn_grobs[i]->get_property ("numbering-assertion-function");
171           if (ly_is_procedure (assertion_function))
172             (void) scm_call_1 (assertion_function, scm_from_int (counter));
173         }
174       SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
175       Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
176       if (!s)
177         {
178           programming_error ("Your numbering function needs to return a stencil.");
179           markup = SCM_EOL;
180           s = new Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL);
181         }
182       footnote_number_markups.push_back (markup);
183       footnote_number_stencils.push_back (s);
184       counter++;
185     }
186
187   // find the maximum X_AXIS length
188   Real max_length = -infinity_f;
189   for (vsize i = 0; i < fn_count; i++)
190     max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
191
192   /*
193     translate each stencil such that it attains the correct maximum length and bundle the
194     footnotes into a scheme object.
195   */
196   SCM *tail = &numbers;
197   SCM *in_text_tail = &in_text_numbers;
198
199   for (vsize i = 0; i < fn_count; i++)
200     {
201       *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
202       in_text_tail = SCM_CDRLOC (*in_text_tail);
203       footnote_number_stencils[i]->translate_axis ((max_length
204                                                     - footnote_number_stencils[i]->extent (X_AXIS).length ()),
205                                                    X_AXIS);
206       *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
207       tail = SCM_CDRLOC (*tail);
208     }
209   // build the footnotes
210
211   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
212     {
213       // Take care of musical systems.
214       if (Grob *g = unsmob_grob (scm_car (s)))
215         {
216           System *sys = dynamic_cast<System *> (g);
217           if (!sys)
218             {
219               programming_error ("got a grob for footnotes that wasn't a System");
220               continue;
221             }
222           Stencil mol;
223           Stencil in_note_mol;
224           extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
225           for (vsize i = 0; i < footnote_grobs.size (); i++)
226             {
227               Grob *footnote = footnote_grobs[i];
228               SCM footnote_markup = footnote->get_property ("footnote-text");
229               if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
230                 if (orig->is_broken ())
231                   footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
232
233               SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
234                                       paper->self_scm ());
235
236               SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
237                                                                    props, footnote_markup);
238
239               Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
240               bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
241               if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
242                 {
243                   if (orig->is_broken ())
244                     for (vsize i = 0; i < orig->broken_intos_.size (); i++)
245                       do_numbering = do_numbering
246                                      || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
247                 }
248               if (do_numbering)
249                 {
250                   SCM annotation_scm = scm_car (in_text_numbers);
251                   footnote->set_property ("text", annotation_scm);
252                   if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
253                     {
254                       orig->set_property ("text", annotation_scm);
255                       if (orig->is_broken ())
256                         for (vsize i = 0; i < orig->broken_intos_.size (); i++)
257                           orig->broken_intos_[i]->set_property ("text", annotation_scm);
258                     }
259
260                   Stencil *annotation = unsmob_stencil (scm_car (numbers));
261                   annotation->translate_axis ((footnote_stencil->extent (Y_AXIS)[UP]
262                                                + number_raise
263                                                - annotation->extent (Y_AXIS)[UP]),
264                                               Y_AXIS);
265                   footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
266                   numbers = scm_cdr (numbers);
267                   in_text_numbers = scm_cdr (in_text_numbers);
268                 }
269               if (!footnote_stencil->is_empty ())
270                 {
271                   if (to_boolean (footnote->get_property ("footnote")))
272                     mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
273                   else
274                     in_note_mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
275                 }
276             }
277           sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
278           sys->set_property ("footnote-stencil", mol.smobbed_copy ());
279         }
280       // Take care of top-level markups
281       else if (Prob *p = unsmob_prob (scm_car (s)))
282         {
283           SCM stencils = p->get_property ("footnotes");
284           Stencil mol;
285
286           for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
287             {
288               Stencil footnote_stencil;
289               Stencil *footnote = unsmob_stencil (scm_caddar (st));
290               footnote_stencil.add_stencil (*footnote);
291               bool do_numbering = to_boolean (scm_cadar (st));
292               SCM in_text_stencil = Stencil ().smobbed_copy ();
293               if (do_numbering)
294                 {
295                   Stencil *annotation = unsmob_stencil (scm_car (numbers));
296                   SCM in_text_annotation = scm_car (in_text_numbers);
297                   in_text_stencil = Text_interface::interpret_markup (layout,
298                                                                       props,
299                                                                       in_text_annotation);
300                   if (!unsmob_stencil (in_text_stencil))
301                     in_text_stencil = SCM_EOL;
302                   annotation->translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
303                                                + number_raise
304                                                - annotation->extent (Y_AXIS)[UP]),
305                                               Y_AXIS);
306                   footnote_stencil.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
307                   numbers = scm_cdr (numbers);
308                   in_text_numbers = scm_cdr (in_text_numbers);
309                 }
310               number_footnote_table = scm_cons (scm_cons (scm_caar (st),
311                                                           in_text_stencil),
312                                                 number_footnote_table);
313               if (!footnote_stencil.is_empty ())
314                 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
315             }
316           p->set_property ("footnote-stencil", mol.smobbed_copy ());
317         }
318     }
319
320   // note that this line of code doesn't do anything if numbering isn't turned on
321   pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
322 }
323
324 Stencil *
325 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
326 {
327   SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
328                           paper->self_scm ());
329
330   SCM markup = paper->c_variable ("footnote-separator-markup");
331
332   if (!Text_interface::is_markup (markup))
333     return NULL;
334
335   SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
336                                                            props, markup);
337
338   Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
339
340   return footnote_separator;
341 }
342
343 void
344 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
345 {
346   if (!foot && scm_is_pair (footnotes))
347     {
348       warning ("Must have a footer to add footnotes.");
349       return;
350     }
351   bool footnotes_found = false;
352   Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
353   Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
354
355   footnotes = scm_reverse (footnotes);
356
357   for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
358     {
359       Stencil *stencil = unsmob_stencil (scm_car (s));
360
361       if (!stencil)
362         continue;
363
364       if (!stencil->is_empty ())
365         {
366           foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
367           footnotes_found = true;
368         }
369     }
370
371   if (footnotes_found)
372     {
373       Stencil *separator = get_footnote_separator_stencil (pb->paper_);
374       if (separator)
375         foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
376     }
377 }
378
379 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
380   : bottom_skyline_ (DOWN)
381 {
382   Prob *page = unsmob_prob (page_scm);
383   header_height_ = 0;
384   footer_height_ = 0;
385   header_padding_ = 0;
386   footer_padding_ = 0;
387   page_height_ = 100;
388   force_ = 0;
389
390   if (page)
391     {
392       Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
393       Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
394
395       if (pb && pb->paper_)
396         {
397           SCM footnotes = get_footnotes_from_lines (systems);
398           add_footnotes_to_footer (footnotes, foot, pb);
399         }
400       else
401         warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
402
403       header_height_ = head ? head->extent (Y_AXIS).length () : 0;
404       footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
405       page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
406     }
407
408   // Initially, bottom_skyline_ represents the top of the page. Make
409   // it solid, so that the top of the first system will be forced
410   // below the top of the printable area.
411   bottom_skyline_.set_minimum_height (-header_height_);
412
413   SCM system_system_spacing = SCM_EOL;
414   SCM score_system_spacing = SCM_EOL;
415   SCM markup_system_spacing = SCM_EOL;
416   SCM score_markup_spacing = SCM_EOL;
417   SCM markup_markup_spacing = SCM_EOL;
418
419   // top_system_spacing controls the spring from the top of the printable
420   // area to the first staff. It allows the user to control the offset of
421   // the first staff (as opposed to the top of the first system) from the
422   // top of the page. Similarly for last_bottom_spacing.
423   SCM top_system_spacing = SCM_EOL;
424   SCM last_bottom_spacing = SCM_EOL;
425   if (pb && pb->paper_)
426     {
427       Output_def *paper = pb->paper_;
428       system_system_spacing = paper->c_variable ("system-system-spacing");
429       score_system_spacing = paper->c_variable ("score-system-spacing");
430       markup_system_spacing = paper->c_variable ("markup-system-spacing");
431       score_markup_spacing = paper->c_variable ("score-markup-spacing");
432       markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
433       last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
434       top_system_spacing = paper->c_variable ("top-system-spacing");
435       if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
436         top_system_spacing = paper->c_variable ("top-markup-spacing");
437
438       // Note: the page height here does _not_ reserve space for headers and
439       // footers. This is because we want to anchor the top-system-spacing
440       // spring at the _top_ of the header.
441       page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
442                       + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
443
444       read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
445       read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
446       in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
447       in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
448     }
449   bool last_system_was_title = false;
450
451   for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
452     {
453       bool first = (s == systems);
454
455       if (Grob *g = unsmob_grob (scm_car (s)))
456         {
457           System *sys = dynamic_cast<System *> (g);
458           if (!sys)
459             {
460               programming_error ("got a grob for vertical spacing that wasn't a System");
461               continue;
462             }
463
464           SCM spec = system_system_spacing;
465           if (first)
466             spec = top_system_spacing;
467           else if (last_system_was_title)
468             spec = markup_system_spacing;
469           else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
470             spec = score_system_spacing;
471
472           Spring spring (0, 0);
473           Real padding = 0.0;
474           Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
475           alter_spring_from_spacing_spec (spec, &spring);
476           read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
477
478           append_system (sys, spring, indent, padding);
479           last_system_was_title = false;
480         }
481       else if (Prob *p = unsmob_prob (scm_car (s)))
482         {
483           SCM spec = first ? top_system_spacing
484                      : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
485           Spring spring (0, 0);
486           Real padding = 0.0;
487           alter_spring_from_spacing_spec (spec, &spring);
488           read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
489
490           append_prob (p, spring, padding);
491           last_system_was_title = true;
492         }
493       else
494         programming_error ("got a system that was neither a Grob nor a Prob");
495     }
496
497   Spring last_spring (0, 0);
498   Real last_padding = 0;
499   alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
500   read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
501   last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
502   springs_.push_back (last_spring);
503
504   if (elements_.size ())
505     {
506       Real bottom_padding = 0;
507
508       // TODO: junk bottom-space now that we have last-bottom-spacing?
509       // bottom-space has the flexibility that one can do it per-system.
510       // NOTE: bottom-space is misnamed since it is not stretchable space.
511       if (Prob *p = elements_.back ().prob)
512         bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
513       else if (elements_.back ().staves.size ())
514         {
515           SCM details = get_details (elements_.back ());
516           bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
517                                                             details,
518                                                             SCM_BOOL_F),
519                                               0.0);
520         }
521       page_height_ -= bottom_padding;
522     }
523 }
524
525 void
526 Page_layout_problem::set_header_height (Real height)
527 {
528   header_height_ = height;
529 }
530
531 void
532 Page_layout_problem::set_footer_height (Real height)
533 {
534   footer_height_ = height;
535 }
536
537 void
538 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
539 {
540   Grob *align = sys->get_vertical_alignment ();
541   if (!align)
542     return;
543
544   align->set_property ("positioning-done", SCM_BOOL_T);
545
546   extract_grob_set (align, "elements", all_elts);
547   vector<Grob *> elts = filter_dead_elements (all_elts);
548   vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
549   vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
550
551   Skyline up_skyline (UP);
552   Skyline down_skyline (DOWN);
553   build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
554   up_skyline.shift (indent);
555   down_skyline.shift (indent);
556   Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
557
558   if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
559     {
560       sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
561       sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
562       Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
563       sky->set_minimum_height (sky->max_height ()
564                                + in_note_direction_
565                                * (in_note_padding_
566                                   + in_note_stencil->extent (Y_AXIS).length ()));
567     }
568
569   /*
570     We need to call distance with skyline-horizontal-padding because
571     the system skyline-horizontal-padding is not added during the creation
572     of an individual staff.  So we add the padding for the distance check
573     at the time of adding in the system.
574   */
575   Real minimum_distance = up_skyline.distance (bottom_skyline_,
576                                                robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
577                                                    0))
578                           + padding;
579
580   Spring spring_copy = spring;
581   spring_copy.ensure_min_distance (minimum_distance);
582   springs_.push_back (spring_copy);
583
584   bottom_skyline_ = down_skyline;
585   elements_.push_back (Element (elts, minimum_offsets, padding));
586
587   // Add the springs for the VerticalAxisGroups in this system.
588
589   // If the user has specified the offsets of the individual staves, fix the
590   // springs at the given distances. Otherwise, use stretchable springs.
591   SCM details = get_details (elements_.back ());
592   SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
593   vsize last_spaceable_staff = 0;
594   bool found_spaceable_staff = false;
595   for (vsize i = 0; i < elts.size (); ++i)
596     {
597       if (is_spaceable (elts[i]))
598         {
599           // We don't add a spring for the first staff, since
600           // we are only adding springs _between_ staves here.
601           if (!found_spaceable_staff)
602             {
603               found_spaceable_staff = true;
604               last_spaceable_staff = i;
605               continue;
606             }
607
608           Spring spring (0.5, 0.0);
609           SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
610           alter_spring_from_spacing_spec (spec, &spring);
611
612           springs_.push_back (spring);
613           Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
614           springs_.back ().ensure_min_distance (min_distance);
615
616           if (scm_is_pair (manual_dists))
617             {
618               if (scm_is_number (scm_car (manual_dists)))
619                 {
620                   Real dy = scm_to_double (scm_car (manual_dists));
621
622                   springs_.back ().set_distance (dy);
623                   springs_.back ().set_min_distance (dy);
624                   springs_.back ().set_inverse_stretch_strength (0);
625                 }
626               manual_dists = scm_cdr (manual_dists);
627             }
628           last_spaceable_staff = i;
629         }
630     }
631
632   // Corner case: there was only one staff, and it wasn't spaceable.
633   // Mark it spaceable, because we do not allow non-spaceable staves
634   // to be at the top or bottom of a system.
635   if (!found_spaceable_staff && elts.size ())
636     mark_as_spaceable (elts[0]);
637 }
638
639 void
640 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
641 {
642   Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
643   Real minimum_distance = 0;
644   bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
645
646   if (sky)
647     {
648       minimum_distance = (*sky)[UP].distance (bottom_skyline_);
649       bottom_skyline_ = (*sky)[DOWN];
650     }
651   else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
652     {
653       Interval iv = sten->extent (Y_AXIS);
654       minimum_distance = iv[UP] - bottom_skyline_.max_height ();
655
656       bottom_skyline_.clear ();
657       bottom_skyline_.set_minimum_height (iv[DOWN]);
658     }
659
660   Spring spring_copy = spring;
661   if (tight_spacing)
662     {
663       spring_copy.set_min_distance (minimum_distance);
664       spring_copy.set_inverse_stretch_strength (0.0);
665       spring_copy.set_distance (0.0);
666     }
667   else
668     spring_copy.ensure_min_distance (minimum_distance + padding);
669
670   springs_.push_back (spring_copy);
671   elements_.push_back (Element (prob, padding));
672 }
673
674 /**
675    For ragged-last pages, we usually want to stretch the page so that it
676    is not much more compressed than the previous page.  Here, if ragged is
677    true and you pass a value of fixed_force that !isinf, then I will try
678    to space this page using the given force.  If it does not fit, I will
679    resort to just filling the page (non-raggedly).
680 */
681 void
682 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
683 {
684   Simple_spacer spacer;
685
686   for (vsize i = 0; i < springs_.size (); ++i)
687     spacer.add_spring (springs_[i]);
688
689   if (ragged && !isinf (fixed_force))
690     {
691       // We need to tell the spacer it isn't ragged.  Otherwise, it will
692       // refuse to stretch.
693       spacer.solve (page_height_, false);
694
695       if (spacer.configuration_length (fixed_force) <= page_height_)
696         spacer.set_force (fixed_force);
697     }
698   else
699     spacer.solve (page_height_, ragged);
700
701   solution_ = spacer.spring_positions ();
702   force_ = spacer.force ();
703
704   if (!spacer.fits ())
705     {
706       Real overflow = spacer.configuration_length (spacer.force ())
707                       - page_height_;
708       if (ragged && overflow < 1e-6)
709         warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
710       else
711         {
712           warning (_f ("cannot fit music on page: overflow is %f",
713                        overflow));
714           warning (_ ("compressing music to fit"));
715           vsize space_count = solution_.size ();
716           Real spacing_increment = overflow / (space_count - 2);
717           for (vsize i = 2; i < space_count; i++)
718             solution_[i] -= (i - 1) * spacing_increment;
719         }
720     }
721 }
722
723 Real
724 Page_layout_problem::force () const
725 {
726   return force_;
727 }
728
729 // The solution_ vector stores the position of every live VerticalAxisGroup
730 // and every title. From that information,
731 // 1) within each system, stretch the staves so they land at the right position
732 // 2) find the offset of each system (relative to the printable area of the page).
733 // TODO: this function is getting too long, maybe split it up?
734 SCM
735 Page_layout_problem::find_system_offsets ()
736 {
737   SCM system_offsets = SCM_EOL;
738   SCM *tail = &system_offsets;
739
740   // spring_idx 0 is the top of the page. Interesting values start from 1.
741   vsize spring_idx = 1;
742   vector<Grob *> loose_lines;
743   vector<Real> loose_line_min_distances;
744   Grob *last_spaceable_line = 0;
745   Real last_spaceable_line_translation = 0;
746   Interval last_title_extent;
747   for (vsize i = 0; i < elements_.size (); ++i)
748     {
749       if (elements_[i].prob)
750         {
751           *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
752           tail = SCM_CDRLOC (*tail);
753           Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
754
755           // Lay out any non-spaceable lines between this line and
756           // the last one.
757           if (loose_lines.size ())
758             {
759               Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
760               Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
761                                    + elements_[i].padding);
762
763               loose_line_min_distances.push_back (min_distance);
764               loose_lines.push_back (0);
765
766               distribute_loose_lines (loose_lines, loose_line_min_distances,
767                                       last_spaceable_line_translation, -solution_[spring_idx]);
768               loose_lines.clear ();
769               loose_line_min_distances.clear ();
770             }
771
772           last_spaceable_line = 0;
773           last_spaceable_line_translation = -solution_[spring_idx];
774           last_title_extent = prob_extent;
775           spring_idx++;
776         }
777       else
778         {
779           // Getting this signs right here is a little tricky. The configuration
780           // we return has zero at the top of the page and positive numbers further
781           // down, as does the solution_ vector.  Within a staff, however, positive
782           // numbers are up.
783           // TODO: perhaps change the way the page 'configuration variable works so
784           // that it is consistent with the usual up/down sign conventions in
785           // Lilypond. Then this would be less confusing.
786
787           // These two positions are relative to the page (with positive numbers being
788           // down).
789           Real first_staff_position = solution_[spring_idx];
790           Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
791           Real system_position = first_staff_position + first_staff_min_translation;
792
793           // Position the staves within this system.
794           vector<Real> const &min_offsets = elements_[i].min_offsets;
795           bool found_spaceable_staff = false;
796           for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
797             {
798               Grob *staff = elements_[i].staves[staff_idx];
799               staff->set_property ("system-Y-offset", scm_from_double (-system_position));
800
801               if (is_spaceable (staff))
802                 {
803                   // this is relative to the system: negative numbers are down.
804                   staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
805
806                   // Lay out any non-spaceable lines between this line and
807                   // the last one.
808                   if (loose_lines.size ())
809                     {
810                       if (staff_idx)
811                         loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
812                       else
813                         loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
814                       loose_lines.push_back (staff);
815
816                       distribute_loose_lines (loose_lines, loose_line_min_distances,
817                                               last_spaceable_line_translation, -solution_[spring_idx]);
818                       loose_lines.clear ();
819                       loose_line_min_distances.clear ();
820                     }
821                   last_spaceable_line = staff;
822                   last_spaceable_line_translation = -solution_[spring_idx];
823                   found_spaceable_staff = true;
824                   spring_idx++;
825                 }
826               else
827                 {
828                   if (loose_lines.empty ())
829                     loose_lines.push_back (last_spaceable_line);
830
831                   if (staff_idx)
832                     // NOTE: the way we do distances between loose lines (and other lines too, actually)
833                     // is not the most accurate way possible: we only insert rods between adjacent
834                     // lines.  To be more accurate, we could insert rods between non-adjacent lines
835                     // using a scheme similar to the one in set_column_rods.
836                     loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
837                   else
838                     {
839                       // this is the first line in a system
840                       Real min_dist = 0;
841                       if (loose_lines.back ())
842                         // distance to the final line in the preceding system,
843                         // including 'system-system-spacing 'padding
844                         min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
845                                                                             staff,
846                                                                             Y_AXIS)
847                                     + elements_[i].padding);
848                       else if (!last_title_extent.is_empty ())
849                         // distance to the preceding title,
850                         //  including 'markup-system-spacing 'padding
851                         min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
852                                     + elements_[i].padding);
853                       else // distance to the top margin
854                         min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
855
856                       loose_line_min_distances.push_back (min_dist);
857                     }
858                   loose_lines.push_back (staff);
859                 }
860             }
861
862           // Corner case: even if a system has no live staves, it still takes up
863           // one spring (a system with one live staff also takes up one spring),
864           // which we need to increment past.
865           if (!found_spaceable_staff)
866             spring_idx++;
867
868           *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
869           tail = SCM_CDRLOC (*tail);
870         }
871     }
872
873   if (loose_lines.size ())
874     {
875       Grob *last = loose_lines.back ();
876       Interval last_ext = last->extent (last, Y_AXIS);
877       loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
878       loose_lines.push_back (0);
879
880       distribute_loose_lines (loose_lines, loose_line_min_distances,
881                               last_spaceable_line_translation, -page_height_);
882
883     }
884
885   assert (spring_idx == solution_.size () - 1);
886   return system_offsets;
887 }
888
889 // Given two lines that are already spaced (the first and last
890 // elements of loose_lines), distribute some unspaced lines between
891 // them.
892 // first_translation and last_translation are relative to the page.
893 void
894 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
895                                              vector<Real> const &min_distances,
896                                              Real first_translation, Real last_translation)
897 {
898   Simple_spacer spacer;
899   for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
900     {
901       SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
902       Spring spring (1.0, 0.0);
903       alter_spring_from_spacing_spec (spec, &spring);
904       spring.ensure_min_distance (min_distances[i]);
905       spacer.add_spring (spring);
906     }
907
908   // Remember: offsets are decreasing, since we're going from UP to DOWN!
909   spacer.solve (first_translation - last_translation, false);
910
911   vector<Real> solution = spacer.spring_positions ();
912   for (vsize i = 1; i + 1 < solution.size (); ++i)
913     {
914       Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
915       loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
916     }
917 }
918
919 SCM
920 Page_layout_problem::fixed_force_solution (Real force)
921 {
922   solve_rod_spring_problem (true, force);
923   return find_system_offsets ();
924 }
925
926 SCM
927 Page_layout_problem::solution (bool ragged)
928 {
929   solve_rod_spring_problem (ragged, -infinity_f);
930   return find_system_offsets ();
931 }
932
933 // Build upper and lower skylines for a system. We don't yet know the positions
934 // of the staves within the system, so we make the skyline as conservative as
935 // possible. That is, for the upper skyline, we pretend that all of the staves
936 // in the system are packed together close to the top system; for the lower
937 // skyline, we pretend that all of the staves are packed together close to
938 // the bottom system.
939 //
940 // The upper skyline is relative to the top staff; the lower skyline is relative to
941 // the bottom staff.
942 void
943 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
944                                            vector<Real> const &minimum_translations,
945                                            Skyline *up,
946                                            Skyline *down)
947 {
948   if (minimum_translations.empty ())
949     return;
950
951   assert (staves.size () == minimum_translations.size ());
952   Real first_translation = minimum_translations[0];
953   Real last_spaceable_dy = 0;
954   Real first_spaceable_dy = 0;
955   bool found_spaceable_staff = false;
956
957   for (vsize i = 0; i < staves.size (); ++i)
958     {
959       Real dy = minimum_translations[i] - first_translation;
960       Grob *g = staves[i];
961       Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
962       if (sky)
963         {
964           up->raise (-dy);
965           up->merge ((*sky)[UP]);
966           up->raise (dy);
967
968           down->raise (-dy);
969           down->merge ((*sky)[DOWN]);
970           down->raise (dy);
971         }
972       if (is_spaceable (staves[i]))
973         {
974           if (!found_spaceable_staff)
975             {
976               found_spaceable_staff = true;
977               first_spaceable_dy = dy;
978             }
979           last_spaceable_dy = dy;
980         }
981     }
982
983   // Leave the up skyline at a position relative
984   // to the top spaceable staff.
985   up->raise (-first_spaceable_dy);
986
987   // Leave the down skyline at a position
988   // relative to the bottom spaceable staff.
989   down->raise (-last_spaceable_dy);
990 }
991
992 Interval
993 Page_layout_problem::prob_extent (Prob *p)
994 {
995   Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
996   return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
997 }
998
999 Interval
1000 Page_layout_problem::first_staff_extent (Element const &e)
1001 {
1002   if (e.prob)
1003     return prob_extent (e.prob);
1004   else if (e.staves.size ())
1005     return e.staves[0]->extent (e.staves[0], Y_AXIS);
1006
1007   return Interval (0, 0);
1008 }
1009
1010 Interval
1011 Page_layout_problem::last_staff_extent (Element const &e)
1012 {
1013   if (e.prob)
1014     return prob_extent (e.prob);
1015   else if (e.staves.size ())
1016     return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1017
1018   return Interval (0, 0);
1019 }
1020
1021 SCM
1022 Page_layout_problem::get_details (Element const &elt)
1023 {
1024   if (elt.staves.empty ())
1025     return SCM_EOL;
1026
1027   return get_details (elt.staves.back ()->get_system ());
1028 }
1029
1030 SCM
1031 Page_layout_problem::get_details (Grob *g)
1032 {
1033   Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1034   return left_bound->get_property ("line-break-system-details");
1035 }
1036
1037 bool
1038 Page_layout_problem::is_spaceable (Grob *g)
1039 {
1040   return !scm_is_number (g->get_property ("staff-affinity"));
1041 }
1042
1043 void
1044 Page_layout_problem::mark_as_spaceable (Grob *g)
1045 {
1046   g->set_property ("staff-affinity", SCM_BOOL_F);
1047 }
1048
1049 bool
1050 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1051 {
1052   SCM pair = scm_sloppy_assq (sym, spec);
1053   if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1054     {
1055       *dest = scm_to_double (scm_cdr (pair));
1056       return true;
1057     }
1058   return false;
1059 }
1060
1061 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1062 // Otherwise, return -infinity_f.
1063 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1064 // its alignment.
1065 Real
1066 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1067 {
1068   Spanner *after_sp = dynamic_cast<Spanner *> (after);
1069   SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1070                      ? ly_symbol2scm ("spaceable-fixed-spacing")
1071                      : ly_symbol2scm ("loose-fixed-spacing");
1072   if (pure)
1073     {
1074       // The result of this function doesn't depend on "end," so we can reduce the
1075       // size of the cache by ignoring it.
1076       SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1077       if (scm_is_number (cached))
1078         return robust_scm2double (cached, 0.0);
1079     }
1080
1081   Real ret = -infinity_f;
1082
1083   // If we're pure, then paper-columns have not had their systems set,
1084   // and so elts[i]->get_system () is unreliable.
1085   System *sys = pure ? Grob::get_system (before) : before->get_system ();
1086   Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1087
1088   if (is_spaceable (before) && is_spaceable (after) && left_bound)
1089     {
1090       SCM details = left_bound->get_property ("line-break-system-details");
1091       SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1092       if (scm_is_pair (manual_dists))
1093         {
1094           SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1095           if (scm_is_number (forced))
1096             ret = max (ret, scm_to_double (forced));
1097         }
1098     }
1099
1100   // Cache the result.  As above, we ignore "end."
1101   if (pure)
1102     after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1103
1104   return ret;
1105 }
1106
1107 static SCM
1108 add_stretchability (SCM alist, Real stretch)
1109 {
1110   if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1111     return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1112
1113   return alist;
1114 }
1115
1116 // We want to put a large stretch between a non-spaceable line and its
1117 // non-affinity staff. We want to put an even larger stretch between
1118 // a non-spaceable line and the top/bottom of the page. That way,
1119 // a spacing-affinity UP line at the bottom of the page will still be
1120 // placed close to its staff.
1121 const double LARGE_STRETCH = 10e5;
1122 const double HUGE_STRETCH = 10e7;
1123
1124 // Returns the spacing spec connecting BEFORE to AFTER.
1125 SCM
1126 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1127 {
1128   // If there are no spacing wishes, return a very flexible spring.
1129   // This will occur, for example, if there are lyrics at the bottom of
1130   // the page, in which case we don't want the spring from the lyrics to
1131   // the bottom of the page to have much effect.
1132   if (!before || !after)
1133     return add_stretchability (SCM_EOL, HUGE_STRETCH);
1134
1135   if (is_spaceable (before))
1136     {
1137       if (is_spaceable (after))
1138         return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1139       else
1140         {
1141           Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1142           return (affinity == DOWN)
1143                  ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1144                                        LARGE_STRETCH)
1145                  : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1146         }
1147     }
1148   else
1149     {
1150       if (is_spaceable (after))
1151         {
1152           Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1153           return (affinity == UP)
1154                  ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1155                                        LARGE_STRETCH)
1156                  : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1157         }
1158       else
1159         {
1160           Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1161           Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1162           static bool warned = false;
1163           if (after_affinity > before_affinity
1164               && !warned && !pure)
1165             {
1166               warning (_ ("staff-affinities should only decrease"));
1167               warned = true;
1168             }
1169           if (before_affinity != UP)
1170             return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1171           else if (after_affinity != DOWN)
1172             return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1173           return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1174                                      LARGE_STRETCH);
1175         }
1176     }
1177
1178   assert (0);
1179   return SCM_BOOL_F;
1180 }
1181
1182 void
1183 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1184 {
1185   Real space;
1186   Real stretch;
1187   Real min_dist;
1188   if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1189     spring->set_distance (space);
1190   if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1191     spring->set_min_distance (min_dist);
1192   spring->set_default_strength ();
1193
1194   if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1195     spring->set_inverse_stretch_strength (stretch);
1196 }
1197
1198 vector<Grob *>
1199 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1200 {
1201   vector<Grob *> output;
1202   for (vsize i = 0; i < input.size (); ++i)
1203     {
1204       if (Hara_kiri_group_spanner::has_interface (input[i]))
1205         Hara_kiri_group_spanner::consider_suicide (input[i]);
1206
1207       if (input[i]->is_live ())
1208         output.push_back (input[i]);
1209     }
1210
1211   return output;
1212 }