+bool
+staff_priority_less (Grob * const &g1, Grob * const &g2)
+{
+ Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
+ Real priority_2 = robust_scm2double (g2->get_property ("outside-staff-priority"), -infinity_f);
+
+ if (priority_1 < priority_2)
+ return true;
+ else if (priority_1 > priority_2)
+ return false;
+
+ /* if neither grob has an outside-staff priority, the ordering will have no
+ effect -- we just need to choose a consistent ordering. We do this to
+ avoid the side-effect of calculating extents. */
+ if (isinf (priority_1))
+ return g1 < g2;
+
+ /* if there is no preference in staff priority, choose the left-most one */
+ Grob *common = g1->common_refpoint (g2, X_AXIS);
+ Real start_1 = g1->extent (common, X_AXIS)[LEFT];
+ Real start_2 = g2->extent (common, X_AXIS)[LEFT];
+ return start_1 < start_2;
+}
+
+static void
+add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
+{
+ /* if a child has skylines, use them instead of the extent box */
+ if (Skyline_pair *pair = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
+ {
+ Skyline_pair s = *pair;
+ s.shift (me->relative_coordinate (x_common, X_AXIS));
+ s.raise (me->relative_coordinate (y_common, Y_AXIS));
+ skylines->merge (s);
+ }
+ else if (Grob_array *elements = unsmob_grob_array (me->get_object ("elements")))
+ {
+ for (vsize i = 0; i < elements->size (); i++)
+ add_boxes (elements->grob (i), x_common, y_common, boxes, skylines);
+ }
+ else if (!scm_is_number (me->get_property ("outside-staff-priority"))
+ && !to_boolean (me->get_property ("cross-staff")))
+ {
+ boxes->push_back (Box (me->extent (x_common, X_AXIS),
+ me->extent (y_common, Y_AXIS)));
+ }
+}
+
+/* We want to avoid situations like this:
+ still more text
+ more text
+ text
+ -------------------
+ staff
+ -------------------
+
+ The point is that "still more text" should be positioned under
+ "more text". In order to achieve this, we place the grobs in several
+ passes. We keep track of the right-most horizontal position that has been
+ affected by the current pass so far (actually we keep track of 2
+ positions, one for above the staff, one for below).
+
+ In each pass, we loop through the unplaced grobs from left to right.
+ If the grob doesn't overlap the right-most affected position, we place it
+ (and then update the right-most affected position to point to the right
+ edge of the just-placed grob). Otherwise, we skip it until the next pass.
+*/
+static void
+add_grobs_of_one_priority (Skyline_pair *const skylines,
+ vector<Grob*> elements,
+ Grob *x_common,
+ Grob *y_common)
+{
+ vector<Box> boxes;
+ Drul_array<Real> last_affected_position;
+
+ reverse (elements);
+ while (!elements.empty ())
+ {
+ last_affected_position[UP] = -infinity_f;
+ last_affected_position[DOWN] = -infinity_f;
+ /* do one pass */
+ for (vsize i = elements.size (); i--;)
+ {
+ Direction dir = get_grob_direction (elements[i]);
+ if (dir == CENTER)
+ {
+ warning (_ ("an outside-staff object should have a direction, defaulting to up"));
+ dir = UP;
+ }
+
+ Box b (elements[i]->extent (x_common, X_AXIS),
+ elements[i]->extent (y_common, Y_AXIS));
+ SCM horizon_padding_scm = elements[i]->get_property ("outside-staff-horizontal-padding");
+ Real horizon_padding = robust_scm2double (horizon_padding_scm, 0.0);
+
+ if (b[X_AXIS][LEFT] - 2*horizon_padding < last_affected_position[dir])
+ continue;
+
+ if (!b[X_AXIS].is_empty () && !b[Y_AXIS].is_empty ())
+ {
+ boxes.clear ();
+ boxes.push_back (b);
+ Skyline other = Skyline (boxes, horizon_padding, X_AXIS, -dir);
+ Real padding = robust_scm2double (elements[i]->get_property ("outside-staff-padding"), 0.5);
+ Real dist = (*skylines)[dir].distance (other) + padding;
+
+ if (dist > 0)
+ {
+ b.translate (Offset (0, dir*dist));
+ elements[i]->translate_axis (dir*dist, Y_AXIS);
+ }
+ (*skylines)[dir].insert (b, 0, X_AXIS);
+ elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
+ last_affected_position[dir] = b[X_AXIS][RIGHT];
+ }
+
+ /*
+ Ugh: quadratic. --hwn
+ */
+ elements.erase (elements.begin () + i);
+ }
+ }
+}
+
+Skyline_pair
+Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
+{
+ vector_sort (elements, staff_priority_less);
+ Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
+ Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
+
+ assert (y_common == me);
+
+ vsize i = 0;
+ vector<Box> boxes;
+
+ Skyline_pair skylines;
+ for (i = 0; i < elements.size ()
+ && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
+ add_boxes (elements[i], x_common, y_common, &boxes, &skylines);
+
+ SCM padding_scm = me->get_property ("skyline-horizontal-padding");
+ Real padding = robust_scm2double (padding_scm, 0.1);
+ skylines.merge (Skyline_pair (boxes, padding, X_AXIS));
+ for (; i < elements.size (); i++)
+ {
+ SCM priority = elements[i]->get_property ("outside-staff-priority");
+ vector<Grob*> current_elts;
+ current_elts.push_back (elements[i]);
+ while (i + 1 < elements.size ()
+ && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
+ current_elts.push_back (elements[++i]);
+
+ add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
+ }
+ skylines.shift (-me->relative_coordinate (x_common, X_AXIS));
+ return skylines;
+}
+
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
+SCM
+Axis_group_interface::calc_max_stretch (SCM smob)
+{
+ Grob *me = unsmob_grob (smob);
+ Real ret = 0;
+ extract_grob_set (me, "elements", elts);
+
+ for (vsize i = 0; i < elts.size (); i++)
+ if (Axis_group_interface::has_interface (elts[i]))
+ ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
+
+ return scm_from_double (ret);
+}
+
+extern bool debug_skylines;
+MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
+SCM
+Axis_group_interface::print (SCM smob)
+{
+ if (!debug_skylines)
+ return SCM_BOOL_F;
+
+ Grob *me = unsmob_grob (smob);
+ Stencil ret;
+ if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
+ {
+ ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[UP].to_points (X_AXIS)).in_color (255, 0, 255));
+ ret.add_stencil (Lookup::points_to_line_stencil (0.1, (*s)[DOWN].to_points (X_AXIS)).in_color (0, 255, 255));
+ }
+ return ret.smobbed_copy ();
+}
+
+ADD_INTERFACE (Axis_group_interface,