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