From 20670d51f8d97fd390210dd239b3b2427f071e7c Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Fri, 30 Sep 2011 08:16:07 +0200 Subject: [PATCH] Improves horizontal spacing of axis groups that SpanBars traverse (issue 1846). Also fixes issue 910, where lyrics force notes to take up too much horizontal space because of incorrect SpanBar pure heights. --- input/regression/span-bar-allow-span-bar.ly | 59 ++++++++ lily/grob.cc | 76 ++++++++++ lily/include/grob.hh | 6 + lily/include/pure-from-neighbor-interface.hh | 34 +++++ lily/pure-from-neighbor-engraver.cc | 104 ++++++++++++++ lily/pure-from-neighbor-interface.cc | 57 ++++++++ lily/span-bar-engraver.cc | 25 +++- lily/span-bar-stub-engraver.cc | 143 +++++++++++++++++++ ly/engraver-init.ly | 2 + scm/define-grob-properties.scm | 1 + scm/define-grobs.scm | 15 +- scm/output-lib.scm | 6 + 12 files changed, 521 insertions(+), 7 deletions(-) create mode 100644 input/regression/span-bar-allow-span-bar.ly create mode 100644 lily/include/pure-from-neighbor-interface.hh create mode 100644 lily/pure-from-neighbor-engraver.cc create mode 100644 lily/pure-from-neighbor-interface.cc create mode 100644 lily/span-bar-stub-engraver.cc diff --git a/input/regression/span-bar-allow-span-bar.ly b/input/regression/span-bar-allow-span-bar.ly new file mode 100644 index 0000000000..5bb765d4d3 --- /dev/null +++ b/input/regression/span-bar-allow-span-bar.ly @@ -0,0 +1,59 @@ + +\version "2.15.10" + +\header { + texidoc = "The @code{SpanBarStub} grob takes care of horizontal spacing +for @code{SpanBar} grobs. When the @code{SpanBar} is disallowed, objects +in contexts that the span bar would have otherwise crossed align as if the +span bar were not there. +" +} + +<< + \new Staff { + \repeat unfold 64 { c''8 } + } + \new GrandStaff << + \new Staff + \new Voice = "upper" + \relative c'' { + c2 c c c + \once \override Staff . BarLine #'allow-span-bar = ##f + c2 c c c + c2 c c c + \once \override Staff . BarLine #'allow-span-bar = ##f + c2 c c c + } + \new Lyrics \lyricsto "upper" \lyricmode { + long-syllable a b c long-syllable a b c + long-syllable a b c long-syllable a b c + } + + \new Staff + \new Voice = "middle" + \relative c'' { + c2 c c c + c2 c c c + c2 c c c + \once \override Staff . BarLine #'allow-span-bar = ##f + c2 c c c + } + \new Lyrics \lyricsto "middle" \lyricmode { + syllable a b c syllable a b c + syllable a b c syllable a b c + } + + \new Staff + \new Voice = "lower" + \relative c'' { + c2 c c c + c2 c c c + c2 c c c + c2 c c c + } + \new Lyrics \lyricsto "lower" \lyricmode { + word a b c word a b c + word a b c word a b c + } + >> +>> diff --git a/lily/grob.cc b/lily/grob.cc index 36aadb4e93..fa8e1f6843 100644 --- a/lily/grob.cc +++ b/lily/grob.cc @@ -572,6 +572,82 @@ Grob::fixup_refpoint () } } +/**************************************************************** + VERTICAL ORDERING +****************************************************************/ + +Grob* +get_maybe_root_vertical_alignment (Grob *g, Grob *maybe) +{ + if (!g) + return maybe; + if (Align_interface::has_interface (g)) + return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), g); + return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), maybe); + +} + +Grob* +Grob::get_root_vertical_alignment (Grob *g) +{ + return get_maybe_root_vertical_alignment (g, 0); +} + +Grob* +Grob::get_vertical_axis_group (Grob *g) +{ + if (!g) + return 0; + if (Axis_group_interface::has_interface (g) + && Align_interface::has_interface (g->get_parent (Y_AXIS))) + return g; + return get_vertical_axis_group (g->get_parent (Y_AXIS)); + +} + +int +Grob::get_vertical_axis_group_index (Grob *g) +{ + Grob *val = get_root_vertical_alignment (g); + if (!val) + return -1; + Grob *vax = get_vertical_axis_group (g); + extract_grob_set (val, "elements", elts); + for (vsize i = 0; i < elts.size (); i++) + if (elts[i] == vax) + return (int) i; + g->programming_error ("could not find this grob's vertical axis group in the vertical alignment"); + return -1; +} + +bool +Grob::vertical_less (Grob *g1, Grob *g2) +{ + Grob *vag = get_root_vertical_alignment (g1); + if (!vag) + return false; + if (!vag) + { + g1->programming_error ("grob does not belong to a VerticalAlignment?"); + return false; + } + Grob *ag1 = get_vertical_axis_group (g1); + Grob *ag2 = get_vertical_axis_group (g2); + + extract_grob_set (vag, "elements", elts); + + for (vsize i = 0; i < elts.size (); i++) + { + if (elts[i] == ag1) + return true; + if (elts[i] == ag2) + return false; + } + + g1->programming_error ("could not place this grob in its axis group"); + return false; +} + /**************************************************************** MESSAGES ****************************************************************/ diff --git a/lily/include/grob.hh b/lily/include/grob.hh index 5ca85402e0..36f912a289 100644 --- a/lily/include/grob.hh +++ b/lily/include/grob.hh @@ -140,6 +140,12 @@ public: Grob *get_parent (Axis a) const; void fixup_refpoint (); + /* vertical ordering */ + static Grob *get_root_vertical_alignment (Grob *g); + static Grob *get_vertical_axis_group (Grob *g); + static bool vertical_less (Grob *g1, Grob *g2); + static int get_vertical_axis_group_index (Grob *g); + virtual Interval_t spanned_rank_interval () const; virtual bool pure_is_visible (int start, int end) const; bool check_cross_staff (Grob *common); diff --git a/lily/include/pure-from-neighbor-interface.hh b/lily/include/pure-from-neighbor-interface.hh new file mode 100644 index 0000000000..eb46139367 --- /dev/null +++ b/lily/include/pure-from-neighbor-interface.hh @@ -0,0 +1,34 @@ +/* + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2011 Mike Solomon + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . +*/ + +#ifndef PURE_FROM_NEIGHBOR_INTERFACE_HH +#define PURE_FROM_NEIGHBOR_INTERFACE_HH + +#include "lily-proto.hh" +#include "grob-interface.hh" + +class Pure_from_neighbor_interface +{ +public: + DECLARE_SCHEME_CALLBACK (filter_elements, (SCM)); + DECLARE_GROB_INTERFACE (); + +}; + +#endif diff --git a/lily/pure-from-neighbor-engraver.cc b/lily/pure-from-neighbor-engraver.cc new file mode 100644 index 0000000000..14d94730b8 --- /dev/null +++ b/lily/pure-from-neighbor-engraver.cc @@ -0,0 +1,104 @@ +/* + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2011 Mike Solomon + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . +*/ + +#include +#include + +#include "grob.hh" +#include "item.hh" +#include "pointer-group-interface.hh" +#include "pure-from-neighbor-interface.hh" +#include "engraver.hh" + +class Pure_from_neighbor_engraver : public Engraver +{ + vector items_then_; + vector items_now_; + vector pures_then_; + vector pures_now_; + +public: + TRANSLATOR_DECLARATIONS (Pure_from_neighbor_engraver); +protected: + DECLARE_ACKNOWLEDGER (pure_from_neighbor); + DECLARE_ACKNOWLEDGER (item); + void stop_translation_timestep (); +}; + +Pure_from_neighbor_engraver::Pure_from_neighbor_engraver () +{ +} + +void +Pure_from_neighbor_engraver::acknowledge_item (Grob_info i) +{ + SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?"); + if (!Pure_from_neighbor_interface::has_interface (i.grob ()) + && to_boolean (scm_call_1 (pure_relevant_p, i.item ()->self_scm ()))) + items_now_.push_back (i.item ()); +} + +// note that this can get out of hand if there are lots of vertical axis groups... + +void +Pure_from_neighbor_engraver::acknowledge_pure_from_neighbor (Grob_info i) +{ + pures_now_.push_back (i.item ()); +} + +void +Pure_from_neighbor_engraver::stop_translation_timestep () +{ + if (pures_now_.size ()) + { + for (vsize i = 0; i < pures_now_.size (); i++) + for (vsize j = 0; j < items_then_.size (); j++) + Pointer_group_interface::add_grob (pures_now_[i], ly_symbol2scm ("elements"), items_then_[j]); + + for (vsize i = 0; i < pures_then_.size (); i++) + for (vsize j = 0; j < items_now_.size (); j++) + Pointer_group_interface::add_grob (pures_then_[i], ly_symbol2scm ("elements"), items_now_[j]); + + items_then_.clear (); + items_then_.insert (items_then_.end (), items_now_.begin (), items_now_.end ()); + items_now_.clear (); + + pures_then_.clear (); + pures_then_.insert (pures_then_.end (), pures_now_.begin (), pures_now_.end ()); + pures_now_.clear (); + } +} + +#include "translator.icc" + +ADD_ACKNOWLEDGER (Pure_from_neighbor_engraver, item); +ADD_ACKNOWLEDGER (Pure_from_neighbor_engraver, pure_from_neighbor); +ADD_TRANSLATOR (Pure_from_neighbor_engraver, + /* doc */ + "Coordinates items that get their pure heights from their neighbors.", + + /* create */ + "", + + /* read */ + "", + + /* write */ + "" + ); diff --git a/lily/pure-from-neighbor-interface.cc b/lily/pure-from-neighbor-interface.cc new file mode 100644 index 0000000000..8d5e724a7b --- /dev/null +++ b/lily/pure-from-neighbor-interface.cc @@ -0,0 +1,57 @@ +/* + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2011 Mike Solomon + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . +*/ + +#include "grob.hh" +#include "grob-array.hh" +#include "pointer-group-interface.hh" +#include "pure-from-neighbor-interface.hh" +#include "spanner.hh" +#include "system.hh" + +MAKE_SCHEME_CALLBACK (Pure_from_neighbor_interface, filter_elements, 1); +SCM +Pure_from_neighbor_interface::filter_elements (SCM smob) +{ + Grob *me = unsmob_grob (smob); + extract_grob_set (me, "elements", elts); + vector new_elts; + Interval_t srl = me->get_system ()->spanned_rank_interval (); + for (vsize i = 0; i < elts.size (); i++) + if (srl.contains (elts[i]->spanned_rank_interval ()[LEFT])) + new_elts.push_back (elts[i]); + + SCM elements_scm = me->get_object ("elements"); + if (Grob_array::unsmob (elements_scm)) + { + vector &arr + = unsmob_grob_array (elements_scm)->array_reference (); + arr = new_elts; + } + + return SCM_BOOL_T; +} + +ADD_INTERFACE (Pure_from_neighbor_interface, + "A collection of routines to allow for objects' pure" + "heights and heights to be calculated based on the" + "heights of the objects' neighbors.", + + /* properties */ + "elements-filtered " + ); diff --git a/lily/span-bar-engraver.cc b/lily/span-bar-engraver.cc index 6d145898f3..5ebe256346 100644 --- a/lily/span-bar-engraver.cc +++ b/lily/span-bar-engraver.cc @@ -33,6 +33,7 @@ dependencies to the spanbars. class Span_bar_engraver : public Engraver { Item *spanbar_; + bool make_spanbar_; vector bars_; public: @@ -40,11 +41,13 @@ public: protected: DECLARE_ACKNOWLEDGER (bar_line); void stop_translation_timestep (); + void process_acknowledged (); }; Span_bar_engraver::Span_bar_engraver () { spanbar_ = 0; + make_spanbar_ = false; } void @@ -57,11 +60,24 @@ Span_bar_engraver::acknowledge_bar_line (Grob_info i) bars_.push_back (it); if (bars_.size () >= 2 && !spanbar_) - { - spanbar_ = make_item ("SpanBar", SCM_EOL); + make_spanbar_ = true; + } +} + +void +Span_bar_engraver::process_acknowledged () +{ + if (make_spanbar_) + { + Grob *vag = Grob::get_root_vertical_alignment (bars_[0]); + if (vag) + vector_sort (bars_, Grob::vertical_less); + spanbar_ = make_item ("SpanBar", SCM_EOL); - spanbar_->set_parent (bars_[0], X_AXIS); - } + spanbar_->set_parent (bars_[0], X_AXIS); + for (vsize i = 0; i < bars_.size (); i++) + Span_bar::add_bar (spanbar_, bars_[i]); + make_spanbar_ = false; } } @@ -70,6 +86,7 @@ Span_bar_engraver::stop_translation_timestep () { if (spanbar_) { + vector_sort (bars_, Grob::vertical_less); for (vsize i = 0; i < bars_.size (); i++) Span_bar::add_bar (spanbar_, bars_[i]); diff --git a/lily/span-bar-stub-engraver.cc b/lily/span-bar-stub-engraver.cc new file mode 100644 index 0000000000..47b0c1a4d5 --- /dev/null +++ b/lily/span-bar-stub-engraver.cc @@ -0,0 +1,143 @@ +/* + This file is part of LilyPond, the GNU music typesetter. + + Copyright (C) 2011 Mike Solomon + + LilyPond is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + LilyPond is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LilyPond. If not, see . +*/ + +#include +#include + +#include "align-interface.hh" +#include "bar-line.hh" +#include "context.hh" +#include "grob.hh" +#include "item.hh" +#include "pointer-group-interface.hh" +#include "span-bar.hh" +#include "engraver.hh" + +class Span_bar_stub_engraver : public Engraver +{ + vector spanbars_; + map axis_groups_; + +public: + TRANSLATOR_DECLARATIONS (Span_bar_stub_engraver); +protected: + DECLARE_ACKNOWLEDGER (span_bar); + DECLARE_ACKNOWLEDGER (hara_kiri_group_spanner); + void process_acknowledged (); +}; + +Span_bar_stub_engraver::Span_bar_stub_engraver () +{ +} + +void +Span_bar_stub_engraver::acknowledge_span_bar (Grob_info i) +{ + spanbars_.push_back (i.grob ()); +} + +// note that this can get out of hand if there are lots of vertical axis groups... + +void +Span_bar_stub_engraver::acknowledge_hara_kiri_group_spanner (Grob_info i) +{ + axis_groups_[i.grob ()] = i.context (); +} + +void +Span_bar_stub_engraver::process_acknowledged () +{ + if (!spanbars_.size ()) + return; + + Grob *vertical_alignment = Grob::get_root_vertical_alignment ((*axis_groups_.begin ()).first); + if (!vertical_alignment) // we are at the beginning of a score, so no need for stubs + return; + + extract_grob_set (vertical_alignment, "elements", elts); + + for (vsize i = 0; i < spanbars_.size (); i++) + { + extract_grob_set (spanbars_[i], "elements", bars); + vector bar_axis_indices; + for (vsize j = 0; j < bars.size (); j++) + { + int i = Grob::get_vertical_axis_group_index (bars[j]); + if (i >= 0) + bar_axis_indices.push_back ((vsize) i); + } + vector affected_contexts; + vector y_parents; + vector keep_extent; + for (vsize j = 0; j < elts.size (); j++) + { + if (j > bar_axis_indices[0] + && j < bar_axis_indices.back () + && find (bar_axis_indices.begin (), bar_axis_indices.end (), j) == bar_axis_indices.end ()) + { + vsize k = 0; + for (; k < bar_axis_indices.size (); k++) + if (bar_axis_indices[k] > j) + break; + + k--; + keep_extent.push_back (to_boolean (bars[k]->get_property ("allow-span-bar"))); + + Context *c = axis_groups_[elts[j]]; + if (c && c->get_parent_context ()) + { + y_parents.push_back (elts[j]); + affected_contexts.push_back (c); + } + } + } + reverse (affected_contexts); // from bottom to top + reverse (y_parents); // from bottom to top + reverse (keep_extent); // from bottom to top + for (vsize j = 0; j < affected_contexts.size (); j++) + { + Item *it = new Item (updated_grob_properties (affected_contexts[j], ly_symbol2scm ("SpanBarStub"))); + it->set_parent (spanbars_[i], X_AXIS); + Grob_info gi = make_grob_info (it, spanbars_[i]->self_scm ()); + gi.rerouting_daddy_context_ = affected_contexts[j]; + announce_grob (gi); + if (!keep_extent[j]) + it->set_property ("Y-extent", ly_interval2scm (Interval (infinity_f, -infinity_f))); + } + } + spanbars_.clear (); +} + +#include "translator.icc" + +ADD_ACKNOWLEDGER (Span_bar_stub_engraver, span_bar); +ADD_ACKNOWLEDGER (Span_bar_stub_engraver, hara_kiri_group_spanner); +ADD_TRANSLATOR (Span_bar_stub_engraver, + /* doc */ + "Make stubs for span bars in all contexts that the span bars cross.", + + /* create */ + "SpanBarStub ", + + /* read */ + "", + + /* write */ + "" + ); diff --git a/ly/engraver-init.ly b/ly/engraver-init.ly index d719d4a43f..41569cd4ae 100644 --- a/ly/engraver-init.ly +++ b/ly/engraver-init.ly @@ -312,6 +312,7 @@ contained staves are connected vertically." \consists "Instrument_name_engraver" \consists "Span_bar_engraver" + \consists "Span_bar_stub_engraver" \consists "Span_arpeggio_engraver" \consists "System_start_delimiter_engraver" \consists "Vertical_align_engraver" @@ -428,6 +429,7 @@ printing of a single line of lyrics." \consists "Instrument_name_engraver" \consists "Font_size_engraver" \consists "Hara_kiri_engraver" + \consists "Pure_from_neighbor_engraver" searchForVoice = ##f %% explicitly set instrument, so it is not inherited from the parent instrumentName = #'() diff --git a/scm/define-grob-properties.scm b/scm/define-grob-properties.scm index eea44eed4d..58a085800f 100644 --- a/scm/define-grob-properties.scm +++ b/scm/define-grob-properties.scm @@ -242,6 +242,7 @@ Positive means move the center to the right.") the vertical edges: @code{(@var{left-height} . @var{right-height})}.") (edge-text ,pair? "A pair specifying the texts to be set at the edges: @code{(@var{left-text} . @var{right-text})}.") + (elements-filtered ,boolean? "Callback to filter an element list.") (round-up-exceptions ,list? "A list of pairs where car is the numerator and cdr the denominator of a moment. Each pair in this list means that the multi-measure rests of the corresponding length will be rounded up to diff --git a/scm/define-grobs.scm b/scm/define-grobs.scm index 5e551acf8f..db39b6889c 100644 --- a/scm/define-grobs.scm +++ b/scm/define-grobs.scm @@ -1840,14 +1840,22 @@ (non-musical . #t) (stencil . ,ly:span-bar::print) (X-extent . ,ly:span-bar::width) - (Y-extent . ,ly:axis-group-interface::height) + (Y-extent . #f) (meta . ((class . Item) - (object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common) - (pure-relevant-grobs . ,ly:axis-group-interface::calc-pure-relevant-grobs))) (interfaces . (bar-line-interface font-interface span-bar-interface)))))) + (SpanBarStub + . ( + (elements-filtered . ,ly:pure-from-neighbor-interface::filter-elements) + (X-extent . ,grob::x-parent-width) + (Y-extent . ,span-bar-stub::height) + (meta . ((class . Item) + (object-callbacks . ((pure-Y-common . ,ly:axis-group-interface::calc-pure-y-common) + (pure-relevant-grobs . ,ly:axis-group-interface::calc-pure-relevant-grobs))) + (interfaces . (pure-from-neighbor-interface)))))) + (StaffGrouper . ( (staff-staff-spacing . ((basic-distance . 9) @@ -2650,6 +2658,7 @@ (,ly:side-position-interface::y-aligned-side . ,ly:side-position-interface::pure-y-aligned-side) (,ly:slur::height . ,ly:slur::pure-height) (,ly:slur::outside-slur-callback . ,ly:slur::pure-outside-slur-callback) + (,span-bar-stub::height . ,ly:axis-group-interface::pure-height) (,ly:stem::calc-stem-begin-position . ,ly:stem::pure-calc-stem-begin-position) (,ly:stem::calc-stem-end-position . ,ly:stem::pure-calc-stem-end-position) (,stem::length . ,stem::pure-length) diff --git a/scm/output-lib.scm b/scm/output-lib.scm index 0fbe9ee3c9..5a15551b34 100644 --- a/scm/output-lib.scm +++ b/scm/output-lib.scm @@ -26,6 +26,9 @@ (define-public (grob::is-live? grob) (pair? (ly:grob-basic-properties grob))) +(define-public (grob::x-parent-width grob) + (ly:grob-property (ly:grob-parent grob X) 'X-extent)) + (define-public (make-stencil-boxer thickness padding callback) "Return function that adds a box around the grob passed as argument." (lambda (grob) @@ -345,6 +348,9 @@ and duration-log @var{log}." (equal? (ly:item-break-dir g) RIGHT)) (ly:grob-translate-axis! g 3.5 X))) +(define-public (span-bar-stub::height grob) + (ly:grob-property grob 'elements-filtered) + (ly:axis-group-interface::height grob)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tuplets -- 2.39.2