]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/tuplet-bracket.cc
Fix some bugs in the dynamic engraver and PostScript backend
[lilypond.git] / lily / tuplet-bracket.cc
index 0260b2a1cecd639eeb831921cfbedcdddde4da5b..4ca50328295c88d6319b39f69706925bff100a27 100644 (file)
@@ -3,7 +3,7 @@
 
   source file of the GNU LilyPond music typesetter
 
-  (c) 1997--2004 Jan Nieuwenhuizen <janneke@gnu.org>
+  (c) 1997--2006 Jan Nieuwenhuizen <janneke@gnu.org>
   Han-Wen Nienhuys <hanwen@xs4all.nl>
 */
 
   - There is no support for kneed brackets, or nested brackets.
 
   - number placement for parallel beams should be much more advanced:
-    for sloped beams some extra horizontal offset must be introduced.
+  for sloped beams some extra horizontal offset must be introduced.
 
   - number placement is usually done over the center note, not the
-    graphical center.
-  
- */
+  graphical center.
+*/
 
-#include <math.h>
+/*
+  TODO: quantise, we don't want to collide with staff lines.
+  (or should we be above staff?)
 
+  todo: handle breaking elegantly.
+*/
+
+
+#include "tuplet-bracket.hh"
 #include "line-interface.hh"
 #include "beam.hh"
-#include "box.hh"
 #include "warn.hh"
-#include "font-interface.hh"
-#include "stencil.hh"
 #include "output-def.hh"
-#include "text-item.hh"
-#include "tuplet-bracket.hh"
+#include "font-interface.hh"
+#include "text-interface.hh"
 #include "stem.hh"
 #include "note-column.hh"
-#include "group-interface.hh"
+#include "pointer-group-interface.hh"
 #include "directional-element-interface.hh"
 #include "spanner.hh"
 #include "staff-symbol-referencer.hh"
 #include "lookup.hh"
+#include "paper-column.hh"
+#include "moment.hh"
 
-
-static Grob*
-get_x_bound_grob (Grob *g, Direction my_dir)
+static Item *
+get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
 {
-  if (Note_column::get_stem (g)
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
+  Item *g = me->get_bound (hdir);
+  if (Note_column::has_interface (g)
+      && Note_column::get_stem (g)
       && Note_column::dir (g) == my_dir)
-    {
-      g = Note_column::get_stem (g);
-    }
+    g = Note_column::get_stem (g);
+
   return g;
 }
 
 
+void
+flatten_number_pair_property (Grob *me,
+                             Direction xdir,  SCM sym)
+{
+  Drul_array<Real> zero (0, 0);
+  Drul_array<Real> pair
+    = robust_scm2drul (me->internal_get_property (sym), zero);
+  pair[xdir] = 0.0;
+  
+  me->internal_set_property (sym, ly_interval2scm (pair));
+}
 
-Grob*
-Tuplet_bracket::parallel_beam (Grob *me, Link_array<Grob> const &cols, bool *equally_long)
+
+Grob *
+Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob*> const &cols, bool *equally_long)
 {
-  /*
-    ugh: code dup. 
-  */
-  Grob *s1 = Note_column::get_stem (cols[0]); 
-  Grob *s2 = Note_column::get_stem (cols.top ());    
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
 
-  Grob*b1 = s1 ? Stem::get_beam (s1) : 0;
-  Grob*b2 = s2 ? Stem::get_beam (s2) : 0;
-  
-  Spanner*sp = dynamic_cast<Spanner*> (me);  
+  if (me->get_bound (LEFT)->break_status_dir ()
+      || me->get_bound (RIGHT)->break_status_dir ())
+    return 0;
 
-  *equally_long= false;
-  if (! (b1 && (b1 == b2) && !sp->is_broken ()))
-      return 0;
+  Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
+                          Note_column::get_stem (cols.back ()));
 
-  Link_array<Grob> beam_stems = Pointer_group_interface__extract_grobs
-    (b1, (Grob*)0, "stems");
+  if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
+      != me->get_bound (RIGHT)->get_column())
+    return 0;
+
+  Drul_array<Grob*> beams;
+  Direction d = LEFT;
+  do {
+    beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
+  } while (flip (&d) != LEFT);
+  
+  *equally_long = false;
+  if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
+    return 0;
 
