]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/axis-group-interface.cc
add vcs lines to debian/control
[lilypond.git] / lily / axis-group-interface.cc
index b62465322eb8044081bd5801b560c3b60ef21ea5..0b399fd2482e5fa8ef9da603a416471dd9281ac8 100644 (file)
@@ -1,27 +1,43 @@
 /*
 /*
-  axis-group-interface.cc -- implement Axis_group_interface
+  This file is part of LilyPond, the GNU music typesetter.
 
 
-  source file of the GNU LilyPond music typesetter
+  Copyright (C) 2000--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
 
 
-  (c) 2000--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
+  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 "axis-group-interface.hh"
 
 #include "align-interface.hh"
 #include "directional-element-interface.hh"
 */
 
 #include "axis-group-interface.hh"
 
 #include "align-interface.hh"
 #include "directional-element-interface.hh"
-#include "pointer-group-interface.hh"
 #include "grob-array.hh"
 #include "hara-kiri-group-spanner.hh"
 #include "international.hh"
 #include "lookup.hh"
 #include "paper-column.hh"
 #include "paper-score.hh"
 #include "grob-array.hh"
 #include "hara-kiri-group-spanner.hh"
 #include "international.hh"
 #include "lookup.hh"
 #include "paper-column.hh"
 #include "paper-score.hh"
+#include "pointer-group-interface.hh"
 #include "separation-item.hh"
 #include "separation-item.hh"
+#include "skyline-pair.hh"
+#include "staff-grouper-interface.hh"
 #include "stencil.hh"
 #include "system.hh"
 #include "warn.hh"
 
 #include "stencil.hh"
 #include "system.hh"
 #include "warn.hh"
 
