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 Returns a stencil for the footnote of each system. This stencil may
72 itself be comprised of several footnotes.
74 This is a long function, but it seems better to keep it intact rather than
75 splitting it into parts.
79 Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_book *pb)
82 first, we have to see how many footnotes are on this page.
83 we need to do this first so that we can line them up
86 Output_def *paper = pb->paper_;
90 programming_error ("Cannot get footnotes because there is no valid paper block.");
94 SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
95 if (!scm_is_pair (number_footnote_table))
96 number_footnote_table = SCM_EOL;
97 SCM numbering_function = paper->c_variable ("footnote-numbering-function");
98 SCM layout = paper->self_scm ();
99 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
101 Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
102 Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
104 vsize fn_count = get_footnote_count (lines);
106 // now, make the footnote stencils with the numbering function
107 SCM numbers = SCM_EOL;
108 SCM in_text_numbers = SCM_EOL;
109 bool do_numbering = to_boolean (paper->c_variable ("footnote-auto-numbering"));
110 // if there's no numbering, skip all this
112 TODO: This recalculates numbering every time this function is called, including once
113 after the balloon prints are called. Although it is not a huge computational drain,
114 it'd be more elegant to turn this calculation off when it is no longer needed.
116 In a separate commit, it'd be nice to streamline the way that page layout property
117 is handled so that the process of building `config's in page-breaking does result
118 in duplicated work, either by making this process less complicated or (preferably)
119 by passing its results downstream.
123 vector<SCM> footnote_number_markups; // Holds the numbering markups.
124 vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
125 for (vsize i = 0; i < fn_count; i++)
127 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
128 Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
131 programming_error ("Your numbering function needs to return a stencil.");
133 s = new Stencil (Box (Interval (0,0), Interval (0,0)), SCM_EOL);
135 footnote_number_markups.push_back (markup);
136 footnote_number_stencils.push_back (s);
140 // find the maximum X_AXIS length
141 Real max_length = -infinity_f;
142 for (vsize i = 0; i < fn_count; i++)
143 max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
146 translate each stencil such that it attains the correct maximum length and bundle the
147 footnotes into a scheme object.
149 SCM *tail = &numbers;
150 SCM *in_text_tail = &in_text_numbers;
152 for (vsize i = 0; i < fn_count; i++)
154 *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
155 in_text_tail = SCM_CDRLOC (*in_text_tail);
156 footnote_number_stencils[i]->translate_axis (max_length - footnote_number_stencils[i]->extent (X_AXIS).length (), X_AXIS);
157 *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
158 tail = SCM_CDRLOC (*tail);
161 // build the footnotes
163 SCM footnotes = SCM_EOL;
165 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
167 // Take care of musical systems.
168 if (Grob *g = unsmob_grob (scm_car (s)))
170 System *sys = dynamic_cast<System *> (g);
173 programming_error ("got a grob for footnotes that wasn't a System");
178 for (vsize i = 0; i < sys->footnote_grobs ()->size (); i++)
180 Grob *footnote = sys->footnote_grobs ()->at (i);
181 SCM footnote_markup = footnote->get_property ("footnote-text");
182 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
183 if (orig->is_broken ())
184 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
186 if (!Text_interface::is_markup (footnote_markup))
189 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
192 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
193 props, footnote_markup);
195 Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
198 SCM annotation_scm = scm_car (in_text_numbers);
199 footnote->set_property ("text", annotation_scm);
200 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
202 orig->set_property ("text", annotation_scm);
203 if (orig->is_broken ())
204 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
205 orig->broken_intos_[i]->set_property ("text", annotation_scm);
208 Stencil *annotation = unsmob_stencil (scm_car (numbers));
209 annotation->translate_axis (footnote_stencil->extent (Y_AXIS)[UP] + number_raise - annotation->extent(Y_AXIS)[UP], Y_AXIS);
210 footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
211 numbers = scm_cdr (numbers);
212 in_text_numbers = scm_cdr (in_text_numbers);
214 mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
216 footnotes = scm_cons (mol.smobbed_copy (), footnotes);
218 // Take care of top-level markups
219 else if (Prob *p = unsmob_prob (scm_car (s)))
221 SCM stencils = p->get_property ("footnotes");
222 if (stencils == SCM_EOL)
224 Stencil footnote_stencil;
226 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
229 Stencil *footnote = unsmob_stencil (scm_cadar (st));
230 mol.add_stencil (*footnote);
233 Stencil *annotation = unsmob_stencil (scm_car (numbers));
234 SCM in_text_annotation = scm_car (in_text_numbers);
235 SCM in_text_stencil = Text_interface::interpret_markup (layout, props, in_text_annotation);
236 if (!unsmob_stencil (in_text_stencil))
237 in_text_stencil = SCM_EOL;
238 number_footnote_table = scm_cons (scm_cons (scm_caar (st), in_text_stencil), number_footnote_table);
239 annotation->translate_axis (mol.extent (Y_AXIS)[UP] + number_raise - annotation->extent(Y_AXIS)[UP], Y_AXIS);
240 mol.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
241 numbers = scm_cdr (numbers);
242 in_text_numbers = scm_cdr (in_text_numbers);
244 footnote_stencil.add_at_edge (Y_AXIS, DOWN, mol, padding);
246 footnotes = scm_cons (footnote_stencil.smobbed_copy (), footnotes);
250 // note that this line of code doesn't do anything if numbering isn't turned on
251 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
252 if (!scm_is_pair (footnotes))
255 return scm_reverse (footnotes);
259 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
261 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
264 SCM markup = paper->c_variable ("footnote-separator-markup");
266 if (!Text_interface::is_markup (markup))
269 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
272 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
274 return footnote_separator;
278 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
280 bool footnotes_found = false;
281 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
282 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
284 footnotes = scm_reverse (footnotes);
286 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
288 Stencil *stencil = unsmob_stencil (scm_car (s));
293 if (!stencil->is_empty ())
295 foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
296 footnotes_found = true;
302 Stencil *separator = get_footnote_separator_stencil (pb->paper_);
304 foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
308 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems, int footnote_count)
309 : bottom_skyline_ (DOWN)
311 Prob *page = unsmob_prob (page_scm);
320 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
321 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
323 if (pb && pb->paper_)
325 if (to_boolean (pb->paper_->c_variable ("reset-footnotes-on-new-page")))
327 SCM footnotes = get_footnotes_from_lines (systems, footnote_count, pb);
328 add_footnotes_to_footer (footnotes, foot, pb);
331 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
333 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
334 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
335 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
338 // Initially, bottom_skyline_ represents the top of the page. Make
339 // it solid, so that the top of the first system will be forced
340 // below the top of the printable area.
341 bottom_skyline_.set_minimum_height (-header_height_);
343 SCM system_system_spacing = SCM_EOL;
344 SCM score_system_spacing = SCM_EOL;
345 SCM markup_system_spacing = SCM_EOL;
346 SCM score_markup_spacing = SCM_EOL;
347 SCM markup_markup_spacing = SCM_EOL;
349 // top_system_spacing controls the spring from the top of the printable
350 // area to the first staff. It allows the user to control the offset of
351 // the first staff (as opposed to the top of the first system) from the
352 // top of the page. Similarly for last_bottom_spacing.
353 SCM top_system_spacing = SCM_EOL;
354 SCM last_bottom_spacing = SCM_EOL;
355 if (pb && pb->paper_)
357 Output_def *paper = pb->paper_;
358 system_system_spacing = paper->c_variable ("system-system-spacing");
359 score_system_spacing = paper->c_variable ("score-system-spacing");
360 markup_system_spacing = paper->c_variable ("markup-system-spacing");
361 score_markup_spacing = paper->c_variable ("score-markup-spacing");
362 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
363 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
364 top_system_spacing = paper->c_variable ("top-system-spacing");
365 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
366 top_system_spacing = paper->c_variable ("top-markup-spacing");
368 // Note: the page height here does _not_ reserve space for headers and
369 // footers. This is because we want to anchor the top-system-spacing
370 // spring at the _top_ of the header.
371 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
372 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
374 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
375 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
377 bool last_system_was_title = false;
380 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
382 bool first = (s == systems);
384 if (Grob *g = unsmob_grob (scm_car (s)))
386 System *sys = dynamic_cast<System*> (g);
389 programming_error ("got a grob for vertical spacing that wasn't a System");
393 SCM spec = system_system_spacing;
395 spec = top_system_spacing;
396 else if (last_system_was_title)
397 spec = markup_system_spacing;
398 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
399 spec = score_system_spacing;
401 Spring spring (0, 0);
403 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
404 alter_spring_from_spacing_spec (spec, &spring);
405 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
407 append_system (sys, spring, indent, padding);
408 last_system_was_title = false;
410 else if (Prob *p = unsmob_prob (scm_car (s)))
412 SCM spec = first ? top_system_spacing
413 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
414 Spring spring (0, 0);
416 alter_spring_from_spacing_spec (spec, &spring);
417 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
419 append_prob (p, spring, padding);
420 last_system_was_title = true;
423 programming_error ("got a system that was neither a Grob nor a Prob");
426 Spring last_spring (0, 0);
427 Real last_padding = 0;
428 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
429 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
430 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
431 springs_.push_back (last_spring);
433 if (elements_.size ())
435 Real bottom_padding = 0;
437 // TODO: junk bottom-space now that we have last-bottom-spacing?
438 // bottom-space has the flexibility that one can do it per-system.
439 // NOTE: bottom-space is misnamed since it is not stretchable space.
440 if (Prob *p = elements_.back ().prob)
441 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
442 else if (elements_.back ().staves.size ())
444 SCM details = get_details (elements_.back ());
445 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
450 page_height_ -= bottom_padding;
455 Page_layout_problem::set_header_height (Real height)
457 header_height_ = height;
461 Page_layout_problem::set_footer_height (Real height)
463 footer_height_ = height;
467 Page_layout_problem::append_system (System *sys, Spring const& spring, Real indent, Real padding)
469 Grob *align = sys->get_vertical_alignment ();
473 align->set_property ("positioning-done", SCM_BOOL_T);
475 extract_grob_set (align, "elements", all_elts);
476 vector<Grob*> elts = filter_dead_elements (all_elts);
477 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
478 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
480 Skyline up_skyline (UP);
481 Skyline down_skyline (DOWN);
482 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
483 up_skyline.shift (indent);
484 down_skyline.shift (indent);
487 We need to call distance with skyline-horizontal-padding because
488 the system skyline-horizontal-padding is not added during the creation
489 of an individual staff. So we add the padding for the distance check
490 at the time of adding in the system.
492 Real minimum_distance = up_skyline.distance (bottom_skyline_, robust_scm2double (sys->get_property ("skyline-horizontal-padding"), 0)) + padding;
494 Spring spring_copy = spring;
495 spring_copy.ensure_min_distance (minimum_distance);
496 springs_.push_back (spring_copy);
498 bottom_skyline_ = down_skyline;
499 elements_.push_back (Element (elts, minimum_offsets, padding));
501 // Add the springs for the VerticalAxisGroups in this system.
503 // If the user has specified the offsets of the individual staves, fix the
504 // springs at the given distances. Otherwise, use stretchable springs.
505 SCM details = get_details (elements_.back ());
506 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
507 vsize last_spaceable_staff = 0;
508 bool found_spaceable_staff = false;
509 for (vsize i = 0; i < elts.size (); ++i)
511 if (is_spaceable (elts[i]))
513 // We don't add a spring for the first staff, since
514 // we are only adding springs _between_ staves here.
515 if (!found_spaceable_staff)
517 found_spaceable_staff = true;
518 last_spaceable_staff = i;
522 Spring spring (0.5, 0.0);
523 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
524 alter_spring_from_spacing_spec (spec, &spring);
526 springs_.push_back (spring);
527 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
528 springs_.back ().ensure_min_distance (min_distance);
530 if (scm_is_pair (manual_dists))
532 if (scm_is_number (scm_car (manual_dists)))
534 Real dy = scm_to_double (scm_car (manual_dists));
536 springs_.back ().set_distance (dy);
537 springs_.back ().set_min_distance (dy);
538 springs_.back ().set_inverse_stretch_strength (0);
540 manual_dists = scm_cdr (manual_dists);
542 last_spaceable_staff = i;
546 // Corner case: there was only one staff, and it wasn't spaceable.
547 // Mark it spaceable, because we do not allow non-spaceable staves
548 // to be at the top or bottom of a system.
549 if (!found_spaceable_staff && elts.size ())
550 mark_as_spaceable (elts[0]);
554 Page_layout_problem::append_prob (Prob *prob, Spring const& spring, Real padding)
556 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
557 Real minimum_distance = 0;
558 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
562 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
563 bottom_skyline_ = (*sky)[DOWN];
565 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
567 Interval iv = sten->extent (Y_AXIS);
568 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
570 bottom_skyline_.clear ();
571 bottom_skyline_.set_minimum_height (iv[DOWN]);
574 Spring spring_copy = spring;
577 spring_copy.set_min_distance (minimum_distance);
578 spring_copy.set_inverse_stretch_strength (0.0);
579 spring_copy.set_distance (0.0);
582 spring_copy.ensure_min_distance (minimum_distance + padding);
584 springs_.push_back (spring_copy);
585 elements_.push_back (Element (prob, padding));
589 Page_layout_problem::solve_rod_spring_problem (bool ragged)
591 Simple_spacer spacer;
593 for (vsize i = 0; i < springs_.size (); ++i)
594 spacer.add_spring (springs_[i]);
596 spacer.solve (page_height_, ragged);
597 solution_ = spacer.spring_positions ();
601 Real overflow = spacer.configuration_length (spacer.force ())
603 if (ragged && overflow < 1e-6)
604 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
607 warning (_f ("cannot fit music on page: overflow is %f",
609 warning (_ ("compressing music to fit"));
610 vsize space_count = solution_.size ();
611 Real spacing_increment = overflow / (space_count - 2);
612 for (vsize i = 2; i < space_count; i++)
613 solution_[i] -= (i-1) * spacing_increment;
618 // The solution_ vector stores the position of every live VerticalAxisGroup
619 // and every title. From that information,
620 // 1) within each system, stretch the staves so they land at the right position
621 // 2) find the offset of each system (relative to the printable area of the page).
622 // TODO: this function is getting too long, maybe split it up?
624 Page_layout_problem::find_system_offsets ()
626 SCM system_offsets = SCM_EOL;
627 SCM *tail = &system_offsets;
629 // spring_idx 0 is the top of the page. Interesting values start from 1.
630 vsize spring_idx = 1;
631 vector<Grob*> loose_lines;
632 vector<Real> loose_line_min_distances;
633 Grob *last_spaceable_line = 0;
634 Real last_spaceable_line_translation = 0;
635 Interval last_title_extent;
636 for (vsize i = 0; i < elements_.size (); ++i)
638 if (elements_[i].prob)
640 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
641 tail = SCM_CDRLOC (*tail);
642 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
644 // Lay out any non-spaceable lines between this line and
646 if (loose_lines.size ())
648 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
649 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
650 + elements_[i].padding);
652 loose_line_min_distances.push_back (min_distance);
653 loose_lines.push_back (0);
655 distribute_loose_lines (loose_lines, loose_line_min_distances,
656 last_spaceable_line_translation, -solution_[spring_idx]);
657 loose_lines.clear ();
658 loose_line_min_distances.clear ();
661 last_spaceable_line = 0;
662 last_spaceable_line_translation = -solution_[spring_idx];
663 last_title_extent = prob_extent;
668 // Getting this signs right here is a little tricky. The configuration
669 // we return has zero at the top of the page and positive numbers further
670 // down, as does the solution_ vector. Within a staff, however, positive
672 // TODO: perhaps change the way the page 'configuration variable works so
673 // that it is consistent with the usual up/down sign conventions in
674 // Lilypond. Then this would be less confusing.
676 // These two positions are relative to the page (with positive numbers being
678 Real first_staff_position = solution_[spring_idx];
679 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
680 Real system_position = first_staff_position + first_staff_min_translation;
682 // Position the staves within this system.
683 vector<Real> const& min_offsets = elements_[i].min_offsets;
684 bool found_spaceable_staff = false;
685 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
687 Grob *staff = elements_[i].staves[staff_idx];
688 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
690 if (is_spaceable (staff))
692 // this is relative to the system: negative numbers are down.
693 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
695 // Lay out any non-spaceable lines between this line and
697 if (loose_lines.size ())
700 loose_line_min_distances.push_back (min_offsets[staff_idx-1] - min_offsets[staff_idx]);
702 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
703 loose_lines.push_back (staff);
705 distribute_loose_lines (loose_lines, loose_line_min_distances,
706 last_spaceable_line_translation, -solution_[spring_idx]);
707 loose_lines.clear ();
708 loose_line_min_distances.clear ();
710 last_spaceable_line = staff;
711 last_spaceable_line_translation = -solution_[spring_idx];
712 found_spaceable_staff = true;
717 if (loose_lines.empty ())
718 loose_lines.push_back (last_spaceable_line);
721 // NOTE: the way we do distances between loose lines (and other lines too, actually)
722 // is not the most accurate way possible: we only insert rods between adjacent
723 // lines. To be more accurate, we could insert rods between non-adjacent lines
724 // using a scheme similar to the one in set_column_rods.
725 loose_line_min_distances.push_back (min_offsets[staff_idx-1] - min_offsets[staff_idx]);
727 { // this is the first line in a system
729 if (loose_lines.back ())
730 // distance to the final line in the preceding system,
731 // including 'system-system-spacing 'padding
732 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
735 + elements_[i].padding);
736 else if (!last_title_extent.is_empty ())
737 // distance to the preceding title,
738 // including 'markup-system-spacing 'padding
739 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
740 + elements_[i].padding);
741 else // distance to the top margin
742 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
744 loose_line_min_distances.push_back (min_dist);
746 loose_lines.push_back (staff);
750 // Corner case: even if a system has no live staves, it still takes up
751 // one spring (a system with one live staff also takes up one spring),
752 // which we need to increment past.
753 if (!found_spaceable_staff)
756 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
757 tail = SCM_CDRLOC (*tail);
761 if (loose_lines.size ())
763 Grob *last = loose_lines.back ();
764 Interval last_ext = last->extent (last, Y_AXIS);
765 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
766 loose_lines.push_back (0);
768 distribute_loose_lines (loose_lines, loose_line_min_distances,
769 last_spaceable_line_translation, -page_height_);
773 assert (spring_idx == solution_.size () - 1);
774 return system_offsets;
777 // Given two lines that are already spaced (the first and last
778 // elements of loose_lines), distribute some unspaced lines between
780 // first_translation and last_translation are relative to the page.
782 Page_layout_problem::distribute_loose_lines (vector<Grob*> const &loose_lines,
783 vector<Real> const &min_distances,
784 Real first_translation, Real last_translation)
786 Simple_spacer spacer;
787 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
789 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i+1], false, 0, INT_MAX);
790 Spring spring (1.0, 0.0);
791 alter_spring_from_spacing_spec (spec, &spring);
792 spring.ensure_min_distance (min_distances[i]);
793 spacer.add_spring (spring);
796 // Remember: offsets are decreasing, since we're going from UP to DOWN!
797 spacer.solve (first_translation - last_translation, false);
799 vector<Real> solution = spacer.spring_positions ();
800 for (vsize i = 1; i + 1 < solution.size (); ++i)
802 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
803 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
808 Page_layout_problem::solution (bool ragged)
810 solve_rod_spring_problem (ragged);
811 return find_system_offsets ();
814 // Build upper and lower skylines for a system. We don't yet know the positions
815 // of the staves within the system, so we make the skyline as conservative as
816 // possible. That is, for the upper skyline, we pretend that all of the staves
817 // in the system are packed together close to the top system; for the lower
818 // skyline, we pretend that all of the staves are packed together close to
819 // the bottom system.
821 // The upper skyline is relative to the top staff; the lower skyline is relative to
824 Page_layout_problem::build_system_skyline (vector<Grob*> const& staves,
825 vector<Real> const& minimum_translations,
829 if (minimum_translations.empty ())
832 assert (staves.size () == minimum_translations.size ());
833 Real first_translation = minimum_translations[0];
834 Real last_spaceable_dy = 0;
835 Real first_spaceable_dy = 0;
836 bool found_spaceable_staff = false;
838 for (vsize i = 0; i < staves.size (); ++i)
840 Real dy = minimum_translations[i] - first_translation;
842 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
846 up->merge ((*sky)[UP]);
850 down->merge ((*sky)[DOWN]);
853 if (is_spaceable (staves[i]))
855 if (!found_spaceable_staff)
857 found_spaceable_staff = true;
858 first_spaceable_dy = dy;
860 last_spaceable_dy = dy;
864 // Leave the up skyline at a position relative
865 // to the top spaceable staff.
866 up->raise (-first_spaceable_dy);
868 // Leave the down skyline at a position
869 // relative to the bottom spaceable staff.
870 down->raise (-last_spaceable_dy);
874 Page_layout_problem::prob_extent (Prob *p)
876 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
877 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
881 Page_layout_problem::first_staff_extent (Element const& e)
884 return prob_extent (e.prob);
885 else if (e.staves.size ())
886 return e.staves[0]->extent (e.staves[0], Y_AXIS);
888 return Interval (0, 0);
892 Page_layout_problem::last_staff_extent (Element const& e)
895 return prob_extent (e.prob);
896 else if (e.staves.size ())
897 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
899 return Interval (0, 0);
903 Page_layout_problem::get_details (Element const& elt)
905 if (elt.staves.empty ())
908 return get_details (elt.staves.back ()->get_system ());
912 Page_layout_problem::get_details (Grob *g)
914 Grob *left_bound = dynamic_cast<Spanner*> (g)->get_bound (LEFT);
915 return left_bound->get_property ("line-break-system-details");
919 Page_layout_problem::is_spaceable (Grob *g)
921 return !scm_is_number (g->get_property ("staff-affinity"));
925 Page_layout_problem::mark_as_spaceable (Grob *g)
927 g->set_property ("staff-affinity", SCM_BOOL_F);
931 Page_layout_problem::read_spacing_spec (SCM spec, Real* dest, SCM sym)
933 SCM pair = scm_sloppy_assq (sym, spec);
934 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
936 *dest = scm_to_double (scm_cdr (pair));
942 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
943 // Otherwise, return -infinity_f.
944 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
947 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
949 Spanner *after_sp = dynamic_cast<Spanner*> (after);
950 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
951 ? ly_symbol2scm ("spaceable-fixed-spacing")
952 : ly_symbol2scm ("loose-fixed-spacing");
955 // The result of this function doesn't depend on "end," so we can reduce the
956 // size of the cache by ignoring it.
957 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
958 if (scm_is_number (cached))
959 return robust_scm2double (cached, 0.0);
962 Real ret = -infinity_f;
964 // If we're pure, then paper-columns have not had their systems set,
965 // and so elts[i]->get_system () is unreliable.
966 System *sys = pure ? Grob::get_system (before) : before->get_system ();
967 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
969 if (is_spaceable (before) && is_spaceable (after) && left_bound)
971 SCM details = left_bound->get_property ("line-break-system-details");
972 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
973 if (scm_is_pair (manual_dists))
975 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
976 if (scm_is_number (forced))
977 ret = max (ret, scm_to_double (forced));
981 // Cache the result. As above, we ignore "end."
983 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
989 add_stretchability (SCM alist, Real stretch)
991 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
992 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
997 // We want to put a large stretch between a non-spaceable line and its
998 // non-affinity staff. We want to put an even larger stretch between
999 // a non-spaceable line and the top/bottom of the page. That way,
1000 // a spacing-affinity UP line at the bottom of the page will still be
1001 // placed close to its staff.
1002 const double LARGE_STRETCH = 10e5;
1003 const double HUGE_STRETCH = 10e7;
1005 // Returns the spacing spec connecting BEFORE to AFTER.
1007 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1009 // If there are no spacing wishes, return a very flexible spring.
1010 // This will occur, for example, if there are lyrics at the bottom of
1011 // the page, in which case we don't want the spring from the lyrics to
1012 // the bottom of the page to have much effect.
1013 if (!before || !after)
1014 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1016 if (is_spaceable (before))
1018 if (is_spaceable (after))
1019 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1022 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1023 return (affinity == DOWN)
1024 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1026 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1031 if (is_spaceable (after))
1033 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1034 return (affinity == UP)
1035 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1037 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1041 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1042 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1043 static bool warned = false;
1044 if (after_affinity > before_affinity
1045 && !warned && !pure)
1047 warning (_ ("staff-affinities should only decrease"));
1050 if (before_affinity != UP)
1051 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1052 else if (after_affinity != DOWN)
1053 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1054 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1064 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring* spring)
1069 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1070 spring->set_distance (space);
1071 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1072 spring->set_min_distance (min_dist);
1073 spring->set_default_strength ();
1075 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1076 spring->set_inverse_stretch_strength (stretch);
1080 Page_layout_problem::filter_dead_elements (vector<Grob*> const& input)
1082 vector<Grob*> output;
1083 for (vsize i = 0; i < input.size (); ++i)
1085 if (Hara_kiri_group_spanner::has_interface (input[i]))
1086 Hara_kiri_group_spanner::consider_suicide (input[i]);
1088 if (input[i]->is_live ())
1089 output.push_back (input[i]);