Index: lily/outside-staff-interface.cc |
diff --git a/lily/outside-staff-interface.cc b/lily/outside-staff-interface.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..40dfb8a305d72beda3fd0b4c9a2851be0a21644c |
--- /dev/null |
+++ b/lily/outside-staff-interface.cc |
@@ -0,0 +1,225 @@ |
+/* |
+ This file is part of LilyPond, the GNU music typesetter. |
+ |
+ Copyright (C) 2013 Mike Solomon <mike@mikesolomon.org> |
+ |
+ 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 <http://www.gnu.org/licenses/>. |
+*/ |
+ |
+#include "outside-staff-interface.hh" |
+ |
+#include "axis-group-interface.hh" |
+#include "directional-element-interface.hh" |
+#include "grob.hh" |
+#include "international.hh" |
+#include "interval-set.hh" |
+#include "pointer-group-interface.hh" |
+#include "skyline-pair.hh" |
+#include "unpure-pure-container.hh" |
+ |
+// Returns the value that the grob elt (whose skylines are given by |
+// v_skyline) needs to move UP or DOWN so that it doesn't intersect with |
+// anything in other_v_skylines. This can be added to the grob's Y-offset |
+// to avoid collisions. |
+ |
+// the other_X arguments represent other skylines, their vertical padding |
+// (amount of vertical space between them and other skylines) and |
+// horizontal padding used to determine if there is overlap along |
+// the X-axis between two skylines. dir is the placement above or |
+// below the staff. |
+ |
+// Note that this function will try to keep elements as close to the staff |
+// as possible. So, if the element fits under other elements above the staff, |
+// it will be shifted under instead of over these elements. So, the dir |
+// property does not refer to the placement with respect to other vertical |
+// skylines, but rather with respect to the staff. |
+ |
+Real |
+distance_needed_to_avoid_outside_staff_collisions (Skyline_pair *v_skyline, |
+ Real padding, |
+ Real horizon_padding, |
+ vector<Skyline_pair> const &other_v_skylines, |
+ vector<Real> const &other_padding, |
+ vector<Real> const &other_horizon_padding, |
+ Direction const dir) |
+{ |
+ assert (other_v_skylines.size () == other_padding.size ()); |
+ assert (other_v_skylines.size () == other_horizon_padding.size ()); |
+ vector<Interval> forbidden_intervals; |
+ for (vsize j = 0; j < other_v_skylines.size (); j++) |
+ { |
+ Skyline_pair const &v_other = other_v_skylines[j]; |
+ Real pad = (padding + other_padding[j]); |
+ Real horizon_pad = (horizon_padding + other_horizon_padding[j]); |
+ |
+ // We need to push elt up by at least this much to be above v_other. |
+ Real up = (*v_skyline)[DOWN].distance (v_other[UP], horizon_pad) + pad; |
+ // We need to push elt down by at least this much to be below v_other. |
+ Real down = (*v_skyline)[UP].distance (v_other[DOWN], horizon_pad) + pad; |
+ |
+ forbidden_intervals.push_back (Interval (-down, up)); |
+ } |
+ |
+ Interval_set allowed_shifts |
+ = Interval_set::interval_union (forbidden_intervals).complement (); |
+ return allowed_shifts.nearest_point (0, dir); |
+} |
+ |
+/* |
+ TODO - this will eventually need to handle pure skylines for outside staff |
+ placement approximations. It'll require better pure skyline approximations |
+ (for now they are all rather brute) as well as calls to maybe_pure_property |
+ for the skylines. |
+*/ |
+ |
+MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Outside_staff_interface, y_aligned_side, 2, 1, ""); |
+SCM |
+Outside_staff_interface::y_aligned_side (SCM smob, SCM current_off) |
+{ |
+ Grob *me = unsmob_grob (smob); |
+ Real current = robust_scm2double (current_off, 0.0); |
+ return scm_from_double (calc_outside_staff_offset (me, false, 0, 0, current)); |
+} |
+ |
+MAKE_SCHEME_CALLBACK_WITH_OPTARGS (Outside_staff_interface, pure_y_aligned_side, 4, 1, ""); |
+SCM |
+Outside_staff_interface::pure_y_aligned_side (SCM /*smob*/, SCM /*start*/, SCM /*end*/, SCM current_off) |
+{ |
+ return current_off; |
+} |
+ |
+Real |
+Outside_staff_interface::calc_outside_staff_offset (Grob *me, bool pure, int start, int end, Real current_offset) |
+{ |
+ /* |
+ If the grob has an outside-staff-priority, we do outside-staff-placement |
+ based on previously-placed grobs. |
+ */ |
+ |
+ if (!scm_is_number (me->get_property ("outside-staff-priority")) |
+ || pure) |
+ return current_offset; |
+ |
+ Direction dir = get_grob_direction (me); |
+ SCM v_orig_scm = me->get_property ("vertical-skylines"); |
+ Skyline_pair *v_orig = Skyline_pair::unsmob (v_orig_scm); |
+ assert (v_orig); // Should be assured by the Grob constructor |
+ Skyline_pair v_skylines (*v_orig); |
+ |
+ // possible if empty stencil |
+ if (v_orig->is_empty ()) |
+ return current_offset; |
+ |
+ Grob *vag = Grob::get_vertical_axis_group (me); |
+ // possible if vetical axis group has been hara-kiri'd |
+ if (!vag || !vag->is_live ()) |
+ return current_offset; |
+ |
+ // ugh...gets called too often...? |
+ Axis_group_interface::prepare_for_outside_staff_calculations (vag); |
+ extract_grob_set (vag, "vertical-skyline-elements", elements); |
+ |
+ Grob *x_common = common_refpoint_of_array (elements, vag, X_AXIS); |
+ Grob *y_common = common_refpoint_of_array (elements, vag, Y_AXIS); |
+ |
+ assert (y_common == vag); |
+ |
+ Skyline_pair skylines; |
+ SCM iss = vag->get_object ("inside-staff-skylines"); |
+ if (Skyline_pair *inside_staff_skylines = Skyline_pair::unsmob (iss)) |
+ skylines = Skyline_pair (*inside_staff_skylines); |
+ |
+ // These are the skylines of all outside-staff grobs |
+ // that have already been processed. We keep them around in order to |
+ // check them for collisions with the currently active outside-staff grob. |
+ Drul_array<vector<Skyline_pair> > all_v_skylines; |
+ Drul_array<vector<Real> > all_paddings; |
+ Drul_array<vector<Real> > all_horizon_paddings; |
+ for (UP_and_DOWN (d)) |
+ { |
+ all_v_skylines[d].push_back (skylines); |
+ all_paddings[d].push_back (0); |
+ all_horizon_paddings[d].push_back (0); |
+ } |
+ |
+ vsize i = 0; |
+ |
+ // ensure inside staff elements are placed |
+ for (; i < elements.size () |
+ && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++) |
+ (void) elements[i]->get_property ("Y-offset"); |
+ |
+ // then build skyline vector for placement |
+ for (; i < elements.size () && elements[i] != me; i++) |
+ { |
+ Grob *elt = elements[i]; |
+ SCM elt_sky_scm = elt->get_property ("vertical-skylines"); |
+ Skyline_pair *elt_sky = Skyline_pair::unsmob (elt_sky_scm); |
+ if (elt_sky->is_empty ()) |
+ continue; |
+ |
+ Skyline_pair elt_v_skylines (*elt_sky); |
+ elt_v_skylines.shift (elt->relative_coordinate (x_common, X_AXIS)); |
+ elt_v_skylines.raise (elt->relative_coordinate (y_common, Y_AXIS)); |
+ Real padding |
+ = robust_scm2double (elt->get_property ("outside-staff-padding"), 0.25); |
+ Real horizon_padding |
+ = robust_scm2double (elt->get_property ("outside-staff-horizontal-padding"), 0.0); |
+ |
+ all_v_skylines[dir].push_back (elt_v_skylines); |
+ all_paddings[dir].push_back (padding); |
+ all_horizon_paddings[dir].push_back (horizon_padding); |
+ } |
+ |
+ Real padding |
+ = robust_scm2double (me->get_property ("outside-staff-padding"), 0.25); |
+ Real horizon_padding |
+ = robust_scm2double (me->get_property ("outside-staff-horizontal-padding"), 0.0); |
+ |
+ if (dir == CENTER) |
+ { |
+ warning (_ ("an outside-staff object should have a direction, defaulting to up")); |
+ dir = UP; |
+ } |
+ |
+ v_skylines.shift (me->relative_coordinate (x_common, X_AXIS)); |
+ Real mommy_offset = me->get_parent (Y_AXIS)->maybe_pure_coordinate (y_common, Y_AXIS, pure, start, end); |
+ v_skylines.raise (current_offset + mommy_offset); |
+ |
+ return current_offset |
+ + distance_needed_to_avoid_outside_staff_collisions (&v_skylines, |
+ padding, |
+ horizon_padding, |
+ all_v_skylines[dir], |
+ all_paddings[dir], |
+ all_horizon_paddings[dir], |
+ dir); |
+} |
+ |
+void |
+Outside_staff_interface::chain_y_offset_callback (Grob *me) |
+{ |
+ chain_offset_callback (me, |
+ ly_make_unpure_pure_container (y_aligned_side_proc, pure_y_aligned_side_proc), |
+ Y_AXIS); |
+} |
+ |
+ADD_INTERFACE (Outside_staff_interface, |
+ "Position a victim object (this one) outside the staff.", |
+ |
+ /* properties */ |
+ "outside-staff-horizontal-padding " |
+ "outside-staff-padding " |
+ "outside-staff-priority " |
+ ); |