2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2009--2012 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);
383 bottom_loose_baseline_ = 0;
393 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
394 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
396 if (pb && pb->paper_)
398 SCM footnotes = get_footnotes_from_lines (systems);
399 add_footnotes_to_footer (footnotes, foot, pb);
402 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
404 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
405 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
406 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
409 // Initially, bottom_skyline_ represents the top of the page. Make
410 // it solid, so that the top of the first system will be forced
411 // below the top of the printable area.
412 bottom_skyline_.set_minimum_height (-header_height_);
414 SCM system_system_spacing = SCM_EOL;
415 SCM score_system_spacing = SCM_EOL;
416 SCM markup_system_spacing = SCM_EOL;
417 SCM score_markup_spacing = SCM_EOL;
418 SCM markup_markup_spacing = SCM_EOL;
420 // top_system_spacing controls the spring from the top of the printable
421 // area to the first staff. It allows the user to control the offset of
422 // the first staff (as opposed to the top of the first system) from the
423 // top of the page. Similarly for last_bottom_spacing.
424 SCM top_system_spacing = SCM_EOL;
425 SCM last_bottom_spacing = SCM_EOL;
426 if (pb && pb->paper_)
428 Output_def *paper = pb->paper_;
429 system_system_spacing = paper->c_variable ("system-system-spacing");
430 score_system_spacing = paper->c_variable ("score-system-spacing");
431 markup_system_spacing = paper->c_variable ("markup-system-spacing");
432 score_markup_spacing = paper->c_variable ("score-markup-spacing");
433 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
434 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
435 top_system_spacing = paper->c_variable ("top-system-spacing");
436 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
437 top_system_spacing = paper->c_variable ("top-markup-spacing");
439 // Note: the page height here does _not_ reserve space for headers and
440 // footers. This is because we want to anchor the top-system-spacing
441 // spring at the _top_ of the header.
442 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
443 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
445 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
446 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
447 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
448 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
450 bool last_system_was_title = false;
452 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
454 bool first = (s == systems);
456 if (Grob *g = unsmob_grob (scm_car (s)))
458 System *sys = dynamic_cast<System *> (g);
461 programming_error ("got a grob for vertical spacing that wasn't a System");
465 SCM spec = system_system_spacing;
467 spec = top_system_spacing;
468 else if (last_system_was_title)
469 spec = markup_system_spacing;
470 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
471 spec = score_system_spacing;
473 Spring spring (0, 0);
475 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
476 alter_spring_from_spacing_spec (spec, &spring);
477 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
479 append_system (sys, spring, indent, padding);
480 last_system_was_title = false;
482 else if (Prob *p = unsmob_prob (scm_car (s)))
484 SCM spec = first ? top_system_spacing
485 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
486 Spring spring (0, 0);
488 alter_spring_from_spacing_spec (spec, &spring);
489 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
491 append_prob (p, spring, padding);
492 last_system_was_title = true;
495 programming_error ("got a system that was neither a Grob nor a Prob");
498 Spring last_spring (0, 0);
499 Real last_padding = 0;
500 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
501 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
502 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
503 springs_.push_back (last_spring);
505 if (elements_.size ())
507 Real bottom_padding = 0;
509 // TODO: junk bottom-space now that we have last-bottom-spacing?
510 // bottom-space has the flexibility that one can do it per-system.
511 // NOTE: bottom-space is misnamed since it is not stretchable space.
512 if (Prob *p = elements_.back ().prob)
513 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
514 else if (elements_.back ().staves.size ())
516 SCM details = get_details (elements_.back ());
517 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
522 page_height_ -= bottom_padding;
527 Page_layout_problem::set_header_height (Real height)
529 header_height_ = height;
533 Page_layout_problem::set_footer_height (Real height)
535 footer_height_ = height;
539 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
541 Grob *align = sys->get_vertical_alignment ();
545 align->set_property ("positioning-done", SCM_BOOL_T);
547 extract_grob_set (align, "elements", all_elts);
548 vector<Grob *> elts = filter_dead_elements (all_elts);
549 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
550 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
552 Skyline up_skyline (UP);
553 Skyline down_skyline (DOWN);
554 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
555 up_skyline.shift (indent);
556 down_skyline.shift (indent);
557 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
559 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
561 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
562 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
563 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
564 sky->set_minimum_height (sky->max_height ()
567 + in_note_stencil->extent (Y_AXIS).length ()));
571 We need to call distance with skyline-horizontal-padding because
572 the system skyline-horizontal-padding is not added during the creation
573 of an individual staff. So we add the padding for the distance check
574 at the time of adding in the system.
576 Real minimum_distance = up_skyline.distance (bottom_skyline_,
577 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
581 Spring spring_copy = spring;
582 spring_copy.ensure_min_distance (minimum_distance);
583 springs_.push_back (spring_copy);
585 bottom_skyline_ = down_skyline;
586 elements_.push_back (Element (elts, minimum_offsets, padding));
588 // Add the springs for the VerticalAxisGroups in this system.
590 // If the user has specified the offsets of the individual staves, fix the
591 // springs at the given distances. Otherwise, use stretchable springs.
592 SCM details = get_details (elements_.back ());
593 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
594 vsize last_spaceable_staff = 0;
595 bool found_spaceable_staff = false;
596 for (vsize i = 0; i < elts.size (); ++i)
598 if (is_spaceable (elts[i]))
600 if (!found_spaceable_staff)
602 // Ensure space for any loose lines above this system
604 springs_.back ().ensure_min_distance (bottom_loose_baseline_
605 - minimum_offsets_with_min_dist[i]
607 found_spaceable_staff = true;
608 last_spaceable_staff = i;
609 // We don't add a spring for the first staff, since
610 // we are only adding springs _between_ staves here.
614 Spring spring (0.5, 0.0);
615 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
616 alter_spring_from_spacing_spec (spec, &spring);
618 springs_.push_back (spring);
619 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
620 springs_.back ().ensure_min_distance (min_distance);
622 if (scm_is_pair (manual_dists))
624 if (scm_is_number (scm_car (manual_dists)))
626 Real dy = scm_to_double (scm_car (manual_dists));
628 springs_.back ().set_distance (dy);
629 springs_.back ().set_min_distance (dy);
630 springs_.back ().set_inverse_stretch_strength (0);
632 manual_dists = scm_cdr (manual_dists);
634 last_spaceable_staff = i;
638 bottom_loose_baseline_ = found_spaceable_staff
639 ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
640 - minimum_offsets_with_min_dist.back ())
643 // Corner case: there was only one staff, and it wasn't spaceable.
644 // Mark it spaceable, because we do not allow non-spaceable staves
645 // to be at the top or bottom of a system.
646 if (!found_spaceable_staff && elts.size ())
647 mark_as_spaceable (elts[0]);
651 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
653 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
654 Real minimum_distance = 0;
655 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
659 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
660 bottom_skyline_ = (*sky)[DOWN];
662 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
664 Interval iv = sten->extent (Y_AXIS);
665 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
667 bottom_skyline_.clear ();
668 bottom_skyline_.set_minimum_height (iv[DOWN]);
671 Spring spring_copy = spring;
674 spring_copy.set_min_distance (minimum_distance);
675 spring_copy.set_inverse_stretch_strength (0.0);
676 spring_copy.set_distance (0.0);
679 spring_copy.ensure_min_distance (minimum_distance + padding);
681 springs_.push_back (spring_copy);
682 elements_.push_back (Element (prob, padding));
686 For ragged-last pages, we usually want to stretch the page so that it
687 is not much more compressed than the previous page. Here, if ragged is
688 true and you pass a value of fixed_force that !isinf, then I will try
689 to space this page using the given force. If it does not fit, I will
690 resort to just filling the page (non-raggedly).
693 Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
695 Simple_spacer spacer;
697 for (vsize i = 0; i < springs_.size (); ++i)
698 spacer.add_spring (springs_[i]);
700 if (ragged && !isinf (fixed_force))
702 // We need to tell the spacer it isn't ragged. Otherwise, it will
703 // refuse to stretch.
704 spacer.solve (page_height_, false);
706 if (spacer.configuration_length (fixed_force) <= page_height_)
707 spacer.set_force (fixed_force);
710 spacer.solve (page_height_, ragged);
712 solution_ = spacer.spring_positions ();
713 force_ = spacer.force ();
717 Real overflow = spacer.configuration_length (spacer.force ())
719 if (ragged && overflow < 1e-6)
720 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
723 warning (_f ("cannot fit music on page: overflow is %f",
725 warning (_ ("compressing music to fit"));
726 vsize space_count = solution_.size ();
727 Real spacing_increment = overflow / (space_count - 2);
728 for (vsize i = 2; i < space_count; i++)
729 solution_[i] -= (i - 1) * spacing_increment;
735 Page_layout_problem::force () const
740 // The solution_ vector stores the position of every live VerticalAxisGroup
741 // and every title. From that information,
742 // 1) within each system, stretch the staves so they land at the right position
743 // 2) find the offset of each system (relative to the printable area of the page).
744 // TODO: this function is getting too long, maybe split it up?
746 Page_layout_problem::find_system_offsets ()
748 SCM system_offsets = SCM_EOL;
749 SCM *tail = &system_offsets;
751 // spring_idx 0 is the top of the page. Interesting values start from 1.
752 vsize spring_idx = 1;
753 vector<Grob *> loose_lines;
754 vector<Real> loose_line_min_distances;
755 Grob *last_spaceable_line = 0;
756 Real last_spaceable_line_translation = 0;
757 Interval last_title_extent;
758 for (vsize i = 0; i < elements_.size (); ++i)
760 if (elements_[i].prob)
762 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
763 tail = SCM_CDRLOC (*tail);
764 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
766 // Lay out any non-spaceable lines between this line and
768 if (loose_lines.size ())
770 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
771 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
772 + elements_[i].padding);
774 loose_line_min_distances.push_back (min_distance);
775 loose_lines.push_back (0);
777 distribute_loose_lines (loose_lines, loose_line_min_distances,
778 last_spaceable_line_translation, -solution_[spring_idx]);
779 loose_lines.clear ();
780 loose_line_min_distances.clear ();
783 last_spaceable_line = 0;
784 last_spaceable_line_translation = -solution_[spring_idx];
785 last_title_extent = prob_extent;
790 // Getting this signs right here is a little tricky. The configuration
791 // we return has zero at the top of the page and positive numbers further
792 // down, as does the solution_ vector. Within a staff, however, positive
794 // TODO: perhaps change the way the page 'configuration variable works so
795 // that it is consistent with the usual up/down sign conventions in
796 // Lilypond. Then this would be less confusing.
798 // These two positions are relative to the page (with positive numbers being
800 Real first_staff_position = solution_[spring_idx];
801 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
802 Real system_position = first_staff_position + first_staff_min_translation;
804 // Position the staves within this system.
805 vector<Real> const &min_offsets = elements_[i].min_offsets;
806 bool found_spaceable_staff = false;
807 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
809 Grob *staff = elements_[i].staves[staff_idx];
810 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
812 if (is_spaceable (staff))
814 // this is relative to the system: negative numbers are down.
815 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
817 // Lay out any non-spaceable lines between this line and
819 if (loose_lines.size ())
822 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
825 // A null line to break any staff-affinity from the previous system
826 loose_line_min_distances.push_back (0.0);
827 loose_lines.push_back (0);
828 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
830 loose_lines.push_back (staff);
832 distribute_loose_lines (loose_lines, loose_line_min_distances,
833 last_spaceable_line_translation, -solution_[spring_idx]);
834 loose_lines.clear ();
835 loose_line_min_distances.clear ();
837 last_spaceable_line = staff;
838 last_spaceable_line_translation = -solution_[spring_idx];
839 found_spaceable_staff = true;
844 if (loose_lines.empty ())
845 loose_lines.push_back (last_spaceable_line);
848 // NOTE: the way we do distances between loose lines (and other lines too, actually)
849 // is not the most accurate way possible: we only insert rods between adjacent
850 // lines. To be more accurate, we could insert rods between non-adjacent lines
851 // using a scheme similar to the one in set_column_rods.
852 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
855 // this is the first line in a system
857 if (loose_lines.back ())
859 // distance to the final line in the preceding system,
860 // including 'system-system-spacing 'padding
861 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
863 + elements_[i].padding);
864 // A null line to break any staff-affinity for the previous system
865 loose_line_min_distances.push_back (0.0);
866 loose_lines.push_back (0);
868 else if (!last_title_extent.is_empty ())
869 // distance to the preceding title,
870 // including 'markup-system-spacing 'padding
871 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
872 + elements_[i].padding);
873 else // distance to the top margin
874 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
876 loose_line_min_distances.push_back (min_dist);
878 loose_lines.push_back (staff);
882 // Corner case: even if a system has no live staves, it still takes up
883 // one spring (a system with one live staff also takes up one spring),
884 // which we need to increment past.
885 if (!found_spaceable_staff)
888 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
889 tail = SCM_CDRLOC (*tail);
893 if (loose_lines.size ())
895 Grob *last = loose_lines.back ();
896 Interval last_ext = last->extent (last, Y_AXIS);
897 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
898 loose_lines.push_back (0);
900 distribute_loose_lines (loose_lines, loose_line_min_distances,
901 last_spaceable_line_translation, -page_height_);
905 assert (spring_idx == solution_.size () - 1);
906 return system_offsets;
909 // Given two lines that are already spaced (the first and last
910 // elements of loose_lines), distribute some unspaced lines between
912 // first_translation and last_translation are relative to the page.
914 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
915 vector<Real> const &min_distances,
916 Real first_translation, Real last_translation)
918 Simple_spacer spacer;
919 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
921 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
922 Spring spring (1.0, 0.0);
923 alter_spring_from_spacing_spec (spec, &spring);
924 spring.ensure_min_distance (min_distances[i]);
925 spacer.add_spring (spring);
928 // Remember: offsets are decreasing, since we're going from UP to DOWN!
929 spacer.solve (first_translation - last_translation, false);
931 vector<Real> solution = spacer.spring_positions ();
932 for (vsize i = 1; i + 1 < solution.size (); ++i)
935 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
936 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
941 Page_layout_problem::fixed_force_solution (Real force)
943 solve_rod_spring_problem (true, force);
944 return find_system_offsets ();
948 Page_layout_problem::solution (bool ragged)
950 solve_rod_spring_problem (ragged, -infinity_f);
951 return find_system_offsets ();
954 // Build upper and lower skylines for a system. We don't yet know the positions
955 // of the staves within the system, so we make the skyline as conservative as
956 // possible. That is, for the upper skyline, we pretend that all of the staves
957 // in the system are packed together close to the top system; for the lower
958 // skyline, we pretend that all of the staves are packed together close to
959 // the bottom system.
961 // The upper skyline is relative to the top staff; the lower skyline is relative to
964 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
965 vector<Real> const &minimum_translations,
969 if (minimum_translations.empty ())
972 assert (staves.size () == minimum_translations.size ());
973 Real first_translation = minimum_translations[0];
974 Real last_spaceable_dy = 0;
975 Real first_spaceable_dy = 0;
976 bool found_spaceable_staff = false;
978 for (vsize i = 0; i < staves.size (); ++i)
980 Real dy = minimum_translations[i] - first_translation;
982 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
986 up->merge ((*sky)[UP]);
990 down->merge ((*sky)[DOWN]);
993 if (is_spaceable (staves[i]))
995 if (!found_spaceable_staff)
997 found_spaceable_staff = true;
998 first_spaceable_dy = dy;
1000 last_spaceable_dy = dy;
1004 // Leave the up skyline at a position relative
1005 // to the top spaceable staff.
1006 up->raise (-first_spaceable_dy);
1008 // Leave the down skyline at a position
1009 // relative to the bottom spaceable staff.
1010 down->raise (-last_spaceable_dy);
1014 Page_layout_problem::prob_extent (Prob *p)
1016 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
1017 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
1021 Page_layout_problem::first_staff_extent (Element const &e)
1024 return prob_extent (e.prob);
1025 else if (e.staves.size ())
1026 return e.staves[0]->extent (e.staves[0], Y_AXIS);
1028 return Interval (0, 0);
1032 Page_layout_problem::last_staff_extent (Element const &e)
1035 return prob_extent (e.prob);
1036 else if (e.staves.size ())
1037 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
1039 return Interval (0, 0);
1043 Page_layout_problem::get_details (Element const &elt)
1045 if (elt.staves.empty ())
1048 return get_details (elt.staves.back ()->get_system ());
1052 Page_layout_problem::get_details (Grob *g)
1054 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1055 return left_bound->get_property ("line-break-system-details");
1059 Page_layout_problem::is_spaceable (Grob *g)
1061 return !scm_is_number (g->get_property ("staff-affinity"));
1065 Page_layout_problem::mark_as_spaceable (Grob *g)
1067 g->set_property ("staff-affinity", SCM_BOOL_F);
1071 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1073 SCM pair = scm_sloppy_assq (sym, spec);
1074 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1076 *dest = scm_to_double (scm_cdr (pair));
1082 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1083 // Otherwise, return -infinity_f.
1084 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1087 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1089 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1090 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1091 ? ly_symbol2scm ("spaceable-fixed-spacing")
1092 : ly_symbol2scm ("loose-fixed-spacing");
1095 // The result of this function doesn't depend on "end," so we can reduce the
1096 // size of the cache by ignoring it.
1097 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1098 if (scm_is_number (cached))
1099 return robust_scm2double (cached, 0.0);
1102 Real ret = -infinity_f;
1104 // If we're pure, then paper-columns have not had their systems set,
1105 // and so elts[i]->get_system () is unreliable.
1106 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1107 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1109 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1111 SCM details = left_bound->get_property ("line-break-system-details");
1112 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1113 if (scm_is_pair (manual_dists))
1115 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1116 if (scm_is_number (forced))
1117 ret = max (ret, scm_to_double (forced));
1121 // Cache the result. As above, we ignore "end."
1123 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1129 add_stretchability (SCM alist, Real stretch)
1131 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1132 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1137 // We want to put a large stretch between a non-spaceable line and its
1138 // non-affinity staff. We want to put an even larger stretch between
1139 // a non-spaceable line and the top/bottom of the page. That way,
1140 // a spacing-affinity UP line at the bottom of the page will still be
1141 // placed close to its staff.
1142 const double LARGE_STRETCH = 10e5;
1143 const double HUGE_STRETCH = 10e7;
1145 // Returns the spacing spec connecting BEFORE to AFTER.
1147 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1149 // If there are no spacing wishes, return a very flexible spring.
1150 // This will occur, for example, if there are lyrics at the bottom of
1151 // the page, in which case we don't want the spring from the lyrics to
1152 // the bottom of the page to have much effect.
1153 if (!before || !after)
1154 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1156 if (is_spaceable (before))
1158 if (is_spaceable (after))
1159 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1162 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1163 return (affinity == DOWN)
1164 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1166 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1171 if (is_spaceable (after))
1173 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1174 return (affinity == UP)
1175 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1177 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1181 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1182 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1183 static bool warned = false;
1184 if (after_affinity > before_affinity
1185 && !warned && !pure)
1187 warning (_ ("staff-affinities should only decrease"));
1190 if (before_affinity != UP)
1191 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1192 else if (after_affinity != DOWN)
1193 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1194 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1204 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1209 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1210 spring->set_distance (space);
1211 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1212 spring->set_min_distance (min_dist);
1213 spring->set_default_strength ();
1215 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1216 spring->set_inverse_stretch_strength (stretch);
1220 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1222 vector<Grob *> output;
1223 for (vsize i = 0; i < input.size (); ++i)
1225 if (Hara_kiri_group_spanner::has_interface (input[i]))
1226 Hara_kiri_group_spanner::consider_suicide (input[i]);
1228 if (input[i]->is_live ())
1229 output.push_back (input[i]);