+static bool
+pure_staff_priority_less (Grob *const &g1, Grob *const &g2);
+
 void
 Axis_group_interface::add_element (Grob *me, Grob *e)
 {
 void
 Axis_group_interface::add_element (Grob *me, Grob *e)
 {
@@ -73,92 +89,212 @@ Axis_group_interface::relative_group_extent (vector<Grob*> const &elts,
   return r;
 }
 
   return r;
 }
 
+Interval
+Axis_group_interface::sum_partial_pure_heights (Grob *me, int start, int end)
+{
+  Interval iv = begin_of_line_pure_height (me, start);
+  iv.unite (rest_of_line_pure_height (me, start, end));
 
 
-/*
-  FIXME: pure extent handling has a lot of ad-hoc caching.
-  This should be done with grob property callbacks.
+  return iv;
+}
 
 
-  --hwn
-*/
+Interval
+Axis_group_interface::part_of_line_pure_height (Grob *me, bool begin, int start, int end)
+{
+  Spanner *sp = dynamic_cast<Spanner*> (me);
+  SCM cache_symbol = begin
+    ? ly_symbol2scm ("begin-of-line-pure-height")
+    : ly_symbol2scm ("rest-of-line-pure-height");
+  SCM cached = sp->get_cached_pure_property (cache_symbol, start, end);
+  if (scm_is_pair (cached))
+    return robust_scm2interval (cached, Interval (0, 0));
+
+  SCM adjacent_pure_heights = me->get_property ("adjacent-pure-heights");
+  Interval ret;
+
+  if (!scm_is_pair (adjacent_pure_heights))
+    ret = Interval (0, 0);
+  else
+    {
+      SCM these_pure_heights = begin
+       ? scm_car (adjacent_pure_heights)
+       : scm_cdr (adjacent_pure_heights);
+
+      if (scm_is_vector (these_pure_heights))
+       ret = combine_pure_heights (me, these_pure_heights, start, end);
+      else
+       ret = Interval (0, 0);
+    }
+
+  sp->cache_pure_property (cache_symbol, start, end, ly_interval2scm (ret));
+  return ret;
+}
+
+Interval
+Axis_group_interface::begin_of_line_pure_height (Grob *me, int start)
+{
+  return part_of_line_pure_height (me, true, start, start + 1);
+}
 
 Interval
 
 Interval
-Axis_group_interface::cached_pure_height (Grob *me,
-                                         vector<Grob*> const &elts,
-                                         Grob *common,
-                                         int start, int end)
+Axis_group_interface::rest_of_line_pure_height (Grob *me, int start, int end)
+{
+  return part_of_line_pure_height (me, false, start, end);
+}
+
+Interval
+Axis_group_interface::combine_pure_heights (Grob *me, SCM measure_extents, int start, int end)
 {
   Paper_score *ps = get_root_system (me)->paper_score ();
   vector<vsize> breaks = ps->get_break_indices ();
   vector<Grob*> cols = ps->get_columns ();
 {
   Paper_score *ps = get_root_system (me)->paper_score ();
   vector<vsize> breaks = ps->get_break_indices ();
   vector<Grob*> cols = ps->get_columns ();
-  vsize start_index = VPOS;
-  vsize end_index = VPOS;
 
 
-  for (vsize i = 0; i < breaks.size (); i++)
+  Interval ext;
+  for (vsize i = 0; i + 1 < breaks.size (); i++)
     {
       int r = Paper_column::get_rank (cols[breaks[i]]);
     {
       int r = Paper_column::get_rank (cols[breaks[i]]);
-      if (start == r)
-       start_index = i;
-      if (end == r)
-       end_index = i;
-    }
-  if (end == INT_MAX)
-    end_index = breaks.size () - 1;
+      if (r >= end)
+       break;
 
 
-  if (start_index == VPOS || end_index == VPOS)
-    {
-      programming_error (_ ("tried to calculate pure-height at a non-breakpoint"));
-      return Interval (0, 0);
+      if (r >= start)
+       ext.unite (ly_scm2interval (scm_c_vector_ref (measure_extents, i)));
     }
 
     }
 
-  SCM extents = me->get_property ("cached-pure-extents");
-  if (!scm_is_vector (extents))
+  return ext;
+}
+
+// adjacent-pure-heights is a pair of vectors, each of which has one element
+// for every measure in the score. The first vector stores, for each measure,
+// the combined height of the elements that are present only when the bar
+// is at the beginning of a line. The second vector stores, for each measure,
+// the combined height of the elements that are present only when the bar
+// is not at the beginning of a line.
+MAKE_SCHEME_CALLBACK (Axis_group_interface, adjacent_pure_heights, 1)
+SCM
+Axis_group_interface::adjacent_pure_heights (SCM smob)
+{
+  Grob *me = unsmob_grob (smob);
+
+  Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
+  extract_grob_set (me, "pure-relevant-grobs", elts);
+
+  Paper_score *ps = get_root_system (me)->paper_score ();
+  vector<vsize> ranks = ps->get_break_ranks ();
+
+  vector<Interval> begin_line_heights;
+  vector<Interval> mid_line_heights;
+  vector<Interval> begin_line_staff_heights;
+  vector<Interval> mid_line_staff_heights;
+  begin_line_heights.resize (ranks.size () - 1);
+  mid_line_heights.resize (ranks.size () - 1);
+
+  for (vsize i = 0; i < elts.size (); ++i)
     {
     {
-      extents = scm_c_make_vector (breaks.size () - 1, SCM_EOL);
-      for (vsize i = 0; i + 1 < breaks.size (); i++)
+      Grob *g = elts[i];
+
+      if (to_boolean (g->get_property ("cross-staff")))
+       continue;
+
+      bool outside_staff = scm_is_number (g->get_property ("outside-staff-priority"));
+      Real padding = robust_scm2double (g->get_property ("outside-staff-padding"), 0.5);
+
+      // When we encounter the first outside-staff grob, make a copy
+      // of the current heights to use as an estimate for the staff heights.
+      // Note that the outside-staff approximation that we use here doesn't
+      // consider any collisions that might occur between outside-staff grobs,
+      // but only the fact that outside-staff grobs may need to be raised above
+      // the staff.
+      if (outside_staff && begin_line_staff_heights.empty ())
+       {
+         begin_line_staff_heights = begin_line_heights;
+         mid_line_staff_heights = mid_line_heights;
+       }
+
+      // TODO: consider a pure version of get_grob_direction?
+      Direction d = to_dir (g->get_property_data ("direction"));
+      d = (d == CENTER) ? UP : d;
+
+      Interval_t<int> rank_span = g->spanned_rank_interval ();
+      vsize first_break = lower_bound (ranks, (vsize)rank_span[LEFT], less<vsize> ());
+      if (first_break > 0 && ranks[first_break] >= (vsize)rank_span[LEFT])
+       first_break--;
+
+      for (vsize j = first_break; j+1 < ranks.size () && (int)ranks[j] <= rank_span[RIGHT]; ++j)
        {
        {
-         int st = Paper_column::get_rank (cols[breaks[i]]);
-         int ed = Paper_column::get_rank (cols[breaks[i+1]]);
-         Interval iv = relative_pure_height (me, elts, common, st, ed, false);
-         scm_vector_set_x (extents, scm_from_int (i), ly_interval2scm (iv));
+         int start = ranks[j];
+         int end = ranks[j+1];
+
+         // Take grobs that are visible with respect to a slightly longer line.
+         // Otherwise, we will never include grobs at breakpoints which aren't
+         // end-of-line-visible.
+         int visibility_end = j + 2 < ranks.size () ? ranks[j+2] : end;
+
+         if (g->pure_is_visible (start, visibility_end))
+           {
+             Interval dims = g->pure_height (common, start, end);
+             if (!dims.is_empty ())
+               {
+                 if (rank_span[LEFT] <= start)
+                   {
+                     if (outside_staff)
+                       begin_line_heights[j].unite (
+                           begin_line_staff_heights[j].union_disjoint (dims, padding, d));
+                     else
+                       begin_line_heights[j].unite (dims);
+                   }
+                  if (rank_span[RIGHT] > start)
+                   {
+                     if (outside_staff)
+                       mid_line_heights[j].unite (
+                            mid_line_staff_heights[j].union_disjoint (dims, padding, d));
+                     else
+                       mid_line_heights[j].unite (dims);
+                   }
+               }
+           }
        }
        }
-      me->set_property ("cached-pure-extents", extents);
     }
 
     }
 
-  Interval ext (0, 0);
-  for (vsize i = start_index; i < end_index; i++)
-    ext.unite (ly_scm2interval (scm_c_vector_ref (extents, i)));
-  return ext;
+  // Convert begin_line_heights and min_line_heights to SCM.
+  SCM begin_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
+  SCM mid_scm = scm_c_make_vector (ranks.size () - 1, SCM_EOL);
+  for (vsize i = 0; i < begin_line_heights.size (); ++i)
+    {
+      scm_vector_set_x (begin_scm, scm_from_int (i), ly_interval2scm (begin_line_heights[i]));
+      scm_vector_set_x (mid_scm, scm_from_int (i), ly_interval2scm (mid_line_heights[i]));
+    }
+
+  return scm_cons (begin_scm, mid_scm);
 }
 
 Interval
 }
 
 Interval
-Axis_group_interface::relative_pure_height (Grob *me,
-                                           vector<Grob*> const &elts,
-                                           Grob *common,
-                                           int start, int end,
-                                           bool use_cache)
+Axis_group_interface::relative_pure_height (Grob *me, int start, int end)
 {
   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
 {
   /* It saves a _lot_ of time if we assume a VerticalAxisGroup is additive
-     (ie. height (i, k) = height (i, j) + height (j, k) for all i <= j <= k).
+     (ie. height (i, k) = max (height (i, j) height (j, k)) for all i <= j <= k).
      Unfortunately, it isn't always true, particularly if there is a
      VerticalAlignment somewhere in the descendants.
 
      Unfortunately, it isn't always true, particularly if there is a
      VerticalAlignment somewhere in the descendants.
 
-     Apart from PianoStaff, which has a fixed VerticalAlignment so it doesn't
-     count, the only VerticalAlignment comes from Score. This makes it
+     Usually, the only VerticalAlignment comes from Score. This makes it
      reasonably safe to assume that if our parent is a VerticalAlignment,
      we can assume additivity and cache things nicely. */
   Grob *p = me->get_parent (Y_AXIS);
      reasonably safe to assume that if our parent is a VerticalAlignment,
      we can assume additivity and cache things nicely. */
   Grob *p = me->get_parent (Y_AXIS);
-  if (use_cache && p && Align_interface::has_interface (p))
-    return Axis_group_interface::cached_pure_height (me, elts, common, start, end);
+  if (p && Align_interface::has_interface (p))
+    return Axis_group_interface::sum_partial_pure_heights (me, start, end);
 
 
-  Interval r;
+  Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
+  extract_grob_set (me, "pure-relevant-grobs", elts);
 
 
+  Interval r;
   for (vsize i = 0; i < elts.size (); i++)
     {
   for (vsize i = 0; i < elts.size (); i++)
     {
-      Interval_t<int> rank_span = elts[i]->spanned_rank_interval ();
-      Item *it = dynamic_cast<Item*> (elts[i]);
-      if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start && (!it || it->pure_is_visible (start, end)))
+      Grob *g = elts[i];
+      Interval_t<int> rank_span = g->spanned_rank_interval ();
+      if (rank_span[LEFT] <= end && rank_span[RIGHT] >= start
+         && g->pure_is_visible (start, end)
+         && !to_boolean (g->get_property ("cross-staff")))
        {
        {
-         Interval dims = elts[i]->pure_height (common, start, end);
+         Interval dims = g->pure_height (common, start, end);
          if (!dims.is_empty ())
            r.unite (dims);
        }
          if (!dims.is_empty ())
            r.unite (dims);
        }
@@ -201,7 +337,7 @@ Axis_group_interface::pure_height (SCM smob, SCM start_scm, SCM end_scm)
        return scm_cdr (system_y_extent);
     }
 
        return scm_cdr (system_y_extent);
     }
 
-  return pure_group_height (me, start, end);
+  return ly_interval2scm (pure_group_height (me, start, end));
 }
 
 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
 }
 
 MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_skylines, 1);
