X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=lily%2Fspacing-loose-columns.cc;h=bf8e13593aa4ed71074dc1c2e52fc6a2b186baf0;hb=2ec0a40573d7ed968d4f520d04cebb7e1eb97f9f;hp=c50ee887d9df2bf0bd9b37e10514dec15fa00daf;hpb=3db73bcf98d9ac3ccfe33e08ff3cd0292bf65d06;p=lilypond.git diff --git a/lily/spacing-loose-columns.cc b/lily/spacing-loose-columns.cc index c50ee887d9..bf8e13593a 100644 --- a/lily/spacing-loose-columns.cc +++ b/lily/spacing-loose-columns.cc @@ -1,21 +1,32 @@ /* - spacing-loose-columns.cc -- implement loose column spacing. + This file is part of LilyPond, the GNU music typesetter. - source file of the GNU LilyPond music typesetter + Copyright (C) 2005--2012 Han-Wen Nienhuys - (c) 2005 Han-Wen Nienhuys -*/ + 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 "system.hh" #include "paper-column.hh" #include "column-x-positions.hh" -#include "staff-spacing.hh" #include "pointer-group-interface.hh" -#include "spacing-spanner.hh" +#include "staff-spacing.hh" #include "note-spacing.hh" - -#include "break-align-interface.hh" +#include "spacing-spanner.hh" +#include "warn.hh" +#include "moment.hh" +#include "spacing-options.hh" /* Find the loose columns in POSNS, and drape them around the columns specified in BETWEEN-COLS. */ @@ -23,400 +34,185 @@ void set_loose_columns (System *which, Column_x_positions const *posns) { int loose_col_count = posns->loose_cols_.size (); + if (!loose_col_count) + return; + + for (int i = 0; i < loose_col_count; i++) + dynamic_cast (posns->loose_cols_[i])->set_system (which); + for (int i = 0; i < loose_col_count; i++) { int divide_over = 1; Item *loose = dynamic_cast (posns->loose_cols_[i]); - Paper_column *col = dynamic_cast (loose); - - if (col->get_system ()) - continue; Item *left = 0; Item *right = 0; - while (1) - { - SCM between = loose->get_object ("between-cols"); - if (!scm_is_pair (between)) - break; - - Item *le = dynamic_cast (unsmob_grob (scm_car (between))); - Item *re = dynamic_cast (unsmob_grob (scm_cdr (between))); - - if (! (le && re)) - break; - - if (!left && le) - { - left = le->get_column (); - if (!left->get_system ()) - left = left->find_prebroken_piece (RIGHT); - } - - divide_over++; - loose = right = re->get_column (); - } - - if (!right->get_system ()) - right = right->find_prebroken_piece (LEFT); - - Grob *common = right->common_refpoint (left, X_AXIS); - - int count = 0; - Real total_space = 0.0; - Real total_fixed = 0.0; - - extract_grob_set (col, "spacing-wishes", wishes); - for (int i = 0; i < wishes.size (); i++) - { - Grob *spacing = wishes[i]; - if (Staff_spacing::has_interface (spacing)) - { - Real space = 0.0; - Real fixed = 0.0; - Staff_spacing::get_spacing_params (spacing, &space, &fixed); - - total_fixed += fixed; - total_space += space; - count++; - } - } - - Real right_point = 0.0; - Real distance_to_next = 0.0; - if (count) - { - total_space /= count; - total_fixed /= count; - - distance_to_next = total_space; - right_point = right->relative_coordinate (common, X_AXIS); - } - else - { - Interval my_extent = col->extent (col, X_AXIS); - distance_to_next = my_extent[RIGHT] + 1.0; - right_point = right->extent (common, X_AXIS)[LEFT]; - } - - Real my_offset = right_point - distance_to_next; - - col->system_ = which; - col->translate_axis (my_offset - col->relative_coordinate (common, X_AXIS), X_AXIS); - } -} - - - -/* - Return whether COL is fixed to its neighbors by some kind of spacing - constraint. - - - If in doubt, then we're not loose; the spacing engine should space - for it, risking suboptimal spacing. - - (Otherwise, we might risk core dumps, and other weird stuff.) -*/ -static bool -loose_column (Grob *l, Grob *c, Grob *r) -{ - extract_grob_set (c, "right-neighbors", rns); - extract_grob_set (c, "left-neighbors", lns); - - /* - If this column doesn't have a proper neighbor, we should really - make it loose, but spacing it correctly is more than we can - currently can handle. - - (this happens in the following situation: - - | - | clef G - * - - | | || - | | || - O O || - - - the column containing the clef is really loose, and should be - attached right to the first column, but that is a lot of work for - such a borderline case.) - */ - if (lns.is_empty () || rns.is_empty ()) - return false; - - Item *l_neighbor = dynamic_cast (lns[0]); - Item *r_neighbor = dynamic_cast (rns[0]); - - if (!l_neighbor || !r_neighbor) - return false; - - l_neighbor = l_neighbor->get_column (); - r_neighbor = dynamic_cast (Note_spacing::right_column (r_neighbor)); - - if (l == l_neighbor && r == r_neighbor) - return false; - - if (!l_neighbor || !r_neighbor) - return false; - - /* - Only declare loose if the bounds make a little sense. This means - some cases (two isolated, consecutive clef changes) won't be - nicely folded, but hey, then don't do that. - */ - if (! ((Paper_column::is_musical (l_neighbor) || Item::is_breakable (l_neighbor)) - && (Paper_column::is_musical (r_neighbor) || Item::is_breakable (r_neighbor)))) - { - return false; - } - - /* - A rather hairy check, but we really only want to move around - clefs. (anything else?) - - in any case, we don't want to move bar lines. - */ - extract_grob_set (c, "elements", elts); - for (int i = elts.size (); i--; ) - { - Grob *g = elts[i]; - if (g && Break_align_interface::has_interface (g)) - { - extract_grob_set (g, "elements", gelts); - for (int j = gelts.size (); j--; ) - { - Grob *h = gelts[j]; - - /* - ugh. -- fix staff-bar name? - */ - if (h && h->get_property ("break-align-symbol") == ly_symbol2scm ("staff-bar")) - return false; - } - } - } - - return true; -} - -/* - Remove columns that are not tightly fitting from COLS. In the - removed columns, set 'between-cols to the columns where it is in - between. -*/ -void -Spacing_spanner::prune_loose_columns (Grob *me, Link_array *cols, - Spacing_options const *options) -{ - Link_array newcols; - Real increment = robust_scm2double (me->get_property ("spacing-increment"), 1.2); - for (int i = 0; i < cols->size (); i++) - { - if (Item::is_breakable (cols->elem (i)) - || Paper_column::is_musical (cols->elem (i))) - { - newcols.push (cols->elem (i)); - continue; - } - - Grob *c = cols->elem (i); - if (loose_column (cols->elem (i - 1), c, cols->elem (i + 1))) - { - extract_grob_set (c, "right-neighbors", rns_arr); - extract_grob_set (c, "left-neighbors", lns_arr); - - SCM lns = lns_arr.size () ? lns_arr.top()->self_scm () : SCM_BOOL_F; - SCM rns = rns_arr.size () ? rns_arr.top()->self_scm () : SCM_BOOL_F; - - /* - Either object can be non existent, if the score ends - prematurely. - */ - - extract_grob_set (unsmob_grob (rns), "right-items", right_items); - c->set_object ("between-cols", scm_cons (lns, - right_items[0]->self_scm ())); - - /* - Set distance constraints for loose columns - */ - Drul_array next_door; - next_door[LEFT] = cols->elem (i - 1); - next_door[RIGHT] = cols->elem (i + 1); - Direction d = LEFT; - Drul_array dists (0, 0); - - do - { - dists[d] = 0.0; - Item *lc = dynamic_cast ((d == LEFT) ? next_door[LEFT] : c); - Item *rc = dynamic_cast (d == LEFT ? c : next_door[RIGHT]); - - - extract_grob_set (lc, "spacing-wishes", wishes); - for (int k = wishes.size(); k--;) - { - Grob *sp = wishes[k]; - if (Note_spacing::left_column (sp) != lc - || Note_spacing::right_column (sp) != rc) - continue; - - Real space, fixed; - fixed = 0.0; - bool dummy; - - if (d == LEFT) - { - /* - The note spacing should be taken from the musical - columns. - - */ - Real base = note_spacing (me, lc, rc, options, &dummy); - Note_spacing::get_spacing (sp, rc, base, increment, &space, &fixed); - - space -= increment; - - dists[d] = max (dists[d], space); - } - else - { - Real space, fixed_space; - Staff_spacing::get_spacing_params (sp, - &space, &fixed_space); - - dists[d] = max (dists[d], fixed_space); - } - } - } - while (flip (&d) != LEFT); - - Rod r; - r.distance_ = dists[LEFT] + dists[RIGHT]; - r.item_drul_[LEFT] = dynamic_cast (cols->elem (i - 1)); - r.item_drul_[RIGHT] = dynamic_cast (cols->elem (i + 1)); - - r.add_to_cols (); - } + vector clique; + while (1) + { + SCM between = loose->get_object ("between-cols"); + if (!scm_is_pair (between)) + break; + + /* If the line was broken at one of the loose columns, split + the clique at that column. */ + if (!loose->get_system ()) + break; + + Paper_column *le = dynamic_cast (unsmob_grob (scm_car (between))); + Paper_column *re = dynamic_cast (unsmob_grob (scm_cdr (between))); + + if (! (le && re)) + break; + + if (!left && le) + { + left = le->get_column (); + if (!left->get_system ()) + left = left->find_prebroken_piece (RIGHT); + + clique.push_back (left); + } + + clique.push_back (loose); + + divide_over++; + loose = right = re->get_column (); + } + + if (!right) + { + programming_error ("Can't attach loose column sensibly." + " Attaching to end of system."); + right = which->get_bound (RIGHT); + } + + if (right->get_system ()) + ; /* do nothing */ + else if (right->find_prebroken_piece (LEFT) + && right->find_prebroken_piece (LEFT)->get_system () == which) + right = right->find_prebroken_piece (LEFT); + else if (Paper_column::get_rank (which->get_bound (RIGHT)) < Paper_column::get_rank (right)) + right = which->get_bound (RIGHT); else - { - newcols.push (c); - } - } + { + clique.back ()->programming_error ("Loose column does not have right side to attach to."); + System *base_system = dynamic_cast (which->original ()); + int j = Paper_column::get_rank (clique.back ()) + 1; + int end_rank = Paper_column::get_rank (which->get_bound (RIGHT)); + extract_grob_set (base_system, "columns", base_cols); + for (; j < end_rank; j++) + { + if (base_cols[j]->get_system () == which) + right = dynamic_cast ((Grob *)base_cols[j]); + } + } - *cols = newcols; -} - -/* - Set neighboring columns determined by the spacing-wishes grob property. -*/ -void -Spacing_spanner::set_explicit_neighbor_columns (Link_array const &cols) -{ - for (int i = 0; i < cols.size (); i++) - { - SCM right_neighbors = Grob_array::make_array (); - Grob_array *rn_arr = unsmob_grob_array (right_neighbors); - int min_rank = 100000; // inf. - - extract_grob_set (cols[i], "spacing-wishes", wishes); - for (int k = wishes.size(); k--;) - { - Item *wish = dynamic_cast ( wishes[k]); - - Item *lc = wish->get_column (); - Grob *right = Note_spacing::right_column (wish); - - if (!right) - continue; - - Item *rc = dynamic_cast (right); - - int right_rank = Paper_column::get_rank (rc); - int left_rank = Paper_column::get_rank (lc); - - /* - update the left column. - */ - if (right_rank <= min_rank) - { - if (right_rank < min_rank) - rn_arr->clear (); - - min_rank = right_rank; - rn_arr->add (wish); - } - - /* - update the right column of the wish. - */ - int maxrank = 0; - - extract_grob_set (rc, "left-neighbors", lns_arr); - if (lns_arr.size ()) - { - Item *it = dynamic_cast (lns_arr.top()); - maxrank = Paper_column::get_rank (it->get_column ()); - } - - if (left_rank >= maxrank) - { - - if (left_rank > maxrank) - { - Grob_array *ga = unsmob_grob_array (rc->get_object ("left-neighbors")); - if (ga) - ga->clear (); - } - - Pointer_group_interface::add_grob (rc, ly_symbol2scm ("left-neighbors"), wish); - } - } - - if (rn_arr->size ()) - { - cols[i]->set_object ("right-neighbors", right_neighbors); - } - } -} - -/* - Set neighboring columns that have no left/right-neighbor set - yet. Only do breakable non-musical columns, and musical columns. -*/ -void -Spacing_spanner::set_implicit_neighbor_columns (Link_array const &cols) -{ - for (int i = 0; i < cols.size (); i++) - { - Item *it = dynamic_cast (cols[i]); - if (!Item::is_breakable (it) && !Paper_column::is_musical (it)) - continue; + Grob *common = right->common_refpoint (left, X_AXIS); - // it->breakable || it->musical + clique.push_back (right); /* - sloppy with typing left/right-neighbors should take list, but paper-column found instead. + We use two vectors to keep track of loose column spacing: + clique_spacing keeps track of ideal spaces. + clique_tight_spacing keeps track of minimum spaces. + Below, a scale factor is applied to the shifting of loose columns that + aims to preserve clique_spacing but gets closer to clique_tight_spacing as the + space becomes smaller. This is used because the rods placed for loose columns + are tight (meaning they use minimum distances - see set_distances_for_loose_columns). + However, other rods may widen this distance, in which case we don't want a crammed score. + Thus, we aim for non-crammed, and fall back on crammed as needed. */ - extract_grob_set (cols[i], "left-neighbors", lns); - if (lns.is_empty () && i ) - { - SCM ga_scm = Grob_array::make_array(); - Grob_array *ga = unsmob_grob_array (ga_scm); - ga->add (cols[i-1]); - cols[i]->set_object ("left-neighbors", ga_scm); - } - extract_grob_set (cols[i], "right-neighbors", rns); - if (rns.is_empty () && i < cols.size () - 1) - { - SCM ga_scm = Grob_array::make_array(); - Grob_array *ga = unsmob_grob_array (ga_scm); - ga->add (cols[i+1]); - cols[i]->set_object ("right-neighbors", ga_scm); - } + vector clique_spacing; + vector clique_tight_spacing; + clique_spacing.push_back (0.0); + clique_tight_spacing.push_back (0.0); + for (vsize j = 1; j + 1 < clique.size (); j++) + { + Grob *clique_col = clique[j]; + + Paper_column *loose_col = dynamic_cast (clique[j]); + Paper_column *next_col = dynamic_cast (clique[j + 1]); + + Grob *spacing = unsmob_grob (clique_col->get_object ("spacing")); + if (Grob *grace_spacing = unsmob_grob (clique_col->get_object ("grace-spacing"))) + { + spacing = grace_spacing; + } + + Spacing_options options; + if (spacing) + options.init_from_grob (spacing); + else + programming_error ("Column without spacing object"); + + Real base_note_space = 0.0; + Real tight_note_space = 0.0; + + if (Paper_column::is_musical (next_col) + && Paper_column::is_musical (loose_col)) + { + Real base = Spacing_spanner::note_spacing (spacing, loose_col, next_col, + &options); + if (Note_spacing::has_interface (spacing)) + { + Spring spring = Note_spacing::get_spacing (spacing, next_col, base, options.increment_);; + base_note_space = spring.distance (); + tight_note_space = spring.min_distance (); + } + else + { + base_note_space = base; + tight_note_space = base; + } + } + else + { + Spring spring = Spacing_spanner::standard_breakable_column_spacing (spacing, + loose_col, next_col, + &options); + + base_note_space = spring.distance (); + tight_note_space = spring.min_distance (); + } + + Real loose_col_horizontal_length = loose_col->extent (loose_col, X_AXIS).length (); + base_note_space = max (base_note_space, loose_col_horizontal_length); + tight_note_space = max (tight_note_space, loose_col_horizontal_length); + + clique_spacing.push_back (base_note_space); + clique_tight_spacing.push_back (tight_note_space); + } + + Real permissible_distance = clique.back ()->relative_coordinate (common, X_AXIS) - robust_relative_extent (clique[0], common, X_AXIS)[RIGHT]; + Real right_point = robust_relative_extent (clique.back (), common, X_AXIS)[LEFT]; + Grob *finished_right_column = clique.back (); + + Real sum_tight_spacing = 0; + Real sum_spacing = 0; + // currently a magic number - what would be a good grob to hold this property? + Real left_padding = 0.15; + for (vsize j = 0; j < clique_spacing.size (); j++) + { + sum_tight_spacing += clique_tight_spacing[j]; + sum_spacing += clique_spacing[j]; + } + Real scale_factor = max (0.0, min (1.0, (permissible_distance - left_padding - sum_tight_spacing) / (sum_spacing - sum_tight_spacing))); + for (vsize j = clique.size () - 2; j > 0; j--) + { + Paper_column *clique_col = dynamic_cast (clique[j]); + + right_point = finished_right_column->relative_coordinate (common, X_AXIS); + + Real distance_to_next = clique_tight_spacing[j] + (clique_spacing[j] - clique_tight_spacing[j]) * scale_factor; + + Real my_offset = right_point - distance_to_next; + + clique_col->translate_axis (my_offset - clique_col->relative_coordinate (common, X_AXIS), X_AXIS); + + finished_right_column = clique_col; + } } } +