2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2009--2015 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"
36 #include "lily-imports.hh"
39 Returns the number of footnotes associated with a given line.
43 Page_layout_problem::get_footnote_grobs (SCM lines)
45 vector<Grob *> footnotes;
46 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
48 if (Grob *g = unsmob<Grob> (scm_car (s)))
50 System *sys = dynamic_cast<System *> (g);
53 programming_error ("got a grob for footnotes that wasn't a System");
56 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
57 footnotes.insert (footnotes.end (), footnote_grobs.begin (), footnote_grobs.end ());
59 else if (Prob *p = unsmob<Prob> (scm_car (s)))
61 SCM stencils = p->get_property ("footnotes");
62 if (scm_is_null (stencils))
64 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
65 footnotes.push_back (0);
73 Page_layout_problem::get_footnote_count (SCM lines)
75 vector<Grob *> notes = get_footnote_grobs (lines);
80 Page_layout_problem::get_footnotes_from_lines (SCM lines)
82 if (!scm_is_pair (lines))
86 if (Grob *g = unsmob<Grob> (scm_car (lines)))
87 footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
88 else if (Prob *p = unsmob<Prob> (scm_car (lines)))
89 footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
92 programming_error ("Systems on a page must be a prob or grob.");
97 programming_error ("Footnotes must be added to lines before they are retrieved.");
102 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
104 if (Grob *g = unsmob<Grob> (scm_car (s)))
105 out = scm_cons (g->get_property ("footnote-stencil"), out);
106 else if (Prob *p = unsmob<Prob> (scm_car (s)))
107 out = scm_cons (p->get_property ("footnote-stencil"), out);
109 programming_error ("Systems on a page must be a prob or grob.");
112 return scm_reverse_x (out, SCM_EOL);
116 Adds a footnote stencil to each system. This stencil may
117 itself be comprised of several footnotes.
119 This is a long function, but it seems better to keep it intact rather than
120 splitting it into parts.
124 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
127 first, we have to see how many footnotes are on this page.
128 we need to do this first so that we can line them up
131 Output_def *paper = pb->paper_;
135 programming_error ("Cannot get footnotes because there is no valid paper block.");
139 SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
140 if (!scm_is_pair (number_footnote_table))
141 number_footnote_table = SCM_EOL;
142 SCM numbering_function = paper->c_variable ("footnote-numbering-function");
143 SCM layout = paper->self_scm ();
144 SCM props = Lily::layout_extract_page_properties (layout);
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 = Lily::layout_extract_page_properties (paper->self_scm ());
237 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
238 props, footnote_markup);
240 Stencil footnote_stencil = *unsmob<Stencil> (footnote_stl);
241 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
242 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
244 if (orig->is_broken ())
245 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
246 do_numbering = do_numbering
247 || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
251 SCM annotation_scm = scm_car (in_text_numbers);
252 footnote->set_property ("text", annotation_scm);
253 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
255 orig->set_property ("text", annotation_scm);
256 if (orig->is_broken ())
257 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
258 orig->broken_intos_[i]->set_property ("text", annotation_scm);
261 Stencil annotation = *unsmob<Stencil> (scm_car (numbers));
262 annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
264 - annotation.extent (Y_AXIS)[UP]),
266 footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
267 numbers = scm_cdr (numbers);
268 in_text_numbers = scm_cdr (in_text_numbers);
270 if (!footnote_stencil.is_empty ())
272 if (to_boolean (footnote->get_property ("footnote")))
273 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
275 in_note_mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
278 sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
279 sys->set_property ("footnote-stencil", mol.smobbed_copy ());
281 // Take care of top-level markups
282 else if (Prob *p = unsmob<Prob> (scm_car (s)))
284 SCM stencils = p->get_property ("footnotes");
287 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
289 Stencil footnote_stencil = *unsmob<Stencil> (scm_caddar (st));
290 bool do_numbering = to_boolean (scm_cadar (st));
291 SCM in_text_stencil = Stencil ().smobbed_copy ();
294 Stencil annotation = *unsmob<Stencil> (scm_car (numbers));
295 SCM in_text_annotation = scm_car (in_text_numbers);
296 in_text_stencil = Text_interface::interpret_markup (layout,
299 if (!unsmob<Stencil> (in_text_stencil))
300 in_text_stencil = SCM_EOL;
301 annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
303 - annotation.extent (Y_AXIS)[UP]),
305 footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
306 numbers = scm_cdr (numbers);
307 in_text_numbers = scm_cdr (in_text_numbers);
309 number_footnote_table = scm_cons (scm_cons (scm_caar (st),
311 number_footnote_table);
312 if (!footnote_stencil.is_empty ())
313 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
315 p->set_property ("footnote-stencil", mol.smobbed_copy ());
319 // note that this line of code doesn't do anything if numbering isn't turned on
320 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
324 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
326 SCM props = Lily::layout_extract_page_properties (paper->self_scm ());
328 SCM markup = paper->c_variable ("footnote-separator-markup");
330 if (!Text_interface::is_markup (markup))
333 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
336 Stencil *footnote_separator = unsmob<Stencil> (footnote_stencil);
338 return footnote_separator ? *footnote_separator : Stencil ();
342 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil foot, Paper_book *pb)
345 bool footnotes_found = false;
346 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
347 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
349 footnotes = scm_reverse (footnotes);
351 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
353 Stencil *stencil = unsmob<Stencil> (scm_car (s));
358 if (!stencil->is_empty ())
360 foot.add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
361 footnotes_found = true;
367 Stencil separator = get_footnote_separator_stencil (pb->paper_);
368 if (!separator.is_empty ())
369 foot.add_at_edge (Y_AXIS, UP, separator, footnote_padding);
375 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
376 : bottom_skyline_ (DOWN)
378 Prob *page = unsmob<Prob> (page_scm);
379 bottom_loose_baseline_ = 0;
389 Stencil *head = unsmob<Stencil> (page->get_property ("head-stencil"));
390 Stencil *foot = unsmob<Stencil> (page->get_property ("foot-stencil"));
392 Stencil foot_stencil = foot ? *foot : Stencil ();
394 if (pb && pb->paper_)
396 SCM footnotes = get_footnotes_from_lines (systems);
397 foot_stencil = add_footnotes_to_footer (footnotes, foot_stencil, pb);
400 warning (_ ("A page layout problem has been initiated that cannot accommodate footnotes."));
402 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
403 footer_height_ = foot_stencil.extent (Y_AXIS).length ();
404 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
407 // Initially, bottom_skyline_ represents the top of the page. Make
408 // it solid, so that the top of the first system will be forced
409 // below the top of the printable area.
410 bottom_skyline_.set_minimum_height (-header_height_);
412 SCM system_system_spacing = SCM_EOL;
413 SCM score_system_spacing = SCM_EOL;
414 SCM markup_system_spacing = SCM_EOL;
415 SCM score_markup_spacing = SCM_EOL;
416 SCM markup_markup_spacing = SCM_EOL;
418 // top_system_spacing controls the spring from the top of the printable
419 // area to the first staff. It allows the user to control the offset of
420 // the first staff (as opposed to the top of the first system) from the
421 // top of the page. Similarly for last_bottom_spacing.
422 SCM top_system_spacing = SCM_EOL;
423 SCM last_bottom_spacing = SCM_EOL;
424 if (pb && pb->paper_)
426 Output_def *paper = pb->paper_;
427 system_system_spacing = paper->c_variable ("system-system-spacing");
428 score_system_spacing = paper->c_variable ("score-system-spacing");
429 markup_system_spacing = paper->c_variable ("markup-system-spacing");
430 score_markup_spacing = paper->c_variable ("score-markup-spacing");
431 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
432 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
433 top_system_spacing = paper->c_variable ("top-system-spacing");
434 if (scm_is_pair (systems) && unsmob<Prob> (scm_car (systems)))
435 top_system_spacing = paper->c_variable ("top-markup-spacing");
437 // Note: the page height here does _not_ reserve space for headers and
438 // footers. This is because we want to anchor the top-system-spacing
439 // spring at the _top_ of the header.
440 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
441 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
443 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
444 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
445 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
446 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
448 bool last_system_was_title = false;
450 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
452 bool first = scm_is_eq (s, systems);
454 if (Grob *g = unsmob<Grob> (scm_car (s)))
456 System *sys = dynamic_cast<System *> (g);
459 programming_error ("got a grob for vertical spacing that wasn't a System");
463 SCM spec = system_system_spacing;
465 spec = top_system_spacing;
466 else if (last_system_was_title)
467 spec = markup_system_spacing;
468 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
469 spec = score_system_spacing;
471 Spring spring (0, 0);
473 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
474 alter_spring_from_spacing_spec (spec, &spring);
475 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
477 append_system (sys, spring, indent, padding);
478 last_system_was_title = false;
480 else if (Prob *p = unsmob<Prob> (scm_car (s)))
482 SCM spec = first ? top_system_spacing
483 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
484 Spring spring (0, 0);
486 alter_spring_from_spacing_spec (spec, &spring);
487 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
489 append_prob (p, spring, padding);
490 last_system_was_title = true;
493 programming_error ("got a system that was neither a Grob nor a Prob");
496 Spring last_spring (0, 0);
497 Real last_padding = 0;
498 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
499 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
500 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
501 springs_.push_back (last_spring);
503 if (elements_.size ())
505 Real bottom_padding = 0;
507 // TODO: junk bottom-space now that we have last-bottom-spacing?
508 // bottom-space has the flexibility that one can do it per-system.
509 // NOTE: bottom-space is misnamed since it is not stretchable space.
510 if (Prob *p = elements_.back ().prob)
511 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
512 else if (elements_.back ().staves.size ())
514 SCM details = get_details (elements_.back ());
515 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
520 page_height_ -= bottom_padding;
525 Page_layout_problem::set_header_height (Real height)
527 header_height_ = height;
531 Page_layout_problem::set_footer_height (Real height)
533 footer_height_ = height;
537 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
539 Grob *align = unsmob<Grob> (sys->get_object ("vertical-alignment"));
543 align->set_property ("positioning-done", SCM_BOOL_T);
545 extract_grob_set (align, "elements", all_elts);
546 vector<Grob *> elts = filter_dead_elements (all_elts);
547 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
548 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
550 Skyline up_skyline (UP);
551 Skyline down_skyline (DOWN);
552 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
553 up_skyline.shift (indent);
554 down_skyline.shift (indent);
555 Stencil *in_note_stencil = unsmob<Stencil> (sys->get_property ("in-note-stencil"));
557 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
559 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
560 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
561 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
562 sky->set_minimum_height (sky->max_height ()
565 + in_note_stencil->extent (Y_AXIS).length ()));
569 We need to call distance with skyline-horizontal-padding because
570 the system skyline-horizontal-padding is not added during the creation
571 of an individual staff. So we add the padding for the distance check
572 at the time of adding in the system.
574 Real minimum_distance = up_skyline.distance (bottom_skyline_,
575 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
579 Spring spring_copy = spring;
580 spring_copy.ensure_min_distance (minimum_distance);
581 springs_.push_back (spring_copy);
583 if (elts.size () && !is_spaceable (elts[0]))
585 // store the minimum distance, considering relative indents,
587 Skyline first_skyline (UP);
588 Skyline_pair *sky = unsmob<Skyline_pair> (elts[0]->get_property ("vertical-skylines"));
590 first_skyline.merge ((*sky)[UP]);
591 first_skyline.shift (indent);
592 minimum_distance = first_skyline.distance (bottom_skyline_) - bottom_loose_baseline_;
594 bottom_skyline_ = down_skyline;
595 elements_.push_back (Element (elts, minimum_offsets, minimum_distance, padding));
597 // Add the springs for the VerticalAxisGroups in this system.
599 // If the user has specified the offsets of the individual staves, fix the
600 // springs at the given distances. Otherwise, use stretchable springs.
601 SCM details = get_details (elements_.back ());
602 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
603 vsize last_spaceable_staff = 0;
604 bool found_spaceable_staff = false;
605 for (vsize i = 0; i < elts.size (); ++i)
607 if (is_spaceable (elts[i]))
609 if (!found_spaceable_staff)
611 // Ensure space for any loose lines above this system
613 springs_.back ().ensure_min_distance (bottom_loose_baseline_
614 - minimum_offsets_with_min_dist[i]
616 found_spaceable_staff = true;
617 last_spaceable_staff = i;
618 // We don't add a spring for the first staff, since
619 // we are only adding springs _between_ staves here.
623 Spring spring (0.5, 0.0);
624 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
625 alter_spring_from_spacing_spec (spec, &spring);
627 springs_.push_back (spring);
628 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
629 springs_.back ().ensure_min_distance (min_distance);
631 if (scm_is_pair (manual_dists))
633 if (scm_is_number (scm_car (manual_dists)))
635 Real dy = scm_to_double (scm_car (manual_dists));
637 springs_.back ().set_distance (dy);
638 springs_.back ().set_min_distance (dy);
639 springs_.back ().set_inverse_stretch_strength (0);
641 manual_dists = scm_cdr (manual_dists);
643 last_spaceable_staff = i;
647 bottom_loose_baseline_ = found_spaceable_staff
648 ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
649 - minimum_offsets_with_min_dist.back ())
652 // Corner case: there was only one staff, and it wasn't spaceable.
653 if (!found_spaceable_staff && elts.size ())
654 mark_as_spaceable (elts[0]);
658 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
660 Skyline_pair *sky = unsmob<Skyline_pair> (prob->get_property ("vertical-skylines"));
661 Real minimum_distance = 0;
662 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
666 minimum_distance = max ((*sky)[UP].distance (bottom_skyline_),
667 bottom_loose_baseline_);
668 bottom_skyline_ = (*sky)[DOWN];
670 else if (Stencil *sten = unsmob<Stencil> (prob->get_property ("stencil")))
672 Interval iv = sten->extent (Y_AXIS);
673 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
675 bottom_skyline_.clear ();
676 bottom_skyline_.set_minimum_height (iv[DOWN]);
678 bottom_loose_baseline_ = 0.0;
680 Spring spring_copy = spring;
683 spring_copy.set_min_distance (minimum_distance);
684 spring_copy.set_inverse_stretch_strength (0.0);
685 spring_copy.set_distance (0.0);
688 spring_copy.ensure_min_distance (minimum_distance + padding);
690 springs_.push_back (spring_copy);
691 elements_.push_back (Element (prob, padding));
695 For ragged-last pages, we usually want to stretch the page so that it
696 is not much more compressed than the previous page. Here, if ragged is
697 true and you pass a value of fixed_force that !isinf, then I will try
698 to space this page using the given force. If it does not fit, I will
699 resort to just filling the page (non-raggedly).
702 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
704 Simple_spacer spacer;
706 for (vsize i = 0; i < springs_.size (); ++i)
707 spacer.add_spring (springs_[i]);
709 if (ragged && !isinf (fixed_force))
711 // We need to tell the spacer it isn't ragged. Otherwise, it will
712 // refuse to stretch.
713 spacer.solve (page_height_, false);
715 if (spacer.configuration_length (fixed_force) <= page_height_)
716 spacer.set_force (fixed_force);
719 spacer.solve (page_height_, ragged);
721 solution_ = spacer.spring_positions ();
722 force_ = spacer.force ();
726 Real overflow = spacer.configuration_length (spacer.force ())
728 if (ragged && overflow < 1e-6)
729 warning (_ ("ragged-bottom was specified, but page must be compressed"));
732 warning (_f ("compressing over-full page by %.1f staff-spaces",
734 force_ = -infinity_f;
735 vsize space_count = solution_.size ();
736 Real spacing_increment = overflow / (space_count - 2);
737 for (vsize i = 2; i < space_count; i++)
738 solution_[i] -= (i - 1) * spacing_increment;
744 Page_layout_problem::force () const
749 // The solution_ vector stores the position of every live VerticalAxisGroup
750 // and every title. From that information,
751 // 1) within each system, stretch the staves so they land at the right position
752 // 2) find the offset of each system (relative to the printable area of the page).
753 // TODO: this function is getting too long, maybe split it up?
755 Page_layout_problem::find_system_offsets ()
757 SCM system_offsets = SCM_EOL;
758 SCM *tail = &system_offsets;
760 // spring_idx 0 is the top of the page. Interesting values start from 1.
761 vsize spring_idx = 1;
762 vector<Grob *> loose_lines;
763 vector<Real> loose_line_min_distances;
764 Grob *last_spaceable_line = 0;
765 Real last_spaceable_line_translation = 0;
766 Interval last_title_extent;
767 for (vsize i = 0; i < elements_.size (); ++i)
769 if (elements_[i].prob)
771 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
772 tail = SCM_CDRLOC (*tail);
773 Interval prob_extent = unsmob<Stencil> (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
775 // Lay out any non-spaceable lines between this line and
777 if (loose_lines.size ())
779 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
780 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
781 + elements_[i].padding);
783 loose_line_min_distances.push_back (min_distance);
784 loose_lines.push_back (0);
786 distribute_loose_lines (loose_lines, loose_line_min_distances,
787 last_spaceable_line_translation, -solution_[spring_idx]);
788 loose_lines.clear ();
789 loose_line_min_distances.clear ();
792 last_spaceable_line = 0;
793 last_spaceable_line_translation = -solution_[spring_idx];
794 last_title_extent = prob_extent;
799 // Getting this signs right here is a little tricky. The configuration
800 // we return has zero at the top of the page and positive numbers further
801 // down, as does the solution_ vector. Within a staff, however, positive
803 // TODO: perhaps change the way the page 'configuration variable works so
804 // that it is consistent with the usual up/down sign conventions in
805 // Lilypond. Then this would be less confusing.
807 // These two positions are relative to the page (with positive numbers being
809 Real first_staff_position = solution_[spring_idx];
810 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
811 Real system_position = first_staff_position + first_staff_min_translation;
813 // Position the staves within this system.
814 vector<Real> const &min_offsets = elements_[i].min_offsets;
815 bool found_spaceable_staff = false;
816 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
818 Grob *staff = elements_[i].staves[staff_idx];
819 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
821 if (is_spaceable (staff))
823 // this is relative to the system: negative numbers are down.
824 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
826 // Lay out any non-spaceable lines between this line and
828 if (loose_lines.size ())
831 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
834 // A null line to break any staff-affinity from the previous system
835 loose_line_min_distances.push_back (0.0);
836 loose_lines.push_back (0);
837 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
839 loose_lines.push_back (staff);
841 distribute_loose_lines (loose_lines, loose_line_min_distances,
842 last_spaceable_line_translation, -solution_[spring_idx]);
843 loose_lines.clear ();
844 loose_line_min_distances.clear ();
846 last_spaceable_line = staff;
847 last_spaceable_line_translation = -solution_[spring_idx];
848 found_spaceable_staff = true;
851 else // ! is_spaceable
853 if (staff->extent (staff, Y_AXIS).is_empty ())
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 = unsmob<Skyline_pair> (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 (has_interface<Hara_kiri_group_spanner> (input[i]))
1236 Hara_kiri_group_spanner::consider_suicide (input[i]);
1238 if (input[i]->is_live ())
1239 output.push_back (input[i]);