@@ -212,13 +348,6 @@ Axis_group_interface::calc_skylines (SCM smob)
   extract_grob_set (me, "elements", elts);
   Skyline_pair skylines = skyline_spacing (me, elts);
 
   extract_grob_set (me, "elements", elts);
   Skyline_pair skylines = skyline_spacing (me, elts);
 
-  /* add a minimum-Y-extent-sized box to the skyline */
-  SCM min_y_extent = me->get_property ("minimum-Y-extent");
-  if (is_number_pair (min_y_extent))
-    {
-      Box b (me->extent (me, X_AXIS), ly_scm2interval (min_y_extent));
-      skylines.insert (b, 0, X_AXIS);
-    }
   return skylines.smobbed_copy ();
 }
 
   return skylines.smobbed_copy ();
 }
 
@@ -235,8 +364,10 @@ Axis_group_interface::combine_skylines (SCM smob)
   Grob *me = unsmob_grob (smob);
   extract_grob_set (me, "elements", elements);
   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
   Grob *me = unsmob_grob (smob);
   extract_grob_set (me, "elements", elements);
   Grob *y_common = common_refpoint_of_array (elements, me, Y_AXIS);
+  Grob *x_common = common_refpoint_of_array (elements, me, X_AXIS);
 
 
-  assert (y_common == me);
+  if (y_common != me)
+    programming_error ("combining skylines that don't belong to me");
 
   Skyline_pair ret;
   for (vsize i = 0; i < elements.size (); i++)
 
   Skyline_pair ret;
   for (vsize i = 0; i < elements.size (); i++)
