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