LEFT | RIGHT |
(no file at all) | |
| 1 /* |
| 2 This file is part of LilyPond, the GNU music typesetter. |
| 3 |
| 4 Copyright (C) 2013 Mike Solomon <mike@mikesolomon.org> |
| 5 |
| 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. |
| 10 |
| 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. |
| 15 |
| 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/>. |
| 18 */ |
| 19 |
| 20 #include "outside-staff-interface.hh" |
| 21 |
| 22 #include "axis-group-interface.hh" |
| 23 #include "directional-element-interface.hh" |
| 24 #include "grob.hh" |
| 25 #include "international.hh" |
| 26 #include "interval-set.hh" |
| 27 #include "pointer-group-interface.hh" |
| 28 #include "skyline-pair.hh" |
| 29 #include "unpure-pure-container.hh" |
| 30 |
| 31 // Returns the value that the grob elt (whose skylines are given by |
| 32 // v_skyline) needs to move UP or DOWN so that it doesn't intersect with |
| 33 // anything in other_v_skylines. This can be added to the grob's Y-offset |
| 34 // to avoid collisions. |
| 35 |
| 36 // the other_X arguments represent other skylines, their vertical padding |
| 37 // (amount of vertical space between them and other skylines) and |
| 38 // horizontal padding used to determine if there is overlap along |
| 39 // the X-axis between two skylines. dir is the placement above or |
| 40 // below the staff. |
| 41 |
| 42 // Note that this function will try to keep elements as close to the staff |
| 43 // as possible. So, if the element fits under other elements above the staff, |
| 44 // it will be shifted under instead of over these elements. So, the dir |
| 45 // property does not refer to the placement with respect to other vertical |
| 46 // skylines, but rather with respect to the staff. |
| 47 |
| 48 Real |
| 49 distance_needed_to_avoid_outside_staff_collisions (Skyline_pair *v_skyline, |
| 50 Real padding, |
| 51 Real horizon_padding, |
| 52 vector<Skyline_pair> const &o
ther_v_skylines, |
| 53 vector<Real> const &other_pad
ding, |
| 54 vector<Real> const &other_hor
izon_padding, |
| 55 Direction const dir) |
| 56 { |
| 57 assert (other_v_skylines.size () == other_padding.size ()); |
| 58 assert (other_v_skylines.size () == other_horizon_padding.size ()); |
| 59 vector<Interval> forbidden_intervals; |
| 60 for (vsize j = 0; j < other_v_skylines.size (); j++) |
| 61 { |
| 62 Skyline_pair const &v_other = other_v_skylines[j]; |
| 63 Real pad = (padding + other_padding[j]); |
| 64 Real horizon_pad = (horizon_padding + other_horizon_padding[j]); |
| 65 |
| 66 // We need to push elt up by at least this much to be above v_other. |
| 67 Real up = (*v_skyline)[DOWN].distance (v_other[UP], horizon_pad) + pad; |
| 68 // We need to push elt down by at least this much to be below v_other. |
| 69 Real down = (*v_skyline)[UP].distance (v_other[DOWN], horizon_pad) + pad; |
| 70 |
| 71 forbidden_intervals.push_back (Interval (-down, up)); |
| 72 } |
| 73 |
| 74 Interval_set allowed_shifts |
| 75 = Interval_set::interval_union (forbidden_intervals).complement (); |
| 76 return allowed_shifts.nearest_point (0, dir); |
| 77 } |
| 78 |
| 79 /* |
| 80 TODO - this will eventually need to handle pure skylines for outside staff |
| 81 placement approximations. It'll require better pure skyline approximations |
| 82 (for now they are all rather brute) as well as calls to maybe_pure_property |
| 83 for the skylines. |
| 84 */ |
| 85 |
| 86 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Outside_staff_interface, y_aligned_side, 2, 1
, ""); |
| 87 SCM |
| 88 Outside_staff_interface::y_aligned_side (SCM smob, SCM current_off) |
| 89 { |
| 90 Grob *me = unsmob_grob (smob); |
| 91 Real current = robust_scm2double (current_off, 0.0); |
| 92 return scm_from_double (calc_outside_staff_offset (me, false, 0, 0, current)); |
| 93 } |
| 94 |
| 95 MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Outside_staff_interface, pure_y_aligned_side,
4, 1, ""); |
| 96 SCM |
| 97 Outside_staff_interface::pure_y_aligned_side (SCM /*smob*/, SCM /*start*/, SCM /
*end*/, SCM current_off) |
| 98 { |
| 99 return current_off; |
| 100 } |
| 101 |
| 102 Real |
| 103 Outside_staff_interface::calc_outside_staff_offset (Grob *me, bool pure, int sta
rt, int end, Real current_offset) |
| 104 { |
| 105 /* |
| 106 If the grob has an outside-staff-priority, we do outside-staff-placement |
| 107 based on previously-placed grobs. |
| 108 */ |
| 109 |
| 110 if (!scm_is_number (me->get_property ("outside-staff-priority")) |
| 111 || pure) |
| 112 return current_offset; |
| 113 |
| 114 Direction dir = get_grob_direction (me); |
| 115 SCM v_orig_scm = me->get_property ("vertical-skylines"); |
| 116 Skyline_pair *v_orig = Skyline_pair::unsmob (v_orig_scm); |
| 117 assert (v_orig); // Should be assured by the Grob constructor |
| 118 Skyline_pair v_skylines (*v_orig); |
| 119 |
| 120 // possible if empty stencil |
| 121 if (v_orig->is_empty ()) |
| 122 return current_offset; |
| 123 |
| 124 Grob *vag = Grob::get_vertical_axis_group (me); |
| 125 // possible if vetical axis group has been hara-kiri'd |
| 126 if (!vag || !vag->is_live ()) |
| 127 return current_offset; |
| 128 |
| 129 // ugh...gets called too often...? |
| 130 Axis_group_interface::prepare_for_outside_staff_calculations (vag); |
| 131 extract_grob_set (vag, "vertical-skyline-elements", elements); |
| 132 |
| 133 Grob *x_common = common_refpoint_of_array (elements, vag, X_AXIS); |
| 134 Grob *y_common = common_refpoint_of_array (elements, vag, Y_AXIS); |
| 135 |
| 136 assert (y_common == vag); |
| 137 |
| 138 Skyline_pair skylines; |
| 139 SCM iss = vag->get_object ("inside-staff-skylines"); |
| 140 if (Skyline_pair *inside_staff_skylines = Skyline_pair::unsmob (iss)) |
| 141 skylines = Skyline_pair (*inside_staff_skylines); |
| 142 |
| 143 // These are the skylines of all outside-staff grobs |
| 144 // that have already been processed. We keep them around in order to |
| 145 // check them for collisions with the currently active outside-staff grob. |
| 146 Drul_array<vector<Skyline_pair> > all_v_skylines; |
| 147 Drul_array<vector<Real> > all_paddings; |
| 148 Drul_array<vector<Real> > all_horizon_paddings; |
| 149 for (UP_and_DOWN (d)) |
| 150 { |
| 151 all_v_skylines[d].push_back (skylines); |
| 152 all_paddings[d].push_back (0); |
| 153 all_horizon_paddings[d].push_back (0); |
| 154 } |
| 155 |
| 156 vsize i = 0; |
| 157 |
| 158 // ensure inside staff elements are placed |
| 159 for (; i < elements.size () |
| 160 && !scm_is_number (elements[i]->get_property ("outside-staff-priority"));
i++) |
| 161 (void) elements[i]->get_property ("Y-offset"); |
| 162 |
| 163 // then build skyline vector for placement |
| 164 for (; i < elements.size () && elements[i] != me; i++) |
| 165 { |
| 166 Grob *elt = elements[i]; |
| 167 SCM elt_sky_scm = elt->get_property ("vertical-skylines"); |
| 168 Skyline_pair *elt_sky = Skyline_pair::unsmob (elt_sky_scm); |
| 169 if (elt_sky->is_empty ()) |
| 170 continue; |
| 171 |
| 172 Skyline_pair elt_v_skylines (*elt_sky); |
| 173 elt_v_skylines.shift (elt->relative_coordinate (x_common, X_AXIS)); |
| 174 elt_v_skylines.raise (elt->relative_coordinate (y_common, Y_AXIS)); |
| 175 Real padding |
| 176 = robust_scm2double (elt->get_property ("outside-staff-padding"), 0.25); |
| 177 Real horizon_padding |
| 178 = robust_scm2double (elt->get_property ("outside-staff-horizontal-paddin
g"), 0.0); |
| 179 |
| 180 all_v_skylines[dir].push_back (elt_v_skylines); |
| 181 all_paddings[dir].push_back (padding); |
| 182 all_horizon_paddings[dir].push_back (horizon_padding); |
| 183 } |
| 184 |
| 185 Real padding |
| 186 = robust_scm2double (me->get_property ("outside-staff-padding"), 0.25); |
| 187 Real horizon_padding |
| 188 = robust_scm2double (me->get_property ("outside-staff-horizontal-padding"),
0.0); |
| 189 |
| 190 if (dir == CENTER) |
| 191 { |
| 192 warning (_ ("an outside-staff object should have a direction, defaulting t
o up")); |
| 193 dir = UP; |
| 194 } |
| 195 |
| 196 v_skylines.shift (me->relative_coordinate (x_common, X_AXIS)); |
| 197 Real mommy_offset = me->get_parent (Y_AXIS)->maybe_pure_coordinate (y_common,
Y_AXIS, pure, start, end); |
| 198 v_skylines.raise (current_offset + mommy_offset); |
| 199 |
| 200 return current_offset |
| 201 + distance_needed_to_avoid_outside_staff_collisions (&v_skylines, |
| 202 padding, |
| 203 horizon_padding, |
| 204 all_v_skylines[dir
], |
| 205 all_paddings[dir], |
| 206 all_horizon_paddin
gs[dir], |
| 207 dir); |
| 208 } |
| 209 |
| 210 void |
| 211 Outside_staff_interface::chain_y_offset_callback (Grob *me) |
| 212 { |
| 213 chain_offset_callback (me, |
| 214 ly_make_unpure_pure_container (y_aligned_side_proc, pur
e_y_aligned_side_proc), |
| 215 Y_AXIS); |
| 216 } |
| 217 |
| 218 ADD_INTERFACE (Outside_staff_interface, |
| 219 "Position a victim object (this one) outside the staff.", |
| 220 |
| 221 /* properties */ |
| 222 "outside-staff-horizontal-padding " |
| 223 "outside-staff-padding " |
| 224 "outside-staff-priority " |
| 225 ); |
LEFT | RIGHT |