2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2009--2011 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_count (SCM lines)
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 fn_count += sys->num_footnotes ();
57 else if (Prob *p = unsmob_prob (scm_car (s)))
59 SCM stencils = p->get_property ("footnotes");
60 if (stencils == SCM_EOL)
62 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
71 Page_layout_problem::get_footnotes_from_lines (SCM lines)
73 if (!scm_is_pair (lines))
77 if (Grob *g = unsmob_grob (scm_car (lines)))
78 footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
79 else if (Prob *p = unsmob_prob (scm_car (lines)))
80 footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
83 programming_error ("Systems on a page must be a prob or grob.");
88 programming_error ("Footnotes must be added to lines before they are retrieved.");
93 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
95 if (Grob *g = unsmob_grob (scm_car (s)))
96 out = scm_cons (g->get_property ("footnote-stencil"), out);
97 else if (Prob *p = unsmob_prob (scm_car (s)))
98 out = scm_cons (p->get_property ("footnote-stencil"), out);
100 programming_error ("Systems on a page must be a prob or grob.");
103 return scm_reverse (out);
107 Adds a footnote stencil to each system. This stencil may
108 itself be comprised of several footnotes.
110 This is a long function, but it seems better to keep it intact rather than
111 splitting it into parts.
115 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
118 first, we have to see how many footnotes are on this page.
119 we need to do this first so that we can line them up
122 Output_def *paper = pb->paper_;
126 programming_error ("Cannot get footnotes because there is no valid paper block.");
130 SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
131 if (!scm_is_pair (number_footnote_table))
132 number_footnote_table = SCM_EOL;
133 SCM numbering_function = paper->c_variable ("footnote-numbering-function");
134 SCM layout = paper->self_scm ();
135 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
137 Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
138 Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
140 vsize fn_count = get_footnote_count (lines);
142 // now, make the footnote stencils with the numbering function
143 SCM numbers = SCM_EOL;
144 SCM in_text_numbers = SCM_EOL;
146 TODO: This recalculates numbering every time this function is called, including once
147 after the balloon prints are called. Although it is not a huge computational drain,
148 it'd be more elegant to turn this calculation off when it is no longer needed.
150 In a separate commit, it'd be nice to streamline the way that page layout property
151 is handled so that the process of building `config's in page-breaking does result
152 in duplicated work, either by making this process less complicated or (preferably)
153 by passing its results downstream.
155 vector<SCM> footnote_number_markups; // Holds the numbering markups.
156 vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
157 for (vsize i = 0; i < fn_count; i++)
159 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
160 Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
163 programming_error ("Your numbering function needs to return a stencil.");
165 s = new Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL);
167 footnote_number_markups.push_back (markup);
168 footnote_number_stencils.push_back (s);
172 // find the maximum X_AXIS length
173 Real max_length = -infinity_f;
174 for (vsize i = 0; i < fn_count; i++)
175 max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
178 translate each stencil such that it attains the correct maximum length and bundle the
179 footnotes into a scheme object.
181 SCM *tail = &numbers;
182 SCM *in_text_tail = &in_text_numbers;
184 for (vsize i = 0; i < fn_count; i++)
186 *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
187 in_text_tail = SCM_CDRLOC (*in_text_tail);
188 footnote_number_stencils[i]->translate_axis ((max_length
189 - footnote_number_stencils[i]->extent (X_AXIS).length ()),
191 *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
192 tail = SCM_CDRLOC (*tail);
194 // build the footnotes
196 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
198 // Take care of musical systems.
199 if (Grob *g = unsmob_grob (scm_car (s)))
201 System *sys = dynamic_cast<System *> (g);
204 programming_error ("got a grob for footnotes that wasn't a System");
209 for (vsize i = 0; i < sys->footnote_grobs ()->size (); i++)
211 Grob *footnote = sys->footnote_grobs ()->at (i);
212 SCM footnote_markup = footnote->get_property ("footnote-text");
213 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
214 if (orig->is_broken ())
215 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
217 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
220 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
221 props, footnote_markup);
223 Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
224 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
225 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
227 if (orig->is_broken ())
228 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
229 do_numbering = do_numbering
230 || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
234 SCM annotation_scm = scm_car (in_text_numbers);
235 footnote->set_property ("text", annotation_scm);
236 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
238 orig->set_property ("text", annotation_scm);
239 if (orig->is_broken ())
240 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
241 orig->broken_intos_[i]->set_property ("text", annotation_scm);
244 Stencil *annotation = unsmob_stencil (scm_car (numbers));
245 annotation->translate_axis ((footnote_stencil->extent (Y_AXIS)[UP]
247 - annotation->extent (Y_AXIS)[UP]),
249 footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
250 numbers = scm_cdr (numbers);
251 in_text_numbers = scm_cdr (in_text_numbers);
253 if (!footnote_stencil->is_empty ())
255 if (to_boolean (footnote->get_property ("footnote")))
256 mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
258 in_note_mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
261 sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
262 sys->set_property ("footnote-stencil", mol.smobbed_copy ());
264 // Take care of top-level markups
265 else if (Prob *p = unsmob_prob (scm_car (s)))
267 SCM stencils = p->get_property ("footnotes");
270 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
272 Stencil footnote_stencil;
273 Stencil *footnote = unsmob_stencil (scm_caddar (st));
274 footnote_stencil.add_stencil (*footnote);
275 bool do_numbering = to_boolean (scm_cadar (st));
276 SCM in_text_stencil = Stencil ().smobbed_copy ();
279 Stencil *annotation = unsmob_stencil (scm_car (numbers));
280 SCM in_text_annotation = scm_car (in_text_numbers);
281 in_text_stencil = Text_interface::interpret_markup (layout,
284 if (!unsmob_stencil (in_text_stencil))
285 in_text_stencil = SCM_EOL;
286 annotation->translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
288 - annotation->extent (Y_AXIS)[UP]),
290 footnote_stencil.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
291 numbers = scm_cdr (numbers);
292 in_text_numbers = scm_cdr (in_text_numbers);
294 number_footnote_table = scm_cons (scm_cons (scm_caar (st),
296 number_footnote_table);
297 if (!footnote_stencil.is_empty ())
298 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
300 p->set_property ("footnote-stencil", mol.smobbed_copy ());
304 // note that this line of code doesn't do anything if numbering isn't turned on
305 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
309 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
311 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
314 SCM markup = paper->c_variable ("footnote-separator-markup");
316 if (!Text_interface::is_markup (markup))
319 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
322 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
324 return footnote_separator;
328 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
330 if (!foot && scm_is_pair (footnotes))
332 warning ("Must have a footer to add footnotes.");
335 bool footnotes_found = false;
336 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
337 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
339 footnotes = scm_reverse (footnotes);
341 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
343 Stencil *stencil = unsmob_stencil (scm_car (s));
348 if (!stencil->is_empty ())
350 foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
351 footnotes_found = true;
357 Stencil *separator = get_footnote_separator_stencil (pb->paper_);
359 foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
363 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
364 : bottom_skyline_ (DOWN)
366 Prob *page = unsmob_prob (page_scm);
375 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
376 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
378 if (pb && pb->paper_)
380 SCM footnotes = get_footnotes_from_lines (systems);
381 add_footnotes_to_footer (footnotes, foot, pb);
384 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
386 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
387 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
388 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
391 // Initially, bottom_skyline_ represents the top of the page. Make
392 // it solid, so that the top of the first system will be forced
393 // below the top of the printable area.
394 bottom_skyline_.set_minimum_height (-header_height_);
396 SCM system_system_spacing = SCM_EOL;
397 SCM score_system_spacing = SCM_EOL;
398 SCM markup_system_spacing = SCM_EOL;
399 SCM score_markup_spacing = SCM_EOL;
400 SCM markup_markup_spacing = SCM_EOL;
402 // top_system_spacing controls the spring from the top of the printable
403 // area to the first staff. It allows the user to control the offset of
404 // the first staff (as opposed to the top of the first system) from the
405 // top of the page. Similarly for last_bottom_spacing.
406 SCM top_system_spacing = SCM_EOL;
407 SCM last_bottom_spacing = SCM_EOL;
408 if (pb && pb->paper_)
410 Output_def *paper = pb->paper_;
411 system_system_spacing = paper->c_variable ("system-system-spacing");
412 score_system_spacing = paper->c_variable ("score-system-spacing");
413 markup_system_spacing = paper->c_variable ("markup-system-spacing");
414 score_markup_spacing = paper->c_variable ("score-markup-spacing");
415 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
416 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
417 top_system_spacing = paper->c_variable ("top-system-spacing");
418 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
419 top_system_spacing = paper->c_variable ("top-markup-spacing");
421 // Note: the page height here does _not_ reserve space for headers and
422 // footers. This is because we want to anchor the top-system-spacing
423 // spring at the _top_ of the header.
424 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
425 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
427 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
428 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
429 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
430 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
432 bool last_system_was_title = false;
434 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
436 bool first = (s == systems);
438 if (Grob *g = unsmob_grob (scm_car (s)))
440 System *sys = dynamic_cast<System *> (g);
443 programming_error ("got a grob for vertical spacing that wasn't a System");
447 SCM spec = system_system_spacing;
449 spec = top_system_spacing;
450 else if (last_system_was_title)
451 spec = markup_system_spacing;
452 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
453 spec = score_system_spacing;
455 Spring spring (0, 0);
457 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
458 alter_spring_from_spacing_spec (spec, &spring);
459 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
461 append_system (sys, spring, indent, padding);
462 last_system_was_title = false;
464 else if (Prob *p = unsmob_prob (scm_car (s)))
466 SCM spec = first ? top_system_spacing
467 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
468 Spring spring (0, 0);
470 alter_spring_from_spacing_spec (spec, &spring);
471 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
473 append_prob (p, spring, padding);
474 last_system_was_title = true;
477 programming_error ("got a system that was neither a Grob nor a Prob");
480 Spring last_spring (0, 0);
481 Real last_padding = 0;
482 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
483 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
484 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
485 springs_.push_back (last_spring);
487 if (elements_.size ())
489 Real bottom_padding = 0;
491 // TODO: junk bottom-space now that we have last-bottom-spacing?
492 // bottom-space has the flexibility that one can do it per-system.
493 // NOTE: bottom-space is misnamed since it is not stretchable space.
494 if (Prob *p = elements_.back ().prob)
495 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
496 else if (elements_.back ().staves.size ())
498 SCM details = get_details (elements_.back ());
499 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
504 page_height_ -= bottom_padding;
509 Page_layout_problem::set_header_height (Real height)
511 header_height_ = height;
515 Page_layout_problem::set_footer_height (Real height)
517 footer_height_ = height;
521 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
523 Grob *align = sys->get_vertical_alignment ();
527 align->set_property ("positioning-done", SCM_BOOL_T);
529 extract_grob_set (align, "elements", all_elts);
530 vector<Grob *> elts = filter_dead_elements (all_elts);
531 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
532 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
534 Skyline up_skyline (UP);
535 Skyline down_skyline (DOWN);
536 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
537 up_skyline.shift (indent);
538 down_skyline.shift (indent);
539 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
541 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
543 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
544 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
545 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
546 sky->set_minimum_height (sky->max_height ()
549 + in_note_stencil->extent (Y_AXIS).length ()));
553 We need to call distance with skyline-horizontal-padding because
554 the system skyline-horizontal-padding is not added during the creation
555 of an individual staff. So we add the padding for the distance check
556 at the time of adding in the system.
558 Real minimum_distance = up_skyline.distance (bottom_skyline_,
559 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
563 Spring spring_copy = spring;
564 spring_copy.ensure_min_distance (minimum_distance);
565 springs_.push_back (spring_copy);
567 bottom_skyline_ = down_skyline;
568 elements_.push_back (Element (elts, minimum_offsets, padding));
570 // Add the springs for the VerticalAxisGroups in this system.
572 // If the user has specified the offsets of the individual staves, fix the
573 // springs at the given distances. Otherwise, use stretchable springs.
574 SCM details = get_details (elements_.back ());
575 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
576 vsize last_spaceable_staff = 0;
577 bool found_spaceable_staff = false;
578 for (vsize i = 0; i < elts.size (); ++i)
580 if (is_spaceable (elts[i]))
582 // We don't add a spring for the first staff, since
583 // we are only adding springs _between_ staves here.
584 if (!found_spaceable_staff)
586 found_spaceable_staff = true;
587 last_spaceable_staff = i;
591 Spring spring (0.5, 0.0);
592 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
593 alter_spring_from_spacing_spec (spec, &spring);
595 springs_.push_back (spring);
596 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
597 springs_.back ().ensure_min_distance (min_distance);
599 if (scm_is_pair (manual_dists))
601 if (scm_is_number (scm_car (manual_dists)))
603 Real dy = scm_to_double (scm_car (manual_dists));
605 springs_.back ().set_distance (dy);
606 springs_.back ().set_min_distance (dy);
607 springs_.back ().set_inverse_stretch_strength (0);
609 manual_dists = scm_cdr (manual_dists);
611 last_spaceable_staff = i;
615 // Corner case: there was only one staff, and it wasn't spaceable.
616 // Mark it spaceable, because we do not allow non-spaceable staves
617 // to be at the top or bottom of a system.
618 if (!found_spaceable_staff && elts.size ())
619 mark_as_spaceable (elts[0]);
623 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
625 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
626 Real minimum_distance = 0;
627 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
631 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
632 bottom_skyline_ = (*sky)[DOWN];
634 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
636 Interval iv = sten->extent (Y_AXIS);
637 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
639 bottom_skyline_.clear ();
640 bottom_skyline_.set_minimum_height (iv[DOWN]);
643 Spring spring_copy = spring;
646 spring_copy.set_min_distance (minimum_distance);
647 spring_copy.set_inverse_stretch_strength (0.0);
648 spring_copy.set_distance (0.0);
651 spring_copy.ensure_min_distance (minimum_distance + padding);
653 springs_.push_back (spring_copy);
654 elements_.push_back (Element (prob, padding));
658 Page_layout_problem::solve_rod_spring_problem (bool ragged)
660 Simple_spacer spacer;
662 for (vsize i = 0; i < springs_.size (); ++i)
663 spacer.add_spring (springs_[i]);
665 spacer.solve (page_height_, ragged);
666 solution_ = spacer.spring_positions ();
670 Real overflow = spacer.configuration_length (spacer.force ())
672 if (ragged && overflow < 1e-6)
673 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
676 warning (_f ("cannot fit music on page: overflow is %f",
678 warning (_ ("compressing music to fit"));
679 vsize space_count = solution_.size ();
680 Real spacing_increment = overflow / (space_count - 2);
681 for (vsize i = 2; i < space_count; i++)
682 solution_[i] -= (i - 1) * spacing_increment;
687 // The solution_ vector stores the position of every live VerticalAxisGroup
688 // and every title. From that information,
689 // 1) within each system, stretch the staves so they land at the right position
690 // 2) find the offset of each system (relative to the printable area of the page).
691 // TODO: this function is getting too long, maybe split it up?
693 Page_layout_problem::find_system_offsets ()
695 SCM system_offsets = SCM_EOL;
696 SCM *tail = &system_offsets;
698 // spring_idx 0 is the top of the page. Interesting values start from 1.
699 vsize spring_idx = 1;
700 vector<Grob *> loose_lines;
701 vector<Real> loose_line_min_distances;
702 Grob *last_spaceable_line = 0;
703 Real last_spaceable_line_translation = 0;
704 Interval last_title_extent;
705 for (vsize i = 0; i < elements_.size (); ++i)
707 if (elements_[i].prob)
709 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
710 tail = SCM_CDRLOC (*tail);
711 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
713 // Lay out any non-spaceable lines between this line and
715 if (loose_lines.size ())
717 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
718 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
719 + elements_[i].padding);
721 loose_line_min_distances.push_back (min_distance);
722 loose_lines.push_back (0);
724 distribute_loose_lines (loose_lines, loose_line_min_distances,
725 last_spaceable_line_translation, -solution_[spring_idx]);
726 loose_lines.clear ();
727 loose_line_min_distances.clear ();
730 last_spaceable_line = 0;
731 last_spaceable_line_translation = -solution_[spring_idx];
732 last_title_extent = prob_extent;
737 // Getting this signs right here is a little tricky. The configuration
738 // we return has zero at the top of the page and positive numbers further
739 // down, as does the solution_ vector. Within a staff, however, positive
741 // TODO: perhaps change the way the page 'configuration variable works so
742 // that it is consistent with the usual up/down sign conventions in
743 // Lilypond. Then this would be less confusing.
745 // These two positions are relative to the page (with positive numbers being
747 Real first_staff_position = solution_[spring_idx];
748 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
749 Real system_position = first_staff_position + first_staff_min_translation;
751 // Position the staves within this system.
752 vector<Real> const &min_offsets = elements_[i].min_offsets;
753 bool found_spaceable_staff = false;
754 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
756 Grob *staff = elements_[i].staves[staff_idx];
757 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
759 if (is_spaceable (staff))
761 // this is relative to the system: negative numbers are down.
762 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
764 // Lay out any non-spaceable lines between this line and
766 if (loose_lines.size ())
769 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
771 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
772 loose_lines.push_back (staff);
774 distribute_loose_lines (loose_lines, loose_line_min_distances,
775 last_spaceable_line_translation, -solution_[spring_idx]);
776 loose_lines.clear ();
777 loose_line_min_distances.clear ();
779 last_spaceable_line = staff;
780 last_spaceable_line_translation = -solution_[spring_idx];
781 found_spaceable_staff = true;
786 if (loose_lines.empty ())
787 loose_lines.push_back (last_spaceable_line);
790 // NOTE: the way we do distances between loose lines (and other lines too, actually)
791 // is not the most accurate way possible: we only insert rods between adjacent
792 // lines. To be more accurate, we could insert rods between non-adjacent lines
793 // using a scheme similar to the one in set_column_rods.
794 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
797 // this is the first line in a system
799 if (loose_lines.back ())
800 // distance to the final line in the preceding system,
801 // including 'system-system-spacing 'padding
802 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
805 + elements_[i].padding);
806 else if (!last_title_extent.is_empty ())
807 // distance to the preceding title,
808 // including 'markup-system-spacing 'padding
809 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
810 + elements_[i].padding);
811 else // distance to the top margin
812 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
814 loose_line_min_distances.push_back (min_dist);
816 loose_lines.push_back (staff);
820 // Corner case: even if a system has no live staves, it still takes up
821 // one spring (a system with one live staff also takes up one spring),
822 // which we need to increment past.
823 if (!found_spaceable_staff)
826 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
827 tail = SCM_CDRLOC (*tail);
831 if (loose_lines.size ())
833 Grob *last = loose_lines.back ();
834 Interval last_ext = last->extent (last, Y_AXIS);
835 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
836 loose_lines.push_back (0);
838 distribute_loose_lines (loose_lines, loose_line_min_distances,
839 last_spaceable_line_translation, -page_height_);
843 assert (spring_idx == solution_.size () - 1);
844 return system_offsets;
847 // Given two lines that are already spaced (the first and last
848 // elements of loose_lines), distribute some unspaced lines between
850 // first_translation and last_translation are relative to the page.
852 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
853 vector<Real> const &min_distances,
854 Real first_translation, Real last_translation)
856 Simple_spacer spacer;
857 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
859 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
860 Spring spring (1.0, 0.0);
861 alter_spring_from_spacing_spec (spec, &spring);
862 spring.ensure_min_distance (min_distances[i]);
863 spacer.add_spring (spring);
866 // Remember: offsets are decreasing, since we're going from UP to DOWN!
867 spacer.solve (first_translation - last_translation, false);
869 vector<Real> solution = spacer.spring_positions ();
870 for (vsize i = 1; i + 1 < solution.size (); ++i)
872 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
873 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
878 Page_layout_problem::solution (bool ragged)
880 solve_rod_spring_problem (ragged);
881 return find_system_offsets ();
884 // Build upper and lower skylines for a system. We don't yet know the positions
885 // of the staves within the system, so we make the skyline as conservative as
886 // possible. That is, for the upper skyline, we pretend that all of the staves
887 // in the system are packed together close to the top system; for the lower
888 // skyline, we pretend that all of the staves are packed together close to
889 // the bottom system.
891 // The upper skyline is relative to the top staff; the lower skyline is relative to
894 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
895 vector<Real> const &minimum_translations,
899 if (minimum_translations.empty ())
902 assert (staves.size () == minimum_translations.size ());
903 Real first_translation = minimum_translations[0];
904 Real last_spaceable_dy = 0;
905 Real first_spaceable_dy = 0;
906 bool found_spaceable_staff = false;
908 for (vsize i = 0; i < staves.size (); ++i)
910 Real dy = minimum_translations[i] - first_translation;
912 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
916 up->merge ((*sky)[UP]);
920 down->merge ((*sky)[DOWN]);
923 if (is_spaceable (staves[i]))
925 if (!found_spaceable_staff)
927 found_spaceable_staff = true;
928 first_spaceable_dy = dy;
930 last_spaceable_dy = dy;
934 // Leave the up skyline at a position relative
935 // to the top spaceable staff.
936 up->raise (-first_spaceable_dy);
938 // Leave the down skyline at a position
939 // relative to the bottom spaceable staff.
940 down->raise (-last_spaceable_dy);
944 Page_layout_problem::prob_extent (Prob *p)
946 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
947 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
951 Page_layout_problem::first_staff_extent (Element const &e)
954 return prob_extent (e.prob);
955 else if (e.staves.size ())
956 return e.staves[0]->extent (e.staves[0], Y_AXIS);
958 return Interval (0, 0);
962 Page_layout_problem::last_staff_extent (Element const &e)
965 return prob_extent (e.prob);
966 else if (e.staves.size ())
967 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
969 return Interval (0, 0);
973 Page_layout_problem::get_details (Element const &elt)
975 if (elt.staves.empty ())
978 return get_details (elt.staves.back ()->get_system ());
982 Page_layout_problem::get_details (Grob *g)
984 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
985 return left_bound->get_property ("line-break-system-details");
989 Page_layout_problem::is_spaceable (Grob *g)
991 return !scm_is_number (g->get_property ("staff-affinity"));
995 Page_layout_problem::mark_as_spaceable (Grob *g)
997 g->set_property ("staff-affinity", SCM_BOOL_F);
1001 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1003 SCM pair = scm_sloppy_assq (sym, spec);
1004 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1006 *dest = scm_to_double (scm_cdr (pair));
1012 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1013 // Otherwise, return -infinity_f.
1014 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1017 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1019 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1020 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1021 ? ly_symbol2scm ("spaceable-fixed-spacing")
1022 : ly_symbol2scm ("loose-fixed-spacing");
1025 // The result of this function doesn't depend on "end," so we can reduce the
1026 // size of the cache by ignoring it.
1027 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1028 if (scm_is_number (cached))
1029 return robust_scm2double (cached, 0.0);
1032 Real ret = -infinity_f;
1034 // If we're pure, then paper-columns have not had their systems set,
1035 // and so elts[i]->get_system () is unreliable.
1036 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1037 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1039 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1041 SCM details = left_bound->get_property ("line-break-system-details");
1042 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1043 if (scm_is_pair (manual_dists))
1045 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1046 if (scm_is_number (forced))
1047 ret = max (ret, scm_to_double (forced));
1051 // Cache the result. As above, we ignore "end."
1053 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1059 add_stretchability (SCM alist, Real stretch)
1061 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1062 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1067 // We want to put a large stretch between a non-spaceable line and its
1068 // non-affinity staff. We want to put an even larger stretch between
1069 // a non-spaceable line and the top/bottom of the page. That way,
1070 // a spacing-affinity UP line at the bottom of the page will still be
1071 // placed close to its staff.
1072 const double LARGE_STRETCH = 10e5;
1073 const double HUGE_STRETCH = 10e7;
1075 // Returns the spacing spec connecting BEFORE to AFTER.
1077 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1079 // If there are no spacing wishes, return a very flexible spring.
1080 // This will occur, for example, if there are lyrics at the bottom of
1081 // the page, in which case we don't want the spring from the lyrics to
1082 // the bottom of the page to have much effect.
1083 if (!before || !after)
1084 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1086 if (is_spaceable (before))
1088 if (is_spaceable (after))
1089 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1092 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1093 return (affinity == DOWN)
1094 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1096 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1101 if (is_spaceable (after))
1103 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1104 return (affinity == UP)
1105 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1107 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1111 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1112 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1113 static bool warned = false;
1114 if (after_affinity > before_affinity
1115 && !warned && !pure)
1117 warning (_ ("staff-affinities should only decrease"));
1120 if (before_affinity != UP)
1121 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1122 else if (after_affinity != DOWN)
1123 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1124 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1134 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1139 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1140 spring->set_distance (space);
1141 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1142 spring->set_min_distance (min_dist);
1143 spring->set_default_strength ();
1145 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1146 spring->set_inverse_stretch_strength (stretch);
1150 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1152 vector<Grob *> output;
1153 for (vsize i = 0; i < input.size (); ++i)
1155 if (Hara_kiri_group_spanner::has_interface (input[i]))
1156 Hara_kiri_group_spanner::consider_suicide (input[i]);
1158 if (input[i]->is_live ())
1159 output.push_back (input[i]);