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.
165 // find the maximum X_AXIS length
166 Real max_length = -infinity_f;
168 for (vsize i = 0; i < fn_count; i++)
172 SCM assertion_function = fn_grobs[i]->get_property ("numbering-assertion-function");
173 if (ly_is_procedure (assertion_function))
174 (void) scm_call_1 (assertion_function, scm_from_int (counter));
176 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
177 SCM stencil = Text_interface::interpret_markup (layout, props, markup);
178 Stencil *st = unsmob_stencil (stencil);
181 programming_error ("Your numbering function needs to return a stencil.");
183 stencil = Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL).smobbed_copy ();
184 st = unsmob_stencil (stencil);
186 in_text_numbers = scm_cons (markup, in_text_numbers);
187 numbers = scm_cons (stencil, numbers);
189 if (!st->extent (X_AXIS).is_empty ())
190 max_length = max (max_length, st->extent (X_AXIS)[RIGHT]);
195 in_text_numbers = scm_reverse_x (in_text_numbers, SCM_EOL);
196 numbers = scm_reverse_x (numbers, SCM_EOL);
199 translate each stencil such that it attains the correct maximum length and bundle the
200 footnotes into a scheme object.
203 for (SCM p = numbers; scm_is_pair (p); p = scm_cdr (p))
205 Stencil *st = unsmob_stencil (scm_car (p));
206 if (!st->extent (X_AXIS).is_empty ())
207 st->translate_axis ((max_length - st->extent (X_AXIS)[RIGHT]),
211 // build the footnotes
213 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
215 // Take care of musical systems.
216 if (Grob *g = unsmob_grob (scm_car (s)))
218 System *sys = dynamic_cast<System *> (g);
221 programming_error ("got a grob for footnotes that wasn't a System");
226 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
227 for (vsize i = 0; i < footnote_grobs.size (); i++)
229 Grob *footnote = footnote_grobs[i];
230 SCM footnote_markup = footnote->get_property ("footnote-text");
231 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
232 if (orig->is_broken ())
233 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
235 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
238 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
239 props, footnote_markup);
241 Stencil footnote_stencil = *unsmob_stencil (footnote_stl);
242 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
243 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
245 if (orig->is_broken ())
246 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
247 do_numbering = do_numbering
248 || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
252 SCM annotation_scm = scm_car (in_text_numbers);
253 footnote->set_property ("text", annotation_scm);
254 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
256 orig->set_property ("text", annotation_scm);
257 if (orig->is_broken ())
258 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
259 orig->broken_intos_[i]->set_property ("text", annotation_scm);
262 Stencil annotation = *unsmob_stencil (scm_car (numbers));
263 annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
265 - annotation.extent (Y_AXIS)[UP]),
267 footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
268 numbers = scm_cdr (numbers);
269 in_text_numbers = scm_cdr (in_text_numbers);
271 if (!footnote_stencil.is_empty ())
273 if (to_boolean (footnote->get_property ("footnote")))
274 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
276 in_note_mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
279 sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
280 sys->set_property ("footnote-stencil", mol.smobbed_copy ());
282 // Take care of top-level markups
283 else if (Prob *p = unsmob_prob (scm_car (s)))
285 SCM stencils = p->get_property ("footnotes");
288 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
290 Stencil footnote_stencil = *unsmob_stencil (scm_caddar (st));
291 bool do_numbering = to_boolean (scm_cadar (st));
292 SCM in_text_stencil = Stencil ().smobbed_copy ();
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,
300 if (!unsmob_stencil (in_text_stencil))
301 in_text_stencil = SCM_EOL;
302 annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
304 - annotation.extent (Y_AXIS)[UP]),
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);
310 number_footnote_table = scm_cons (scm_cons (scm_caar (st),
312 number_footnote_table);
313 if (!footnote_stencil.is_empty ())
314 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
316 p->set_property ("footnote-stencil", mol.smobbed_copy ());
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);
325 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
327 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
330 SCM markup = paper->c_variable ("footnote-separator-markup");
332 if (!Text_interface::is_markup (markup))
335 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
338 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
340 return footnote_separator ? *footnote_separator : Stencil ();
344 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil foot, Paper_book *pb)
347 bool footnotes_found = false;
348 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
349 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
351 footnotes = scm_reverse (footnotes);
353 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
355 Stencil *stencil = unsmob_stencil (scm_car (s));
360 if (!stencil->is_empty ())
362 foot.add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
363 footnotes_found = true;
369 Stencil separator = get_footnote_separator_stencil (pb->paper_);
370 if (!separator.is_empty ())
371 foot.add_at_edge (Y_AXIS, UP, separator, footnote_padding);
377 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
378 : bottom_skyline_ (DOWN)
380 Prob *page = unsmob_prob (page_scm);
381 bottom_loose_baseline_ = 0;
391 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
392 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
394 Stencil foot_stencil = foot ? *foot : Stencil ();
396 if (pb && pb->paper_)
398 SCM footnotes = get_footnotes_from_lines (systems);
399 foot_stencil = add_footnotes_to_footer (footnotes, foot_stencil, pb);
402 warning (_ ("A page layout problem has been initiated that cannot accommodate footnotes."));
404 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
405 footer_height_ = foot_stencil.extent (Y_AXIS).length ();
406 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
409 // Initially, bottom_skyline_ represents the top of the page. Make
410 // it solid, so that the top of the first system will be forced
411 // below the top of the printable area.
412 bottom_skyline_.set_minimum_height (-header_height_);
414 SCM system_system_spacing = SCM_EOL;
415 SCM score_system_spacing = SCM_EOL;
416 SCM markup_system_spacing = SCM_EOL;
417 SCM score_markup_spacing = SCM_EOL;
418 SCM markup_markup_spacing = SCM_EOL;
420 // top_system_spacing controls the spring from the top of the printable
421 // area to the first staff. It allows the user to control the offset of
422 // the first staff (as opposed to the top of the first system) from the
423 // top of the page. Similarly for last_bottom_spacing.
424 SCM top_system_spacing = SCM_EOL;
425 SCM last_bottom_spacing = SCM_EOL;
426 if (pb && pb->paper_)
428 Output_def *paper = pb->paper_;
429 system_system_spacing = paper->c_variable ("system-system-spacing");
430 score_system_spacing = paper->c_variable ("score-system-spacing");
431 markup_system_spacing = paper->c_variable ("markup-system-spacing");
432 score_markup_spacing = paper->c_variable ("score-markup-spacing");
433 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
434 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
435 top_system_spacing = paper->c_variable ("top-system-spacing");
436 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
437 top_system_spacing = paper->c_variable ("top-markup-spacing");
439 // Note: the page height here does _not_ reserve space for headers and
440 // footers. This is because we want to anchor the top-system-spacing
441 // spring at the _top_ of the header.
442 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
443 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
445 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
446 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
447 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
448 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
450 bool last_system_was_title = false;
452 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
454 bool first = (s == systems);
456 if (Grob *g = unsmob_grob (scm_car (s)))
458 System *sys = dynamic_cast<System *> (g);
461 programming_error ("got a grob for vertical spacing that wasn't a System");
465 SCM spec = system_system_spacing;
467 spec = top_system_spacing;
468 else if (last_system_was_title)
469 spec = markup_system_spacing;
470 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
471 spec = score_system_spacing;
473 Spring spring (0, 0);
475 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
476 alter_spring_from_spacing_spec (spec, &spring);
477 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
479 append_system (sys, spring, indent, padding);
480 last_system_was_title = false;
482 else if (Prob *p = unsmob_prob (scm_car (s)))
484 SCM spec = first ? top_system_spacing
485 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
486 Spring spring (0, 0);
488 alter_spring_from_spacing_spec (spec, &spring);
489 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
491 append_prob (p, spring, padding);
492 last_system_was_title = true;
495 programming_error ("got a system that was neither a Grob nor a Prob");
498 Spring last_spring (0, 0);
499 Real last_padding = 0;
500 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
501 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
502 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
503 springs_.push_back (last_spring);
505 if (elements_.size ())
507 Real bottom_padding = 0;
509 // TODO: junk bottom-space now that we have last-bottom-spacing?
510 // bottom-space has the flexibility that one can do it per-system.
511 // NOTE: bottom-space is misnamed since it is not stretchable space.
512 if (Prob *p = elements_.back ().prob)
513 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
514 else if (elements_.back ().staves.size ())
516 SCM details = get_details (elements_.back ());
517 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
522 page_height_ -= bottom_padding;
527 Page_layout_problem::set_header_height (Real height)
529 header_height_ = height;
533 Page_layout_problem::set_footer_height (Real height)
535 footer_height_ = height;
539 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
541 Grob *align = unsmob_grob (sys->get_object ("vertical-alignment"));
545 align->set_property ("positioning-done", SCM_BOOL_T);
547 extract_grob_set (align, "elements", all_elts);
548 vector<Grob *> elts = filter_dead_elements (all_elts);
549 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
550 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
552 Skyline up_skyline (UP);
553 Skyline down_skyline (DOWN);
554 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
555 up_skyline.shift (indent);
556 down_skyline.shift (indent);
557 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
559 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
561 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
562 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
563 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
564 sky->set_minimum_height (sky->max_height ()
567 + in_note_stencil->extent (Y_AXIS).length ()));
571 We need to call distance with skyline-horizontal-padding because
572 the system skyline-horizontal-padding is not added during the creation
573 of an individual staff. So we add the padding for the distance check
574 at the time of adding in the system.
576 Real minimum_distance = up_skyline.distance (bottom_skyline_,
577 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
581 Spring spring_copy = spring;
582 spring_copy.ensure_min_distance (minimum_distance);
583 springs_.push_back (spring_copy);
585 if (elts.size () && !is_spaceable (elts[0]))
587 // store the minimum distance, considering relative indents,
589 Skyline first_skyline (UP);
590 Skyline_pair *sky = Skyline_pair::unsmob (elts[0]->get_property ("vertical-skylines"));
592 first_skyline.merge ((*sky)[UP]);
593 first_skyline.shift (indent);
594 minimum_distance = first_skyline.distance (bottom_skyline_) - bottom_loose_baseline_;
596 bottom_skyline_ = down_skyline;
597 elements_.push_back (Element (elts, minimum_offsets, minimum_distance, padding));
599 // Add the springs for the VerticalAxisGroups in this system.
601 // If the user has specified the offsets of the individual staves, fix the
602 // springs at the given distances. Otherwise, use stretchable springs.
603 SCM details = get_details (elements_.back ());
604 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
605 vsize last_spaceable_staff = 0;
606 bool found_spaceable_staff = false;
607 for (vsize i = 0; i < elts.size (); ++i)
609 if (is_spaceable (elts[i]))
611 if (!found_spaceable_staff)
613 // Ensure space for any loose lines above this system
615 springs_.back ().ensure_min_distance (bottom_loose_baseline_
616 - minimum_offsets_with_min_dist[i]
618 found_spaceable_staff = true;
619 last_spaceable_staff = i;
620 // We don't add a spring for the first staff, since
621 // we are only adding springs _between_ staves here.
625 Spring spring (0.5, 0.0);
626 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
627 alter_spring_from_spacing_spec (spec, &spring);
629 springs_.push_back (spring);
630 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
631 springs_.back ().ensure_min_distance (min_distance);
633 if (scm_is_pair (manual_dists))
635 if (scm_is_number (scm_car (manual_dists)))
637 Real dy = scm_to_double (scm_car (manual_dists));
639 springs_.back ().set_distance (dy);
640 springs_.back ().set_min_distance (dy);
641 springs_.back ().set_inverse_stretch_strength (0);
643 manual_dists = scm_cdr (manual_dists);
645 last_spaceable_staff = i;
649 bottom_loose_baseline_ = found_spaceable_staff
650 ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
651 - minimum_offsets_with_min_dist.back ())
654 // Corner case: there was only one staff, and it wasn't spaceable.
655 if (!found_spaceable_staff && elts.size ())
656 mark_as_spaceable (elts[0]);
660 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
662 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
663 Real minimum_distance = 0;
664 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
668 minimum_distance = max ((*sky)[UP].distance (bottom_skyline_),
669 bottom_loose_baseline_);
670 bottom_skyline_ = (*sky)[DOWN];
672 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
674 Interval iv = sten->extent (Y_AXIS);
675 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
677 bottom_skyline_.clear ();
678 bottom_skyline_.set_minimum_height (iv[DOWN]);
680 bottom_loose_baseline_ = 0.0;
682 Spring spring_copy = spring;
685 spring_copy.set_min_distance (minimum_distance);
686 spring_copy.set_inverse_stretch_strength (0.0);
687 spring_copy.set_distance (0.0);
690 spring_copy.ensure_min_distance (minimum_distance + padding);
692 springs_.push_back (spring_copy);
693 elements_.push_back (Element (prob, padding));
697 For ragged-last pages, we usually want to stretch the page so that it
698 is not much more compressed than the previous page. Here, if ragged is
699 true and you pass a value of fixed_force that !isinf, then I will try
700 to space this page using the given force. If it does not fit, I will
701 resort to just filling the page (non-raggedly).
704 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
706 Simple_spacer spacer;
708 for (vsize i = 0; i < springs_.size (); ++i)
709 spacer.add_spring (springs_[i]);
711 if (ragged && !isinf (fixed_force))
713 // We need to tell the spacer it isn't ragged. Otherwise, it will
714 // refuse to stretch.
715 spacer.solve (page_height_, false);
717 if (spacer.configuration_length (fixed_force) <= page_height_)
718 spacer.set_force (fixed_force);
721 spacer.solve (page_height_, ragged);
723 solution_ = spacer.spring_positions ();
724 force_ = spacer.force ();
728 Real overflow = spacer.configuration_length (spacer.force ())
730 if (ragged && overflow < 1e-6)
731 warning (_ ("ragged-bottom was specified, but page must be compressed"));
734 warning (_f ("compressing over-full page by %.1f staff-spaces",
736 force_ = -infinity_f;
737 vsize space_count = solution_.size ();
738 Real spacing_increment = overflow / (space_count - 2);
739 for (vsize i = 2; i < space_count; i++)
740 solution_[i] -= (i - 1) * spacing_increment;
746 Page_layout_problem::force () const
751 // The solution_ vector stores the position of every live VerticalAxisGroup
752 // and every title. From that information,
753 // 1) within each system, stretch the staves so they land at the right position
754 // 2) find the offset of each system (relative to the printable area of the page).
755 // TODO: this function is getting too long, maybe split it up?
757 Page_layout_problem::find_system_offsets ()
759 SCM system_offsets = SCM_EOL;
760 SCM *tail = &system_offsets;
762 // spring_idx 0 is the top of the page. Interesting values start from 1.
763 vsize spring_idx = 1;
764 vector<Grob *> loose_lines;
765 vector<Real> loose_line_min_distances;
766 Grob *last_spaceable_line = 0;
767 Real last_spaceable_line_translation = 0;
768 Interval last_title_extent;
769 for (vsize i = 0; i < elements_.size (); ++i)
771 if (elements_[i].prob)
773 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
774 tail = SCM_CDRLOC (*tail);
775 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
777 // Lay out any non-spaceable lines between this line and
779 if (loose_lines.size ())
781 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
782 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
783 + elements_[i].padding);
785 loose_line_min_distances.push_back (min_distance);
786 loose_lines.push_back (0);
788 distribute_loose_lines (loose_lines, loose_line_min_distances,
789 last_spaceable_line_translation, -solution_[spring_idx]);
790 loose_lines.clear ();
791 loose_line_min_distances.clear ();
794 last_spaceable_line = 0;
795 last_spaceable_line_translation = -solution_[spring_idx];
796 last_title_extent = prob_extent;
801 // Getting this signs right here is a little tricky. The configuration
802 // we return has zero at the top of the page and positive numbers further
803 // down, as does the solution_ vector. Within a staff, however, positive
805 // TODO: perhaps change the way the page 'configuration variable works so
806 // that it is consistent with the usual up/down sign conventions in
807 // Lilypond. Then this would be less confusing.
809 // These two positions are relative to the page (with positive numbers being
811 Real first_staff_position = solution_[spring_idx];
812 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
813 Real system_position = first_staff_position + first_staff_min_translation;
815 // Position the staves within this system.
816 vector<Real> const &min_offsets = elements_[i].min_offsets;
817 bool found_spaceable_staff = false;
818 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
820 Grob *staff = elements_[i].staves[staff_idx];
821 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
823 if (is_spaceable (staff))
825 // this is relative to the system: negative numbers are down.
826 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
828 // Lay out any non-spaceable lines between this line and
830 if (loose_lines.size ())
833 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
836 // A null line to break any staff-affinity from the previous system
837 loose_line_min_distances.push_back (0.0);
838 loose_lines.push_back (0);
839 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
841 loose_lines.push_back (staff);
843 distribute_loose_lines (loose_lines, loose_line_min_distances,
844 last_spaceable_line_translation, -solution_[spring_idx]);
845 loose_lines.clear ();
846 loose_line_min_distances.clear ();
848 last_spaceable_line = staff;
849 last_spaceable_line_translation = -solution_[spring_idx];
850 found_spaceable_staff = true;
853 else // ! is_spaceable
855 if (staff->extent (staff, Y_AXIS).is_empty ())
858 if (loose_lines.empty ())
859 loose_lines.push_back (last_spaceable_line);
862 // NOTE: the way we do distances between loose lines (and other lines too, actually)
863 // is not the most accurate way possible: we only insert rods between adjacent
864 // lines. To be more accurate, we could insert rods between non-adjacent lines
865 // using a scheme similar to the one in set_column_rods.
866 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
869 // this is the first line in a system
871 if (loose_lines.back ())
873 // distance to the final line in the preceding system,
874 // including 'system-system-spacing 'padding
875 min_dist = elements_[i].min_distance + elements_[i].padding;
876 // A null line to break any staff-affinity for the previous system
877 loose_line_min_distances.push_back (0.0);
878 loose_lines.push_back (0);
880 else if (!last_title_extent.is_empty ())
881 // distance to the preceding title,
882 // including 'markup-system-wg 'padding
883 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
884 + elements_[i].padding);
885 else // distance to the top margin
886 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
888 loose_line_min_distances.push_back (min_dist);
890 loose_lines.push_back (staff);
894 // Corner case: even if a system has no live staves, it still takes up
895 // one spring (a system with one live staff also takes up one spring),
896 // which we need to increment past.
897 if (!found_spaceable_staff)
900 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
901 tail = SCM_CDRLOC (*tail);
905 if (loose_lines.size ())
907 Grob *last = loose_lines.back ();
908 Interval last_ext = last->extent (last, Y_AXIS);
909 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
910 loose_lines.push_back (0);
912 distribute_loose_lines (loose_lines, loose_line_min_distances,
913 last_spaceable_line_translation, -page_height_);
917 assert (spring_idx == solution_.size () - 1);
918 return system_offsets;
921 // Given two lines that are already spaced (the first and last
922 // elements of loose_lines), distribute some unspaced lines between
924 // first_translation and last_translation are relative to the page.
926 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
927 vector<Real> const &min_distances,
928 Real first_translation, Real last_translation)
930 Simple_spacer spacer;
931 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
933 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
934 Spring spring (1.0, 0.0);
935 alter_spring_from_spacing_spec (spec, &spring);
936 spring.ensure_min_distance (min_distances[i]);
937 spacer.add_spring (spring);
940 // Remember: offsets are decreasing, since we're going from UP to DOWN!
941 spacer.solve (first_translation - last_translation, false);
943 vector<Real> solution = spacer.spring_positions ();
944 for (vsize i = 1; i + 1 < solution.size (); ++i)
947 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
948 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
953 Page_layout_problem::fixed_force_solution (Real force)
955 solve_rod_spring_problem (true, force);
956 return find_system_offsets ();
960 Page_layout_problem::solution (bool ragged)
962 solve_rod_spring_problem (ragged, -infinity_f);
963 return find_system_offsets ();
966 // Build upper and lower skylines for a system. We don't yet know the positions
967 // of the staves within the system, so we make the skyline as conservative as
968 // possible. That is, for the upper skyline, we pretend that all of the staves
969 // in the system are packed together close to the top system; for the lower
970 // skyline, we pretend that all of the staves are packed together close to
971 // the bottom system.
973 // The upper skyline is relative to the top staff; the lower skyline is relative to
976 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
977 vector<Real> const &minimum_translations,
981 if (minimum_translations.empty ())
984 assert (staves.size () == minimum_translations.size ());
985 Real first_translation = minimum_translations[0];
986 Real last_spaceable_dy = 0;
987 Real first_spaceable_dy = 0;
988 bool found_spaceable_staff = false;
990 for (vsize i = 0; i < staves.size (); ++i)
992 Real dy = minimum_translations[i] - first_translation;
994 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
998 up->merge ((*sky)[UP]);
1002 down->merge ((*sky)[DOWN]);
1005 if (is_spaceable (staves[i]))
1007 if (!found_spaceable_staff)
1009 found_spaceable_staff = true;
1010 first_spaceable_dy = dy;
1012 last_spaceable_dy = dy;
1016 // Leave the up skyline at a position relative
1017 // to the top spaceable staff.
1018 up->raise (-first_spaceable_dy);
1020 // Leave the down skyline at a position
1021 // relative to the bottom spaceable staff.
1022 down->raise (-last_spaceable_dy);
1026 Page_layout_problem::prob_extent (Prob *p)
1028 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
1029 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1033 Page_layout_problem::first_staff_extent (Element const &e)
1036 return prob_extent (e.prob);
1037 else if (e.staves.size ())
1038 return e.staves[0]->extent (e.staves[0], Y_AXIS);
1040 return Interval (0, 0);
1044 Page_layout_problem::last_staff_extent (Element const &e)
1047 return prob_extent (e.prob);
1048 else if (e.staves.size ())
1049 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1051 return Interval (0, 0);
1055 Page_layout_problem::get_details (Element const &elt)
1057 if (elt.staves.empty ())
1060 return get_details (elt.staves.back ()->get_system ());
1064 Page_layout_problem::get_details (Grob *g)
1066 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1067 return left_bound->get_property ("line-break-system-details");
1071 Page_layout_problem::is_spaceable (Grob *g)
1073 return !scm_is_number (g->get_property ("staff-affinity"));
1077 Page_layout_problem::mark_as_spaceable (Grob *g)
1079 g->set_property ("staff-affinity", SCM_BOOL_F);
1083 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1085 SCM pair = scm_sloppy_assq (sym, spec);
1086 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1088 *dest = scm_to_double (scm_cdr (pair));
1094 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1095 // Otherwise, return -infinity_f.
1096 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1099 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1101 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1102 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1103 ? ly_symbol2scm ("spaceable-fixed-spacing")
1104 : ly_symbol2scm ("loose-fixed-spacing");
1107 // The result of this function doesn't depend on "end," so we can reduce the
1108 // size of the cache by ignoring it.
1109 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1110 if (scm_is_number (cached))
1111 return robust_scm2double (cached, 0.0);
1114 Real ret = -infinity_f;
1116 // If we're pure, then paper-columns have not had their systems set,
1117 // and so elts[i]->get_system () is unreliable.
1118 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1119 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1121 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1123 SCM details = left_bound->get_property ("line-break-system-details");
1124 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1125 if (scm_is_pair (manual_dists))
1127 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1128 if (scm_is_number (forced))
1129 ret = max (ret, scm_to_double (forced));
1133 // Cache the result. As above, we ignore "end."
1135 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1141 add_stretchability (SCM alist, Real stretch)
1143 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1144 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1149 // We want to put a large stretch between a non-spaceable line and its
1150 // non-affinity staff. We want to put an even larger stretch between
1151 // a non-spaceable line and the top/bottom of the page. That way,
1152 // a spacing-affinity UP line at the bottom of the page will still be
1153 // placed close to its staff.
1154 const double LARGE_STRETCH = 10e5;
1155 const double HUGE_STRETCH = 10e7;
1157 // Returns the spacing spec connecting BEFORE to AFTER.
1159 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1161 // If there are no spacing wishes, return a very flexible spring.
1162 // This will occur, for example, if there are lyrics at the bottom of
1163 // the page, in which case we don't want the spring from the lyrics to
1164 // the bottom of the page to have much effect.
1165 if (!before || !after)
1166 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1168 if (is_spaceable (before))
1170 if (is_spaceable (after))
1171 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1174 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1175 return (affinity == DOWN)
1176 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1178 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1183 if (is_spaceable (after))
1185 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1186 return (affinity == UP)
1187 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1189 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1193 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1194 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1195 static bool warned = false;
1196 if (after_affinity > before_affinity
1197 && !warned && !pure)
1199 warning (_ ("staff-affinities should only decrease"));
1202 if (before_affinity != UP)
1203 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1204 else if (after_affinity != DOWN)
1205 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1206 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1216 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1221 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1222 spring->set_distance (space);
1223 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1224 spring->set_min_distance (min_dist);
1225 spring->set_default_strength ();
1227 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1228 spring->set_inverse_stretch_strength (stretch);
1232 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1234 vector<Grob *> output;
1235 for (vsize i = 0; i < input.size (); ++i)
1237 if (Hara_kiri_group_spanner::has_interface (input[i]))
1238 Hara_kiri_group_spanner::consider_suicide (input[i]);
1240 if (input[i]->is_live ())
1241 output.push_back (input[i]);