+  extract_grob_set (beams[LEFT], "stems", beam_stems);
   if (beam_stems.size () == 0)
     {
-      programming_error ("Beam under tuplet bracket has no stems!");
+      programming_error ("beam under tuplet bracket has no stems");
       *equally_long = 0;
       return 0;
     }
+
+  *equally_long =
+    (beam_stems[0] == stems[LEFT] && beam_stems.back () == stems[RIGHT]);
+  return beams[LEFT];
+}
+
+
+MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_connect_to_neighbors,1);
+SCM
+Tuplet_bracket::calc_connect_to_neighbors (SCM smob)
+{
+  Spanner *me = unsmob_spanner (smob);
+
+  Direction dir = get_grob_direction (me); 
+  Drul_array<Item *> bounds (get_x_bound_item (me, LEFT, dir),
+                            get_x_bound_item (me, RIGHT, dir));
   
-  *equally_long = (beam_stems[0] == s1 && beam_stems.top () == s2);
-  return b1;
+  Drul_array<bool> connect_to_other (false, false);
+  Direction d = LEFT;
+  do
+    {
+      Direction break_dir = bounds[d]->break_status_dir ();
+      
+      Spanner *orig_spanner = dynamic_cast<Spanner*> (me->original ());
+
+      vsize neighbor_idx = me->get_break_index () - break_dir;
+      if (break_dir
+         && d == RIGHT
+         && neighbor_idx < orig_spanner->broken_intos_.size ())
+       {
+         Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
+
+         /* trigger possible suicide*/
+         (void) neighbor->get_property ("positions");
+       }
+
+      connect_to_other[d]
+       = (break_dir
+          && neighbor_idx < orig_spanner->broken_intos_.size ()
+          && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
+    }
+  while (flip (&d) != LEFT);
+
+
+  if (connect_to_other[LEFT] || connect_to_other[RIGHT])
+    return scm_cons (scm_from_bool (connect_to_other[LEFT]),
+                    scm_from_bool (connect_to_other[RIGHT]));
+                    
+  return SCM_EOL;
 }
 
+Grob* 
+Tuplet_bracket::get_common_x (Spanner *me)
+{
+  extract_grob_set (me, "note-columns", columns);
 
-/*
-  TODO:
+  Grob * commonx = common_refpoint_of_array (columns, me, X_AXIS);
+  commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
+  commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
 
-  in the case that there is no bracket, but there is a (single) beam,
-  follow beam precisely for determining tuplet number location.
+  return commonx;
+}
   
- */
-MAKE_SCHEME_CALLBACK (Tuplet_bracket,print,1);
+MAKE_SCHEME_CALLBACK(Tuplet_bracket,calc_control_points,1)
 SCM
-Tuplet_bracket::print (SCM smob) 
+Tuplet_bracket::calc_control_points (SCM smob)
 {
-  Grob *me= unsmob_grob (smob);
-  Stencil  mol;
-  Link_array<Grob> columns=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Spanner *me = unsmob_spanner (smob);
 
-  if (!columns.size ())
-    return mol.smobbed_copy ();
+  extract_grob_set (me, "note-columns", columns);
 
+  SCM scm_positions = me->get_property ("positions");
+  if (!me->is_live ())
+    return SCM_EOL;
   
+  if (!scm_is_pair (scm_positions))
+    programming_error ("Positions should be number pair");
+    
+  Drul_array<Real> positions
+    = robust_scm2drul (scm_positions, Drul_array<Real> (0,0));
 
-  {
-    SCM lp = me->get_property ("left-position");
-    SCM rp = me->get_property ("right-position");  
+  Grob *commonx = get_common_x (me);
+  Direction dir = get_grob_direction (me);
 
-    if (!scm_is_number (rp) || !scm_is_number (lp))
-      after_line_breaking (smob);
-  }
+  Drul_array<Item *> bounds;
+  bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
+  bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
+
+  Drul_array<bool> connect_to_other =
+    robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
+                        Drul_array<bool> (false, false));
   
-  Real ly = robust_scm2double (me->get_property ("left-position"), 0);
-  Real ry = robust_scm2double (me->get_property ("right-position"), 0);  
+    
+  Interval x_span;
+  Direction d = LEFT;
+  do
+    {
+      x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
+
+      if (connect_to_other[d])
+       {
+         Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
+                                              Interval (-0.5, 0.0)));
+
+         if (d == RIGHT)
+           x_span[d] += d * overshoot[d];
+         else
+           x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
+             - overshoot[LEFT];
+       }
+      
+      else if (d == RIGHT
+              && (columns.empty ()
+                  || (bounds[d]->get_column ()
+                      != dynamic_cast<Item *> (columns.back ())->get_column ())))
+       {
+         /*
+           We're connecting to a column, for the last bit of a broken
+           fullLength bracket.
+           
+           TODO: make padding tunable?
+         */
+         Real padding = 1.0;
+
+         if (bounds[d]->break_status_dir ())
+           padding = 0.0;
+         
+         x_span[d]
+           = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT]
+           - padding;
+       }
+    }
+  while (flip (&d) != LEFT);
+
   