@@ -247,12 +378,13 @@ Axis_group_interface::combine_skylines (SCM smob)
          Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
          Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
          other.raise (offset);
          Real offset = elements[i]->relative_coordinate (y_common, Y_AXIS);
          Skyline_pair other = *Skyline_pair::unsmob (skyline_scm);
          other.raise (offset);
+         other.shift (elements[i]->relative_coordinate (x_common, X_AXIS));
          ret.merge (other);
        }
     }
   return ret.smobbed_copy ();
 }
          ret.merge (other);
        }
     }
   return ret.smobbed_copy ();
 }
-  
+
 SCM
 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
 {
 SCM
 Axis_group_interface::generic_group_extent (Grob *me, Axis a)
 {
@@ -269,78 +401,119 @@ Axis_group_interface::generic_group_extent (Grob *me, Axis a)
   return ly_interval2scm (r - my_coord);
 }
 
   return ly_interval2scm (r - my_coord);
 }
 
+/* This is like generic_group_extent, but it only counts the grobs that
+   are children of some other axis-group. This is uncached; if it becomes
+   commonly used, it may be necessary to cache it somehow. */
+Interval
+Axis_group_interface::staff_extent (Grob *me, Grob *refp, Axis ext_a, Grob *staff, Axis parent_a)
+{
+  extract_grob_set (me, "elements", elts);
+  vector<Grob*> new_elts;
+
+  for (vsize i = 0; i < elts.size (); i++)
+    if (elts[i]->common_refpoint (staff, parent_a) == staff)
+      new_elts.push_back (elts[i]);
+
+  return relative_group_extent (new_elts, refp, ext_a);
+}
 
 
-Grob *
-Axis_group_interface::calc_pure_elts_and_common (Grob *me)
+
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_relevant_grobs, 1);
+SCM
+Axis_group_interface::calc_pure_relevant_grobs (SCM smob)
 {
 {
-  if (Grob *c = unsmob_grob (me->get_object ("pure-Y-common")))
-    return c;
-  
+  Grob *me = unsmob_grob (smob);
+
   extract_grob_set (me, "elements", elts);
 
   extract_grob_set (me, "elements", elts);
 
-  vector<Grob*> relevant_elts;
+  vector<Grob*> relevant_grobs;
   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
 
   for (vsize i = 0; i < elts.size (); i++)
     {
       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
   SCM pure_relevant_p = ly_lily_module_constant ("pure-relevant?");
 
   for (vsize i = 0; i < elts.size (); i++)
     {
       if (to_boolean (scm_apply_1 (pure_relevant_p, elts[i]->self_scm (), SCM_EOL)))
-       relevant_elts.push_back (elts[i]);
-
-      Item *it = dynamic_cast<Item*> (elts[i]);
-      Direction d = LEFT;
-      if (it)
-       do
-         {
-           Item *piece = it->find_prebroken_piece (d);
-           if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
-             relevant_elts.push_back (piece);
-         }
-       while (flip (&d) != LEFT);
+       relevant_grobs.push_back (elts[i]);
+
+      if (Item *it = dynamic_cast<Item*> (elts[i]))
+       {
+         Direction d = LEFT;
+         do
+           {
+             Item *piece = it->find_prebroken_piece (d);
+             if (piece && to_boolean (scm_apply_1 (pure_relevant_p, piece->self_scm (), SCM_EOL)))
+               relevant_grobs.push_back (piece);
+           }
+         while (flip (&d) != LEFT);
+       }
     }
 
     }
 
-  Grob *common = common_refpoint_of_array (relevant_elts, me, Y_AXIS);
-  me->set_object ("pure-Y-common", common->self_scm ());
-  
-  SCM ga_scm = Grob_array::make_array ();
-  Grob_array *ga = unsmob_grob_array (ga_scm);
-  ga->set_array (relevant_elts);
-  me->set_object ("pure-relevant-elements", ga_scm);
+  vector_sort (relevant_grobs, pure_staff_priority_less);
+  SCM grobs_scm = Grob_array::make_array ();
+  unsmob_grob_array (grobs_scm)->set_array (relevant_grobs);
 
 
-  return common;
+  return grobs_scm;
 }
 
 }
 
-MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_y_common, 1);
 SCM
 SCM
-Axis_group_interface::calc_x_common (SCM grob)
+Axis_group_interface::calc_pure_y_common (SCM smob)
 {
 {
-  Grob *me = unsmob_grob (grob);
+  Grob *me = unsmob_grob (smob);
+
+  extract_grob_set (me, "pure-relevant-grobs", elts);
+  Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
+  if (!common)
+    {
+      me->programming_error ("No common parent found in calc_pure_y_common.");
+      return SCM_EOL;
+    }
 
 
-  extract_grob_set (me, "elements", elts);
-  Grob *common = common_refpoint_of_array (elts, me, X_AXIS);
   return common->self_scm ();
 }
 
   return common->self_scm ();
 }
 
-MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
 SCM
 SCM
-Axis_group_interface::calc_y_common (SCM grob)
+Axis_group_interface::calc_common (Grob *me, Axis axis)
 {
 {
-  Grob *me = unsmob_grob (grob);
-
   extract_grob_set (me, "elements", elts);
   extract_grob_set (me, "elements", elts);
-  Grob *common = common_refpoint_of_array (elts, me, Y_AXIS);
+  Grob *common = common_refpoint_of_array (elts, me, axis);
+  if (!common)
+    {
+      me->programming_error ("No common parent found in calc_common axis.");
+      return SCM_EOL;
+    }
+
   return common->self_scm ();
 }
 
   return common->self_scm ();
 }
 
+
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_x_common, 1);
 SCM
 SCM
