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