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