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]);
814 // A null line to break any staff-affinity from the previous system
815 loose_line_min_distances.push_back (0.0);
816 loose_lines.push_back (0);
817 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
819 loose_lines.push_back (staff);
821 distribute_loose_lines (loose_lines, loose_line_min_distances,
822 last_spaceable_line_translation, -solution_[spring_idx]);
823 loose_lines.clear ();
824 loose_line_min_distances.clear ();
826 last_spaceable_line = staff;
827 last_spaceable_line_translation = -solution_[spring_idx];
828 found_spaceable_staff = true;
833 if (loose_lines.empty ())
834 loose_lines.push_back (last_spaceable_line);
837 // NOTE: the way we do distances between loose lines (and other lines too, actually)
838 // is not the most accurate way possible: we only insert rods between adjacent
839 // lines. To be more accurate, we could insert rods between non-adjacent lines
840 // using a scheme similar to the one in set_column_rods.
841 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
844 // this is the first line in a system
846 if (loose_lines.back ())
848 // distance to the final line in the preceding system,
849 // including 'system-system-spacing 'padding
850 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
852 + elements_[i].padding);
853 // A null line to break any staff-affinity for the previous system
854 loose_line_min_distances.push_back (0.0);
855 loose_lines.push_back (0);
857 else if (!last_title_extent.is_empty ())
858 // distance to the preceding title,
859 // including 'markup-system-spacing 'padding
860 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
861 + elements_[i].padding);
862 else // distance to the top margin
863 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
865 loose_line_min_distances.push_back (min_dist);
867 loose_lines.push_back (staff);
871 // Corner case: even if a system has no live staves, it still takes up
872 // one spring (a system with one live staff also takes up one spring),
873 // which we need to increment past.
874 if (!found_spaceable_staff)
877 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
878 tail = SCM_CDRLOC (*tail);
882 if (loose_lines.size ())
884 Grob *last = loose_lines.back ();
885 Interval last_ext = last->extent (last, Y_AXIS);
886 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
887 loose_lines.push_back (0);
889 distribute_loose_lines (loose_lines, loose_line_min_distances,
890 last_spaceable_line_translation, -page_height_);
894 assert (spring_idx == solution_.size () - 1);
895 return system_offsets;
898 // Given two lines that are already spaced (the first and last
899 // elements of loose_lines), distribute some unspaced lines between
901 // first_translation and last_translation are relative to the page.
903 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
904 vector<Real> const &min_distances,
905 Real first_translation, Real last_translation)
907 Simple_spacer spacer;
908 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
910 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
911 Spring spring (1.0, 0.0);
912 alter_spring_from_spacing_spec (spec, &spring);
913 spring.ensure_min_distance (min_distances[i]);
914 spacer.add_spring (spring);
917 // Remember: offsets are decreasing, since we're going from UP to DOWN!
918 spacer.solve (first_translation - last_translation, false);
920 vector<Real> solution = spacer.spring_positions ();
921 for (vsize i = 1; i + 1 < solution.size (); ++i)
924 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
925 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
930 Page_layout_problem::fixed_force_solution (Real force)
932 solve_rod_spring_problem (true, force);
933 return find_system_offsets ();
937 Page_layout_problem::solution (bool ragged)
939 solve_rod_spring_problem (ragged, -infinity_f);
940 return find_system_offsets ();
943 // Build upper and lower skylines for a system. We don't yet know the positions
944 // of the staves within the system, so we make the skyline as conservative as
945 // possible. That is, for the upper skyline, we pretend that all of the staves
946 // in the system are packed together close to the top system; for the lower
947 // skyline, we pretend that all of the staves are packed together close to
948 // the bottom system.
950 // The upper skyline is relative to the top staff; the lower skyline is relative to
953 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
954 vector<Real> const &minimum_translations,
958 if (minimum_translations.empty ())
961 assert (staves.size () == minimum_translations.size ());
962 Real first_translation = minimum_translations[0];
963 Real last_spaceable_dy = 0;
964 Real first_spaceable_dy = 0;
965 bool found_spaceable_staff = false;
967 for (vsize i = 0; i < staves.size (); ++i)
969 Real dy = minimum_translations[i] - first_translation;
971 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
975 up->merge ((*sky)[UP]);
979 down->merge ((*sky)[DOWN]);
982 if (is_spaceable (staves[i]))
984 if (!found_spaceable_staff)
986 found_spaceable_staff = true;
987 first_spaceable_dy = dy;
989 last_spaceable_dy = dy;
993 // Leave the up skyline at a position relative
994 // to the top spaceable staff.
995 up->raise (-first_spaceable_dy);
997 // Leave the down skyline at a position
998 // relative to the bottom spaceable staff.
999 down->raise (-last_spaceable_dy);
1003 Page_layout_problem::prob_extent (Prob *p)
1005 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
1006 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1010 Page_layout_problem::first_staff_extent (Element const &e)
1013 return prob_extent (e.prob);
1014 else if (e.staves.size ())
1015 return e.staves[0]->extent (e.staves[0], Y_AXIS);
1017 return Interval (0, 0);
1021 Page_layout_problem::last_staff_extent (Element const &e)
1024 return prob_extent (e.prob);
1025 else if (e.staves.size ())
1026 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1028 return Interval (0, 0);
1032 Page_layout_problem::get_details (Element const &elt)
1034 if (elt.staves.empty ())
1037 return get_details (elt.staves.back ()->get_system ());
1041 Page_layout_problem::get_details (Grob *g)
1043 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1044 return left_bound->get_property ("line-break-system-details");
1048 Page_layout_problem::is_spaceable (Grob *g)
1050 return !scm_is_number (g->get_property ("staff-affinity"));
1054 Page_layout_problem::mark_as_spaceable (Grob *g)
1056 g->set_property ("staff-affinity", SCM_BOOL_F);
1060 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1062 SCM pair = scm_sloppy_assq (sym, spec);
1063 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1065 *dest = scm_to_double (scm_cdr (pair));
1071 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1072 // Otherwise, return -infinity_f.
1073 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1076 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1078 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1079 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1080 ? ly_symbol2scm ("spaceable-fixed-spacing")
1081 : ly_symbol2scm ("loose-fixed-spacing");
1084 // The result of this function doesn't depend on "end," so we can reduce the
1085 // size of the cache by ignoring it.
1086 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1087 if (scm_is_number (cached))
1088 return robust_scm2double (cached, 0.0);
1091 Real ret = -infinity_f;
1093 // If we're pure, then paper-columns have not had their systems set,
1094 // and so elts[i]->get_system () is unreliable.
1095 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1096 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1098 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1100 SCM details = left_bound->get_property ("line-break-system-details");
1101 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1102 if (scm_is_pair (manual_dists))
1104 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1105 if (scm_is_number (forced))
1106 ret = max (ret, scm_to_double (forced));
1110 // Cache the result. As above, we ignore "end."
1112 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1118 add_stretchability (SCM alist, Real stretch)
1120 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1121 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1126 // We want to put a large stretch between a non-spaceable line and its
1127 // non-affinity staff. We want to put an even larger stretch between
1128 // a non-spaceable line and the top/bottom of the page. That way,
1129 // a spacing-affinity UP line at the bottom of the page will still be
1130 // placed close to its staff.
1131 const double LARGE_STRETCH = 10e5;
1132 const double HUGE_STRETCH = 10e7;
1134 // Returns the spacing spec connecting BEFORE to AFTER.
1136 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1138 // If there are no spacing wishes, return a very flexible spring.
1139 // This will occur, for example, if there are lyrics at the bottom of
1140 // the page, in which case we don't want the spring from the lyrics to
1141 // the bottom of the page to have much effect.
1142 if (!before || !after)
1143 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1145 if (is_spaceable (before))
1147 if (is_spaceable (after))
1148 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1151 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1152 return (affinity == DOWN)
1153 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1155 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1160 if (is_spaceable (after))
1162 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1163 return (affinity == UP)
1164 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1166 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1170 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1171 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1172 static bool warned = false;
1173 if (after_affinity > before_affinity
1174 && !warned && !pure)
1176 warning (_ ("staff-affinities should only decrease"));
1179 if (before_affinity != UP)
1180 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1181 else if (after_affinity != DOWN)
1182 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1183 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1193 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1198 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1199 spring->set_distance (space);
1200 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1201 spring->set_min_distance (min_dist);
1202 spring->set_default_strength ();
1204 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1205 spring->set_inverse_stretch_strength (stretch);
1209 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1211 vector<Grob *> output;
1212 for (vsize i = 0; i < input.size (); ++i)
1214 if (Hara_kiri_group_spanner::has_interface (input[i]))
1215 Hara_kiri_group_spanner::consider_suicide (input[i]);
1217 if (input[i]->is_live ())
1218 output.push_back (input[i]);