2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2009--2011 Joe Neeman <joeneeman@gmail.com>
6 LilyPond is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 LilyPond is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
20 #include "page-layout-problem.hh"
22 #include "align-interface.hh"
23 #include "axis-group-interface.hh"
24 #include "hara-kiri-group-spanner.hh"
25 #include "international.hh"
27 #include "output-def.hh"
28 #include "paper-book.hh"
29 #include "paper-column.hh"
30 #include "paper-score.hh"
31 #include "pointer-group-interface.hh"
33 #include "skyline-pair.hh"
35 #include "text-interface.hh"
38 Returns the number of footntoes associated with a given line.
42 Page_layout_problem::get_footnote_count (SCM lines)
45 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
47 if (Grob *g = unsmob_grob (scm_car (s)))
49 System *sys = dynamic_cast<System *> (g);
52 programming_error ("got a grob for footnotes that wasn't a System");
55 fn_count += sys->num_footnotes ();
57 else if (Prob *p = unsmob_prob (scm_car (s)))
59 SCM stencils = p->get_property ("footnotes");
60 if (stencils == SCM_EOL)
62 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
71 Page_layout_problem::get_footnotes_from_lines (SCM lines)
73 if (!scm_is_pair (lines))
77 if (Grob *g = unsmob_grob (scm_car (lines)))
78 footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
79 else if (Prob *p = unsmob_prob (scm_car (lines)))
80 footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
83 programming_error ("Systems on a page must be a prob or grob.");
88 programming_error ("Footnotes must be added to lines before they are retrieved.");
93 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
95 if (Grob *g = unsmob_grob (scm_car (s)))
96 out = scm_cons (g->get_property ("footnote-stencil"), out);
97 else if (Prob *p = unsmob_prob (scm_car (s)))
98 out = scm_cons (p->get_property ("footnote-stencil"), out);
100 programming_error ("Systems on a page must be a prob or grob.");
103 return scm_reverse (out);
107 Adds a footnote stencil to each system. This stencil may
108 itself be comprised of several footnotes.
110 This is a long function, but it seems better to keep it intact rather than
111 splitting it into parts.
115 Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
118 first, we have to see how many footnotes are on this page.
119 we need to do this first so that we can line them up
122 Output_def *paper = pb->paper_;
126 programming_error ("Cannot get footnotes because there is no valid paper block.");
130 SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
131 if (!scm_is_pair (number_footnote_table))
132 number_footnote_table = SCM_EOL;
133 SCM numbering_function = paper->c_variable ("footnote-numbering-function");
134 SCM layout = paper->self_scm ();
135 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
137 Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
138 Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
140 vsize fn_count = get_footnote_count (lines);
142 // now, make the footnote stencils with the numbering function
143 SCM numbers = SCM_EOL;
144 SCM in_text_numbers = SCM_EOL;
146 TODO: This recalculates numbering every time this function is called, including once
147 after the balloon prints are called. Although it is not a huge computational drain,
148 it'd be more elegant to turn this calculation off when it is no longer needed.
150 In a separate commit, it'd be nice to streamline the way that page layout property
151 is handled so that the process of building `config's in page-breaking does result
152 in duplicated work, either by making this process less complicated or (preferably)
153 by passing its results downstream.
155 vector<SCM> footnote_number_markups; // Holds the numbering markups.
156 vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
157 for (vsize i = 0; i < fn_count; i++)
159 SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
160 Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
163 programming_error ("Your numbering function needs to return a stencil.");
165 s = new Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL);
167 footnote_number_markups.push_back (markup);
168 footnote_number_stencils.push_back (s);
172 // find the maximum X_AXIS length
173 Real max_length = -infinity_f;
174 for (vsize i = 0; i < fn_count; i++)
175 max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
178 translate each stencil such that it attains the correct maximum length and bundle the
179 footnotes into a scheme object.
181 SCM *tail = &numbers;
182 SCM *in_text_tail = &in_text_numbers;
184 for (vsize i = 0; i < fn_count; i++)
186 *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
187 in_text_tail = SCM_CDRLOC (*in_text_tail);
188 footnote_number_stencils[i]->translate_axis ((max_length
189 - footnote_number_stencils[i]->extent (X_AXIS).length ()),
191 *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
192 tail = SCM_CDRLOC (*tail);
194 // build the footnotes
196 for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
198 // Take care of musical systems.
199 if (Grob *g = unsmob_grob (scm_car (s)))
201 System *sys = dynamic_cast<System *> (g);
204 programming_error ("got a grob for footnotes that wasn't a System");
209 extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
210 for (vsize i = 0; i < footnote_grobs.size (); i++)
212 Grob *footnote = footnote_grobs[i];
213 SCM footnote_markup = footnote->get_property ("footnote-text");
214 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
215 if (orig->is_broken ())
216 footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
218 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
221 SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
222 props, footnote_markup);
224 Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
225 bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
226 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
228 if (orig->is_broken ())
229 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
230 do_numbering = do_numbering
231 || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
235 SCM annotation_scm = scm_car (in_text_numbers);
236 footnote->set_property ("text", annotation_scm);
237 if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
239 orig->set_property ("text", annotation_scm);
240 if (orig->is_broken ())
241 for (vsize i = 0; i < orig->broken_intos_.size (); i++)
242 orig->broken_intos_[i]->set_property ("text", annotation_scm);
245 Stencil *annotation = unsmob_stencil (scm_car (numbers));
246 annotation->translate_axis ((footnote_stencil->extent (Y_AXIS)[UP]
248 - annotation->extent (Y_AXIS)[UP]),
250 footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
251 numbers = scm_cdr (numbers);
252 in_text_numbers = scm_cdr (in_text_numbers);
254 if (!footnote_stencil->is_empty ())
256 if (to_boolean (footnote->get_property ("footnote")))
257 mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
259 in_note_mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
262 sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
263 sys->set_property ("footnote-stencil", mol.smobbed_copy ());
265 // Take care of top-level markups
266 else if (Prob *p = unsmob_prob (scm_car (s)))
268 SCM stencils = p->get_property ("footnotes");
271 for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
273 Stencil footnote_stencil;
274 Stencil *footnote = unsmob_stencil (scm_caddar (st));
275 footnote_stencil.add_stencil (*footnote);
276 bool do_numbering = to_boolean (scm_cadar (st));
277 SCM in_text_stencil = Stencil ().smobbed_copy ();
280 Stencil *annotation = unsmob_stencil (scm_car (numbers));
281 SCM in_text_annotation = scm_car (in_text_numbers);
282 in_text_stencil = Text_interface::interpret_markup (layout,
285 if (!unsmob_stencil (in_text_stencil))
286 in_text_stencil = SCM_EOL;
287 annotation->translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
289 - annotation->extent (Y_AXIS)[UP]),
291 footnote_stencil.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
292 numbers = scm_cdr (numbers);
293 in_text_numbers = scm_cdr (in_text_numbers);
295 number_footnote_table = scm_cons (scm_cons (scm_caar (st),
297 number_footnote_table);
298 if (!footnote_stencil.is_empty ())
299 mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
301 p->set_property ("footnote-stencil", mol.smobbed_copy ());
305 // note that this line of code doesn't do anything if numbering isn't turned on
306 pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
310 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
312 SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
315 SCM markup = paper->c_variable ("footnote-separator-markup");
317 if (!Text_interface::is_markup (markup))
320 SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
323 Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
325 return footnote_separator;
329 Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
331 if (!foot && scm_is_pair (footnotes))
333 warning ("Must have a footer to add footnotes.");
336 bool footnotes_found = false;
337 Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
338 Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
340 footnotes = scm_reverse (footnotes);
342 for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
344 Stencil *stencil = unsmob_stencil (scm_car (s));
349 if (!stencil->is_empty ())
351 foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
352 footnotes_found = true;
358 Stencil *separator = get_footnote_separator_stencil (pb->paper_);
360 foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
364 Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
365 : bottom_skyline_ (DOWN)
367 Prob *page = unsmob_prob (page_scm);
376 Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
377 Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
379 if (pb && pb->paper_)
381 SCM footnotes = get_footnotes_from_lines (systems);
382 add_footnotes_to_footer (footnotes, foot, pb);
385 warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
387 header_height_ = head ? head->extent (Y_AXIS).length () : 0;
388 footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
389 page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
392 // Initially, bottom_skyline_ represents the top of the page. Make
393 // it solid, so that the top of the first system will be forced
394 // below the top of the printable area.
395 bottom_skyline_.set_minimum_height (-header_height_);
397 SCM system_system_spacing = SCM_EOL;
398 SCM score_system_spacing = SCM_EOL;
399 SCM markup_system_spacing = SCM_EOL;
400 SCM score_markup_spacing = SCM_EOL;
401 SCM markup_markup_spacing = SCM_EOL;
403 // top_system_spacing controls the spring from the top of the printable
404 // area to the first staff. It allows the user to control the offset of
405 // the first staff (as opposed to the top of the first system) from the
406 // top of the page. Similarly for last_bottom_spacing.
407 SCM top_system_spacing = SCM_EOL;
408 SCM last_bottom_spacing = SCM_EOL;
409 if (pb && pb->paper_)
411 Output_def *paper = pb->paper_;
412 system_system_spacing = paper->c_variable ("system-system-spacing");
413 score_system_spacing = paper->c_variable ("score-system-spacing");
414 markup_system_spacing = paper->c_variable ("markup-system-spacing");
415 score_markup_spacing = paper->c_variable ("score-markup-spacing");
416 markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
417 last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
418 top_system_spacing = paper->c_variable ("top-system-spacing");
419 if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
420 top_system_spacing = paper->c_variable ("top-markup-spacing");
422 // Note: the page height here does _not_ reserve space for headers and
423 // footers. This is because we want to anchor the top-system-spacing
424 // spring at the _top_ of the header.
425 page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
426 + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
428 read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
429 read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
430 in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
431 in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
433 bool last_system_was_title = false;
435 for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
437 bool first = (s == systems);
439 if (Grob *g = unsmob_grob (scm_car (s)))
441 System *sys = dynamic_cast<System *> (g);
444 programming_error ("got a grob for vertical spacing that wasn't a System");
448 SCM spec = system_system_spacing;
450 spec = top_system_spacing;
451 else if (last_system_was_title)
452 spec = markup_system_spacing;
453 else if (0 == Paper_column::get_rank (sys->get_bound (LEFT)))
454 spec = score_system_spacing;
456 Spring spring (0, 0);
458 Real indent = line_dimensions_int (sys->paper_score ()->layout (), sys->get_rank ())[LEFT];
459 alter_spring_from_spacing_spec (spec, &spring);
460 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
462 append_system (sys, spring, indent, padding);
463 last_system_was_title = false;
465 else if (Prob *p = unsmob_prob (scm_car (s)))
467 SCM spec = first ? top_system_spacing
468 : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
469 Spring spring (0, 0);
471 alter_spring_from_spacing_spec (spec, &spring);
472 read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
474 append_prob (p, spring, padding);
475 last_system_was_title = true;
478 programming_error ("got a system that was neither a Grob nor a Prob");
481 Spring last_spring (0, 0);
482 Real last_padding = 0;
483 alter_spring_from_spacing_spec (last_bottom_spacing, &last_spring);
484 read_spacing_spec (last_bottom_spacing, &last_padding, ly_symbol2scm ("padding"));
485 last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
486 springs_.push_back (last_spring);
488 if (elements_.size ())
490 Real bottom_padding = 0;
492 // TODO: junk bottom-space now that we have last-bottom-spacing?
493 // bottom-space has the flexibility that one can do it per-system.
494 // NOTE: bottom-space is misnamed since it is not stretchable space.
495 if (Prob *p = elements_.back ().prob)
496 bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
497 else if (elements_.back ().staves.size ())
499 SCM details = get_details (elements_.back ());
500 bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
505 page_height_ -= bottom_padding;
510 Page_layout_problem::set_header_height (Real height)
512 header_height_ = height;
516 Page_layout_problem::set_footer_height (Real height)
518 footer_height_ = height;
522 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
524 Grob *align = sys->get_vertical_alignment ();
528 align->set_property ("positioning-done", SCM_BOOL_T);
530 extract_grob_set (align, "elements", all_elts);
531 vector<Grob *> elts = filter_dead_elements (all_elts);
532 vector<Real> minimum_offsets = Align_interface::get_minimum_translations_without_min_dist (align, elts, Y_AXIS);
533 vector<Real> minimum_offsets_with_min_dist = Align_interface::get_minimum_translations (align, elts, Y_AXIS);
535 Skyline up_skyline (UP);
536 Skyline down_skyline (DOWN);
537 build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
538 up_skyline.shift (indent);
539 down_skyline.shift (indent);
540 Stencil *in_note_stencil = unsmob_stencil (sys->get_property ("in-note-stencil"));
542 if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
544 sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
545 sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
546 Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
547 sky->set_minimum_height (sky->max_height ()
550 + in_note_stencil->extent (Y_AXIS).length ()));
554 We need to call distance with skyline-horizontal-padding because
555 the system skyline-horizontal-padding is not added during the creation
556 of an individual staff. So we add the padding for the distance check
557 at the time of adding in the system.
559 Real minimum_distance = up_skyline.distance (bottom_skyline_,
560 robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
564 Spring spring_copy = spring;
565 spring_copy.ensure_min_distance (minimum_distance);
566 springs_.push_back (spring_copy);
568 bottom_skyline_ = down_skyline;
569 elements_.push_back (Element (elts, minimum_offsets, padding));
571 // Add the springs for the VerticalAxisGroups in this system.
573 // If the user has specified the offsets of the individual staves, fix the
574 // springs at the given distances. Otherwise, use stretchable springs.
575 SCM details = get_details (elements_.back ());
576 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
577 vsize last_spaceable_staff = 0;
578 bool found_spaceable_staff = false;
579 for (vsize i = 0; i < elts.size (); ++i)
581 if (is_spaceable (elts[i]))
583 // We don't add a spring for the first staff, since
584 // we are only adding springs _between_ staves here.
585 if (!found_spaceable_staff)
587 found_spaceable_staff = true;
588 last_spaceable_staff = i;
592 Spring spring (0.5, 0.0);
593 SCM spec = elts[last_spaceable_staff]->get_property ("staff-staff-spacing");
594 alter_spring_from_spacing_spec (spec, &spring);
596 springs_.push_back (spring);
597 Real min_distance = (found_spaceable_staff ? minimum_offsets_with_min_dist[last_spaceable_staff] : 0) - minimum_offsets_with_min_dist[i];
598 springs_.back ().ensure_min_distance (min_distance);
600 if (scm_is_pair (manual_dists))
602 if (scm_is_number (scm_car (manual_dists)))
604 Real dy = scm_to_double (scm_car (manual_dists));
606 springs_.back ().set_distance (dy);
607 springs_.back ().set_min_distance (dy);
608 springs_.back ().set_inverse_stretch_strength (0);
610 manual_dists = scm_cdr (manual_dists);
612 last_spaceable_staff = i;
616 // Corner case: there was only one staff, and it wasn't spaceable.
617 // Mark it spaceable, because we do not allow non-spaceable staves
618 // to be at the top or bottom of a system.
619 if (!found_spaceable_staff && elts.size ())
620 mark_as_spaceable (elts[0]);
624 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
626 Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
627 Real minimum_distance = 0;
628 bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
632 minimum_distance = (*sky)[UP].distance (bottom_skyline_);
633 bottom_skyline_ = (*sky)[DOWN];
635 else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
637 Interval iv = sten->extent (Y_AXIS);
638 minimum_distance = iv[UP] - bottom_skyline_.max_height ();
640 bottom_skyline_.clear ();
641 bottom_skyline_.set_minimum_height (iv[DOWN]);
644 Spring spring_copy = spring;
647 spring_copy.set_min_distance (minimum_distance);
648 spring_copy.set_inverse_stretch_strength (0.0);
649 spring_copy.set_distance (0.0);
652 spring_copy.ensure_min_distance (minimum_distance + padding);
654 springs_.push_back (spring_copy);
655 elements_.push_back (Element (prob, padding));
659 Page_layout_problem::solve_rod_spring_problem (bool ragged)
661 Simple_spacer spacer;
663 for (vsize i = 0; i < springs_.size (); ++i)
664 spacer.add_spring (springs_[i]);
666 spacer.solve (page_height_, ragged);
667 solution_ = spacer.spring_positions ();
671 Real overflow = spacer.configuration_length (spacer.force ())
673 if (ragged && overflow < 1e-6)
674 warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
677 warning (_f ("cannot fit music on page: overflow is %f",
679 warning (_ ("compressing music to fit"));
680 vsize space_count = solution_.size ();
681 Real spacing_increment = overflow / (space_count - 2);
682 for (vsize i = 2; i < space_count; i++)
683 solution_[i] -= (i - 1) * spacing_increment;
688 // The solution_ vector stores the position of every live VerticalAxisGroup
689 // and every title. From that information,
690 // 1) within each system, stretch the staves so they land at the right position
691 // 2) find the offset of each system (relative to the printable area of the page).
692 // TODO: this function is getting too long, maybe split it up?
694 Page_layout_problem::find_system_offsets ()
696 SCM system_offsets = SCM_EOL;
697 SCM *tail = &system_offsets;
699 // spring_idx 0 is the top of the page. Interesting values start from 1.
700 vsize spring_idx = 1;
701 vector<Grob *> loose_lines;
702 vector<Real> loose_line_min_distances;
703 Grob *last_spaceable_line = 0;
704 Real last_spaceable_line_translation = 0;
705 Interval last_title_extent;
706 for (vsize i = 0; i < elements_.size (); ++i)
708 if (elements_[i].prob)
710 *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
711 tail = SCM_CDRLOC (*tail);
712 Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
714 // Lay out any non-spaceable lines between this line and
716 if (loose_lines.size ())
718 Interval loose_extent = loose_lines.back ()->extent (loose_lines.back (), Y_AXIS);
719 Real min_distance = (-loose_extent[DOWN] + prob_extent[UP]
720 + elements_[i].padding);
722 loose_line_min_distances.push_back (min_distance);
723 loose_lines.push_back (0);
725 distribute_loose_lines (loose_lines, loose_line_min_distances,
726 last_spaceable_line_translation, -solution_[spring_idx]);
727 loose_lines.clear ();
728 loose_line_min_distances.clear ();
731 last_spaceable_line = 0;
732 last_spaceable_line_translation = -solution_[spring_idx];
733 last_title_extent = prob_extent;
738 // Getting this signs right here is a little tricky. The configuration
739 // we return has zero at the top of the page and positive numbers further
740 // down, as does the solution_ vector. Within a staff, however, positive
742 // TODO: perhaps change the way the page 'configuration variable works so
743 // that it is consistent with the usual up/down sign conventions in
744 // Lilypond. Then this would be less confusing.
746 // These two positions are relative to the page (with positive numbers being
748 Real first_staff_position = solution_[spring_idx];
749 Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
750 Real system_position = first_staff_position + first_staff_min_translation;
752 // Position the staves within this system.
753 vector<Real> const &min_offsets = elements_[i].min_offsets;
754 bool found_spaceable_staff = false;
755 for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
757 Grob *staff = elements_[i].staves[staff_idx];
758 staff->set_property ("system-Y-offset", scm_from_double (-system_position));
760 if (is_spaceable (staff))
762 // this is relative to the system: negative numbers are down.
763 staff->translate_axis (system_position - solution_[spring_idx], Y_AXIS);
765 // Lay out any non-spaceable lines between this line and
767 if (loose_lines.size ())
770 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
772 loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
773 loose_lines.push_back (staff);
775 distribute_loose_lines (loose_lines, loose_line_min_distances,
776 last_spaceable_line_translation, -solution_[spring_idx]);
777 loose_lines.clear ();
778 loose_line_min_distances.clear ();
780 last_spaceable_line = staff;
781 last_spaceable_line_translation = -solution_[spring_idx];
782 found_spaceable_staff = true;
787 if (loose_lines.empty ())
788 loose_lines.push_back (last_spaceable_line);
791 // NOTE: the way we do distances between loose lines (and other lines too, actually)
792 // is not the most accurate way possible: we only insert rods between adjacent
793 // lines. To be more accurate, we could insert rods between non-adjacent lines
794 // using a scheme similar to the one in set_column_rods.
795 loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
798 // this is the first line in a system
800 if (loose_lines.back ())
801 // distance to the final line in the preceding system,
802 // including 'system-system-spacing 'padding
803 min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
806 + elements_[i].padding);
807 else if (!last_title_extent.is_empty ())
808 // distance to the preceding title,
809 // including 'markup-system-spacing 'padding
810 min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
811 + elements_[i].padding);
812 else // distance to the top margin
813 min_dist = header_padding_ + header_height_ + staff->extent (staff, Y_AXIS)[UP];
815 loose_line_min_distances.push_back (min_dist);
817 loose_lines.push_back (staff);
821 // Corner case: even if a system has no live staves, it still takes up
822 // one spring (a system with one live staff also takes up one spring),
823 // which we need to increment past.
824 if (!found_spaceable_staff)
827 *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
828 tail = SCM_CDRLOC (*tail);
832 if (loose_lines.size ())
834 Grob *last = loose_lines.back ();
835 Interval last_ext = last->extent (last, Y_AXIS);
836 loose_line_min_distances.push_back (-last_ext[DOWN] + footer_height_ + footer_padding_);
837 loose_lines.push_back (0);
839 distribute_loose_lines (loose_lines, loose_line_min_distances,
840 last_spaceable_line_translation, -page_height_);
844 assert (spring_idx == solution_.size () - 1);
845 return system_offsets;
848 // Given two lines that are already spaced (the first and last
849 // elements of loose_lines), distribute some unspaced lines between
851 // first_translation and last_translation are relative to the page.
853 Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
854 vector<Real> const &min_distances,
855 Real first_translation, Real last_translation)
857 Simple_spacer spacer;
858 for (vsize i = 0; i + 1 < loose_lines.size (); ++i)
860 SCM spec = get_spacing_spec (loose_lines[i], loose_lines[i + 1], false, 0, INT_MAX);
861 Spring spring (1.0, 0.0);
862 alter_spring_from_spacing_spec (spec, &spring);
863 spring.ensure_min_distance (min_distances[i]);
864 spacer.add_spring (spring);
867 // Remember: offsets are decreasing, since we're going from UP to DOWN!
868 spacer.solve (first_translation - last_translation, false);
870 vector<Real> solution = spacer.spring_positions ();
871 for (vsize i = 1; i + 1 < solution.size (); ++i)
873 Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
874 loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
879 Page_layout_problem::solution (bool ragged)
881 solve_rod_spring_problem (ragged);
882 return find_system_offsets ();
885 // Build upper and lower skylines for a system. We don't yet know the positions
886 // of the staves within the system, so we make the skyline as conservative as
887 // possible. That is, for the upper skyline, we pretend that all of the staves
888 // in the system are packed together close to the top system; for the lower
889 // skyline, we pretend that all of the staves are packed together close to
890 // the bottom system.
892 // The upper skyline is relative to the top staff; the lower skyline is relative to
895 Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
896 vector<Real> const &minimum_translations,
900 if (minimum_translations.empty ())
903 assert (staves.size () == minimum_translations.size ());
904 Real first_translation = minimum_translations[0];
905 Real last_spaceable_dy = 0;
906 Real first_spaceable_dy = 0;
907 bool found_spaceable_staff = false;
909 for (vsize i = 0; i < staves.size (); ++i)
911 Real dy = minimum_translations[i] - first_translation;
913 Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
917 up->merge ((*sky)[UP]);
921 down->merge ((*sky)[DOWN]);
924 if (is_spaceable (staves[i]))
926 if (!found_spaceable_staff)
928 found_spaceable_staff = true;
929 first_spaceable_dy = dy;
931 last_spaceable_dy = dy;
935 // Leave the up skyline at a position relative
936 // to the top spaceable staff.
937 up->raise (-first_spaceable_dy);
939 // Leave the down skyline at a position
940 // relative to the bottom spaceable staff.
941 down->raise (-last_spaceable_dy);
945 Page_layout_problem::prob_extent (Prob *p)
947 Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
948 return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
952 Page_layout_problem::first_staff_extent (Element const &e)
955 return prob_extent (e.prob);
956 else if (e.staves.size ())
957 return e.staves[0]->extent (e.staves[0], Y_AXIS);
959 return Interval (0, 0);
963 Page_layout_problem::last_staff_extent (Element const &e)
966 return prob_extent (e.prob);
967 else if (e.staves.size ())
968 return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
970 return Interval (0, 0);
974 Page_layout_problem::get_details (Element const &elt)
976 if (elt.staves.empty ())
979 return get_details (elt.staves.back ()->get_system ());
983 Page_layout_problem::get_details (Grob *g)
985 Grob *left_bound = dynamic_cast<Spanner *> (g)->get_bound (LEFT);
986 return left_bound->get_property ("line-break-system-details");
990 Page_layout_problem::is_spaceable (Grob *g)
992 return !scm_is_number (g->get_property ("staff-affinity"));
996 Page_layout_problem::mark_as_spaceable (Grob *g)
998 g->set_property ("staff-affinity", SCM_BOOL_F);
1002 Page_layout_problem::read_spacing_spec (SCM spec, Real *dest, SCM sym)
1004 SCM pair = scm_sloppy_assq (sym, spec);
1005 if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
1007 *dest = scm_to_double (scm_cdr (pair));
1013 // If there is a forced, fixed spacing between BEFORE and AFTER, return it.
1014 // Otherwise, return -infinity_f.
1015 // If after is spaceable, it is the (spaceable_index + 1)th spaceable grob in
1018 Page_layout_problem::get_fixed_spacing (Grob *before, Grob *after, int spaceable_index, bool pure, int start, int end)
1020 Spanner *after_sp = dynamic_cast<Spanner *> (after);
1021 SCM cache_symbol = (is_spaceable (before) && is_spaceable (after))
1022 ? ly_symbol2scm ("spaceable-fixed-spacing")
1023 : ly_symbol2scm ("loose-fixed-spacing");
1026 // The result of this function doesn't depend on "end," so we can reduce the
1027 // size of the cache by ignoring it.
1028 SCM cached = after_sp->get_cached_pure_property (cache_symbol, start, 0);
1029 if (scm_is_number (cached))
1030 return robust_scm2double (cached, 0.0);
1033 Real ret = -infinity_f;
1035 // If we're pure, then paper-columns have not had their systems set,
1036 // and so elts[i]->get_system () is unreliable.
1037 System *sys = pure ? Grob::get_system (before) : before->get_system ();
1038 Grob *left_bound = sys ? sys->get_maybe_pure_bound (LEFT, pure, start, end) : 0;
1040 if (is_spaceable (before) && is_spaceable (after) && left_bound)
1042 SCM details = left_bound->get_property ("line-break-system-details");
1043 SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
1044 if (scm_is_pair (manual_dists))
1046 SCM forced = robust_list_ref (spaceable_index - 1, manual_dists);
1047 if (scm_is_number (forced))
1048 ret = max (ret, scm_to_double (forced));
1052 // Cache the result. As above, we ignore "end."
1054 after_sp->cache_pure_property (cache_symbol, start, 0, scm_from_double (ret));
1060 add_stretchability (SCM alist, Real stretch)
1062 if (!scm_is_pair (scm_sloppy_assq (ly_symbol2scm ("stretchability"), alist)))
1063 return scm_acons (ly_symbol2scm ("stretchability"), scm_from_double (stretch), alist);
1068 // We want to put a large stretch between a non-spaceable line and its
1069 // non-affinity staff. We want to put an even larger stretch between
1070 // a non-spaceable line and the top/bottom of the page. That way,
1071 // a spacing-affinity UP line at the bottom of the page will still be
1072 // placed close to its staff.
1073 const double LARGE_STRETCH = 10e5;
1074 const double HUGE_STRETCH = 10e7;
1076 // Returns the spacing spec connecting BEFORE to AFTER.
1078 Page_layout_problem::get_spacing_spec (Grob *before, Grob *after, bool pure, int start, int end)
1080 // If there are no spacing wishes, return a very flexible spring.
1081 // This will occur, for example, if there are lyrics at the bottom of
1082 // the page, in which case we don't want the spring from the lyrics to
1083 // the bottom of the page to have much effect.
1084 if (!before || !after)
1085 return add_stretchability (SCM_EOL, HUGE_STRETCH);
1087 if (is_spaceable (before))
1089 if (is_spaceable (after))
1090 return before->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
1093 Direction affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1094 return (affinity == DOWN)
1095 ? add_stretchability (after->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1097 : after->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1102 if (is_spaceable (after))
1104 Direction affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1105 return (affinity == UP)
1106 ? add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1108 : before->get_maybe_pure_property ("nonstaff-relatedstaff-spacing", pure, start, end);
1112 Direction before_affinity = to_dir (before->get_maybe_pure_property ("staff-affinity", pure, start, end));
1113 Direction after_affinity = to_dir (after->get_maybe_pure_property ("staff-affinity", pure, start, end));
1114 static bool warned = false;
1115 if (after_affinity > before_affinity
1116 && !warned && !pure)
1118 warning (_ ("staff-affinities should only decrease"));
1121 if (before_affinity != UP)
1122 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1123 else if (after_affinity != DOWN)
1124 return before->get_maybe_pure_property ("nonstaff-nonstaff-spacing", pure, start, end);
1125 return add_stretchability (before->get_maybe_pure_property ("nonstaff-unrelatedstaff-spacing", pure, start, end),
1135 Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring *spring)
1140 if (read_spacing_spec (spec, &space, ly_symbol2scm ("basic-distance")))
1141 spring->set_distance (space);
1142 if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
1143 spring->set_min_distance (min_dist);
1144 spring->set_default_strength ();
1146 if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
1147 spring->set_inverse_stretch_strength (stretch);
1151 Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
1153 vector<Grob *> output;
1154 for (vsize i = 0; i < input.size (); ++i)
1156 if (Hara_kiri_group_spanner::has_interface (input[i]))
1157 Hara_kiri_group_spanner::consider_suicide (input[i]);
1159 if (input[i]->is_live ())
1160 output.push_back (input[i]);