-  bool equally_long = false;
-  Grob * par_beam = parallel_beam (me, columns, &equally_long);
+  
+  x_span -= me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS);
+  return scm_list_2 (ly_offset2scm (Offset (x_span[LEFT], positions[LEFT])),
+                    ly_offset2scm (Offset (x_span[RIGHT], positions[RIGHT])));
+}
 
-  Spanner*sp = dynamic_cast<Spanner*> (me);  
+/*
+  TODO:
 
-  bool bracket_visibility = !(par_beam && equally_long);
-  bool number_visibility = true;
+  in the case that there is no bracket, but there is a (single) beam,
+  follow beam precisely for determining tuplet number location.
+*/
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
+SCM
+Tuplet_bracket::print (SCM smob)
+{
+  Spanner *me = unsmob_spanner (smob);
+  Stencil mol;
 
+  extract_grob_set (me, "note-columns", columns);
+  bool equally_long = false;
+  Grob *par_beam = parallel_beam (me, columns, &equally_long);
+  
+  bool bracket_visibility = !(par_beam && equally_long);
   /*
     Fixme: the type of this prop is sucky.
-   */
+  */
   SCM bracket = me->get_property ("bracket-visibility");
   if (scm_is_bool (bracket))
-    {
-      bracket_visibility = ly_scm2bool (bracket);
-    }
+    bracket_visibility = ly_scm2bool (bracket);
   else if (bracket == ly_symbol2scm ("if-no-beam"))
     bracket_visibility = !par_beam;
 
-  SCM numb = me->get_property ("number-visibility");  
-  if (scm_is_bool (numb))
+  
+  SCM cpoints =  me->get_property ("control-points");
+  if (scm_ilength (cpoints) < 2)
     {
-      number_visibility = ly_scm2bool (numb);
+      me->suicide ();
+      return SCM_EOL;
     }
-  else if (numb == ly_symbol2scm ("if-no-beam"))
-    number_visibility = !par_beam;
-       
-  Grob * commonx = columns[0]->common_refpoint (columns.top (),X_AXIS);
-
-  /*
-    Tuplet brackets are normally not broken, but we shouldn't crash if
-    they are.
-   */
-  commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
-  commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);  
-  
-  Direction dir = get_grob_direction (me);
-
-  Grob * lgr = get_x_bound_grob (columns[0], dir);
-  Grob * rgr = get_x_bound_grob (columns.top (), dir);
   
-  Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
-  Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
-  Real w = x1 -x0;
+  Drul_array<Offset> points;
+  points[LEFT] = ly_scm2offset (scm_car (cpoints));
+  points[RIGHT] = ly_scm2offset (scm_cadr (cpoints));
   
-  SCM number = me->get_property ("text");
+  Interval x_span (points[LEFT][X_AXIS], points[RIGHT][X_AXIS]);
+  Drul_array<Real> positions (points[LEFT][Y_AXIS], points[RIGHT][Y_AXIS]);
 
