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 (out);
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 bottom_skyline_ = down_skyline;
587 elements_.push_back (Element (elts, minimum_offsets, padding));
589 // Add the springs for the VerticalAxisGroups in this system.
591 // If the user has specified the offsets of the individual staves, fix the
592 // springs at the given distances. Otherwise, use stretchable springs.
593 SCM details = get_details (elements_.back ());
594 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
595 vsize last_spaceable_staff = 0;
596 bool found_spaceable_staff = false;
597 for (vsize i = 0; i < elts.size (); ++i)
599 if (is_spaceable (elts[i]))
601 if (!found_spaceable_staff)
603 // Ensure space for any loose lines above this system
605 springs_.back ().ensure_min_distance (bottom_loose_baseline_
606 - minimum_offsets_with_min_dist[i]
608 found_spaceable_staff = true;
609 last_spaceable_staff = i;
610 // We don't add a spring for the first staff, since
611 // we are only adding springs _between_ staves here.
615 Spring spring (0.5, 0.0);
616 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
617 alter_spring_from_spacing_spec (spec, &spring);
619 springs_.push_back (spring);
620 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
621 springs_.back ().ensure_min_distance (min_distance);
623 if (scm_is_pair (manual_dists))
625 if (scm_is_number (scm_car (manual_dists)))
627 Real dy = scm_to_double (scm_car (manual_dists));
629 springs_.back ().set_distance (dy);
630 springs_.back ().set_min_distance (dy);
631 springs_.back ().set_inverse_stretch_strength (0);
633 manual_dists = scm_cdr (manual_dists);
635 last_spaceable_staff = i;
639 bottom_loose_baseline_ = found_spaceable_staff
640 ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
641 - minimum_offsets_with_min_dist.back ())
644 // Corner case: there was only one staff, and it wasn't spaceable.
645 // Mark it spaceable, because we do not allow non-spaceable staves
646 // to be at the top or bottom of a system.
647 if (!found_spaceable_staff && elts.size ())
648 mark_as_spaceable (elts[0]);
652 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
654 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
655 Real minimum_distance = 0;
656 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
660 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
661 bottom_skyline_ = (*sky)[DOWN];
663 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
665 Interval iv = sten->extent (Y_AXIS);
666 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
668 bottom_skyline_.clear ();
669 bottom_skyline_.set_minimum_height (iv[DOWN]);
672 Spring spring_copy = spring;
675 spring_copy.set_min_distance (minimum_distance);
676 spring_copy.set_inverse_stretch_strength (0.0);
677 spring_copy.set_distance (0.0);
680 spring_copy.ensure_min_distance (minimum_distance + padding);
682 springs_.push_back (spring_copy);
683 elements_.push_back (Element (prob, padding));
687 For ragged-last pages, we usually want to stretch the page so that it
688 is not much more compressed than the previous page. Here, if ragged is
689 true and you pass a value of fixed_force that !isinf, then I will try
690 to space this page using the given force. If it does not fit, I will
691 resort to just filling the page (non-raggedly).
694 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
696 Simple_spacer spacer;
698 for (vsize i = 0; i < springs_.size (); ++i)
699 spacer.add_spring (springs_[i]);
701 if (ragged && !isinf (fixed_force))
703 // We need to tell the spacer it isn't ragged. Otherwise, it will
704 // refuse to stretch.
705 spacer.solve (page_height_, false);
707 if (spacer.configuration_length (fixed_force) <= page_height_)
708 spacer.set_force (fixed_force);
711 spacer.solve (page_height_, ragged);
713 solution_ = spacer.spring_positions ();
714 force_ = spacer.force ();
718 Real overflow = spacer.configuration_length (spacer.force ())
720 if (ragged && overflow < 1e-6)
721 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
724 warning (_f ("cannot fit music on page: overflow is %f",
726 warning (_ ("compressing music to fit"));
727 vsize space_count = solution_.size ();
728 Real spacing_increment = overflow / (space_count - 2);
729 for (vsize i = 2; i < space_count; i++)
730 solution_[i] -= (i - 1) * spacing_increment;
736 Page_layout_problem::force () const
741 // The solution_ vector stores the position of every live VerticalAxisGroup
742 // and every title. From that information,
743 // 1) within each system, stretch the staves so they land at the right position
744 // 2) find the offset of each system (relative to the printable area of the page).
745 // TODO: this function is getting too long, maybe split it up?
747 Page_layout_problem::find_system_offsets ()
749 SCM system_offsets = SCM_EOL;
750 SCM *tail = &system_offsets;
752 // spring_idx 0 is the top of the page. Interesting values start from 1.
753 vsize spring_idx = 1;
754 vector<Grob *> loose_lines;
755 vector<Real> loose_line_min_distances;
756 Grob *last_spaceable_line = 0;
757 Real last_spaceable_line_translation = 0;
758 Interval last_title_extent;
759 for (vsize i = 0; i < elements_.size (); ++i)
761 if (elements_[i].prob)
763 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
764 tail = SCM_CDRLOC (*tail);
765 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
767 // Lay out any non-spaceable lines between this line and
769 if (loose_lines.size ())
771 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
772 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
773 + elements_[i].padding);
775 loose_line_min_distances.push_back (min_distance);
776 loose_lines.push_back (0);
778 distribute_loose_lines (loose_lines, loose_line_min_distances,
779 last_spaceable_line_translation, -solution_[spring_idx]);
780 loose_lines.clear ();
781 loose_line_min_distances.clear ();
784 last_spaceable_line = 0;
785 last_spaceable_line_translation = -solution_[spring_idx];
786 last_title_extent = prob_extent;
791 // Getting this signs right here is a little tricky. The configuration
792 // we return has zero at the top of the page and positive numbers further
793 // down, as does the solution_ vector. Within a staff, however, positive
795 // TODO: perhaps change the way the page 'configuration variable works so
796 // that it is consistent with the usual up/down sign conventions in
797 // Lilypond. Then this would be less confusing.
799 // These two positions are relative to the page (with positive numbers being
801 Real first_staff_position = solution_[spring_idx];
802 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
803 Real system_position = first_staff_position + first_staff_min_translation;
805 // Position the staves within this system.
806 vector<Real> const &min_offsets = elements_[i].min_offsets;
807 bool found_spaceable_staff = false;
808 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
810 Grob *staff = elements_[i].staves[staff_idx];
811 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
813 if (is_spaceable (staff))
815 // this is relative to the system: negative numbers are down.
816 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
818 // Lay out any non-spaceable lines between this line and
820 if (loose_lines.size ())
823 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
826 // A null line to break any staff-affinity from the previous system
827 loose_line_min_distances.push_back (0.0);
828 loose_lines.push_back (0);
829 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
831 loose_lines.push_back (staff);
833 distribute_loose_lines (loose_lines, loose_line_min_distances,
834 last_spaceable_line_translation, -solution_[spring_idx]);
835 loose_lines.clear ();
836 loose_line_min_distances.clear ();
838 last_spaceable_line = staff;
839 last_spaceable_line_translation = -solution_[spring_idx];
840 found_spaceable_staff = true;
845 if (loose_lines.empty ())
846 loose_lines.push_back (last_spaceable_line);
849 // NOTE: the way we do distances between loose lines (and other lines too, actually)
850 // is not the most accurate way possible: we only insert rods between adjacent
851 // lines. To be more accurate, we could insert rods between non-adjacent lines
852 // using a scheme similar to the one in set_column_rods.
853 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
856 // this is the first line in a system
858 if (loose_lines.back ())
860 // distance to the final line in the preceding system,
861 // including 'system-system-spacing 'padding
862 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
864 + elements_[i].padding);
865 // A null line to break any staff-affinity for the previous system
866 loose_line_min_distances.push_back (0.0);
867 loose_lines.push_back (0);
869 else if (!last_title_extent.is_empty ())
870 // distance to the preceding title,
871 // including 'markup-system-spacing 'padding
872 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
873 + elements_[i].padding);
874 else // distance to the top margin
875 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
877 loose_line_min_distances.push_back (min_dist);
879 loose_lines.push_back (staff);
883 // Corner case: even if a system has no live staves, it still takes up
884 // one spring (a system with one live staff also takes up one spring),
885 // which we need to increment past.
886 if (!found_spaceable_staff)
889 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
890 tail = SCM_CDRLOC (*tail);
894 if (loose_lines.size ())
896 Grob *last = loose_lines.back ();
897 Interval last_ext = last->extent (last, Y_AXIS);
898 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
899 loose_lines.push_back (0);
901 distribute_loose_lines (loose_lines, loose_line_min_distances,
902 last_spaceable_line_translation, -page_height_);
906 assert (spring_idx == solution_.size () - 1);
907 return system_offsets;
910 // Given two lines that are already spaced (the first and last
911 // elements of loose_lines), distribute some unspaced lines between
913 // first_translation and last_translation are relative to the page.
915 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
916 vector<Real> const &min_distances,
917 Real first_translation, Real last_translation)
919 Simple_spacer spacer;
920 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
922 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
923 Spring spring (1.0, 0.0);
924 alter_spring_from_spacing_spec (spec, &spring);
925 spring.ensure_min_distance (min_distances[i]);
926 spacer.add_spring (spring);
929 // Remember: offsets are decreasing, since we're going from UP to DOWN!
930 spacer.solve (first_translation - last_translation, false);
932 vector<Real> solution = spacer.spring_positions ();
933 for (vsize i = 1; i + 1 < solution.size (); ++i)
936 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
937 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
942 Page_layout_problem::fixed_force_solution (Real force)
944 solve_rod_spring_problem (true, force);
945 return find_system_offsets ();
949 Page_layout_problem::solution (bool ragged)
951 solve_rod_spring_problem (ragged, -infinity_f);
952 return find_system_offsets ();
955 // Build upper and lower skylines for a system. We don't yet know the positions
956 // of the staves within the system, so we make the skyline as conservative as
957 // possible. That is, for the upper skyline, we pretend that all of the staves
958 // in the system are packed together close to the top system; for the lower
959 // skyline, we pretend that all of the staves are packed together close to
960 // the bottom system.
962 // The upper skyline is relative to the top staff; the lower skyline is relative to
965 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
966 vector<Real> const &minimum_translations,
970 if (minimum_translations.empty ())
973 assert (staves.size () == minimum_translations.size ());
974 Real first_translation = minimum_translations[0];
975 Real last_spaceable_dy = 0;
976 Real first_spaceable_dy = 0;
977 bool found_spaceable_staff = false;
979 for (vsize i = 0; i < staves.size (); ++i)
981 Real dy = minimum_translations[i] - first_translation;
983 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
987 up->merge ((*sky)[UP]);
991 down->merge ((*sky)[DOWN]);
994 if (is_spaceable (staves[i]))
996 if (!found_spaceable_staff)
998 found_spaceable_staff = true;
999 first_spaceable_dy = dy;
1001 last_spaceable_dy = dy;
1005 // Leave the up skyline at a position relative
1006 // to the top spaceable staff.
1007 up->raise (-first_spaceable_dy);
1009 // Leave the down skyline at a position
1010 // relative to the bottom spaceable staff.
1011 down->raise (-last_spaceable_dy);
1015 Page_layout_problem::prob_extent (Prob *p)
1017 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
1018 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1022 Page_layout_problem::first_staff_extent (Element const &e)
1025 return prob_extent (e.prob);
1026 else if (e.staves.size ())
1027 return e.staves[0]->extent (e.staves[0], Y_AXIS);
1029 return Interval (0, 0);
1033 Page_layout_problem::last_staff_extent (Element const &e)
1036 return prob_extent (e.prob);
1037 else if (e.staves.size ())
1038 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1040 return Interval (0, 0);
1044 Page_layout_problem::get_details (Element const &elt)
1046 if (elt.staves.empty ())
1049 return get_details (elt.staves.back ()->get_system ());
1053 Page_layout_problem::get_details (Grob *g)
1055 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1056 return left_bound->get_property ("line-break-system-details");
1060 Page_layout_problem::is_spaceable (Grob *g)
1062 return !scm_is_number (g->get_property ("staff-affinity"));
1066 Page_layout_problem::mark_as_spaceable (Grob *g)
1068 g->set_property ("staff-affinity", SCM_BOOL_F);
1072 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1074 SCM pair = scm_sloppy_assq (sym, spec);
1075 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1077 *dest = scm_to_double (scm_cdr (pair));
1083 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1084 // Otherwise, return -infinity_f.
1085 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1088 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1090 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1091 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1092 ? ly_symbol2scm ("spaceable-fixed-spacing")
1093 : ly_symbol2scm ("loose-fixed-spacing");
1096 // The result of this function doesn't depend on "end," so we can reduce the
1097 // size of the cache by ignoring it.
1098 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1099 if (scm_is_number (cached))
1100 return robust_scm2double (cached, 0.0);
1103 Real ret = -infinity_f;
1105 // If we're pure, then paper-columns have not had their systems set,
1106 // and so elts[i]->get_system () is unreliable.
1107 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1108 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1110 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1112 SCM details = left_bound->get_property ("line-break-system-details");
1113 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1114 if (scm_is_pair (manual_dists))
1116 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1117 if (scm_is_number (forced))
1118 ret = max (ret, scm_to_double (forced));
1122 // Cache the result. As above, we ignore "end."
1124 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1130 add_stretchability (SCM alist, Real stretch)
1132 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1133 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1138 // We want to put a large stretch between a non-spaceable line and its
1139 // non-affinity staff. We want to put an even larger stretch between
1140 // a non-spaceable line and the top/bottom of the page. That way,
1141 // a spacing-affinity UP line at the bottom of the page will still be
1142 // placed close to its staff.
1143 const double LARGE_STRETCH = 10e5;
1144 const double HUGE_STRETCH = 10e7;
1146 // Returns the spacing spec connecting BEFORE to AFTER.
1148 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1150 // If there are no spacing wishes, return a very flexible spring.
1151 // This will occur, for example, if there are lyrics at the bottom of
1152 // the page, in which case we don't want the spring from the lyrics to
1153 // the bottom of the page to have much effect.
1154 if (!before || !after)
1155 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1157 if (is_spaceable (before))
1159 if (is_spaceable (after))
1160 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1163 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1164 return (affinity == DOWN)
1165 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1167 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1172 if (is_spaceable (after))
1174 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1175 return (affinity == UP)
1176 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1178 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1182 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1183 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1184 static bool warned = false;
1185 if (after_affinity > before_affinity
1186 && !warned && !pure)
1188 warning (_ ("staff-affinities should only decrease"));
1191 if (before_affinity != UP)
1192 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1193 else if (after_affinity != DOWN)
1194 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1195 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1205 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1210 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1211 spring->set_distance (space);
1212 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1213 spring->set_min_distance (min_dist);
1214 spring->set_default_strength ();
1216 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1217 spring->set_inverse_stretch_strength (stretch);
1221 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1223 vector<Grob *> output;
1224 for (vsize i = 0; i < input.size (); ++i)
1226 if (Hara_kiri_group_spanner::has_interface (input[i]))
1227 Hara_kiri_group_spanner::consider_suicide (input[i]);
1229 if (input[i]->is_live ())
1230 output.push_back (input[i]);