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"
36 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
37 : bottom_skyline_ (DOWN)
39 Prob *page = unsmob_prob (page_scm);
48 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
49 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
51 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
52 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
53 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
56 // Initially, bottom_skyline_ represents the top of the page. Make
57 // it solid, so that the top of the first system will be forced
58 // below the top of the printable area.
59 bottom_skyline_.set_minimum_height (-header_height_);
61 SCM system_system_spacing = SCM_EOL;
62 SCM score_system_spacing = SCM_EOL;
63 SCM markup_system_spacing = SCM_EOL;
64 SCM score_markup_spacing = SCM_EOL;
65 SCM markup_markup_spacing = SCM_EOL;
67 // top_system_spacing controls the spring from the top of the printable
68 // area to the first staff. It allows the user to control the offset of
69 // the first staff (as opposed to the top of the first system) from the
70 // top of the page. Similarly for last_bottom_spacing.
71 SCM top_system_spacing = SCM_EOL;
72 SCM last_bottom_spacing = SCM_EOL;
75 Output_def *paper = pb->paper_;
76 system_system_spacing = paper->c_variable ("system-system-spacing");
77 score_system_spacing = paper->c_variable ("score-system-spacing");
78 markup_system_spacing = paper->c_variable ("markup-system-spacing");
79 score_markup_spacing = paper->c_variable ("score-markup-spacing");
80 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
81 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
82 top_system_spacing = paper->c_variable ("top-system-spacing");
83 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
84 top_system_spacing = paper->c_variable ("top-markup-spacing");
86 // Note: the page height here does _not_ reserve space for headers and
87 // footers. This is because we want to anchor the top-system-spacing
88 // spring at the _top_ of the header.
89 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
90 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
92 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
93 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
95 bool last_system_was_title = false;
98 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
100 bool first = (s == systems);
102 if (Grob *g = unsmob_grob (scm_car (s)))
104 System *sys = dynamic_cast<System*> (g);
107 programming_error ("got a grob for vertical spacing that wasn't a System");
111 SCM spec = system_system_spacing;
113 spec = top_system_spacing;
114 else if (last_system_was_title)
115 spec = markup_system_spacing;
116 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
117 spec = score_system_spacing;
119 Spring spring (0, 0);
121 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
122 alter_spring_from_spacing_spec (spec, &spring);
123 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
125 append_system (sys, spring, indent, padding);
126 last_system_was_title = false;
128 else if (Prob *p = unsmob_prob (scm_car (s)))
130 SCM spec = first ? top_system_spacing
131 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
132 Spring spring (0, 0);
134 alter_spring_from_spacing_spec (spec, &spring);
135 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
137 append_prob (p, spring, padding);
138 last_system_was_title = true;
141 programming_error ("got a system that was neither a Grob nor a Prob");
144 Spring last_spring (0, 0);
145 Real last_padding = 0;
146 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
147 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
148 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
149 springs_.push_back (last_spring);
151 if (elements_.size ())
153 Real bottom_padding = 0;
155 // TODO: junk bottom-space now that we have last-bottom-spacing?
156 // bottom-space has the flexibility that one can do it per-system.
157 // NOTE: bottom-space is misnamed since it is not stretchable space.
158 if (Prob *p = elements_.back ().prob)
159 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
160 else if (elements_.back ().staves.size ())
162 SCM details = get_details (elements_.back ());
163 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
168 page_height_ -= bottom_padding;
173 Page_layout_problem::set_header_height (Real height)
175 header_height_ = height;
179 Page_layout_problem::set_footer_height (Real height)
181 footer_height_ = height;
185 Page_layout_problem::append_system (System *sys, Spring const& spring, Real indent, Real padding)
187 Grob *align = sys->get_vertical_alignment ();
191 align->set_property ("positioning-done", SCM_BOOL_T);
193 extract_grob_set (align, "elements", all_elts);
194 vector<Grob*> elts = filter_dead_elements (all_elts);
195 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
196 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
198 Skyline up_skyline (UP);
199 Skyline down_skyline (DOWN);
200 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
201 up_skyline.shift (indent);
202 down_skyline.shift (indent);
205 We need to call distance with skyline-horizontal-padding because
206 the system skyline-horizontal-padding is not added during the creation
207 of an individual staff. So we add the padding for the distance check
208 at the time of adding in the system.
210 Real minimum_distance = up_skyline.distance (bottom_skyline_, robust_scm2double (sys->get_property ("skyline-horizontal-padding"), 0)) + padding;
212 Spring spring_copy = spring;
213 spring_copy.ensure_min_distance (minimum_distance);
214 springs_.push_back (spring_copy);
216 bottom_skyline_ = down_skyline;
217 elements_.push_back (Element (elts, minimum_offsets));
219 // Add the springs for the VerticalAxisGroups in this system.
221 // If the user has specified the offsets of the individual staves, fix the
222 // springs at the given distances. Otherwise, use stretchable springs.
223 SCM details = get_details (elements_.back ());
224 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
225 vsize last_spaceable_staff = 0;
226 bool found_spaceable_staff = false;
227 for (vsize i = 0; i < elts.size (); ++i)
229 if (is_spaceable (elts[i]))
231 // We don't add a spring for the first staff, since
232 // we are only adding springs _between_ staves here.
233 if (!found_spaceable_staff)
235 found_spaceable_staff = true;
236 last_spaceable_staff = i;
240 Spring spring (0.5, 0.0);
241 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
242 alter_spring_from_spacing_spec (spec, &spring);
244 springs_.push_back (spring);
245 Real min_distance = (found_spaceable_staff ? minimum_offsets[last_spaceable_staff] : 0) - minimum_offsets[i];
246 springs_.back ().ensure_min_distance (min_distance);
248 if (scm_is_pair (manual_dists))
250 if (scm_is_number (scm_car (manual_dists)))
252 Real dy = scm_to_double (scm_car (manual_dists));
254 springs_.back ().set_distance (dy);
255 springs_.back ().set_min_distance (dy);
256 springs_.back ().set_inverse_stretch_strength (0);
258 manual_dists = scm_cdr (manual_dists);
260 last_spaceable_staff = i;
264 // Corner case: there was only one staff, and it wasn't spaceable.
265 // Mark it spaceable, because we do not allow non-spaceable staves
266 // to be at the top or bottom of a system.
267 if (!found_spaceable_staff && elts.size ())
268 mark_as_spaceable (elts[0]);
272 Page_layout_problem::append_prob (Prob *prob, Spring const& spring, Real padding)
274 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
275 Real minimum_distance = 0;
276 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
280 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
281 bottom_skyline_ = (*sky)[DOWN];
283 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
285 Interval iv = sten->extent (Y_AXIS);
286 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
288 bottom_skyline_.clear ();
289 bottom_skyline_.set_minimum_height (iv[DOWN]);
292 Spring spring_copy = spring;
295 spring_copy.set_min_distance (minimum_distance);
296 spring_copy.set_inverse_stretch_strength (0.0);
297 spring_copy.set_distance (0.0);
300 spring_copy.ensure_min_distance (minimum_distance + padding);
302 springs_.push_back (spring_copy);
303 elements_.push_back (Element (prob));
307 Page_layout_problem::solve_rod_spring_problem (bool ragged)
309 Simple_spacer spacer;
311 for (vsize i = 0; i < springs_.size (); ++i)
312 spacer.add_spring (springs_[i]);
314 spacer.solve (page_height_, ragged);
315 solution_ = spacer.spring_positions ();
319 Real overflow = spacer.configuration_length (spacer.force ())
321 if (ragged && overflow < 1e-6)
322 warning (_ ("couldn't fit music on page: ragged-spacing was requested, but page was compressed"));
325 warning (_f ("couldn't fit music on page: overflow is %f",
327 warning (_ ("compressing music to fit"));
328 vsize space_count = solution_.size ();
329 Real spacing_increment = overflow / (space_count - 2);
330 for (vsize i = 2; i < space_count; i++)
331 solution_[i] -= (i-1) * spacing_increment;
336 // The solution_ vector stores the position of every live VerticalAxisGroup
337 // and every title. From that information,
338 // 1) within each system, stretch the staves so they land at the right position
339 // 2) find the offset of each system (relative to the printable area of the page).
340 // TODO: this function is getting too long, maybe split it up?
342 Page_layout_problem::find_system_offsets ()
344 SCM system_offsets = SCM_EOL;
345 SCM *tail = &system_offsets;
347 // spring_idx 0 is the top of the page. Interesting values start from 1.
348 vsize spring_idx = 1;
349 vector<Grob*> loose_lines;
350 vector<Real> loose_line_min_distances;
351 Grob *last_spaceable_line = 0;
352 Real last_spaceable_line_translation = 0;
353 Interval last_title_extent;
354 for (vsize i = 0; i < elements_.size (); ++i)
356 if (elements_[i].prob)
358 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
359 tail = SCM_CDRLOC (*tail);
360 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
362 // Lay out any non-spaceable lines between this line and
364 if (loose_lines.size ())
366 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
367 Real min_distance = -loose_extent[DOWN] + prob_extent[UP]; // TODO: include padding/minimum-distance
369 loose_line_min_distances.push_back (min_distance);
370 loose_lines.push_back (0);
372 distribute_loose_lines (loose_lines, loose_line_min_distances,
373 last_spaceable_line_translation, -solution_[spring_idx]);
374 loose_lines.clear ();
375 loose_line_min_distances.clear ();
378 last_spaceable_line = 0;
379 last_spaceable_line_translation = -solution_[spring_idx];
380 last_title_extent = prob_extent;
385 // Getting this signs right here is a little tricky. The configuration
386 // we return has zero at the top of the page and positive numbers further
387 // down, as does the solution_ vector. Within a staff, however, positive
389 // TODO: perhaps change the way the page 'configuration variable works so
390 // that it is consistent with the usual up/down sign conventions in
391 // Lilypond. Then this would be less confusing.
393 // These two positions are relative to the page (with positive numbers being
395 Real first_staff_position = solution_[spring_idx];
396 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
397 Real system_position = first_staff_position + first_staff_min_translation;
399 // Position the staves within this system.
400 Real translation = 0;
401 vector<Real> const& min_offsets = elements_[i].min_offsets;
402 bool found_spaceable_staff = false;
403 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
405 Grob *staff = elements_[i].staves[staff_idx];
406 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
408 if (is_spaceable (staff))
410 // this is relative to the system: negative numbers are down.
411 translation = system_position - solution_[spring_idx];
414 // Lay out any non-spaceable lines between this line and
416 if (loose_lines.size ())
418 loose_line_min_distances.push_back (min_offsets[staff_idx-1] - min_offsets[staff_idx]);
419 loose_lines.push_back (staff);
421 distribute_loose_lines (loose_lines, loose_line_min_distances,
422 last_spaceable_line_translation, translation - system_position);
423 loose_lines.clear ();
424 loose_line_min_distances.clear ();
426 last_spaceable_line = staff;
427 last_spaceable_line_translation = -solution_[spring_idx - 1];
429 staff->translate_axis (translation, Y_AXIS);
430 found_spaceable_staff = true;
434 if (loose_lines.empty ())
435 loose_lines.push_back (last_spaceable_line);
438 // NOTE: the way we do distances between loose lines (and other lines too, actually)
439 // is not the most accurate way possible: we only insert rods between adjacent
440 // lines. To be more accurate, we could insert rods between non-adjacent lines
441 // using a scheme similar to the one in set_column_rods.
442 loose_line_min_distances.push_back (min_offsets[staff_idx-1] - min_offsets[staff_idx]);
446 if (loose_lines.back ())
447 min_dist = Axis_group_interface::minimum_distance (loose_lines.back (),
450 else if (!last_title_extent.is_empty ())
451 { // distance to the preceding title
452 // TODO: add options for controlling the space between a loose line
453 // and a title/markup preceding it.
454 min_dist = staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN];
456 else // distance to the top margin
457 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
459 loose_line_min_distances.push_back (min_dist);
461 loose_lines.push_back (staff);
465 // Corner case: even if a system has no live staves, it still takes up
466 // one spring (a system with one live staff also takes up one spring),
467 // which we need to increment past.
468 if (!found_spaceable_staff)
471 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
472 tail = SCM_CDRLOC (*tail);
476 if (loose_lines.size ())
478 Grob *last = loose_lines.back ();
479 Interval last_ext = last->extent (last, Y_AXIS);
480 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
481 loose_lines.push_back (0);
483 distribute_loose_lines (loose_lines, loose_line_min_distances,
484 last_spaceable_line_translation, -page_height_);
488 assert (spring_idx == solution_.size () - 1);
489 return system_offsets;
492 // Given two lines that are already spaced (the first and last
493 // elements of loose_lines), distribute some unspaced lines between
495 // first_translation and last_translation are relative to the page.
497 Page_layout_problem::distribute_loose_lines (vector<Grob*> const &loose_lines,
498 vector<Real> const &min_distances,
499 Real first_translation, Real last_translation)
501 Simple_spacer spacer;
502 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
504 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i+1], false, 0, INT_MAX);
505 Spring spring (1.0, 0.0);
506 alter_spring_from_spacing_spec (spec, &spring);
507 spring.ensure_min_distance (min_distances[i]);
508 spacer.add_spring (spring);
511 // Remember: offsets are decreasing, since we're going from UP to DOWN!
512 spacer.solve (first_translation - last_translation, false);
514 vector<Real> solution = spacer.spring_positions ();
515 for (vsize i = 1; i + 1 < solution.size (); ++i)
517 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
518 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
523 Page_layout_problem::solution (bool ragged)
525 solve_rod_spring_problem (ragged);
526 return find_system_offsets ();
529 // Build upper and lower skylines for a system. We don't yet know the positions
530 // of the staves within the system, so we make the skyline as conservative as
531 // possible. That is, for the upper skyline, we pretend that all of the staves
532 // in the system are packed together close to the top system; for the lower
533 // skyline, we pretend that all of the staves are packed together close to
534 // the bottom system.
536 // The upper skyline is relative to the top staff; the lower skyline is relative to
539 Page_layout_problem::build_system_skyline (vector<Grob*> const& staves,
540 vector<Real> const& minimum_translations,
544 if (minimum_translations.empty ())
547 assert (staves.size () == minimum_translations.size ());
548 Real first_translation = minimum_translations[0];
549 Real last_spaceable_dy = 0;
550 Real first_spaceable_dy = 0;
551 bool found_spaceable_staff = false;
553 for (vsize i = 0; i < staves.size (); ++i)
555 Real dy = minimum_translations[i] - first_translation;
557 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
561 up->merge ((*sky)[UP]);
565 down->merge ((*sky)[DOWN]);
568 if (is_spaceable (staves[i]))
570 if (!found_spaceable_staff)
572 found_spaceable_staff = true;
573 first_spaceable_dy = dy;
575 last_spaceable_dy = dy;
579 // Leave the up skyline at a position relative
580 // to the top spaceable staff.
581 up->raise (-first_spaceable_dy);
583 // Leave the down skyline at a position
584 // relative to the bottom spaceable staff.
585 down->raise (-last_spaceable_dy);
589 Page_layout_problem::prob_extent (Prob *p)
591 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
592 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
596 Page_layout_problem::first_staff_extent (Element const& e)
599 return prob_extent (e.prob);
600 else if (e.staves.size ())
601 return e.staves[0]->extent (e.staves[0], Y_AXIS);
603 return Interval (0, 0);
607 Page_layout_problem::last_staff_extent (Element const& e)
610 return prob_extent (e.prob);
611 else if (e.staves.size ())
612 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
614 return Interval (0, 0);
618 Page_layout_problem::get_details (Element const& elt)
620 if (elt.staves.empty ())
623 return get_details (elt.staves.back ()->get_system ());
627 Page_layout_problem::get_details (Grob *g)
629 Grob *left_bound = dynamic_cast<Spanner*> (g)->get_bound (LEFT);
630 return left_bound->get_property ("line-break-system-details");
634 Page_layout_problem::is_spaceable (Grob *g)
636 return !scm_is_number (g->get_property ("staff-affinity"));
640 Page_layout_problem::mark_as_spaceable (Grob *g)
642 g->set_property ("staff-affinity", SCM_BOOL_F);
646 Page_layout_problem::read_spacing_spec (SCM spec, Real* dest, SCM sym)
648 SCM pair = scm_sloppy_assq (sym, spec);
649 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
651 *dest = scm_to_double (scm_cdr (pair));
657 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
658 // Otherwise, return -infinity_f.
659 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
662 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
664 Spanner *after_sp = dynamic_cast<Spanner*> (after);
665 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
666 ? ly_symbol2scm ("spaceable-fixed-spacing")
667 : ly_symbol2scm ("loose-fixed-spacing");
670 // The result of this function doesn't depend on "end," so we can reduce the
671 // size of the cache by ignoring it.
672 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
673 if (scm_is_number (cached))
674 return robust_scm2double (cached, 0.0);
677 SCM spec = Page_layout_problem::get_spacing_spec (before, after, pure, start, end);
678 Real ret = -infinity_f;
679 Real stretchability = 0;
680 if (Page_layout_problem::read_spacing_spec (spec, &stretchability, ly_symbol2scm ("stretchability"))
681 && stretchability == 0)
682 Page_layout_problem::read_spacing_spec (spec, &ret, ly_symbol2scm ("basic-distance"));
684 // If we're pure, then paper-columns have not had their systems set,
685 // and so elts[i]->get_system () is unreliable.
686 System *sys = pure ? Grob::get_system (before) : before->get_system ();
687 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
689 if (is_spaceable (before) && is_spaceable (after) && left_bound)
691 SCM details = left_bound->get_property ("line-break-system-details");
692 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
693 if (scm_is_pair (manual_dists))
695 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
696 if (scm_is_number (forced))
697 ret = max (ret, scm_to_double (forced));
701 // Cache the result. As above, we ignore "end."
703 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
709 add_stretchability (SCM alist, Real stretch)
711 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
712 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
717 // We want to put a large stretch between a non-spaceable line and its
718 // non-affinity staff. We want to put an even larger stretch between
719 // a non-spaceable line and the top/bottom of the page. That way,
720 // a spacing-affinity UP line at the bottom of the page will still be
721 // placed close to its staff.
722 const double LARGE_STRETCH = 10e5;
723 const double HUGE_STRETCH = 10e7;
725 // Returns the spacing spec connecting BEFORE to AFTER.
727 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
729 // If there are no spacing wishes, return a very flexible spring.
730 // This will occur, for example, if there are lyrics at the bottom of
731 // the page, in which case we don't want the spring from the lyrics to
732 // the bottom of the page to have much effect.
733 if (!before || !after)
734 return add_stretchability (SCM_EOL, HUGE_STRETCH);
736 if (is_spaceable (before))
738 if (is_spaceable (after))
739 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
742 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
743 return (affinity == DOWN)
744 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
746 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
751 if (is_spaceable (after))
753 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
754 return (affinity == UP)
755 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
757 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
761 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
762 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
763 if (after_affinity > before_affinity)
765 warning (_ ("staff-affinities should only decrease"));
766 after_affinity = before_affinity;
768 if (before_affinity != UP)
769 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
770 else if (after_affinity != DOWN)
771 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
772 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
782 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring* spring)
787 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
788 spring->set_distance (space);
789 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
790 spring->set_min_distance (min_dist);
791 spring->set_default_strength ();
793 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
795 spring->set_inverse_stretch_strength (stretch);
796 spring->set_inverse_compress_strength (stretch);
801 Page_layout_problem::filter_dead_elements (vector<Grob*> const& input)
803 vector<Grob*> output;
804 for (vsize i = 0; i < input.size (); ++i)
806 if (Hara_kiri_group_spanner::has_interface (input[i]))
807 Hara_kiri_group_spanner::consider_suicide (input[i]);
809 if (input[i]->is_live ())
810 output.push_back (input[i]);