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