-  Output_def *pap = me->get_paper ();
-  Stencil num;
- if (scm_is_string (number) && number_visibility)
-    {
-      SCM properties = Font_interface::text_font_alist_chain (me);
-      SCM snum = Text_item::interpret_markup (pap->self_scm (), properties, number);
-      num = *unsmob_stencil (snum);
-      num.align_to (X_AXIS, CENTER);
-      num.translate_axis (w/2, X_AXIS);
-      num.align_to (Y_AXIS, CENTER);
-       
-      num.translate_axis ((ry-ly)/2, Y_AXIS);
-
-      mol.add_stencil (num);
-    }
 
 
+  Output_def *pap = me->layout ();
+
+  Grob *number_grob = unsmob_grob (me->get_object ("tuplet-number"));
+  
   /*
     No bracket when it would be smaller than the number.
-
-    TODO: should use GAP in calculation too.
-   */
-  if (bracket_visibility && number_visibility
-      && mol.extent (X_AXIS).length () > w)
+  */
+  Real gap = 0.;
+  if (bracket_visibility && number_grob)
     {
-      bracket_visibility = false;
+      Interval ext = number_grob->extent (number_grob, X_AXIS);
+      if (!ext.is_empty ())
+       {
+         gap = ext.length () + 1.0;
+      
+         if (0.75 * x_span.length () < gap)
+           bracket_visibility = false;
+       }
     }
-  
-  if (bracket_visibility)      
-    {
-      Real ss =   Staff_symbol_referencer::staff_space (me);
-      Real gap=0.;
 
-      if (!num.extent (X_AXIS).is_empty ())
-       gap = num.extent (X_AXIS).length () + 1.0;
-      
-      SCM fl = me->get_property ("bracket-flare");
-      SCM eh = me->get_property ("edge-height");
-      SCM sp = me->get_property ("shorten-pair");
+  if (bracket_visibility)
+    {
+      Drul_array<Real> zero (0, 0);
+      Real ss = Staff_symbol_referencer::staff_space (me);
+      Drul_array<Real> height
+       = robust_scm2drul (me->get_property ("edge-height"), zero);
+      Drul_array<Real> flare
+       = robust_scm2drul (me->get_property ("bracket-flare"), zero);
+      Drul_array<Real> shorten
+       = robust_scm2drul (me->get_property ("shorten-pair"), zero);
+      Drul_array<Stencil> edge_stencils;
+
+      Direction dir = get_grob_direction (me);
       
+      scale_drul (&height, -ss * dir);
+      scale_drul (&flare, ss);
+      scale_drul (&shorten, ss);
+
+      Drul_array<bool> connect_to_other =
+       robust_scm2booldrul (me->get_property ("connect-to-neighbor"),
+                            Drul_array<bool> (false, false));
+
       Direction d = LEFT;
-      Drul_array<Real> height, flare, shorten;
-      do {
-       flare[d] =  height[d] = shorten[d] = 0.0;
-       if (is_number_pair (fl))
-         flare[d] +=  ss * scm_to_double (index_get_cell (fl, d));
-       if (is_number_pair (eh))
-         height[d] +=  - dir * ss *scm_to_double (index_get_cell (eh, d));
-       if (is_number_pair (sp))
-         shorten[d] +=  ss *scm_to_double (index_get_cell (sp, d));
-      }
+      do
+       {
+         if (connect_to_other[d])
+           {
+             height[d] = 0.0;
+             flare[d] = 0.0;
+             shorten[d] = 0.0;
+
+             SCM edge_text = me->get_property ("edge-text");
+
+             if (scm_is_pair (edge_text))
+               {
+                 SCM properties = Font_interface::text_font_alist_chain (me);
+                 SCM text = index_get_cell (edge_text, d);
+                 if (Text_interface::is_markup (text))
+                   {
+                     SCM t = Text_interface::interpret_markup (pap->self_scm (),
+                                                               properties, text);
+
+                     Stencil *edge_text = unsmob_stencil (t);
+                     edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
+                     edge_stencils[d] = *edge_text;
+                   }
+               }
+           }
+       }
       while (flip (&d) != LEFT);
-      
+
       Stencil brack = make_bracket (me, Y_AXIS,
-                                    Offset (w, ry - ly), 
-                                    height,
+                                   points[RIGHT] - points[LEFT],
+                                   height,
                                    /*
                                      0.1 = more space at right due to italics
                                      TODO: use italic correction of font.
-                                    */
+                                   */
                                    Interval (-0.5, 0.5) * gap + 0.1,
-                                    flare, shorten);
+                                   flare, shorten);
+
+      do
+       {
+         if (!edge_stencils[d].is_empty ())
+           brack.add_stencil (edge_stencils[d]);
+       }
+      while (flip (&d) != LEFT);
+
       mol.add_stencil (brack);
     }
