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_grobs (SCM lines)
44 vector<Grob *> footnotes;
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 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
56 footnotes.insert (footnotes.end (), footnote_grobs.begin (), footnote_grobs.end ());
58 else if (Prob *p = unsmob_prob (scm_car (s)))
60 SCM stencils = p->get_property ("footnotes");
61 if (stencils == SCM_EOL)
63 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
64 footnotes.push_back (0);
72 Page_layout_problem::get_footnote_count (SCM lines)
74 vector<Grob *> notes = get_footnote_grobs (lines);
79 Page_layout_problem::get_footnotes_from_lines (SCM lines)
81 if (!scm_is_pair (lines))
85 if (Grob *g = unsmob_grob (scm_car (lines)))
86 footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
87 else if (Prob *p = unsmob_prob (scm_car (lines)))
88 footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
91 programming_error ("Systems on a page must be a prob or grob.");
96 programming_error ("Footnotes must be added to lines before they are retrieved.");
101 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
103 if (Grob *g = unsmob_grob (scm_car (s)))
104 out = scm_cons (g->get_property ("footnote-stencil"), out);
105 else if (Prob *p = unsmob_prob (scm_car (s)))
106 out = scm_cons (p->get_property ("footnote-stencil"), out);
108 programming_error ("Systems on a page must be a prob or grob.");
111 return scm_reverse (out);
115 Adds a footnote stencil to each system. This stencil may
116 itself be comprised of several footnotes.
118 This is a long function, but it seems better to keep it intact rather than
119 splitting it into parts.
123 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
126 first, we have to see how many footnotes are on this page.
127 we need to do this first so that we can line them up
130 Output_def *paper = pb->paper_;
134 programming_error ("Cannot get footnotes because there is no valid paper block.");
138 SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
139 if (!scm_is_pair (number_footnote_table))
140 number_footnote_table = SCM_EOL;
141 SCM numbering_function = paper->c_variable ("footnote-numbering-function");
142 SCM layout = paper->self_scm ();
143 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
145 Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
146 Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
148 vector<Grob *> fn_grobs = get_footnote_grobs (lines);
149 vsize fn_count = fn_grobs.size ();
151 // now, make the footnote stencils with the numbering function
152 SCM numbers = SCM_EOL;
153 SCM in_text_numbers = SCM_EOL;
155 TODO: This recalculates numbering every time this function is called, including once
156 after the balloon prints are called. Although it is not a huge computational drain,
157 it'd be more elegant to turn this calculation off when it is no longer needed.
159 In a separate commit, it'd be nice to streamline the way that page layout property
160 is handled so that the process of building `config's in page-breaking does result
161 in duplicated work, either by making this process less complicated or (preferably)
162 by passing its results downstream.
164 vector<SCM> footnote_number_markups; // Holds the numbering markups.
165 vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
166 for (vsize i = 0; i < fn_count; i++)
170 SCM assertion_function = fn_grobs[i]->get_property ("numbering-assertion-function");
171 if (ly_is_procedure (assertion_function))
172 (void) scm_call_1 (assertion_function, scm_from_int (counter));
174 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
175 Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
178 programming_error ("Your numbering function needs to return a stencil.");
180 s = new Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL);
182 footnote_number_markups.push_back (markup);
183 footnote_number_stencils.push_back (s);
187 // find the maximum X_AXIS length
188 Real max_length = -infinity_f;
189 for (vsize i = 0; i < fn_count; i++)
190 max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
193 translate each stencil such that it attains the correct maximum length and bundle the
194 footnotes into a scheme object.
196 SCM *tail = &numbers;
197 SCM *in_text_tail = &in_text_numbers;
199 for (vsize i = 0; i < fn_count; i++)
201 *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
202 in_text_tail = SCM_CDRLOC (*in_text_tail);
203 footnote_number_stencils[i]->translate_axis ((max_length
204 - footnote_number_stencils[i]->extent (X_AXIS).length ()),
206 *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
207 tail = SCM_CDRLOC (*tail);
209 // build the footnotes
211 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
213 // Take care of musical systems.
214 if (Grob *g = unsmob_grob (scm_car (s)))
216 System *sys = dynamic_cast<System *> (g);
219 programming_error ("got a grob for footnotes that wasn't a System");
224 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
225 for (vsize i = 0; i < footnote_grobs.size (); i++)
227 Grob *footnote = footnote_grobs[i];
228 SCM footnote_markup = footnote->get_property ("footnote-text");
229 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
230 if (orig->is_broken ())
231 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
233 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
236 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
237 props, footnote_markup);
239 Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
240 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
241 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
243 if (orig->is_broken ())
244 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
245 do_numbering = do_numbering
246 || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
250 SCM annotation_scm = scm_car (in_text_numbers);
251 footnote->set_property ("text", annotation_scm);
252 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
254 orig->set_property ("text", annotation_scm);
255 if (orig->is_broken ())
256 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
257 orig->broken_intos_[i]->set_property ("text", annotation_scm);
260 Stencil *annotation = unsmob_stencil (scm_car (numbers));
261 annotation->translate_axis ((footnote_stencil->extent (Y_AXIS)[UP]
263 - annotation->extent (Y_AXIS)[UP]),
265 footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
266 numbers = scm_cdr (numbers);
267 in_text_numbers = scm_cdr (in_text_numbers);
269 if (!footnote_stencil->is_empty ())
271 if (to_boolean (footnote->get_property ("footnote")))
272 mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
274 in_note_mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
277 sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
278 sys->set_property ("footnote-stencil", mol.smobbed_copy ());
280 // Take care of top-level markups
281 else if (Prob *p = unsmob_prob (scm_car (s)))
283 SCM stencils = p->get_property ("footnotes");
286 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
288 Stencil footnote_stencil;
289 Stencil *footnote = unsmob_stencil (scm_caddar (st));
290 footnote_stencil.add_stencil (*footnote);
291 bool do_numbering = to_boolean (scm_cadar (st));
292 SCM in_text_stencil = Stencil ().smobbed_copy ();
295 Stencil *annotation = unsmob_stencil (scm_car (numbers));
296 SCM in_text_annotation = scm_car (in_text_numbers);
297 in_text_stencil = Text_interface::interpret_markup (layout,
300 if (!unsmob_stencil (in_text_stencil))
301 in_text_stencil = SCM_EOL;
302 annotation->translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
304 - annotation->extent (Y_AXIS)[UP]),
306 footnote_stencil.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
307 numbers = scm_cdr (numbers);
308 in_text_numbers = scm_cdr (in_text_numbers);
310 number_footnote_table = scm_cons (scm_cons (scm_caar (st),
312 number_footnote_table);
313 if (!footnote_stencil.is_empty ())
314 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
316 p->set_property ("footnote-stencil", mol.smobbed_copy ());
320 // note that this line of code doesn't do anything if numbering isn't turned on
321 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
325 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
327 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
330 SCM markup = paper->c_variable ("footnote-separator-markup");
332 if (!Text_interface::is_markup (markup))
335 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
338 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
340 return footnote_separator;
344 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
346 if (!foot && scm_is_pair (footnotes))
348 warning ("Must have a footer to add footnotes.");
351 bool footnotes_found = false;
352 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
353 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
355 footnotes = scm_reverse (footnotes);
357 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
359 Stencil *stencil = unsmob_stencil (scm_car (s));
364 if (!stencil->is_empty ())
366 foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
367 footnotes_found = true;
373 Stencil *separator = get_footnote_separator_stencil (pb->paper_);
375 foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
379 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
380 : bottom_skyline_ (DOWN)
382 Prob *page = unsmob_prob (page_scm);
392 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
393 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
395 if (pb && pb->paper_)
397 SCM footnotes = get_footnotes_from_lines (systems);
398 add_footnotes_to_footer (footnotes, foot, pb);
401 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
403 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
404 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
405 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
408 // Initially, bottom_skyline_ represents the top of the page. Make
409 // it solid, so that the top of the first system will be forced
410 // below the top of the printable area.
411 bottom_skyline_.set_minimum_height (-header_height_);
413 SCM system_system_spacing = SCM_EOL;
414 SCM score_system_spacing = SCM_EOL;
415 SCM markup_system_spacing = SCM_EOL;
416 SCM score_markup_spacing = SCM_EOL;
417 SCM markup_markup_spacing = SCM_EOL;
419 // top_system_spacing controls the spring from the top of the printable
420 // area to the first staff. It allows the user to control the offset of
421 // the first staff (as opposed to the top of the first system) from the
422 // top of the page. Similarly for last_bottom_spacing.
423 SCM top_system_spacing = SCM_EOL;
424 SCM last_bottom_spacing = SCM_EOL;
425 if (pb && pb->paper_)
427 Output_def *paper = pb->paper_;
428 system_system_spacing = paper->c_variable ("system-system-spacing");
429 score_system_spacing = paper->c_variable ("score-system-spacing");
430 markup_system_spacing = paper->c_variable ("markup-system-spacing");
431 score_markup_spacing = paper->c_variable ("score-markup-spacing");
432 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
433 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
434 top_system_spacing = paper->c_variable ("top-system-spacing");
435 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
436 top_system_spacing = paper->c_variable ("top-markup-spacing");
438 // Note: the page height here does _not_ reserve space for headers and
439 // footers. This is because we want to anchor the top-system-spacing
440 // spring at the _top_ of the header.
441 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
442 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
444 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
445 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
446 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
447 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
449 bool last_system_was_title = false;
451 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
453 bool first = (s == systems);
455 if (Grob *g = unsmob_grob (scm_car (s)))
457 System *sys = dynamic_cast<System *> (g);
460 programming_error ("got a grob for vertical spacing that wasn't a System");
464 SCM spec = system_system_spacing;
466 spec = top_system_spacing;
467 else if (last_system_was_title)
468 spec = markup_system_spacing;
469 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
470 spec = score_system_spacing;
472 Spring spring (0, 0);
474 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
475 alter_spring_from_spacing_spec (spec, &spring);
476 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
478 append_system (sys, spring, indent, padding);
479 last_system_was_title = false;
481 else if (Prob *p = unsmob_prob (scm_car (s)))
483 SCM spec = first ? top_system_spacing
484 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
485 Spring spring (0, 0);
487 alter_spring_from_spacing_spec (spec, &spring);
488 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
490 append_prob (p, spring, padding);
491 last_system_was_title = true;
494 programming_error ("got a system that was neither a Grob nor a Prob");
497 Spring last_spring (0, 0);
498 Real last_padding = 0;
499 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
500 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
501 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
502 springs_.push_back (last_spring);
504 if (elements_.size ())
506 Real bottom_padding = 0;
508 // TODO: junk bottom-space now that we have last-bottom-spacing?
509 // bottom-space has the flexibility that one can do it per-system.
510 // NOTE: bottom-space is misnamed since it is not stretchable space.
511 if (Prob *p = elements_.back ().prob)
512 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
513 else if (elements_.back ().staves.size ())
515 SCM details = get_details (elements_.back ());
516 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
521 page_height_ -= bottom_padding;
526 Page_layout_problem::set_header_height (Real height)
528 header_height_ = height;
532 Page_layout_problem::set_footer_height (Real height)
534 footer_height_ = height;
538 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
540 Grob *align = sys->get_vertical_alignment ();
544 align->set_property ("positioning-done", SCM_BOOL_T);
546 extract_grob_set (align, "elements", all_elts);
547 vector<Grob *> elts = filter_dead_elements (all_elts);
548 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
549 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
551 Skyline up_skyline (UP);
552 Skyline down_skyline (DOWN);
553 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
554 up_skyline.shift (indent);
555 down_skyline.shift (indent);
556 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
558 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
560 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
561 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
562 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
563 sky->set_minimum_height (sky->max_height ()
566 + in_note_stencil->extent (Y_AXIS).length ()));
570 We need to call distance with skyline-horizontal-padding because
571 the system skyline-horizontal-padding is not added during the creation
572 of an individual staff. So we add the padding for the distance check
573 at the time of adding in the system.
575 Real minimum_distance = up_skyline.distance (bottom_skyline_,
576 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
580 Spring spring_copy = spring;
581 spring_copy.ensure_min_distance (minimum_distance);
582 springs_.push_back (spring_copy);
584 bottom_skyline_ = down_skyline;
585 elements_.push_back (Element (elts, minimum_offsets, padding));
587 // Add the springs for the VerticalAxisGroups in this system.
589 // If the user has specified the offsets of the individual staves, fix the
590 // springs at the given distances. Otherwise, use stretchable springs.
591 SCM details = get_details (elements_.back ());
592 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
593 vsize last_spaceable_staff = 0;
594 bool found_spaceable_staff = false;
595 for (vsize i = 0; i < elts.size (); ++i)
597 if (is_spaceable (elts[i]))
599 // We don't add a spring for the first staff, since
600 // we are only adding springs _between_ staves here.
601 if (!found_spaceable_staff)
603 found_spaceable_staff = true;
604 last_spaceable_staff = i;
608 Spring spring (0.5, 0.0);
609 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
610 alter_spring_from_spacing_spec (spec, &spring);
612 springs_.push_back (spring);
613 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
614 springs_.back ().ensure_min_distance (min_distance);
616 if (scm_is_pair (manual_dists))
618 if (scm_is_number (scm_car (manual_dists)))
620 Real dy = scm_to_double (scm_car (manual_dists));
622 springs_.back ().set_distance (dy);
623 springs_.back ().set_min_distance (dy);
624 springs_.back ().set_inverse_stretch_strength (0);
626 manual_dists = scm_cdr (manual_dists);
628 last_spaceable_staff = i;
632 // Corner case: there was only one staff, and it wasn't spaceable.
633 // Mark it spaceable, because we do not allow non-spaceable staves
634 // to be at the top or bottom of a system.
635 if (!found_spaceable_staff && elts.size ())
636 mark_as_spaceable (elts[0]);
640 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
642 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
643 Real minimum_distance = 0;
644 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
648 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
649 bottom_skyline_ = (*sky)[DOWN];
651 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
653 Interval iv = sten->extent (Y_AXIS);
654 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
656 bottom_skyline_.clear ();
657 bottom_skyline_.set_minimum_height (iv[DOWN]);
660 Spring spring_copy = spring;
663 spring_copy.set_min_distance (minimum_distance);
664 spring_copy.set_inverse_stretch_strength (0.0);
665 spring_copy.set_distance (0.0);
668 spring_copy.ensure_min_distance (minimum_distance + padding);
670 springs_.push_back (spring_copy);
671 elements_.push_back (Element (prob, padding));
675 For ragged-last pages, we usually want to stretch the page so that it
676 is not much more compressed than the previous page. Here, if ragged is
677 true and you pass a value of fixed_force that !isinf, then I will try
678 to space this page using the given force. If it does not fit, I will
679 resort to just filling the page (non-raggedly).
682 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
684 Simple_spacer spacer;
686 for (vsize i = 0; i < springs_.size (); ++i)
687 spacer.add_spring (springs_[i]);
689 if (ragged && !isinf (fixed_force))
691 // We need to tell the spacer it isn't ragged. Otherwise, it will
692 // refuse to stretch.
693 spacer.solve (page_height_, false);
695 if (spacer.configuration_length (fixed_force) <= page_height_)
696 spacer.set_force (fixed_force);
699 spacer.solve (page_height_, ragged);
701 solution_ = spacer.spring_positions ();
702 force_ = spacer.force ();
706 Real overflow = spacer.configuration_length (spacer.force ())
708 if (ragged && overflow < 1e-6)
709 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
712 warning (_f ("cannot fit music on page: overflow is %f",
714 warning (_ ("compressing music to fit"));
715 vsize space_count = solution_.size ();
716 Real spacing_increment = overflow / (space_count - 2);
717 for (vsize i = 2; i < space_count; i++)
718 solution_[i] -= (i - 1) * spacing_increment;
724 Page_layout_problem::force () const
729 // The solution_ vector stores the position of every live VerticalAxisGroup
730 // and every title. From that information,
731 // 1) within each system, stretch the staves so they land at the right position
732 // 2) find the offset of each system (relative to the printable area of the page).
733 // TODO: this function is getting too long, maybe split it up?
735 Page_layout_problem::find_system_offsets ()
737 SCM system_offsets = SCM_EOL;
738 SCM *tail = &system_offsets;
740 // spring_idx 0 is the top of the page. Interesting values start from 1.
741 vsize spring_idx = 1;
742 vector<Grob *> loose_lines;
743 vector<Real> loose_line_min_distances;
744 Grob *last_spaceable_line = 0;
745 Real last_spaceable_line_translation = 0;
746 Interval last_title_extent;
747 for (vsize i = 0; i < elements_.size (); ++i)
749 if (elements_[i].prob)
751 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
752 tail = SCM_CDRLOC (*tail);
753 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
755 // Lay out any non-spaceable lines between this line and
757 if (loose_lines.size ())
759 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
760 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
761 + elements_[i].padding);
763 loose_line_min_distances.push_back (min_distance);
764 loose_lines.push_back (0);
766 distribute_loose_lines (loose_lines, loose_line_min_distances,
767 last_spaceable_line_translation, -solution_[spring_idx]);
768 loose_lines.clear ();
769 loose_line_min_distances.clear ();
772 last_spaceable_line = 0;
773 last_spaceable_line_translation = -solution_[spring_idx];
774 last_title_extent = prob_extent;
779 // Getting this signs right here is a little tricky. The configuration
780 // we return has zero at the top of the page and positive numbers further
781 // down, as does the solution_ vector. Within a staff, however, positive
783 // TODO: perhaps change the way the page 'configuration variable works so
784 // that it is consistent with the usual up/down sign conventions in
785 // Lilypond. Then this would be less confusing.
787 // These two positions are relative to the page (with positive numbers being
789 Real first_staff_position = solution_[spring_idx];
790 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
791 Real system_position = first_staff_position + first_staff_min_translation;
793 // Position the staves within this system.
794 vector<Real> const &min_offsets = elements_[i].min_offsets;
795 bool found_spaceable_staff = false;
796 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
798 Grob *staff = elements_[i].staves[staff_idx];
799 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
801 if (is_spaceable (staff))
803 // this is relative to the system: negative numbers are down.
804 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
806 // Lay out any non-spaceable lines between this line and
808 if (loose_lines.size ())
811 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
813 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
814 loose_lines.push_back (staff);
816 distribute_loose_lines (loose_lines, loose_line_min_distances,
817 last_spaceable_line_translation, -solution_[spring_idx]);
818 loose_lines.clear ();
819 loose_line_min_distances.clear ();
821 last_spaceable_line = staff;
822 last_spaceable_line_translation = -solution_[spring_idx];
823 found_spaceable_staff = true;
828 if (loose_lines.empty ())
829 loose_lines.push_back (last_spaceable_line);
832 // NOTE: the way we do distances between loose lines (and other lines too, actually)
833 // is not the most accurate way possible: we only insert rods between adjacent
834 // lines. To be more accurate, we could insert rods between non-adjacent lines
835 // using a scheme similar to the one in set_column_rods.
836 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
839 // this is the first line in a system
841 if (loose_lines.back ())
842 // distance to the final line in the preceding system,
843 // including 'system-system-spacing 'padding
844 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
847 + elements_[i].padding);
848 else if (!last_title_extent.is_empty ())
849 // distance to the preceding title,
850 // including 'markup-system-spacing 'padding
851 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
852 + elements_[i].padding);
853 else // distance to the top margin
854 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
856 loose_line_min_distances.push_back (min_dist);
858 loose_lines.push_back (staff);
862 // Corner case: even if a system has no live staves, it still takes up
863 // one spring (a system with one live staff also takes up one spring),
864 // which we need to increment past.
865 if (!found_spaceable_staff)
868 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
869 tail = SCM_CDRLOC (*tail);
873 if (loose_lines.size ())
875 Grob *last = loose_lines.back ();
876 Interval last_ext = last->extent (last, Y_AXIS);
877 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
878 loose_lines.push_back (0);
880 distribute_loose_lines (loose_lines, loose_line_min_distances,
881 last_spaceable_line_translation, -page_height_);
885 assert (spring_idx == solution_.size () - 1);
886 return system_offsets;
889 // Given two lines that are already spaced (the first and last
890 // elements of loose_lines), distribute some unspaced lines between
892 // first_translation and last_translation are relative to the page.
894 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
895 vector<Real> const &min_distances,
896 Real first_translation, Real last_translation)
898 Simple_spacer spacer;
899 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
901 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
902 Spring spring (1.0, 0.0);
903 alter_spring_from_spacing_spec (spec, &spring);
904 spring.ensure_min_distance (min_distances[i]);
905 spacer.add_spring (spring);
908 // Remember: offsets are decreasing, since we're going from UP to DOWN!
909 spacer.solve (first_translation - last_translation, false);
911 vector<Real> solution = spacer.spring_positions ();
912 for (vsize i = 1; i + 1 < solution.size (); ++i)
914 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
915 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
920 Page_layout_problem::fixed_force_solution (Real force)
922 solve_rod_spring_problem (true, force);
923 return find_system_offsets ();
927 Page_layout_problem::solution (bool ragged)
929 solve_rod_spring_problem (ragged, -infinity_f);
930 return find_system_offsets ();
933 // Build upper and lower skylines for a system. We don't yet know the positions
934 // of the staves within the system, so we make the skyline as conservative as
935 // possible. That is, for the upper skyline, we pretend that all of the staves
936 // in the system are packed together close to the top system; for the lower
937 // skyline, we pretend that all of the staves are packed together close to
938 // the bottom system.
940 // The upper skyline is relative to the top staff; the lower skyline is relative to
943 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
944 vector<Real> const &minimum_translations,
948 if (minimum_translations.empty ())
951 assert (staves.size () == minimum_translations.size ());
952 Real first_translation = minimum_translations[0];
953 Real last_spaceable_dy = 0;
954 Real first_spaceable_dy = 0;
955 bool found_spaceable_staff = false;
957 for (vsize i = 0; i < staves.size (); ++i)
959 Real dy = minimum_translations[i] - first_translation;
961 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
965 up->merge ((*sky)[UP]);
969 down->merge ((*sky)[DOWN]);
972 if (is_spaceable (staves[i]))
974 if (!found_spaceable_staff)
976 found_spaceable_staff = true;
977 first_spaceable_dy = dy;
979 last_spaceable_dy = dy;
983 // Leave the up skyline at a position relative
984 // to the top spaceable staff.
985 up->raise (-first_spaceable_dy);
987 // Leave the down skyline at a position
988 // relative to the bottom spaceable staff.
989 down->raise (-last_spaceable_dy);
993 Page_layout_problem::prob_extent (Prob *p)
995 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
996 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1000 Page_layout_problem::first_staff_extent (Element const &e)
1003 return prob_extent (e.prob);
1004 else if (e.staves.size ())
1005 return e.staves[0]->extent (e.staves[0], Y_AXIS);
1007 return Interval (0, 0);
1011 Page_layout_problem::last_staff_extent (Element const &e)
1014 return prob_extent (e.prob);
1015 else if (e.staves.size ())
1016 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1018 return Interval (0, 0);
1022 Page_layout_problem::get_details (Element const &elt)
1024 if (elt.staves.empty ())
1027 return get_details (elt.staves.back ()->get_system ());
1031 Page_layout_problem::get_details (Grob *g)
1033 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1034 return left_bound->get_property ("line-break-system-details");
1038 Page_layout_problem::is_spaceable (Grob *g)
1040 return !scm_is_number (g->get_property ("staff-affinity"));
1044 Page_layout_problem::mark_as_spaceable (Grob *g)
1046 g->set_property ("staff-affinity", SCM_BOOL_F);
1050 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1052 SCM pair = scm_sloppy_assq (sym, spec);
1053 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1055 *dest = scm_to_double (scm_cdr (pair));
1061 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1062 // Otherwise, return -infinity_f.
1063 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1066 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1068 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1069 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1070 ? ly_symbol2scm ("spaceable-fixed-spacing")
1071 : ly_symbol2scm ("loose-fixed-spacing");
1074 // The result of this function doesn't depend on "end," so we can reduce the
1075 // size of the cache by ignoring it.
1076 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1077 if (scm_is_number (cached))
1078 return robust_scm2double (cached, 0.0);
1081 Real ret = -infinity_f;
1083 // If we're pure, then paper-columns have not had their systems set,
1084 // and so elts[i]->get_system () is unreliable.
1085 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1086 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1088 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1090 SCM details = left_bound->get_property ("line-break-system-details");
1091 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1092 if (scm_is_pair (manual_dists))
1094 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1095 if (scm_is_number (forced))
1096 ret = max (ret, scm_to_double (forced));
1100 // Cache the result. As above, we ignore "end."
1102 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1108 add_stretchability (SCM alist, Real stretch)
1110 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1111 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1116 // We want to put a large stretch between a non-spaceable line and its
1117 // non-affinity staff. We want to put an even larger stretch between
1118 // a non-spaceable line and the top/bottom of the page. That way,
1119 // a spacing-affinity UP line at the bottom of the page will still be
1120 // placed close to its staff.
1121 const double LARGE_STRETCH = 10e5;
1122 const double HUGE_STRETCH = 10e7;
1124 // Returns the spacing spec connecting BEFORE to AFTER.
1126 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1128 // If there are no spacing wishes, return a very flexible spring.
1129 // This will occur, for example, if there are lyrics at the bottom of
1130 // the page, in which case we don't want the spring from the lyrics to
1131 // the bottom of the page to have much effect.
1132 if (!before || !after)
1133 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1135 if (is_spaceable (before))
1137 if (is_spaceable (after))
1138 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1141 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1142 return (affinity == DOWN)
1143 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1145 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1150 if (is_spaceable (after))
1152 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1153 return (affinity == UP)
1154 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1156 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1160 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1161 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1162 static bool warned = false;
1163 if (after_affinity > before_affinity
1164 && !warned && !pure)
1166 warning (_ ("staff-affinities should only decrease"));
1169 if (before_affinity != UP)
1170 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1171 else if (after_affinity != DOWN)
1172 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1173 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1183 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1188 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1189 spring->set_distance (space);
1190 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1191 spring->set_min_distance (min_dist);
1192 spring->set_default_strength ();
1194 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1195 spring->set_inverse_stretch_strength (stretch);
1199 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1201 vector<Grob *> output;
1202 for (vsize i = 0; i < input.size (); ++i)
1204 if (Hara_kiri_group_spanner::has_interface (input[i]))
1205 Hara_kiri_group_spanner::consider_suicide (input[i]);
1207 if (input[i]->is_live ())
1208 output.push_back (input[i]);