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