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