-  
-  mol.translate_axis (ly, Y_AXIS);
-  mol.translate_axis (x0  - sp->get_bound (LEFT)->relative_coordinate (commonx,X_AXIS),X_AXIS);
+
+  mol.translate (points[LEFT]);
   return mol.smobbed_copy ();
 }
 
@@ -243,9 +392,9 @@ Tuplet_bracket::print (SCM smob)
 
   TODO: this will fail for very short (shorter than the flare)
   brackets.
- */
+*/
 Stencil
-Tuplet_bracket::make_bracket (Grob *me,        // for line properties.
+Tuplet_bracket::make_bracket (Grob *me, // for line properties.
                              Axis protusion_axis,
                              Offset dz,
                              Drul_array<Real> height,
@@ -253,90 +402,121 @@ Tuplet_bracket::make_bracket (Grob *me,  // for line properties.
                              Drul_array<Real> flare,
                              Drul_array<Real> shorten)
 {
-  Drul_array<Offset> corners (Offset (0,0), dz);
-  
+  Drul_array<Offset> corners (Offset (0, 0), dz);
+
   Real length = dz.length ();
   Drul_array<Offset> gap_corners;
 
-  
   Axis bracket_axis = other_axis (protusion_axis);
 
   Drul_array<Offset> straight_corners = corners;
 
   Direction d = LEFT;
-  do {
-    straight_corners[d] += - d * shorten[d] /length * dz;
-  } while (flip (&d) != LEFT);
-
-
-  if (gap.is_empty())
-    gap = Interval (0,0);
-  do {
+  do
+    straight_corners[d] += -d * shorten[d] / length * dz;
+  while (flip (&d) != LEFT)
+    ;
+
+  if (gap.is_empty ())
+    gap = Interval (0, 0);
+  do
     gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
-  } while (flip (&d) != LEFT);
+  while (flip (&d) != LEFT)
+    ;
 
   Drul_array<Offset> flare_corners = straight_corners;
-  do {
-    flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
-    flare_corners[d][protusion_axis] += height[d];
-    straight_corners[d][bracket_axis] += - d * flare[d];
-  } while (flip (&d) != LEFT);
+  do
+    {
+      flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
+      flare_corners[d][protusion_axis] += height[d];
+      straight_corners[d][bracket_axis] += -d * flare[d];
+    }
+  while (flip (&d) != LEFT);
 
   Stencil m;
-  do {
-    m.add_stencil (Line_interface::line (me, straight_corners[d],
-                                        gap_corners[d]));
-    
-    m.add_stencil (Line_interface::line (me,  straight_corners[d],
-                                        flare_corners[d]));
-  } while (flip (&d) != LEFT);
+  do
+    {
+      m.add_stencil (Line_interface::line (me, straight_corners[d],
+                                          gap_corners[d]));
 
-  return m;  
+      m.add_stencil (Line_interface::line (me, straight_corners[d],
+                                          flare_corners[d]));
+    }
+  while (flip (&d) != LEFT);
+
+  return m;
 }
 
+void
+Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
+{
+  extract_grob_set (me, "note-columns", columns);
+  vsize l = 0;
+  while (l < columns.size () && Note_column::has_rests (columns[l]))
+    l++;
+
+  vsize r = columns.size ();
+  while (r > l && Note_column::has_rests (columns[r-1]))
+    r--;
+
+  *left = *right = 0;
+
+  if (l < r)
+    {
+      *left = columns[l];
+      *right = columns[r-1];
+    }
+}
 
 /*
   use first -> last note for slope, and then correct for disturbing
   notes in between.  */
 void
-Tuplet_bracket::calc_position_and_height (Grob*me,Real *offset, Real * dy) 
+Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
 {
-  Link_array<Grob> columns=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
+
+  extract_grob_set (me, "note-columns", columns);
+  extract_grob_set (me, "tuplets", tuplets);
 
+  Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
+  commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
+  if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
+    commony = st->common_refpoint (commony, Y_AXIS);
 
-  SCM cols = me->get_property ("note-columns");
-  Grob * commony = common_refpoint_of_list (cols, me, Y_AXIS);
-  Grob * commonx = common_refpoint_of_list (cols, me, X_AXIS);  
+  Grob *commonx = get_common_x (me);
+  commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
 
   Interval staff;
-  if (Grob * st = Staff_symbol_referencer::get_staff_symbol (me))
-    staff = st->extent (commony, Y_AXIS);
+  if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
+    {
+      Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
+      if  (pad >= 0.0)
+       {
+         staff = st->extent (commony, Y_AXIS);
+         staff.widen (pad);
+       }
+    }
   
   Direction dir = get_grob_direction (me);
 
   /*
     Use outer non-rest columns to determine slope
-   */
-  int l = 0;
-  while (l <columns.size () && Note_column::has_rests (columns[l]))
-    l ++;
-
-  int r = columns.size ()- 1;
-  while (r >= l && Note_column::has_rests (columns[r]))
-    r--;
-  
-  if (l < r)
+  */
+  Grob *left_col = 0;
+  Grob *right_col = 0;
+  get_bounds (me, &left_col, &right_col);
+  if (left_col && right_col)
     {
-      Interval rv =columns[r]->extent (commony, Y_AXIS);
-      Interval lv =columns[l]->extent (commony, Y_AXIS);
+      Interval rv = right_col->extent (commony, Y_AXIS);
+      Interval lv = left_col->extent (commony, Y_AXIS);
       rv.unite (staff);
       lv.unite (staff);
-      Real graphical_dy =  rv[dir] - lv[dir];
+      Real graphical_dy = rv[dir] - lv[dir];
+
+      Slice ls = Note_column::head_positions_interval (left_col);
+      Slice rs = Note_column::head_positions_interval (right_col);
 
-      Slice ls = Note_column::head_positions_interval (columns[l]);
-      Slice rs = Note_column::head_positions_interval (columns[r]);
-      
       Interval musical_dy;
       musical_dy[UP] = rs[UP] - ls[UP];
       musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
@@ -348,175 +528,169 @@ Tuplet_bracket::calc_position_and_height (Grob*me,Real *offset, Real * dy)
        *dy = graphical_dy;
     }
   else
-    * dy = 0;
+    *dy = 0;
 
+  *offset = -dir * infinity_f;
 
-  *offset = - dir * infinity_f;
+  Item *lgr = get_x_bound_item (me, LEFT, dir);
+  Item *rgr = get_x_bound_item (me, RIGHT, dir);
+  Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
+  Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
 
-  if (!columns.size ())
-    return;
+  vector<Offset> points;
+  points.push_back (Offset (x0 - x0, staff[dir]));
+  points.push_back (Offset (x1 - x0, staff[dir]));
 
+  for (vsize i = 0; i < columns.size (); i++)
+    {
+      Interval note_ext = columns[i]->extent (commony, Y_AXIS);
+      Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
 
-  
-  Grob * lgr = get_x_bound_grob (columns[0], dir);
-  Grob * rgr = get_x_bound_grob (columns.top (), dir);  
-  Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
-  Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
+      Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
+      points.push_back (Offset (x, notey));
+    }
 
   /*
-    Slope.
+    This is a slight hack. We compute two encompass points from the
+    bbox of the smaller tuplets.
+
+    We assume that the smaller bracket is 1.0 space high.
   */
-  Real factor = columns.size () > 1 ? 1/ (x1 - x0) : 1.0;
-  
-  for (int i = 0; i < columns.size ();  i++)
+  Real ss = Staff_symbol_referencer::staff_space (me);
+  for (vsize i = 0; i < tuplets.size (); i++)
     {
-      Interval note_ext =columns[i]->extent (commony, Y_AXIS);
-      note_ext.unite (staff);
-      Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
+      Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
+      Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
+
+      Direction d = LEFT;
+      Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
+
       
-      Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
-      Real tuplety =  *dy * x * factor;
+      Real other_dy = positions[RIGHT] - positions[LEFT];
+
+      do
+       {
+         Real y
+           = tuplet_y.linear_combination (d * sign (other_dy));
+
+         /*
+           We don't take padding into account for nested tuplets.
+           the edges can come very close to the stems, likewise for
+           nested tuplets?
+         */
 
-      if (notey * dir > (*offset + tuplety) * dir)
-       *offset = notey - tuplety; 
+         points.push_back (Offset (tuplet_x[d] - x0, y));
+       }
+      while (flip (&d) != LEFT);
     }
 
-  // padding
-  *offset +=  scm_to_double (me->get_property ("padding")) *dir;
+  Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
+  for (vsize i = 0; i < points.size (); i++)
+    {
+      Real x = points[i][X_AXIS];
+      Real tuplety = (*dy) * x * factor;
+
+      if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
+       *offset = points[i][Y_AXIS] - tuplety;
+    }
+
+  *offset += scm_to_double (me->get_property ("padding")) * dir;
 
-  
   /*
     horizontal brackets should not collide with staff lines.
-    
-   */
-  Real ss= Staff_symbol_referencer::staff_space (me);
-  if (*dy == 0 && fabs (*offset) <  ss * Staff_symbol_referencer::staff_radius (me))
+
+    Kind of pointless since we put them outside the staff anyway, but
+    let's leave code for the future when possibly allow them to move
+    into the staff once again.
+  */
+  if (*dy == 0
+      && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
     {
       // quantize, then do collision check.
       *offset *= 2 / ss;
-      
+
       *offset = rint (*offset);
-      if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
+      if (Staff_symbol_referencer::on_line (me, (int) rint (*offset)))
        *offset += dir;
 
       *offset *= 0.5 * ss;
     }
-  
 }
 
 
-
-/*
-  We depend on the beams if there are any.
- */
-MAKE_SCHEME_CALLBACK (Tuplet_bracket,before_line_breaking,1);
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
 SCM
-Tuplet_bracket::before_line_breaking (SCM smob)
+Tuplet_bracket::calc_direction (SCM smob)
 {
   Grob *me = unsmob_grob (smob);
-  Link_array<Grob> columns=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
-
-
-  for (int i = columns.size (); i--;)
-    {
-      Grob * s =Note_column::get_stem (columns[i]);
-      Grob * b = s ? Stem::get_beam (s): 0;
-      if (b)
-       me->add_dependency (b);
-    }
-  return SCM_UNSPECIFIED;
+  Direction dir = Tuplet_bracket::get_default_dir (me);
+  return scm_from_int (dir);
 }
 
-MAKE_SCHEME_CALLBACK (Tuplet_bracket,after_line_breaking,1);
-
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
 SCM
-Tuplet_bracket::after_line_breaking (SCM smob)
+Tuplet_bracket::calc_positions (SCM smob)
 {
-  Grob * me = unsmob_grob (smob);
-  Link_array<Grob> columns=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Spanner *me = unsmob_spanner (smob);
+  extract_grob_set (me, "note-columns", columns);
 
-  if (!columns.size ())
-    {
-      me->suicide ();
-      return SCM_UNSPECIFIED;
-    }
-  if (dynamic_cast<Spanner*> (me)->is_broken ())
+  /*
+    Don't print if it doesn't span time.
+   */
+  if (robust_scm2moment (me->get_bound (LEFT)->get_column ()->get_property ("when"), Moment (0))
+      == robust_scm2moment (me->get_bound (RIGHT)->get_column ()->get_property ("when"), Moment (0)))
     {
-      me->warning (_("Killing tuplet bracket across linebreak."));
       me->suicide ();
-      return SCM_UNSPECIFIED;
+      return SCM_EOL;
     }
+
   
   Direction dir = get_grob_direction (me);
-  if (!dir)
-    {
-      dir = Tuplet_bracket::get_default_dir (me);
-      set_grob_direction (me, dir);
-    }
-  
   bool equally_long = false;
-  Grob * par_beam = parallel_beam (me, columns, &equally_long);
+  Grob *par_beam = parallel_beam (me, columns, &equally_long);
 
   /*
     We follow the beam only if there is one, and we are next to it.
-   */
-  Real dy, offset;
+  */
+  Real dy = 0.0;
+  Real offset = 0.0;
   if (!par_beam
       || get_grob_direction (par_beam) != dir)
-    {
-      calc_position_and_height (me,&offset,&dy);
-    }
+    calc_position_and_height (me, &offset, &dy);
   else
     {
-      SCM ps =  par_beam->get_property ("positions"); 
+      SCM ps = par_beam->get_property ("positions");
 
-      Real lp = scm_to_double (ly_car (ps));
-      Real rp = scm_to_double (ly_cdr (ps));
+      Real lp = scm_to_double (scm_car (ps));
+      Real rp = scm_to_double (scm_cdr (ps));
 
-      /*
-       duh. magic.
-       */
+      Real ss = Staff_symbol_referencer::staff_space (me);
+      
       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
-      dy = rp- lp;
+      dy = (rp - lp);
+
+      dy *= ss;
+      offset *= ss;    
     }
+
   
+  SCM x = scm_cons (scm_from_double (offset),
+                   scm_from_double (offset + dy));
   
-  SCM lp =  me->get_property ("left-position");
-  SCM rp = me->get_property ("right-position");  
-  
-  if (scm_is_number (lp) && !scm_is_number (rp))
-    {
-      rp = scm_make_real (scm_to_double (lp) + dy);
-    }
-  else if (scm_is_number (rp) && !scm_is_number (lp))
-    {
-      lp = scm_make_real (scm_to_double (rp) - dy);
-    }
-  else if (!scm_is_number (rp) && !scm_is_number (lp))
-    {
-      lp = scm_make_real (offset);
-      rp = scm_make_real (offset +dy);
-    }
-
-  me->set_property ("left-position", lp);
-  me->set_property ("right-position", rp);
-
-  return SCM_UNSPECIFIED;
+  return x;
 }
 
-
 /*
-  similar to beam ? 
- */
+  similar to beam ?
+*/
 Direction
-Tuplet_bracket::get_default_dir (Grob*me)
+Tuplet_bracket::get_default_dir (Grob *me)
 {
-  Drul_array<int> dirs (0,0);  
-  for (SCM s = me->get_property ("note-columns"); ly_c_pair_p (s); s = ly_cdr (s))
+  Drul_array<int> dirs (0, 0);
+  extract_grob_set (me, "note-columns", columns);
+  for (vsize i = 0; i < columns.size (); i++)
     {
-      Grob * nc = unsmob_grob (ly_car (s));
+      Grob *nc = columns[i];
       Direction d = Note_column::dir (nc);
       if (d)
        dirs[d]++;
@@ -526,20 +700,45 @@ Tuplet_bracket::get_default_dir (Grob*me)
 }
 
 void
-Tuplet_bracket::add_column (Grob*me, Item*n)
+Tuplet_bracket::add_column (Grob *me, Item *n)
 {
   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
-  me->add_dependency (n);
-
-  add_bound_item (dynamic_cast<Spanner*> (me), n);
+  add_bound_item (dynamic_cast<Spanner *> (me), n);
 }
 
+void
+Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
+{
+  Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
+}
 
+ADD_INTERFACE (Tuplet_bracket,
+              "tuplet-bracket-interface",
+              "A bracket with a number in the middle, used for tuplets. "
+              "When the bracket spans  a line break, the value of "
+              "@code{break-overshoot} determines how far it extends "
+              "beyond the staff. "
+              "At a line break, the markups in the @code{edge-text} are printed "
+              "at the edges. ",
+
+
+              /* properties */
+              "bracket-flare "
+              "bracket-visibility "
+              "break-overshoot "
+              "connect-to-neighbor "
+              "control-points "
+              "direction "
+              "edge-height "
+              "edge-text "
+              "gap "
+              "positions "
+              "note-columns "
+              "padding "
+              "tuplet-number "
+              "shorten-pair "
+              "staff-padding "
+              "thickness "
+              "tuplets ");
 
 
-
-
-ADD_INTERFACE (Tuplet_bracket,"tuplet-bracket-interface",
-  "A bracket with a number in the middle, used for tuplets.",
-  "note-columns bracket-flare edge-height shorten-pair padding left-position right-position bracket-visibility number-visibility thickness direction");
-