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;
110 TODO: This recalculates numbering every time this function is called, including once
111 after the balloon prints are called. Although it is not a huge computational drain,
112 it'd be more elegant to turn this calculation off when it is no longer needed.
114 In a separate commit, it'd be nice to streamline the way that page layout property
115 is handled so that the process of building `config's in page-breaking does result
116 in duplicated work, either by making this process less complicated or (preferably)
117 by passing its results downstream.
119 vector<SCM> footnote_number_markups; // Holds the numbering markups.
120 vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
121 for (vsize i = 0; i < fn_count; i++)
123 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
124 Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
127 programming_error ("Your numbering function needs to return a stencil.");
129 s = new Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL);
131 footnote_number_markups.push_back (markup);
132 footnote_number_stencils.push_back (s);
136 // find the maximum X_AXIS length
137 Real max_length = -infinity_f;
138 for (vsize i = 0; i < fn_count; i++)
139 max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
142 translate each stencil such that it attains the correct maximum length and bundle the
143 footnotes into a scheme object.
145 SCM *tail = &numbers;
146 SCM *in_text_tail = &in_text_numbers;
148 for (vsize i = 0; i < fn_count; i++)
150 *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
151 in_text_tail = SCM_CDRLOC (*in_text_tail);
152 footnote_number_stencils[i]->translate_axis (max_length - footnote_number_stencils[i]->extent (X_AXIS).length (), X_AXIS);
153 *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
154 tail = SCM_CDRLOC (*tail);
156 // build the footnotes
158 SCM footnotes = SCM_EOL;
160 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
162 // Take care of musical systems.
163 if (Grob *g = unsmob_grob (scm_car (s)))
165 System *sys = dynamic_cast<System *> (g);
168 programming_error ("got a grob for footnotes that wasn't a System");
173 for (vsize i = 0; i < sys->footnote_grobs ()->size (); i++)
175 Grob *footnote = sys->footnote_grobs ()->at (i);
176 SCM footnote_markup = footnote->get_property ("footnote-text");
177 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
178 if (orig->is_broken ())
179 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
181 if (!Text_interface::is_markup (footnote_markup))
184 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
187 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
188 props, footnote_markup);
190 Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
191 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
192 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
194 if (orig->is_broken ())
195 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
196 do_numbering = do_numbering || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
200 SCM annotation_scm = scm_car (in_text_numbers);
201 footnote->set_property ("text", annotation_scm);
202 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
204 orig->set_property ("text", annotation_scm);
205 if (orig->is_broken ())
206 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
207 orig->broken_intos_[i]->set_property ("text", annotation_scm);
210 Stencil *annotation = unsmob_stencil (scm_car (numbers));
211 annotation->translate_axis (footnote_stencil->extent (Y_AXIS)[UP] + number_raise - annotation->extent (Y_AXIS)[UP], Y_AXIS);
212 footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
213 numbers = scm_cdr (numbers);
214 in_text_numbers = scm_cdr (in_text_numbers);
216 mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
218 footnotes = scm_cons (mol.smobbed_copy (), footnotes);
220 // Take care of top-level markups
221 else if (Prob *p = unsmob_prob (scm_car (s)))
223 SCM stencils = p->get_property ("footnotes");
224 if (stencils == SCM_EOL)
226 Stencil footnote_stencil;
228 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
231 Stencil *footnote = unsmob_stencil (scm_caddar (st));
232 mol.add_stencil (*footnote);
233 bool do_numbering = to_boolean (scm_cadar (st));
234 SCM in_text_stencil = Stencil ().smobbed_copy ();
237 Stencil *annotation = unsmob_stencil (scm_car (numbers));
238 SCM in_text_annotation = scm_car (in_text_numbers);
239 in_text_stencil = Text_interface::interpret_markup (layout, props, in_text_annotation);
240 if (!unsmob_stencil (in_text_stencil))
241 in_text_stencil = SCM_EOL;
242 annotation->translate_axis (mol.extent (Y_AXIS)[UP] + number_raise - annotation->extent (Y_AXIS)[UP], Y_AXIS);
243 mol.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
244 numbers = scm_cdr (numbers);
245 in_text_numbers = scm_cdr (in_text_numbers);
247 number_footnote_table = scm_cons (scm_cons (scm_caar (st), in_text_stencil), number_footnote_table);
248 footnote_stencil.add_at_edge (Y_AXIS, DOWN, mol, padding);
250 footnotes = scm_cons (footnote_stencil.smobbed_copy (), footnotes);
254 // note that this line of code doesn't do anything if numbering isn't turned on
255 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
256 if (!scm_is_pair (footnotes))
259 return scm_reverse (footnotes);
263 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
265 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
268 SCM markup = paper->c_variable ("footnote-separator-markup");
270 if (!Text_interface::is_markup (markup))
273 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
276 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
278 return footnote_separator;
282 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
284 if (!foot && scm_is_pair (footnotes))
286 warning ("Must have a footer to add footnotes.");
289 bool footnotes_found = false;
290 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
291 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
293 footnotes = scm_reverse (footnotes);
295 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
297 Stencil *stencil = unsmob_stencil (scm_car (s));
302 if (!stencil->is_empty ())
304 foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
305 footnotes_found = true;
311 Stencil *separator = get_footnote_separator_stencil (pb->paper_);
313 foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
317 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems, int footnote_count)
318 : bottom_skyline_ (DOWN)
320 Prob *page = unsmob_prob (page_scm);
329 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
330 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
332 if (pb && pb->paper_)
334 if (to_boolean (pb->paper_->c_variable ("reset-footnotes-on-new-page")))
336 SCM footnotes = get_footnotes_from_lines (systems, footnote_count, pb);
337 add_footnotes_to_footer (footnotes, foot, pb);
340 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
342 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
343 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
344 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
347 // Initially, bottom_skyline_ represents the top of the page. Make
348 // it solid, so that the top of the first system will be forced
349 // below the top of the printable area.
350 bottom_skyline_.set_minimum_height (-header_height_);
352 SCM system_system_spacing = SCM_EOL;
353 SCM score_system_spacing = SCM_EOL;
354 SCM markup_system_spacing = SCM_EOL;
355 SCM score_markup_spacing = SCM_EOL;
356 SCM markup_markup_spacing = SCM_EOL;
358 // top_system_spacing controls the spring from the top of the printable
359 // area to the first staff. It allows the user to control the offset of
360 // the first staff (as opposed to the top of the first system) from the
361 // top of the page. Similarly for last_bottom_spacing.
362 SCM top_system_spacing = SCM_EOL;
363 SCM last_bottom_spacing = SCM_EOL;
364 if (pb && pb->paper_)
366 Output_def *paper = pb->paper_;
367 system_system_spacing = paper->c_variable ("system-system-spacing");
368 score_system_spacing = paper->c_variable ("score-system-spacing");
369 markup_system_spacing = paper->c_variable ("markup-system-spacing");
370 score_markup_spacing = paper->c_variable ("score-markup-spacing");
371 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
372 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
373 top_system_spacing = paper->c_variable ("top-system-spacing");
374 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
375 top_system_spacing = paper->c_variable ("top-markup-spacing");
377 // Note: the page height here does _not_ reserve space for headers and
378 // footers. This is because we want to anchor the top-system-spacing
379 // spring at the _top_ of the header.
380 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
381 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
383 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
384 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
386 bool last_system_was_title = false;
388 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
390 bool first = (s == systems);
392 if (Grob *g = unsmob_grob (scm_car (s)))
394 System *sys = dynamic_cast<System *> (g);
397 programming_error ("got a grob for vertical spacing that wasn't a System");
401 SCM spec = system_system_spacing;
403 spec = top_system_spacing;
404 else if (last_system_was_title)
405 spec = markup_system_spacing;
406 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
407 spec = score_system_spacing;
409 Spring spring (0, 0);
411 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
412 alter_spring_from_spacing_spec (spec, &spring);
413 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
415 append_system (sys, spring, indent, padding);
416 last_system_was_title = false;
418 else if (Prob *p = unsmob_prob (scm_car (s)))
420 SCM spec = first ? top_system_spacing
421 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
422 Spring spring (0, 0);
424 alter_spring_from_spacing_spec (spec, &spring);
425 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
427 append_prob (p, spring, padding);
428 last_system_was_title = true;
431 programming_error ("got a system that was neither a Grob nor a Prob");
434 Spring last_spring (0, 0);
435 Real last_padding = 0;
436 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
437 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
438 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
439 springs_.push_back (last_spring);
441 if (elements_.size ())
443 Real bottom_padding = 0;
445 // TODO: junk bottom-space now that we have last-bottom-spacing?
446 // bottom-space has the flexibility that one can do it per-system.
447 // NOTE: bottom-space is misnamed since it is not stretchable space.
448 if (Prob *p = elements_.back ().prob)
449 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
450 else if (elements_.back ().staves.size ())
452 SCM details = get_details (elements_.back ());
453 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
458 page_height_ -= bottom_padding;
463 Page_layout_problem::set_header_height (Real height)
465 header_height_ = height;
469 Page_layout_problem::set_footer_height (Real height)
471 footer_height_ = height;
475 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
477 Grob *align = sys->get_vertical_alignment ();
481 align->set_property ("positioning-done", SCM_BOOL_T);
483 extract_grob_set (align, "elements", all_elts);
484 vector<Grob *> elts = filter_dead_elements (all_elts);
485 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
486 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
488 Skyline up_skyline (UP);
489 Skyline down_skyline (DOWN);
490 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
491 up_skyline.shift (indent);
492 down_skyline.shift (indent);
495 We need to call distance with skyline-horizontal-padding because
496 the system skyline-horizontal-padding is not added during the creation
497 of an individual staff. So we add the padding for the distance check
498 at the time of adding in the system.
500 Real minimum_distance = up_skyline.distance (bottom_skyline_, robust_scm2double (sys->get_property ("skyline-horizontal-padding"), 0)) + padding;
502 Spring spring_copy = spring;
503 spring_copy.ensure_min_distance (minimum_distance);
504 springs_.push_back (spring_copy);
506 bottom_skyline_ = down_skyline;
507 elements_.push_back (Element (elts, minimum_offsets, padding));
509 // Add the springs for the VerticalAxisGroups in this system.
511 // If the user has specified the offsets of the individual staves, fix the
512 // springs at the given distances. Otherwise, use stretchable springs.
513 SCM details = get_details (elements_.back ());
514 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
515 vsize last_spaceable_staff = 0;
516 bool found_spaceable_staff = false;
517 for (vsize i = 0; i < elts.size (); ++i)
519 if (is_spaceable (elts[i]))
521 // We don't add a spring for the first staff, since
522 // we are only adding springs _between_ staves here.
523 if (!found_spaceable_staff)
525 found_spaceable_staff = true;
526 last_spaceable_staff = i;
530 Spring spring (0.5, 0.0);
531 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
532 alter_spring_from_spacing_spec (spec, &spring);
534 springs_.push_back (spring);
535 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
536 springs_.back ().ensure_min_distance (min_distance);
538 if (scm_is_pair (manual_dists))
540 if (scm_is_number (scm_car (manual_dists)))
542 Real dy = scm_to_double (scm_car (manual_dists));
544 springs_.back ().set_distance (dy);
545 springs_.back ().set_min_distance (dy);
546 springs_.back ().set_inverse_stretch_strength (0);
548 manual_dists = scm_cdr (manual_dists);
550 last_spaceable_staff = i;
554 // Corner case: there was only one staff, and it wasn't spaceable.
555 // Mark it spaceable, because we do not allow non-spaceable staves
556 // to be at the top or bottom of a system.
557 if (!found_spaceable_staff && elts.size ())
558 mark_as_spaceable (elts[0]);
562 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
564 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
565 Real minimum_distance = 0;
566 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
570 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
571 bottom_skyline_ = (*sky)[DOWN];
573 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
575 Interval iv = sten->extent (Y_AXIS);
576 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
578 bottom_skyline_.clear ();
579 bottom_skyline_.set_minimum_height (iv[DOWN]);
582 Spring spring_copy = spring;
585 spring_copy.set_min_distance (minimum_distance);
586 spring_copy.set_inverse_stretch_strength (0.0);
587 spring_copy.set_distance (0.0);
590 spring_copy.ensure_min_distance (minimum_distance + padding);
592 springs_.push_back (spring_copy);
593 elements_.push_back (Element (prob, padding));
597 Page_layout_problem::solve_rod_spring_problem (bool ragged)
599 Simple_spacer spacer;
601 for (vsize i = 0; i < springs_.size (); ++i)
602 spacer.add_spring (springs_[i]);
604 spacer.solve (page_height_, ragged);
605 solution_ = spacer.spring_positions ();
609 Real overflow = spacer.configuration_length (spacer.force ())
611 if (ragged && overflow < 1e-6)
612 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
615 warning (_f ("cannot fit music on page: overflow is %f",
617 warning (_ ("compressing music to fit"));
618 vsize space_count = solution_.size ();
619 Real spacing_increment = overflow / (space_count - 2);
620 for (vsize i = 2; i < space_count; i++)
621 solution_[i] -= (i - 1) * spacing_increment;
626 // The solution_ vector stores the position of every live VerticalAxisGroup
627 // and every title. From that information,
628 // 1) within each system, stretch the staves so they land at the right position
629 // 2) find the offset of each system (relative to the printable area of the page).
630 // TODO: this function is getting too long, maybe split it up?
632 Page_layout_problem::find_system_offsets ()
634 SCM system_offsets = SCM_EOL;
635 SCM *tail = &system_offsets;
637 // spring_idx 0 is the top of the page. Interesting values start from 1.
638 vsize spring_idx = 1;
639 vector<Grob *> loose_lines;
640 vector<Real> loose_line_min_distances;
641 Grob *last_spaceable_line = 0;
642 Real last_spaceable_line_translation = 0;
643 Interval last_title_extent;
644 for (vsize i = 0; i < elements_.size (); ++i)
646 if (elements_[i].prob)
648 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
649 tail = SCM_CDRLOC (*tail);
650 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
652 // Lay out any non-spaceable lines between this line and
654 if (loose_lines.size ())
656 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
657 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
658 + elements_[i].padding);
660 loose_line_min_distances.push_back (min_distance);
661 loose_lines.push_back (0);
663 distribute_loose_lines (loose_lines, loose_line_min_distances,
664 last_spaceable_line_translation, -solution_[spring_idx]);
665 loose_lines.clear ();
666 loose_line_min_distances.clear ();
669 last_spaceable_line = 0;
670 last_spaceable_line_translation = -solution_[spring_idx];
671 last_title_extent = prob_extent;
676 // Getting this signs right here is a little tricky. The configuration
677 // we return has zero at the top of the page and positive numbers further
678 // down, as does the solution_ vector. Within a staff, however, positive
680 // TODO: perhaps change the way the page 'configuration variable works so
681 // that it is consistent with the usual up/down sign conventions in
682 // Lilypond. Then this would be less confusing.
684 // These two positions are relative to the page (with positive numbers being
686 Real first_staff_position = solution_[spring_idx];
687 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
688 Real system_position = first_staff_position + first_staff_min_translation;
690 // Position the staves within this system.
691 vector<Real> const &min_offsets = elements_[i].min_offsets;
692 bool found_spaceable_staff = false;
693 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
695 Grob *staff = elements_[i].staves[staff_idx];
696 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
698 if (is_spaceable (staff))
700 // this is relative to the system: negative numbers are down.
701 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
703 // Lay out any non-spaceable lines between this line and
705 if (loose_lines.size ())
708 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
710 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
711 loose_lines.push_back (staff);
713 distribute_loose_lines (loose_lines, loose_line_min_distances,
714 last_spaceable_line_translation, -solution_[spring_idx]);
715 loose_lines.clear ();
716 loose_line_min_distances.clear ();
718 last_spaceable_line = staff;
719 last_spaceable_line_translation = -solution_[spring_idx];
720 found_spaceable_staff = true;
725 if (loose_lines.empty ())
726 loose_lines.push_back (last_spaceable_line);
729 // NOTE: the way we do distances between loose lines (and other lines too, actually)
730 // is not the most accurate way possible: we only insert rods between adjacent
731 // lines. To be more accurate, we could insert rods between non-adjacent lines
732 // using a scheme similar to the one in set_column_rods.
733 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
736 // this is the first line in a system
738 if (loose_lines.back ())
739 // distance to the final line in the preceding system,
740 // including 'system-system-spacing 'padding
741 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
744 + elements_[i].padding);
745 else if (!last_title_extent.is_empty ())
746 // distance to the preceding title,
747 // including 'markup-system-spacing 'padding
748 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
749 + elements_[i].padding);
750 else // distance to the top margin
751 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
753 loose_line_min_distances.push_back (min_dist);
755 loose_lines.push_back (staff);
759 // Corner case: even if a system has no live staves, it still takes up
760 // one spring (a system with one live staff also takes up one spring),
761 // which we need to increment past.
762 if (!found_spaceable_staff)
765 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
766 tail = SCM_CDRLOC (*tail);
770 if (loose_lines.size ())
772 Grob *last = loose_lines.back ();
773 Interval last_ext = last->extent (last, Y_AXIS);
774 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
775 loose_lines.push_back (0);
777 distribute_loose_lines (loose_lines, loose_line_min_distances,
778 last_spaceable_line_translation, -page_height_);
782 assert (spring_idx == solution_.size () - 1);
783 return system_offsets;
786 // Given two lines that are already spaced (the first and last
787 // elements of loose_lines), distribute some unspaced lines between
789 // first_translation and last_translation are relative to the page.
791 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
792 vector<Real> const &min_distances,
793 Real first_translation, Real last_translation)
795 Simple_spacer spacer;
796 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
798 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
799 Spring spring (1.0, 0.0);
800 alter_spring_from_spacing_spec (spec, &spring);
801 spring.ensure_min_distance (min_distances[i]);
802 spacer.add_spring (spring);
805 // Remember: offsets are decreasing, since we're going from UP to DOWN!
806 spacer.solve (first_translation - last_translation, false);
808 vector<Real> solution = spacer.spring_positions ();
809 for (vsize i = 1; i + 1 < solution.size (); ++i)
811 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
812 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
817 Page_layout_problem::solution (bool ragged)
819 solve_rod_spring_problem (ragged);
820 return find_system_offsets ();
823 // Build upper and lower skylines for a system. We don't yet know the positions
824 // of the staves within the system, so we make the skyline as conservative as
825 // possible. That is, for the upper skyline, we pretend that all of the staves
826 // in the system are packed together close to the top system; for the lower
827 // skyline, we pretend that all of the staves are packed together close to
828 // the bottom system.
830 // The upper skyline is relative to the top staff; the lower skyline is relative to
833 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
834 vector<Real> const &minimum_translations,
838 if (minimum_translations.empty ())
841 assert (staves.size () == minimum_translations.size ());
842 Real first_translation = minimum_translations[0];
843 Real last_spaceable_dy = 0;
844 Real first_spaceable_dy = 0;
845 bool found_spaceable_staff = false;
847 for (vsize i = 0; i < staves.size (); ++i)
849 Real dy = minimum_translations[i] - first_translation;
851 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
855 up->merge ((*sky)[UP]);
859 down->merge ((*sky)[DOWN]);
862 if (is_spaceable (staves[i]))
864 if (!found_spaceable_staff)
866 found_spaceable_staff = true;
867 first_spaceable_dy = dy;
869 last_spaceable_dy = dy;
873 // Leave the up skyline at a position relative
874 // to the top spaceable staff.
875 up->raise (-first_spaceable_dy);
877 // Leave the down skyline at a position
878 // relative to the bottom spaceable staff.
879 down->raise (-last_spaceable_dy);
883 Page_layout_problem::prob_extent (Prob *p)
885 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
886 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
890 Page_layout_problem::first_staff_extent (Element const &e)
893 return prob_extent (e.prob);
894 else if (e.staves.size ())
895 return e.staves[0]->extent (e.staves[0], Y_AXIS);
897 return Interval (0, 0);
901 Page_layout_problem::last_staff_extent (Element const &e)
904 return prob_extent (e.prob);
905 else if (e.staves.size ())
906 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
908 return Interval (0, 0);
912 Page_layout_problem::get_details (Element const &elt)
914 if (elt.staves.empty ())
917 return get_details (elt.staves.back ()->get_system ());
921 Page_layout_problem::get_details (Grob *g)
923 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
924 return left_bound->get_property ("line-break-system-details");
928 Page_layout_problem::is_spaceable (Grob *g)
930 return !scm_is_number (g->get_property ("staff-affinity"));
934 Page_layout_problem::mark_as_spaceable (Grob *g)
936 g->set_property ("staff-affinity", SCM_BOOL_F);
940 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
942 SCM pair = scm_sloppy_assq (sym, spec);
943 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
945 *dest = scm_to_double (scm_cdr (pair));
951 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
952 // Otherwise, return -infinity_f.
953 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
956 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
958 Spanner *after_sp = dynamic_cast<Spanner *> (after);
959 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
960 ? ly_symbol2scm ("spaceable-fixed-spacing")
961 : ly_symbol2scm ("loose-fixed-spacing");
964 // The result of this function doesn't depend on "end," so we can reduce the
965 // size of the cache by ignoring it.
966 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
967 if (scm_is_number (cached))
968 return robust_scm2double (cached, 0.0);
971 Real ret = -infinity_f;
973 // If we're pure, then paper-columns have not had their systems set,
974 // and so elts[i]->get_system () is unreliable.
975 System *sys = pure ? Grob::get_system (before) : before->get_system ();
976 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
978 if (is_spaceable (before) && is_spaceable (after) && left_bound)
980 SCM details = left_bound->get_property ("line-break-system-details");
981 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
982 if (scm_is_pair (manual_dists))
984 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
985 if (scm_is_number (forced))
986 ret = max (ret, scm_to_double (forced));
990 // Cache the result. As above, we ignore "end."
992 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
998 add_stretchability (SCM alist, Real stretch)
1000 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1001 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1006 // We want to put a large stretch between a non-spaceable line and its
1007 // non-affinity staff. We want to put an even larger stretch between
1008 // a non-spaceable line and the top/bottom of the page. That way,
1009 // a spacing-affinity UP line at the bottom of the page will still be
1010 // placed close to its staff.
1011 const double LARGE_STRETCH = 10e5;
1012 const double HUGE_STRETCH = 10e7;
1014 // Returns the spacing spec connecting BEFORE to AFTER.
1016 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1018 // If there are no spacing wishes, return a very flexible spring.
1019 // This will occur, for example, if there are lyrics at the bottom of
1020 // the page, in which case we don't want the spring from the lyrics to
1021 // the bottom of the page to have much effect.
1022 if (!before || !after)
1023 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1025 if (is_spaceable (before))
1027 if (is_spaceable (after))
1028 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1031 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1032 return (affinity == DOWN)
1033 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1035 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1040 if (is_spaceable (after))
1042 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1043 return (affinity == UP)
1044 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1046 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1050 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1051 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1052 static bool warned = false;
1053 if (after_affinity > before_affinity
1054 && !warned && !pure)
1056 warning (_ ("staff-affinities should only decrease"));
1059 if (before_affinity != UP)
1060 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1061 else if (after_affinity != DOWN)
1062 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1063 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1073 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1078 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1079 spring->set_distance (space);
1080 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1081 spring->set_min_distance (min_dist);
1082 spring->set_default_strength ();
1084 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1085 spring->set_inverse_stretch_strength (stretch);
1089 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1091 vector<Grob *> output;
1092 for (vsize i = 0; i < input.size (); ++i)
1094 if (Hara_kiri_group_spanner::has_interface (input[i]))
1095 Hara_kiri_group_spanner::consider_suicide (input[i]);
1097 if (input[i]->is_live ())
1098 output.push_back (input[i]);