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);
391 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
392 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
394 if (pb && pb->paper_)
396 SCM footnotes = get_footnotes_from_lines (systems);
397 add_footnotes_to_footer (footnotes, foot, pb);
400 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
402 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
403 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
404 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
407 // Initially, bottom_skyline_ represents the top of the page. Make
408 // it solid, so that the top of the first system will be forced
409 // below the top of the printable area.
410 bottom_skyline_.set_minimum_height (-header_height_);
412 SCM system_system_spacing = SCM_EOL;
413 SCM score_system_spacing = SCM_EOL;
414 SCM markup_system_spacing = SCM_EOL;
415 SCM score_markup_spacing = SCM_EOL;
416 SCM markup_markup_spacing = SCM_EOL;
418 // top_system_spacing controls the spring from the top of the printable
419 // area to the first staff. It allows the user to control the offset of
420 // the first staff (as opposed to the top of the first system) from the
421 // top of the page. Similarly for last_bottom_spacing.
422 SCM top_system_spacing = SCM_EOL;
423 SCM last_bottom_spacing = SCM_EOL;
424 if (pb && pb->paper_)
426 Output_def *paper = pb->paper_;
427 system_system_spacing = paper->c_variable ("system-system-spacing");
428 score_system_spacing = paper->c_variable ("score-system-spacing");
429 markup_system_spacing = paper->c_variable ("markup-system-spacing");
430 score_markup_spacing = paper->c_variable ("score-markup-spacing");
431 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
432 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
433 top_system_spacing = paper->c_variable ("top-system-spacing");
434 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
435 top_system_spacing = paper->c_variable ("top-markup-spacing");
437 // Note: the page height here does _not_ reserve space for headers and
438 // footers. This is because we want to anchor the top-system-spacing
439 // spring at the _top_ of the header.
440 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
441 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
443 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
444 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
445 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
446 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
448 bool last_system_was_title = false;
450 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
452 bool first = (s == systems);
454 if (Grob *g = unsmob_grob (scm_car (s)))
456 System *sys = dynamic_cast<System *> (g);
459 programming_error ("got a grob for vertical spacing that wasn't a System");
463 SCM spec = system_system_spacing;
465 spec = top_system_spacing;
466 else if (last_system_was_title)
467 spec = markup_system_spacing;
468 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
469 spec = score_system_spacing;
471 Spring spring (0, 0);
473 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
474 alter_spring_from_spacing_spec (spec, &spring);
475 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
477 append_system (sys, spring, indent, padding);
478 last_system_was_title = false;
480 else if (Prob *p = unsmob_prob (scm_car (s)))
482 SCM spec = first ? top_system_spacing
483 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
484 Spring spring (0, 0);
486 alter_spring_from_spacing_spec (spec, &spring);
487 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
489 append_prob (p, spring, padding);
490 last_system_was_title = true;
493 programming_error ("got a system that was neither a Grob nor a Prob");
496 Spring last_spring (0, 0);
497 Real last_padding = 0;
498 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
499 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
500 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
501 springs_.push_back (last_spring);
503 if (elements_.size ())
505 Real bottom_padding = 0;
507 // TODO: junk bottom-space now that we have last-bottom-spacing?
508 // bottom-space has the flexibility that one can do it per-system.
509 // NOTE: bottom-space is misnamed since it is not stretchable space.
510 if (Prob *p = elements_.back ().prob)
511 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
512 else if (elements_.back ().staves.size ())
514 SCM details = get_details (elements_.back ());
515 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
520 page_height_ -= bottom_padding;
525 Page_layout_problem::set_header_height (Real height)
527 header_height_ = height;
531 Page_layout_problem::set_footer_height (Real height)
533 footer_height_ = height;
537 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
539 Grob *align = sys->get_vertical_alignment ();
543 align->set_property ("positioning-done", SCM_BOOL_T);
545 extract_grob_set (align, "elements", all_elts);
546 vector<Grob *> elts = filter_dead_elements (all_elts);
547 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
548 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
550 Skyline up_skyline (UP);
551 Skyline down_skyline (DOWN);
552 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
553 up_skyline.shift (indent);
554 down_skyline.shift (indent);
555 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
557 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
559 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
560 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
561 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
562 sky->set_minimum_height (sky->max_height ()
565 + in_note_stencil->extent (Y_AXIS).length ()));
569 We need to call distance with skyline-horizontal-padding because
570 the system skyline-horizontal-padding is not added during the creation
571 of an individual staff. So we add the padding for the distance check
572 at the time of adding in the system.
574 Real minimum_distance = up_skyline.distance (bottom_skyline_,
575 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
579 Spring spring_copy = spring;
580 spring_copy.ensure_min_distance (minimum_distance);
581 springs_.push_back (spring_copy);
583 bottom_skyline_ = down_skyline;
584 elements_.push_back (Element (elts, minimum_offsets, padding));
586 // Add the springs for the VerticalAxisGroups in this system.
588 // If the user has specified the offsets of the individual staves, fix the
589 // springs at the given distances. Otherwise, use stretchable springs.
590 SCM details = get_details (elements_.back ());
591 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
592 vsize last_spaceable_staff = 0;
593 bool found_spaceable_staff = false;
594 for (vsize i = 0; i < elts.size (); ++i)
596 if (is_spaceable (elts[i]))
598 // We don't add a spring for the first staff, since
599 // we are only adding springs _between_ staves here.
600 if (!found_spaceable_staff)
602 found_spaceable_staff = true;
603 last_spaceable_staff = i;
607 Spring spring (0.5, 0.0);
608 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
609 alter_spring_from_spacing_spec (spec, &spring);
611 springs_.push_back (spring);
612 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
613 springs_.back ().ensure_min_distance (min_distance);
615 if (scm_is_pair (manual_dists))
617 if (scm_is_number (scm_car (manual_dists)))
619 Real dy = scm_to_double (scm_car (manual_dists));
621 springs_.back ().set_distance (dy);
622 springs_.back ().set_min_distance (dy);
623 springs_.back ().set_inverse_stretch_strength (0);
625 manual_dists = scm_cdr (manual_dists);
627 last_spaceable_staff = i;
631 // Corner case: there was only one staff, and it wasn't spaceable.
632 // Mark it spaceable, because we do not allow non-spaceable staves
633 // to be at the top or bottom of a system.
634 if (!found_spaceable_staff && elts.size ())
635 mark_as_spaceable (elts[0]);
639 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
641 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
642 Real minimum_distance = 0;
643 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
647 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
648 bottom_skyline_ = (*sky)[DOWN];
650 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
652 Interval iv = sten->extent (Y_AXIS);
653 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
655 bottom_skyline_.clear ();
656 bottom_skyline_.set_minimum_height (iv[DOWN]);
659 Spring spring_copy = spring;
662 spring_copy.set_min_distance (minimum_distance);
663 spring_copy.set_inverse_stretch_strength (0.0);
664 spring_copy.set_distance (0.0);
667 spring_copy.ensure_min_distance (minimum_distance + padding);
669 springs_.push_back (spring_copy);
670 elements_.push_back (Element (prob, padding));
674 Page_layout_problem::solve_rod_spring_problem (bool ragged)
676 Simple_spacer spacer;
678 for (vsize i = 0; i < springs_.size (); ++i)
679 spacer.add_spring (springs_[i]);
681 spacer.solve (page_height_, ragged);
682 solution_ = spacer.spring_positions ();
686 Real overflow = spacer.configuration_length (spacer.force ())
688 if (ragged && overflow < 1e-6)
689 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
692 warning (_f ("cannot fit music on page: overflow is %f",
694 warning (_ ("compressing music to fit"));
695 vsize space_count = solution_.size ();
696 Real spacing_increment = overflow / (space_count - 2);
697 for (vsize i = 2; i < space_count; i++)
698 solution_[i] -= (i - 1) * spacing_increment;
703 // The solution_ vector stores the position of every live VerticalAxisGroup
704 // and every title. From that information,
705 // 1) within each system, stretch the staves so they land at the right position
706 // 2) find the offset of each system (relative to the printable area of the page).
707 // TODO: this function is getting too long, maybe split it up?
709 Page_layout_problem::find_system_offsets ()
711 SCM system_offsets = SCM_EOL;
712 SCM *tail = &system_offsets;
714 // spring_idx 0 is the top of the page. Interesting values start from 1.
715 vsize spring_idx = 1;
716 vector<Grob *> loose_lines;
717 vector<Real> loose_line_min_distances;
718 Grob *last_spaceable_line = 0;
719 Real last_spaceable_line_translation = 0;
720 Interval last_title_extent;
721 for (vsize i = 0; i < elements_.size (); ++i)
723 if (elements_[i].prob)
725 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
726 tail = SCM_CDRLOC (*tail);
727 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
729 // Lay out any non-spaceable lines between this line and
731 if (loose_lines.size ())
733 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
734 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
735 + elements_[i].padding);
737 loose_line_min_distances.push_back (min_distance);
738 loose_lines.push_back (0);
740 distribute_loose_lines (loose_lines, loose_line_min_distances,
741 last_spaceable_line_translation, -solution_[spring_idx]);
742 loose_lines.clear ();
743 loose_line_min_distances.clear ();
746 last_spaceable_line = 0;
747 last_spaceable_line_translation = -solution_[spring_idx];
748 last_title_extent = prob_extent;
753 // Getting this signs right here is a little tricky. The configuration
754 // we return has zero at the top of the page and positive numbers further
755 // down, as does the solution_ vector. Within a staff, however, positive
757 // TODO: perhaps change the way the page 'configuration variable works so
758 // that it is consistent with the usual up/down sign conventions in
759 // Lilypond. Then this would be less confusing.
761 // These two positions are relative to the page (with positive numbers being
763 Real first_staff_position = solution_[spring_idx];
764 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
765 Real system_position = first_staff_position + first_staff_min_translation;
767 // Position the staves within this system.
768 vector<Real> const &min_offsets = elements_[i].min_offsets;
769 bool found_spaceable_staff = false;
770 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
772 Grob *staff = elements_[i].staves[staff_idx];
773 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
775 if (is_spaceable (staff))
777 // this is relative to the system: negative numbers are down.
778 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
780 // Lay out any non-spaceable lines between this line and
782 if (loose_lines.size ())
785 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
787 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
788 loose_lines.push_back (staff);
790 distribute_loose_lines (loose_lines, loose_line_min_distances,
791 last_spaceable_line_translation, -solution_[spring_idx]);
792 loose_lines.clear ();
793 loose_line_min_distances.clear ();
795 last_spaceable_line = staff;
796 last_spaceable_line_translation = -solution_[spring_idx];
797 found_spaceable_staff = true;
802 if (loose_lines.empty ())
803 loose_lines.push_back (last_spaceable_line);
806 // NOTE: the way we do distances between loose lines (and other lines too, actually)
807 // is not the most accurate way possible: we only insert rods between adjacent
808 // lines. To be more accurate, we could insert rods between non-adjacent lines
809 // using a scheme similar to the one in set_column_rods.
810 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
813 // this is the first line in a system
815 if (loose_lines.back ())
816 // distance to the final line in the preceding system,
817 // including 'system-system-spacing 'padding
818 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
821 + elements_[i].padding);
822 else if (!last_title_extent.is_empty ())
823 // distance to the preceding title,
824 // including 'markup-system-spacing 'padding
825 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
826 + elements_[i].padding);
827 else // distance to the top margin
828 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
830 loose_line_min_distances.push_back (min_dist);
832 loose_lines.push_back (staff);
836 // Corner case: even if a system has no live staves, it still takes up
837 // one spring (a system with one live staff also takes up one spring),
838 // which we need to increment past.
839 if (!found_spaceable_staff)
842 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
843 tail = SCM_CDRLOC (*tail);
847 if (loose_lines.size ())
849 Grob *last = loose_lines.back ();
850 Interval last_ext = last->extent (last, Y_AXIS);
851 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
852 loose_lines.push_back (0);
854 distribute_loose_lines (loose_lines, loose_line_min_distances,
855 last_spaceable_line_translation, -page_height_);
859 assert (spring_idx == solution_.size () - 1);
860 return system_offsets;
863 // Given two lines that are already spaced (the first and last
864 // elements of loose_lines), distribute some unspaced lines between
866 // first_translation and last_translation are relative to the page.
868 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
869 vector<Real> const &min_distances,
870 Real first_translation, Real last_translation)
872 Simple_spacer spacer;
873 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
875 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
876 Spring spring (1.0, 0.0);
877 alter_spring_from_spacing_spec (spec, &spring);
878 spring.ensure_min_distance (min_distances[i]);
879 spacer.add_spring (spring);
882 // Remember: offsets are decreasing, since we're going from UP to DOWN!
883 spacer.solve (first_translation - last_translation, false);
885 vector<Real> solution = spacer.spring_positions ();
886 for (vsize i = 1; i + 1 < solution.size (); ++i)
888 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
889 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
894 Page_layout_problem::solution (bool ragged)
896 solve_rod_spring_problem (ragged);
897 return find_system_offsets ();
900 // Build upper and lower skylines for a system. We don't yet know the positions
901 // of the staves within the system, so we make the skyline as conservative as
902 // possible. That is, for the upper skyline, we pretend that all of the staves
903 // in the system are packed together close to the top system; for the lower
904 // skyline, we pretend that all of the staves are packed together close to
905 // the bottom system.
907 // The upper skyline is relative to the top staff; the lower skyline is relative to
910 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
911 vector<Real> const &minimum_translations,
915 if (minimum_translations.empty ())
918 assert (staves.size () == minimum_translations.size ());
919 Real first_translation = minimum_translations[0];
920 Real last_spaceable_dy = 0;
921 Real first_spaceable_dy = 0;
922 bool found_spaceable_staff = false;
924 for (vsize i = 0; i < staves.size (); ++i)
926 Real dy = minimum_translations[i] - first_translation;
928 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
932 up->merge ((*sky)[UP]);
936 down->merge ((*sky)[DOWN]);
939 if (is_spaceable (staves[i]))
941 if (!found_spaceable_staff)
943 found_spaceable_staff = true;
944 first_spaceable_dy = dy;
946 last_spaceable_dy = dy;
950 // Leave the up skyline at a position relative
951 // to the top spaceable staff.
952 up->raise (-first_spaceable_dy);
954 // Leave the down skyline at a position
955 // relative to the bottom spaceable staff.
956 down->raise (-last_spaceable_dy);
960 Page_layout_problem::prob_extent (Prob *p)
962 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
963 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
967 Page_layout_problem::first_staff_extent (Element const &e)
970 return prob_extent (e.prob);
971 else if (e.staves.size ())
972 return e.staves[0]->extent (e.staves[0], Y_AXIS);
974 return Interval (0, 0);
978 Page_layout_problem::last_staff_extent (Element const &e)
981 return prob_extent (e.prob);
982 else if (e.staves.size ())
983 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
985 return Interval (0, 0);
989 Page_layout_problem::get_details (Element const &elt)
991 if (elt.staves.empty ())
994 return get_details (elt.staves.back ()->get_system ());
998 Page_layout_problem::get_details (Grob *g)
1000 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
1001 return left_bound->get_property ("line-break-system-details");
1005 Page_layout_problem::is_spaceable (Grob *g)
1007 return !scm_is_number (g->get_property ("staff-affinity"));
1011 Page_layout_problem::mark_as_spaceable (Grob *g)
1013 g->set_property ("staff-affinity", SCM_BOOL_F);
1017 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1019 SCM pair = scm_sloppy_assq (sym, spec);
1020 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1022 *dest = scm_to_double (scm_cdr (pair));
1028 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1029 // Otherwise, return -infinity_f.
1030 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1033 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1035 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1036 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1037 ? ly_symbol2scm ("spaceable-fixed-spacing")
1038 : ly_symbol2scm ("loose-fixed-spacing");
1041 // The result of this function doesn't depend on "end," so we can reduce the
1042 // size of the cache by ignoring it.
1043 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1044 if (scm_is_number (cached))
1045 return robust_scm2double (cached, 0.0);
1048 Real ret = -infinity_f;
1050 // If we're pure, then paper-columns have not had their systems set,
1051 // and so elts[i]->get_system () is unreliable.
1052 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1053 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1055 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1057 SCM details = left_bound->get_property ("line-break-system-details");
1058 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1059 if (scm_is_pair (manual_dists))
1061 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1062 if (scm_is_number (forced))
1063 ret = max (ret, scm_to_double (forced));
1067 // Cache the result. As above, we ignore "end."
1069 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1075 add_stretchability (SCM alist, Real stretch)
1077 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1078 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1083 // We want to put a large stretch between a non-spaceable line and its
1084 // non-affinity staff. We want to put an even larger stretch between
1085 // a non-spaceable line and the top/bottom of the page. That way,
1086 // a spacing-affinity UP line at the bottom of the page will still be
1087 // placed close to its staff.
1088 const double LARGE_STRETCH = 10e5;
1089 const double HUGE_STRETCH = 10e7;
1091 // Returns the spacing spec connecting BEFORE to AFTER.
1093 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1095 // If there are no spacing wishes, return a very flexible spring.
1096 // This will occur, for example, if there are lyrics at the bottom of
1097 // the page, in which case we don't want the spring from the lyrics to
1098 // the bottom of the page to have much effect.
1099 if (!before || !after)
1100 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1102 if (is_spaceable (before))
1104 if (is_spaceable (after))
1105 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1108 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1109 return (affinity == DOWN)
1110 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1112 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1117 if (is_spaceable (after))
1119 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1120 return (affinity == UP)
1121 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1123 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1127 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1128 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1129 static bool warned = false;
1130 if (after_affinity > before_affinity
1131 && !warned && !pure)
1133 warning (_ ("staff-affinities should only decrease"));
1136 if (before_affinity != UP)
1137 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1138 else if (after_affinity != DOWN)
1139 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1140 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1150 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1155 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1156 spring->set_distance (space);
1157 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1158 spring->set_min_distance (min_dist);
1159 spring->set_default_strength ();
1161 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1162 spring->set_inverse_stretch_strength (stretch);
1166 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1168 vector<Grob *> output;
1169 for (vsize i = 0; i < input.size (); ++i)
1171 if (Hara_kiri_group_spanner::has_interface (input[i]))
1172 Hara_kiri_group_spanner::consider_suicide (input[i]);
1174 if (input[i]->is_live ())
1175 output.push_back (input[i]);