+ return Skyline_pair (boxes, X_AXIS).smobbed_copy ();
+}
+
+/*
+ * USE ME ONLY FOR CROSS STAFF SLURS!
+ * We only want to keep the topmost skyline of the topmost axis group(s)
+ * and the bottommost skyline of the bottommost axis group(s). Otherwise,
+ * the VerticalAxisGroups will be spaced very far apart to accommodate the
+ * slur, which we don't want, as it is cross staff.
+ *
+ * TODO: Currently, the code below keeps the topmost and bottommost axis
+ * groups and gets rid of the rest. This should be more nuanced for
+ * cases like ossias where the topmost staff changes over the course of
+ * the slur. Ditto for the bottommost staff.
+ */
+
+MAKE_SCHEME_CALLBACK (Slur, extremal_stub_vertical_skylines, 1);
+SCM
+Slur::extremal_stub_vertical_skylines (SCM smob)
+{
+ Grob *me = unsmob_grob (smob);
+ Grob *my_vag = Grob::get_vertical_axis_group (me);
+ extract_grob_set (me, "note-columns", ro_note_columns);
+ vector<Grob *> note_columns (ro_note_columns);
+ vector_sort (note_columns, Grob::vertical_less);
+ bool highest = my_vag == Grob::get_vertical_axis_group (note_columns[0]);
+ bool lowest = my_vag == Grob::get_vertical_axis_group (note_columns.back ());
+ if (!highest && !lowest)
+ return Skyline_pair ().smobbed_copy ();
+
+ Skyline_pair sky = *Skyline_pair::unsmob (vertical_skylines (smob));
+
+ if (highest)
+ sky[DOWN] = Skyline (DOWN);
+ else
+ sky[UP] = Skyline (UP);
+
+ return sky.smobbed_copy ();
+}
+
+/*
+ * Used by Slur_engraver:: and Phrasing_slur_engraver::
+ */
+void
+Slur::auxiliary_acknowledge_extra_object (Grob_info const &info,
+ vector<Slur_info> &slur_infos,
+ vector<Slur_info> &end_slur_infos)
+{
+ if (slur_infos.empty () && end_slur_infos.empty ())
+ return;
+
+ Grob *e = info.grob ();
+ SCM avoid = e->get_property ("avoid-slur");
+ Grob *slur;
+ if (end_slur_infos.size () && !slur_infos.size ())
+ slur = end_slur_infos[0].slur_;
+ else
+ slur = slur_infos[0].slur_;
+
+ if (Tie::has_interface (e)
+ || avoid == ly_symbol2scm ("inside"))
+ {
+ for (vsize i = slur_infos.size (); i--;)
+ add_extra_encompass (slur_infos[i].slur_, e);
+ for (vsize i = end_slur_infos.size (); i--;)
+ add_extra_encompass (end_slur_infos[i].slur_, e);
+ if (slur)
+ e->set_object ("slur", slur->self_scm ());
+ }
+ else if (avoid == ly_symbol2scm ("outside")
+ || avoid == ly_symbol2scm ("around"))
+ {
+ if (slur)
+ {
+ chain_offset_callback (e, outside_slur_callback_proc, Y_AXIS);
+ chain_callback (e, outside_slur_cross_staff_proc, ly_symbol2scm ("cross-staff"));
+ e->set_object ("slur", slur->self_scm ());
+ }
+ }
+ else if (avoid != ly_symbol2scm ("ignore"))
+ e->warning (_f ("Ignoring grob for slur: %s. avoid-slur not set?",
+ e->name ().c_str ()));
+}
+
+/*
+ A callback that will be chained together with the original cross-staff
+ value of a grob that is placed 'outside or 'around a slur. This just says
+ that any grob becomes cross-staff if it is placed 'outside or 'around a
+ cross-staff slur.
+*/
+MAKE_SCHEME_CALLBACK (Slur, outside_slur_cross_staff, 2)
+SCM
+Slur::outside_slur_cross_staff (SCM smob, SCM previous)
+{
+ if (previous == SCM_BOOL_T)
+ return previous;
+
+ Grob *me = unsmob_grob (smob);
+ Grob *slur = unsmob_grob (me->get_object ("slur"));
+
+ if (!slur)
+ return SCM_BOOL_F;
+ return slur->get_property ("cross-staff");
+}
+
+MAKE_SCHEME_CALLBACK (Slur, calc_cross_staff, 1)
+SCM
+Slur::calc_cross_staff (SCM smob)
+{
+ Grob *me = unsmob_grob (smob);
+
+ extract_grob_set (me, "note-columns", cols);
+ extract_grob_set (me, "encompass-objects", extras);
+
+ for (vsize i = 0; i < cols.size (); i++)
+ {
+ if (Grob *s = Note_column::get_stem (cols[i]))
+ if (to_boolean (s->get_property ("cross-staff")))
+ return SCM_BOOL_T;
+ }
+
+ /* the separation items are dealt with in replace_breakable_encompass_objects
+ so we can ignore them here */
+ vector<Grob *> non_sep_extras;
+ for (vsize i = 0; i < extras.size (); i++)
+ if (!Separation_item::has_interface (extras[i]))
+ non_sep_extras.push_back (extras[i]);
+
+ Grob *common = common_refpoint_of_array (cols, me, Y_AXIS);
+ common = common_refpoint_of_array (non_sep_extras, common, Y_AXIS);
+
+ return scm_from_bool (common != me->get_parent (Y_AXIS));
+}
+
+ADD_INTERFACE (Slur,
+ "A slur."
+ "\n"
+ "The following properties may be set in the @code{details}"
+ " list.\n"
+ "\n"
+ "@table @code\n"
+ "@item region-size\n"
+ "Size of region (in staff spaces) for determining"
+ " potential endpoints in the Y direction.\n"
+ "@item head-encompass-penalty\n"
+ "Demerit to apply when note heads collide with a slur.\n"
+ "@item stem-encompass-penalty\n"
+ "Demerit to apply when stems collide with a slur.\n"
+ "@item edge-attraction-factor\n"
+ "Factor used to calculate the demerit for distances"
+ " between slur endpoints and their corresponding base"
+ " attachments.\n"
+ "@item same-slope-penalty\n"
+ "Demerit for slurs with attachment points that are"
+ " horizontally aligned.\n"
+ "@item steeper-slope-factor\n"
+ "Factor used to calculate demerit only if this slur is"
+ " not broken.\n"
+ "@item non-horizontal-penalty\n"
+ "Demerit for slurs with attachment points that are not"
+ " horizontally aligned.\n"
+ "@item max-slope\n"
+ "The maximum slope allowed for this slur.\n"
+ "@item max-slope-factor\n"
+ "Factor that calculates demerit based on the max slope.\n"
+ "@item free-head-distance\n"
+ "The amount of vertical free space that must exist"
+ " between a slur and note heads.\n"
+ "@item absolute-closeness-measure\n"
+ "Factor to calculate demerit for variance between a note"
+ " head and slur.\n"
+ "@item extra-object-collision-penalty\n"
+ "Factor to calculate demerit for extra objects that the"
+ " slur encompasses, including accidentals, fingerings, and"
+ " tuplet numbers.\n"
+ "@item accidental-collision\n"
+ "Factor to calculate demerit for @code{Accidental} objects"
+ " that the slur encompasses. This property value replaces"
+ " the value of @code{extra-object-collision-penalty}.\n"
+ "@item extra-encompass-free-distance\n"
+ "The amount of vertical free space that must exist"
+ " between a slur and various objects it encompasses,"
+ " including accidentals, fingerings, and tuplet numbers.\n"
+ "@item extra-encompass-collision-distance\n"
+ "This detail is currently unused.\n"
+ "@item head-slur-distance-factor\n"
+ "Factor to calculate demerit for variance between a note"
+ " head and slur.\n"
+ "@item head-slur-distance-max-ratio\n"
+ "The maximum value for the ratio of distance between a"
+ " note head and slur.\n"
+ "@item free-slur-distance\n"
+ "The amount of vertical free space that must exist"
+ " between adjacent slurs. This subproperty only works"
+ " for @code{PhrasingSlur}.\n"
+ "@item edge-slope-exponent\n"
+ "Factor used to calculate the demerit for the slope of"
+ " a slur near its endpoints; a larger value yields a"
+ " larger demerit.\n"
+ "@end table\n",
+
+ /* properties */
+ "annotation "
+ "avoid-slur " /* UGH. */
+ "control-points "
+ "dash-definition "
+ "details "
+ "direction "
+ "eccentricity "
+ "encompass-objects "
+ "height-limit "
+ "inspect-quants "
+ "inspect-index "
+ "line-thickness "
+ "note-columns "
+ "positions "
+ "ratio "
+ "thickness "
+ );