2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2009--2012 Joe Neeman <joeneeman@gmail.com>
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.
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.
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/>.
20 #include "page-layout-problem.hh"
22 #include "align-interface.hh"
23 #include "axis-group-interface.hh"
24 #include "hara-kiri-group-spanner.hh"
25 #include "international.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"
33 #include "skyline-pair.hh"
35 #include "text-interface.hh"
38 Returns the number of footntoes associated with a given line.
42 Page_layout_problem::get_footnote_grobs (SCM lines)
44 vector<Grob *> footnotes;
45 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
47 if (Grob *g = unsmob_grob (scm_car (s)))
49 System *sys = dynamic_cast<System *> (g);
52 programming_error ("got a grob for footnotes that wasn't a System");
55 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
56 footnotes.insert (footnotes.end (), footnote_grobs.begin (), footnote_grobs.end ());
58 else if (Prob *p = unsmob_prob (scm_car (s)))
60 SCM stencils = p->get_property ("footnotes");
61 if (stencils == SCM_EOL)
63 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
64 footnotes.push_back (0);
72 Page_layout_problem::get_footnote_count (SCM lines)
74 vector<Grob *> notes = get_footnote_grobs (lines);
79 Page_layout_problem::get_footnotes_from_lines (SCM lines)
81 if (!scm_is_pair (lines))
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"));
91 programming_error ("Systems on a page must be a prob or grob.");
96 programming_error ("Footnotes must be added to lines before they are retrieved.");
101 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
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);
108 programming_error ("Systems on a page must be a prob or grob.");
111 return scm_reverse_x (out, SCM_EOL);
115 Adds a footnote stencil to each system. This stencil may
116 itself be comprised of several footnotes.
118 This is a long function, but it seems better to keep it intact rather than
119 splitting it into parts.
123 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
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
130 Output_def *paper = pb->paper_;
134 programming_error ("Cannot get footnotes because there is no valid paper block.");
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"),
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);
148 vector<Grob *> fn_grobs = get_footnote_grobs (lines);
149 vsize fn_count = fn_grobs.size ();
151 // now, make the footnote stencils with the numbering function
152 SCM numbers = SCM_EOL;
153 SCM in_text_numbers = SCM_EOL;
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.
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.
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++)
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));
174 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
175 Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
178 programming_error ("Your numbering function needs to return a stencil.");
179 footnote_number_markups.push_back (SCM_EOL);
180 footnote_number_stencils.push_back (Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL));
184 footnote_number_markups.push_back (markup);
185 footnote_number_stencils.push_back (*s);
190 // find the maximum X_AXIS length
191 Real max_length = -infinity_f;
192 for (vsize i = 0; i < fn_count; i++)
193 max_length = max (max_length, footnote_number_stencils[i].extent (X_AXIS).length ());
196 translate each stencil such that it attains the correct maximum length and bundle the
197 footnotes into a scheme object.
200 for (vsize i = 0; i < fn_count; i++)
202 in_text_numbers = scm_cons (footnote_number_markups[i], in_text_numbers);
203 footnote_number_stencils[i].translate_axis ((max_length
204 - footnote_number_stencils[i].extent (X_AXIS).length ()),
206 numbers = scm_cons (footnote_number_stencils[i].smobbed_copy (), numbers);
209 in_text_numbers = scm_reverse_x (in_text_numbers, SCM_EOL);
210 numbers = scm_reverse_x (numbers, SCM_EOL);
212 // build the footnotes
214 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
216 // Take care of musical systems.
217 if (Grob *g = unsmob_grob (scm_car (s)))
219 System *sys = dynamic_cast<System *> (g);
222 programming_error ("got a grob for footnotes that wasn't a System");
227 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
228 for (vsize i = 0; i < footnote_grobs.size (); i++)
230 Grob *footnote = footnote_grobs[i];
231 SCM footnote_markup = footnote->get_property ("footnote-text");
232 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
233 if (orig->is_broken ())
234 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
236 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
239 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
240 props, footnote_markup);
242 Stencil footnote_stencil = *unsmob_stencil (footnote_stl);
243 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
244 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
246 if (orig->is_broken ())
247 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
248 do_numbering = do_numbering
249 || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
253 SCM annotation_scm = scm_car (in_text_numbers);
254 footnote->set_property ("text", annotation_scm);
255 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
257 orig->set_property ("text", annotation_scm);
258 if (orig->is_broken ())
259 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
260 orig->broken_intos_[i]->set_property ("text", annotation_scm);
263 Stencil annotation = *unsmob_stencil (scm_car (numbers));
264 annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
266 - annotation.extent (Y_AXIS)[UP]),
268 footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
269 numbers = scm_cdr (numbers);
270 in_text_numbers = scm_cdr (in_text_numbers);
272 if (!footnote_stencil.is_empty ())
274 if (to_boolean (footnote->get_property ("footnote")))
275 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
277 in_note_mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
280 sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
281 sys->set_property ("footnote-stencil", mol.smobbed_copy ());
283 // Take care of top-level markups
284 else if (Prob *p = unsmob_prob (scm_car (s)))
286 SCM stencils = p->get_property ("footnotes");
289 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
291 Stencil footnote_stencil = *unsmob_stencil (scm_caddar (st));
292 bool do_numbering = to_boolean (scm_cadar (st));
293 SCM in_text_stencil = Stencil ().smobbed_copy ();
296 Stencil annotation = *unsmob_stencil (scm_car (numbers));
297 SCM in_text_annotation = scm_car (in_text_numbers);
298 in_text_stencil = Text_interface::interpret_markup (layout,
301 if (!unsmob_stencil (in_text_stencil))
302 in_text_stencil = SCM_EOL;
303 annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
305 - annotation.extent (Y_AXIS)[UP]),
307 footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
308 numbers = scm_cdr (numbers);
309 in_text_numbers = scm_cdr (in_text_numbers);
311 number_footnote_table = scm_cons (scm_cons (scm_caar (st),
313 number_footnote_table);
314 if (!footnote_stencil.is_empty ())
315 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
317 p->set_property ("footnote-stencil", mol.smobbed_copy ());
321 // note that this line of code doesn't do anything if numbering isn't turned on
322 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
326 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
328 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
331 SCM markup = paper->c_variable ("footnote-separator-markup");
333 if (!Text_interface::is_markup (markup))
336 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
339 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
341 return footnote_separator ? *footnote_separator : Stencil ();
345 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil foot, Paper_book *pb)
348 bool footnotes_found = false;
349 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
350 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
352 footnotes = scm_reverse (footnotes);
354 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
356 Stencil *stencil = unsmob_stencil (scm_car (s));
361 if (!stencil->is_empty ())
363 foot.add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
364 footnotes_found = true;
370 Stencil separator = get_footnote_separator_stencil (pb->paper_);
371 if (!separator.is_empty ())
372 foot.add_at_edge (Y_AXIS, UP, separator, footnote_padding);
378 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
379 : bottom_skyline_ (DOWN)
381 Prob *page = unsmob_prob (page_scm);
382 bottom_loose_baseline_ = 0;
392 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
393 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
395 Stencil foot_stencil = foot ? *foot : Stencil ();
397 if (pb && pb->paper_)
399 SCM footnotes = get_footnotes_from_lines (systems);
400 foot_stencil = add_footnotes_to_footer (footnotes, foot_stencil, pb);
403 warning (_ ("A page layout problem has been initiated that cannot accommodate footnotes."));
405 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
406 footer_height_ = foot_stencil.extent (Y_AXIS).length ();
407 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
410 // Initially, bottom_skyline_ represents the top of the page. Make
411 // it solid, so that the top of the first system will be forced
412 // below the top of the printable area.
413 bottom_skyline_.set_minimum_height (-header_height_);
415 SCM system_system_spacing = SCM_EOL;
416 SCM score_system_spacing = SCM_EOL;
417 SCM markup_system_spacing = SCM_EOL;
418 SCM score_markup_spacing = SCM_EOL;
419 SCM markup_markup_spacing = SCM_EOL;
421 // top_system_spacing controls the spring from the top of the printable
422 // area to the first staff. It allows the user to control the offset of
423 // the first staff (as opposed to the top of the first system) from the
424 // top of the page. Similarly for last_bottom_spacing.
425 SCM top_system_spacing = SCM_EOL;
426 SCM last_bottom_spacing = SCM_EOL;
427 if (pb && pb->paper_)
429 Output_def *paper = pb->paper_;
430 system_system_spacing = paper->c_variable ("system-system-spacing");
431 score_system_spacing = paper->c_variable ("score-system-spacing");
432 markup_system_spacing = paper->c_variable ("markup-system-spacing");
433 score_markup_spacing = paper->c_variable ("score-markup-spacing");
434 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
435 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
436 top_system_spacing = paper->c_variable ("top-system-spacing");
437 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
438 top_system_spacing = paper->c_variable ("top-markup-spacing");
440 // Note: the page height here does _not_ reserve space for headers and
441 // footers. This is because we want to anchor the top-system-spacing
442 // spring at the _top_ of the header.
443 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
444 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
446 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
447 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
448 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
449 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
451 bool last_system_was_title = false;
453 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
455 bool first = (s == systems);
457 if (Grob *g = unsmob_grob (scm_car (s)))
459 System *sys = dynamic_cast<System *> (g);
462 programming_error ("got a grob for vertical spacing that wasn't a System");
466 SCM spec = system_system_spacing;
468 spec = top_system_spacing;
469 else if (last_system_was_title)
470 spec = markup_system_spacing;
471 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
472 spec = score_system_spacing;
474 Spring spring (0, 0);
476 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
477 alter_spring_from_spacing_spec (spec, &spring);
478 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
480 append_system (sys, spring, indent, padding);
481 last_system_was_title = false;
483 else if (Prob *p = unsmob_prob (scm_car (s)))
485 SCM spec = first ? top_system_spacing
486 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
487 Spring spring (0, 0);
489 alter_spring_from_spacing_spec (spec, &spring);
490 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
492 append_prob (p, spring, padding);
493 last_system_was_title = true;
496 programming_error ("got a system that was neither a Grob nor a Prob");
499 Spring last_spring (0, 0);
500 Real last_padding = 0;
501 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
502 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
503 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
504 springs_.push_back (last_spring);
506 if (elements_.size ())
508 Real bottom_padding = 0;
510 // TODO: junk bottom-space now that we have last-bottom-spacing?
511 // bottom-space has the flexibility that one can do it per-system.
512 // NOTE: bottom-space is misnamed since it is not stretchable space.
513 if (Prob *p = elements_.back ().prob)
514 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
515 else if (elements_.back ().staves.size ())
517 SCM details = get_details (elements_.back ());
518 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
523 page_height_ -= bottom_padding;
528 Page_layout_problem::set_header_height (Real height)
530 header_height_ = height;
534 Page_layout_problem::set_footer_height (Real height)
536 footer_height_ = height;
540 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
542 Grob *align = sys->get_vertical_alignment ();
546 align->set_property ("positioning-done", SCM_BOOL_T);
548 extract_grob_set (align, "elements", all_elts);
549 vector<Grob *> elts = filter_dead_elements (all_elts);
550 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
551 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
553 Skyline up_skyline (UP);
554 Skyline down_skyline (DOWN);
555 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
556 up_skyline.shift (indent);
557 down_skyline.shift (indent);
558 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
560 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
562 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
563 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
564 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
565 sky->set_minimum_height (sky->max_height ()
568 + in_note_stencil->extent (Y_AXIS).length ()));
572 We need to call distance with skyline-horizontal-padding because
573 the system skyline-horizontal-padding is not added during the creation
574 of an individual staff. So we add the padding for the distance check
575 at the time of adding in the system.
577 Real minimum_distance = up_skyline.distance (bottom_skyline_,
578 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
582 Spring spring_copy = spring;
583 spring_copy.ensure_min_distance (minimum_distance);
584 springs_.push_back (spring_copy);
586 if (elts.size () && !is_spaceable (elts[0]))
588 // store the minimum distance, considering relative indents,
590 Skyline first_skyline (UP);
591 Skyline_pair *sky = Skyline_pair::unsmob (elts[0]->get_property ("vertical-skylines"));
593 first_skyline.merge ((*sky)[UP]);
594 first_skyline.shift (indent);
595 minimum_distance = first_skyline.distance (bottom_skyline_) - bottom_loose_baseline_;
597 bottom_skyline_ = down_skyline;
598 elements_.push_back (Element (elts, minimum_offsets, minimum_distance, padding));
600 // Add the springs for the VerticalAxisGroups in this system.
602 // If the user has specified the offsets of the individual staves, fix the
603 // springs at the given distances. Otherwise, use stretchable springs.
604 SCM details = get_details (elements_.back ());
605 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
606 vsize last_spaceable_staff = 0;
607 bool found_spaceable_staff = false;
608 for (vsize i = 0; i < elts.size (); ++i)
610 if (is_spaceable (elts[i]))
612 if (!found_spaceable_staff)
614 // Ensure space for any loose lines above this system
616 springs_.back ().ensure_min_distance (bottom_loose_baseline_
617 - minimum_offsets_with_min_dist[i]
619 found_spaceable_staff = true;
620 last_spaceable_staff = i;
621 // We don't add a spring for the first staff, since
622 // we are only adding springs _between_ staves here.
626 Spring spring (0.5, 0.0);
627 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
628 alter_spring_from_spacing_spec (spec, &spring);
630 springs_.push_back (spring);
631 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
632 springs_.back ().ensure_min_distance (min_distance);
634 if (scm_is_pair (manual_dists))
636 if (scm_is_number (scm_car (manual_dists)))
638 Real dy = scm_to_double (scm_car (manual_dists));
640 springs_.back ().set_distance (dy);
641 springs_.back ().set_min_distance (dy);
642 springs_.back ().set_inverse_stretch_strength (0);
644 manual_dists = scm_cdr (manual_dists);
646 last_spaceable_staff = i;
650 bottom_loose_baseline_ = found_spaceable_staff
651 ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
652 - minimum_offsets_with_min_dist.back ())
655 // Corner case: there was only one staff, and it wasn't spaceable.
656 // Mark it spaceable, because we do not allow non-spaceable staves
657 // to be at the top or bottom of a system.
658 if (!found_spaceable_staff && elts.size ())
659 mark_as_spaceable (elts[0]);
663 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
665 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
666 Real minimum_distance = 0;
667 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
671 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
672 bottom_skyline_ = (*sky)[DOWN];
674 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
676 Interval iv = sten->extent (Y_AXIS);
677 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
679 bottom_skyline_.clear ();
680 bottom_skyline_.set_minimum_height (iv[DOWN]);
683 Spring spring_copy = spring;
686 spring_copy.set_min_distance (minimum_distance);
687 spring_copy.set_inverse_stretch_strength (0.0);
688 spring_copy.set_distance (0.0);
691 spring_copy.ensure_min_distance (minimum_distance + padding);
693 springs_.push_back (spring_copy);
694 elements_.push_back (Element (prob, padding));
698 For ragged-last pages, we usually want to stretch the page so that it
699 is not much more compressed than the previous page. Here, if ragged is
700 true and you pass a value of fixed_force that !isinf, then I will try
701 to space this page using the given force. If it does not fit, I will
702 resort to just filling the page (non-raggedly).
705 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
707 Simple_spacer spacer;
709 for (vsize i = 0; i < springs_.size (); ++i)
710 spacer.add_spring (springs_[i]);
712 if (ragged && !isinf (fixed_force))
714 // We need to tell the spacer it isn't ragged. Otherwise, it will
715 // refuse to stretch.
716 spacer.solve (page_height_, false);
718 if (spacer.configuration_length (fixed_force) <= page_height_)
719 spacer.set_force (fixed_force);
722 spacer.solve (page_height_, ragged);
724 solution_ = spacer.spring_positions ();
725 force_ = spacer.force ();
729 Real overflow = spacer.configuration_length (spacer.force ())
731 if (ragged && overflow < 1e-6)
732 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
735 warning (_f ("cannot fit music on page: overflow is %f",
737 warning (_ ("compressing music to fit"));
738 vsize space_count = solution_.size ();
739 Real spacing_increment = overflow / (space_count - 2);
740 for (vsize i = 2; i < space_count; i++)
741 solution_[i] -= (i - 1) * spacing_increment;
747 Page_layout_problem::force () const
752 // The solution_ vector stores the position of every live VerticalAxisGroup
753 // and every title. From that information,
754 // 1) within each system, stretch the staves so they land at the right position
755 // 2) find the offset of each system (relative to the printable area of the page).
756 // TODO: this function is getting too long, maybe split it up?
758 Page_layout_problem::find_system_offsets ()
760 SCM system_offsets = SCM_EOL;
761 SCM *tail = &system_offsets;
763 // spring_idx 0 is the top of the page. Interesting values start from 1.
764 vsize spring_idx = 1;
765 vector<Grob *> loose_lines;
766 vector<Real> loose_line_min_distances;
767 Grob *last_spaceable_line = 0;
768 Real last_spaceable_line_translation = 0;
769 Interval last_title_extent;
770 for (vsize i = 0; i < elements_.size (); ++i)
772 if (elements_[i].prob)
774 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
775 tail = SCM_CDRLOC (*tail);
776 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
778 // Lay out any non-spaceable lines between this line and
780 if (loose_lines.size ())
782 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
783 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
784 + elements_[i].padding);
786 loose_line_min_distances.push_back (min_distance);
787 loose_lines.push_back (0);
789 distribute_loose_lines (loose_lines, loose_line_min_distances,
790 last_spaceable_line_translation, -solution_[spring_idx]);
791 loose_lines.clear ();
792 loose_line_min_distances.clear ();
795 last_spaceable_line = 0;
796 last_spaceable_line_translation = -solution_[spring_idx];
797 last_title_extent = prob_extent;
802 // Getting this signs right here is a little tricky. The configuration
803 // we return has zero at the top of the page and positive numbers further
804 // down, as does the solution_ vector. Within a staff, however, positive
806 // TODO: perhaps change the way the page 'configuration variable works so
807 // that it is consistent with the usual up/down sign conventions in
808 // Lilypond. Then this would be less confusing.
810 // These two positions are relative to the page (with positive numbers being
812 Real first_staff_position = solution_[spring_idx];
813 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
814 Real system_position = first_staff_position + first_staff_min_translation;
816 // Position the staves within this system.
817 vector<Real> const &min_offsets = elements_[i].min_offsets;
818 bool found_spaceable_staff = false;
819 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
821 Grob *staff = elements_[i].staves[staff_idx];
822 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
824 if (is_spaceable (staff))
826 // this is relative to the system: negative numbers are down.
827 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
829 // Lay out any non-spaceable lines between this line and
831 if (loose_lines.size ())
834 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
837 // A null line to break any staff-affinity from the previous system
838 loose_line_min_distances.push_back (0.0);
839 loose_lines.push_back (0);
840 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
842 loose_lines.push_back (staff);
844 distribute_loose_lines (loose_lines, loose_line_min_distances,
845 last_spaceable_line_translation, -solution_[spring_idx]);
846 loose_lines.clear ();
847 loose_line_min_distances.clear ();
849 last_spaceable_line = staff;
850 last_spaceable_line_translation = -solution_[spring_idx];
851 found_spaceable_staff = true;
854 else // ! is_spaceable
856 if (loose_lines.empty ())
857 loose_lines.push_back (last_spaceable_line);
860 // NOTE: the way we do distances between loose lines (and other lines too, actually)
861 // is not the most accurate way possible: we only insert rods between adjacent
862 // lines. To be more accurate, we could insert rods between non-adjacent lines
863 // using a scheme similar to the one in set_column_rods.
864 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
867 // this is the first line in a system
869 if (loose_lines.back ())
871 // distance to the final line in the preceding system,
872 // including 'system-system-spacing 'padding
873 min_dist = elements_[i].min_distance + elements_[i].padding;
874 // A null line to break any staff-affinity for the previous system
875 loose_line_min_distances.push_back (0.0);
876 loose_lines.push_back (0);
878 else if (!last_title_extent.is_empty ())
879 // distance to the preceding title,
880 // including 'markup-system-wg 'padding
881 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
882 + elements_[i].padding);
883 else // distance to the top margin
884 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
886 loose_line_min_distances.push_back (min_dist);
888 loose_lines.push_back (staff);
892 // Corner case: even if a system has no live staves, it still takes up
893 // one spring (a system with one live staff also takes up one spring),
894 // which we need to increment past.
895 if (!found_spaceable_staff)
898 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
899 tail = SCM_CDRLOC (*tail);
903 if (loose_lines.size ())
905 Grob *last = loose_lines.back ();
906 Interval last_ext = last->extent (last, Y_AXIS);
907 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
908 loose_lines.push_back (0);
910 distribute_loose_lines (loose_lines, loose_line_min_distances,
911 last_spaceable_line_translation, -page_height_);
915 assert (spring_idx == solution_.size () - 1);
916 return system_offsets;
919 // Given two lines that are already spaced (the first and last
920 // elements of loose_lines), distribute some unspaced lines between
922 // first_translation and last_translation are relative to the page.
924 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
925 vector<Real> const &min_distances,
926 Real first_translation, Real last_translation)
928 Simple_spacer spacer;
929 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
931 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
932 Spring spring (1.0, 0.0);
933 alter_spring_from_spacing_spec (spec, &spring);
934 spring.ensure_min_distance (min_distances[i]);
935 spacer.add_spring (spring);
938 // Remember: offsets are decreasing, since we're going from UP to DOWN!
939 spacer.solve (first_translation - last_translation, false);
941 vector<Real> solution = spacer.spring_positions ();
942 for (vsize i = 1; i + 1 < solution.size (); ++i)
945 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
946 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
951 Page_layout_problem::fixed_force_solution (Real force)
953 solve_rod_spring_problem (true, force);
954 return find_system_offsets ();
958 Page_layout_problem::solution (bool ragged)
960 solve_rod_spring_problem (ragged, -infinity_f);
961 return find_system_offsets ();
964 // Build upper and lower skylines for a system. We don't yet know the positions
965 // of the staves within the system, so we make the skyline as conservative as
966 // possible. That is, for the upper skyline, we pretend that all of the staves
967 // in the system are packed together close to the top system; for the lower
968 // skyline, we pretend that all of the staves are packed together close to
969 // the bottom system.
971 // The upper skyline is relative to the top staff; the lower skyline is relative to
974 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
975 vector<Real> const &minimum_translations,
979 if (minimum_translations.empty ())
982 assert (staves.size () == minimum_translations.size ());
983 Real first_translation = minimum_translations[0];
984 Real last_spaceable_dy = 0;
985 Real first_spaceable_dy = 0;
986 bool found_spaceable_staff = false;
988 for (vsize i = 0; i < staves.size (); ++i)
990 Real dy = minimum_translations[i] - first_translation;
992 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
996 up->merge ((*sky)[UP]);
1000 down->merge ((*sky)[DOWN]);
1003 if (is_spaceable (staves[i]))
1005 if (!found_spaceable_staff)
1007 found_spaceable_staff = true;
1008 first_spaceable_dy = dy;
1010 last_spaceable_dy = dy;
1014 // Leave the up skyline at a position relative
1015 // to the top spaceable staff.
1016 up->raise (-first_spaceable_dy);
1018 // Leave the down skyline at a position
1019 // relative to the bottom spaceable staff.
1020 down->raise (-last_spaceable_dy);
1024 Page_layout_problem::prob_extent (Prob *p)
1026 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
1027 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1031 Page_layout_problem::first_staff_extent (Element const &e)
1034 return prob_extent (e.prob);
1035 else if (e.staves.size ())
1036 return e.staves[0]->extent (e.staves[0], Y_AXIS);
1038 return Interval (0, 0);
1042 Page_layout_problem::last_staff_extent (Element const &e)
1045 return prob_extent (e.prob);
1046 else if (e.staves.size ())
1047 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1049 return Interval (0, 0);
1053 Page_layout_problem::get_details (Element const &elt)
1055 if (elt.staves.empty ())
1058 return get_details (elt.staves.back ()->get_system ());
1062 Page_layout_problem::get_details (Grob *g)
1064 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1065 return left_bound->get_property ("line-break-system-details");
1069 Page_layout_problem::is_spaceable (Grob *g)
1071 return !scm_is_number (g->get_property ("staff-affinity"));
1075 Page_layout_problem::mark_as_spaceable (Grob *g)
1077 g->set_property ("staff-affinity", SCM_BOOL_F);
1081 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1083 SCM pair = scm_sloppy_assq (sym, spec);
1084 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1086 *dest = scm_to_double (scm_cdr (pair));
1092 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1093 // Otherwise, return -infinity_f.
1094 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1097 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1099 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1100 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1101 ? ly_symbol2scm ("spaceable-fixed-spacing")
1102 : ly_symbol2scm ("loose-fixed-spacing");
1105 // The result of this function doesn't depend on "end," so we can reduce the
1106 // size of the cache by ignoring it.
1107 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1108 if (scm_is_number (cached))
1109 return robust_scm2double (cached, 0.0);
1112 Real ret = -infinity_f;
1114 // If we're pure, then paper-columns have not had their systems set,
1115 // and so elts[i]->get_system () is unreliable.
1116 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1117 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1119 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1121 SCM details = left_bound->get_property ("line-break-system-details");
1122 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1123 if (scm_is_pair (manual_dists))
1125 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1126 if (scm_is_number (forced))
1127 ret = max (ret, scm_to_double (forced));
1131 // Cache the result. As above, we ignore "end."
1133 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1139 add_stretchability (SCM alist, Real stretch)
1141 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1142 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1147 // We want to put a large stretch between a non-spaceable line and its
1148 // non-affinity staff. We want to put an even larger stretch between
1149 // a non-spaceable line and the top/bottom of the page. That way,
1150 // a spacing-affinity UP line at the bottom of the page will still be
1151 // placed close to its staff.
1152 const double LARGE_STRETCH = 10e5;
1153 const double HUGE_STRETCH = 10e7;
1155 // Returns the spacing spec connecting BEFORE to AFTER.
1157 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1159 // If there are no spacing wishes, return a very flexible spring.
1160 // This will occur, for example, if there are lyrics at the bottom of
1161 // the page, in which case we don't want the spring from the lyrics to
1162 // the bottom of the page to have much effect.
1163 if (!before || !after)
1164 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1166 if (is_spaceable (before))
1168 if (is_spaceable (after))
1169 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1172 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1173 return (affinity == DOWN)
1174 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1176 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1181 if (is_spaceable (after))
1183 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1184 return (affinity == UP)
1185 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1187 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1191 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1192 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1193 static bool warned = false;
1194 if (after_affinity > before_affinity
1195 && !warned && !pure)
1197 warning (_ ("staff-affinities should only decrease"));
1200 if (before_affinity != UP)
1201 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1202 else if (after_affinity != DOWN)
1203 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1204 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1214 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1219 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1220 spring->set_distance (space);
1221 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1222 spring->set_min_distance (min_dist);
1223 spring->set_default_strength ();
1225 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1226 spring->set_inverse_stretch_strength (stretch);
1230 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1232 vector<Grob *> output;
1233 for (vsize i = 0; i < input.size (); ++i)
1235 if (Hara_kiri_group_spanner::has_interface (input[i]))
1236 Hara_kiri_group_spanner::consider_suicide (input[i]);
1238 if (input[i]->is_live ())
1239 output.push_back (input[i]);