+Axis_group_interface::calc_x_common (SCM grob)
+{
+  return calc_common (unsmob_grob (grob), X_AXIS);
+}
+
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_y_common, 1);
+SCM
+Axis_group_interface::calc_y_common (SCM grob)
+{
+  return calc_common (unsmob_grob (grob), Y_AXIS);
+}
+
+Interval
 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
 {
 Axis_group_interface::pure_group_height (Grob *me, int start, int end)
 {
-  Grob *common = calc_pure_elts_and_common (me);
-       
-  extract_grob_set (me, "pure-relevant-elements", elts);
+  Grob *common = unsmob_grob (me->get_object ("pure-Y-common"));
+
+  if (!common)
+    {
+      programming_error ("no pure Y common refpoint");
+      return Interval ();
+    }
   Real my_coord = me->relative_coordinate (common, Y_AXIS);
   Real my_coord = me->relative_coordinate (common, Y_AXIS);
-  Interval r (relative_pure_height (me, elts, common, start, end, true));
+  Interval r (relative_pure_height (me, start, end));
 
 
-  return ly_interval2scm (r - my_coord);
+  return r - my_coord;
 }
 
 void
 }
 
 void
@@ -359,7 +532,7 @@ Axis_group_interface::get_children (Grob *me, vector<Grob*> *found)
     }
 }
 
     }
 }
 
-bool
+static bool
 staff_priority_less (Grob * const &g1, Grob * const &g2)
 {
   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
 staff_priority_less (Grob * const &g1, Grob * const &g2)
 {
   Real priority_1 = robust_scm2double (g1->get_property ("outside-staff-priority"), -infinity_f);
@@ -383,6 +556,15 @@ staff_priority_less (Grob * const &g1, Grob * const &g2)
   return start_1 < start_2;
 }
 
   return start_1 < start_2;
 }
 
+static bool
+pure_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);
+
+  return priority_1 < priority_2;
+}
+
 static void
 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
 {
 static void
 add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, Skyline_pair *skylines)
 {
@@ -422,7 +604,7 @@ add_boxes (Grob *me, Grob *x_common, Grob *y_common, vector<Box> *const boxes, S
    positions, one for above the staff, one for below).
 
    In each pass, we loop through the unplaced grobs from left to right.
    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 overlaps the right-most affected position, we place it
+   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.
 */
    (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.
 */
@@ -471,7 +653,7 @@ add_grobs_of_one_priority (Skyline_pair *const skylines,
                  b.translate (Offset (0, dir*dist));
                  elements[i]->translate_axis (dir*dist, Y_AXIS);
                }
                  b.translate (Offset (0, dir*dist));
                  elements[i]->translate_axis (dir*dist, Y_AXIS);
                }
-             (*skylines)[dir].insert (b, 0, X_AXIS);
+             skylines->insert (b, 0, X_AXIS);
              elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
              last_affected_position[dir] = b[X_AXIS][RIGHT];
            }
              elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
              last_affected_position[dir] = b[X_AXIS][RIGHT];
            }
@@ -484,9 +666,27 @@ add_grobs_of_one_priority (Skyline_pair *const skylines,
     }
 }
 
     }
 }
 
+// TODO: it is tricky to correctly handle skyline placement of cross-staff grobs.
+// For example, cross-staff beams cannot be formatted until the distance between
+// staves is known and therefore any grobs that depend on the beam cannot be placed
+// until the skylines are known. On the other hand, the distance between staves should
+// really depend on position of the cross-staff grobs that lie between them.
+// Currently, we just leave cross-staff grobs out of the
+// skyline altogether, but this could mean that staves are placed so close together
+// that there is no room for the cross-staff grob. It also means, of course, that
+// we don't get the benefits of skyline placement for cross-staff grobs.
 Skyline_pair
 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
 {
 Skyline_pair
 Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
 {
+  /* For grobs with an outside-staff-priority, the sorting function might
+     call extent and cause suicide. This breaks the contract that is required
+     for the STL sort function. To avoid this, we make sure that any suicides
+     are triggered beforehand.
+  */
+  for (vsize i = 0; i < elements.size (); i++)
+    if (scm_is_number (elements[i]->get_property ("outside-staff-priority")))
+      elements[i]->extent (elements[i], X_AXIS);
+
   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);
   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);
@@ -499,19 +699,27 @@ Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
   Skyline_pair skylines;
   for (i = 0; i < elements.size ()
         && !scm_is_number (elements[i]->get_property ("outside-staff-priority")); i++)
   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);
+    if (!to_boolean (elements[i]->get_property ("cross-staff")))
+      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 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++)
     {
+      if (to_boolean (elements[i]->get_property ("cross-staff")))
+       continue;
+
       SCM priority = elements[i]->get_property ("outside-staff-priority");
       vector<Grob*> current_elts;
       current_elts.push_back (elements[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 () 
+      while (i + 1 < elements.size ()
             && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
             && scm_eq_p (elements[i+1]->get_property ("outside-staff-priority"), priority))
-       current_elts.push_back (elements[++i]);
+       {
+         if (!to_boolean (elements[i+1]->get_property ("cross-staff")))
+           current_elts.push_back (elements[i+1]);
+         ++i;
+       }
 
       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
     }
 
       add_grobs_of_one_priority (&skylines, current_elts, x_common, y_common);
     }
@@ -519,22 +727,6 @@ Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
   return skylines;
 }
 
   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)
 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
 SCM
 Axis_group_interface::print (SCM smob)
@@ -546,25 +738,87 @@ Axis_group_interface::print (SCM smob)
   Stencil ret;
   if (Skyline_pair *s = Skyline_pair::unsmob (me->get_property ("vertical-skylines")))
     {
   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));
+      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 ();
 }
 
     }
   return ret.smobbed_copy ();
 }
 
-ADD_INTERFACE (Axis_group_interface,
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_pure_staff_staff_spacing, 3)
+SCM
+Axis_group_interface::calc_pure_staff_staff_spacing (SCM smob, SCM start, SCM end)
+{
+  return calc_maybe_pure_staff_staff_spacing (unsmob_grob (smob),
+                                             true,
+                                             scm_to_int (start),
+                                             scm_to_int (end));
+}
+
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_staff_staff_spacing, 1)
+SCM
+Axis_group_interface::calc_staff_staff_spacing (SCM smob)
+{
+  return calc_maybe_pure_staff_staff_spacing (unsmob_grob (smob),
+                                             false,
+                                             0,
+                                             INT_MAX);
+}
+
+SCM
+Axis_group_interface::calc_maybe_pure_staff_staff_spacing (Grob *me, bool pure, int start, int end)
+{
+  Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
+
+  if (grouper)
+    {
+      bool within_group = Staff_grouper_interface::maybe_pure_within_group (grouper, me, pure, start, end);
+      if (within_group)
+       return grouper->get_maybe_pure_property ("staff-staff-spacing", pure, start, end);
+      else
+       return grouper->get_maybe_pure_property ("staffgroup-staff-spacing", pure, start, end);
+    }
+  return me->get_maybe_pure_property ("default-staff-staff-spacing", pure, start, end);
+}
 
 
+Real
+Axis_group_interface::minimum_distance (Grob *g1, Grob *g2, Axis a)
+{
+  SCM sym = ly_symbol2scm ((a == Y_AXIS) ? "vertical-skylines" : "horizontal-skylines");
+
+  Skyline_pair *s1 = Skyline_pair::unsmob (g1->get_property (sym));
+  Skyline_pair *s2 = Skyline_pair::unsmob (g2->get_property (sym));
+  if (s1 && s2)
+    return (*s1)[DOWN].distance ((*s2)[UP]);
+  return 0;
+}
+
+ADD_INTERFACE (Axis_group_interface,
               "An object that groups other layout objects.",
 
               "An object that groups other layout objects.",
 
+              // TODO: some of these properties are specific to
+              // VerticalAxisGroup. We should split off a
+              // vertical-axis-group-interface.
               /* properties */
               /* properties */
-              "X-common "
-              "Y-common "
+              "adjacent-pure-heights "
               "axes "
               "axes "
+              "default-staff-staff-spacing "
               "elements "
               "elements "
-              "keep-fixed-while-stretching "
               "max-stretch "
               "max-stretch "
+              "no-alignment "
+              "nonstaff-nonstaff-spacing "
+              "nonstaff-relatedstaff-spacing "
+              "nonstaff-unrelatedstaff-spacing "
+              "pure-relevant-grobs "
+              "pure-relevant-items "
+              "pure-relevant-spanners "
               "pure-Y-common "
               "pure-Y-common "
-              "pure-relevant-elements "
+              "staff-affinity "
+              "staff-grouper "
+              "staff-staff-spacing "
+              "system-Y-offset "
               "vertical-skylines "
               "vertical-skylines "
-              "cached-pure-extents "
+              "X-common "
+              "Y-common "
               );
               );