]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/tuplet-bracket.cc
Replace internal_get_property with get_property where possible
[lilypond.git] / lily / tuplet-bracket.cc
index bb397a27fa34cec23ade1098f6fcd23ac67a90c7..0eca57010ceb765a268f03bbd581a3926d37cc6d 100644 (file)
@@ -1,9 +1,21 @@
 /*
-  plet-spanner.cc -- implement Tuplet_bracket
+  This file is part of LilyPond, the GNU music typesetter.
 
-  source file of the GNU LilyPond music typesetter
+  Copyright (C) 1997--2014 Jan Nieuwenhuizen <janneke@gnu.org>
+  Han-Wen Nienhuys <hanwen@xs4all.nl>
 
-  (c)  1997--2002 Jan Nieuwenhuizen <janneke@gnu.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/>.
 */
 
 /*
   - 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 "axis-group-interface.hh"
+#include "line-interface.hh"
 #include "beam.hh"
-#include "box.hh"
-#include "debug.hh"
+#include "warn.hh"
+#include "output-def.hh"
 #include "font-interface.hh"
-#include "molecule.hh"
-#include "paper-def.hh"
-#include "text-item.hh"
-#include "tuplet-bracket.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 "skyline.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::stem_l (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::stem_l (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->get_property (sym), zero);
+  pair[xdir] = 0.0;
 
+  me->set_property (sym, ly_interval2scm (pair));
+}
 
-Grob*
-Tuplet_bracket::parallel_beam (Grob *me, Link_array<Grob> cols, bool *equally_long)
+/*
+  Return beam that encompasses the span of the tuplet bracket.
+*/
+Grob *
+Tuplet_bracket::parallel_beam (Grob *me_grob, vector<Grob *> const &cols,
+                               bool *equally_long)
 {
-  /*
-    ugh: code dup. 
-  */
-  Grob *s1 = Note_column::stem_l (cols[0]); 
-  Grob *s2 = Note_column::stem_l (cols.top());    
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
+
+  Item *left = me->get_bound (LEFT);
+  Item *right = me->get_bound (RIGHT);
+  if (!left || left->break_status_dir ()
+      || !right || right->break_status_dir ())
+    return 0;
+
+  Drul_array<Grob *> stems (Note_column::get_stem (cols[0]),
+                            Note_column::get_stem (cols.back ()));
+
+  if (!stems[RIGHT]
+      || !stems[LEFT]
+      || (dynamic_cast<Item *> (stems[RIGHT])->get_column ()
+          != me->get_bound (RIGHT)->get_column ()))
+    return 0;
 
-  Grob*b1 = s1 ? Stem::beam_l (s1) : 0;
-  Grob*b2 = s2 ? Stem::beam_l (s2) : 0;
-  
-  Spanner*sp = dynamic_cast<Spanner*> (me);  
+  Drul_array<Grob *> beams;
+  for (LEFT_and_RIGHT (d))
+    beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
 
-  *equally_long= false;
-  if (! ( b1 && (b1 == b2) && !sp->broken_b() ))
+  *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");
+      *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 = Spanner::unsmob (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));
+
+  Drul_array<bool> connect_to_other (false, false);
+  for (LEFT_and_RIGHT (d))
+    {
+      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 ());
+    }
+
+  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);
 
-  Link_array<Grob> beam_stems = Pointer_group_interface__extract_grobs
-    (b1, (Grob*)0, "stems");
+  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);
 
-  
-  *equally_long = (beam_stems[0] == s1 && beam_stems.top() == s2);
-  return b1;
+  return commonx;
 }
 
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_x_positions, 1)
+SCM
+Tuplet_bracket::calc_x_positions (SCM smob)
+{
+  Spanner *me = Spanner::unsmob (smob);
+  extract_grob_set (me, "note-columns", columns);
+
+  Grob *commonx = get_common_x (me);
+  Direction dir = get_grob_direction (me);
+
+  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));
+
+  Interval x_span;
+  for (LEFT_and_RIGHT (d))
+    {
+      x_span[d] = Axis_group_interface::generic_bound_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] = (bounds[d]->break_status_dir ()
+                         ? Axis_group_interface::generic_bound_extent (bounds[d], commonx, X_AXIS)[-d]
+                         : robust_relative_extent (bounds[d], commonx, X_AXIS)[-d])
+                        - 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.
+          */
+          Real padding
+            = robust_scm2double (me->get_property ("full-length-padding"), 1.0);
+
+          if (bounds[d]->break_status_dir ())
+            padding = 0.0;
+
+          Real coord = bounds[d]->relative_coordinate (commonx, X_AXIS);
+          if (to_boolean (me->get_property ("full-length-to-extent")))
+            coord = robust_relative_extent (bounds[d], commonx, X_AXIS)[LEFT];
+
+          coord = max (coord, x_span[LEFT]);
+
+          x_span[d] = coord - padding;
+        }
+    }
+
+  return ly_interval2scm (x_span - me->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS));
+}
 
 /*
   TODO:
 
   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,brew_molecule,1);
+*/
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
 SCM
-Tuplet_bracket::brew_molecule (SCM smob) 
+Tuplet_bracket::print (SCM smob)
 {
-  Grob *me= unsmob_grob (smob);
-  Molecule  mol;
-  Link_array<Grob> column_arr=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
-
-  if (!column_arr.size ())
-    return mol.smobbed_copy ();
+  Spanner *me = Spanner::unsmob (smob);
+  Stencil mol;
 
+  extract_grob_set (me, "note-columns", columns);
   bool equally_long = false;
-  Grob * par_beam = parallel_beam (me, column_arr, &equally_long);
-
-  Spanner*sp = dynamic_cast<Spanner*> (me);  
-
-  bool bracket_visibility = !(par_beam && equally_long);
-  bool number_visibility = true;
+  Grob *par_beam = parallel_beam (me, columns, &equally_long);
 
+  bool bracket_visibility = !(par_beam && equally_long); // Flag, print/don't print tuplet bracket.
   /*
-    Fixme: the type of this prop is sucky.
-   */
-  SCM bracket = me->get_grob_property ("bracket-visibility");
-  if (gh_boolean_p (bracket))
-    {
-      bracket_visibility = gh_scm2bool (bracket);
-    }
-  else if (bracket == ly_symbol2scm ("if-no-beam"))
+    FIXME: The type of this prop is sucky.
+  */
+  SCM bracket_vis_prop = me->get_property ("bracket-visibility");
+  bool bracket_prop = ly_scm2bool (bracket_vis_prop); // Flag, user has set bracket-visibility prop.
+  bool bracket = (bracket_vis_prop == ly_symbol2scm ("if-no-beam"));
+  if (scm_is_bool (bracket_vis_prop))
+    bracket_visibility = bracket_prop;
+  else if (bracket)
     bracket_visibility = !par_beam;
 
-  SCM numb = me->get_grob_property ("number-visibility");  
-  if (gh_boolean_p (numb))
+  /*
+    Don't print a tuplet bracket and number if
+    no X or Y positions were calculated.
+  */
+  SCM scm_x_span = me->get_property ("X-positions");
+  SCM scm_positions = me->get_property ("positions");
+  if (!scm_is_pair (scm_x_span) || !scm_is_pair (scm_positions))
     {
-      number_visibility = gh_scm2bool (numb);
+      me->suicide ();
+      return SCM_EOL;
     }
-  else if (numb == ly_symbol2scm ("if-no-beam"))
-    number_visibility = !par_beam;
-       
-  Grob * commonx = column_arr[0]->common_refpoint (column_arr.top (),X_AXIS);
-  Direction dir = Directional_element_interface::get (me);
-
-  Grob * lgr = get_x_bound_grob (column_arr[0], dir);
-  Grob * rgr = get_x_bound_grob (column_arr.top(), dir);  
-  Real x0 = lgr->extent (commonx,X_AXIS)[LEFT];
-  Real x1 = rgr->extent (commonx,X_AXIS)[RIGHT];
-
-  Real w = x1 -x0;
-
-  Real ly = gh_scm2double (me->get_grob_property ("left-position"));
-  Real ry = gh_scm2double (me->get_grob_property ("right-position"));  
-  SCM number = me->get_grob_property ("text");
-  
-  if (gh_string_p (number) && number_visibility)
+  /*  if the tuplet does not span any time, i.e. a single-note tuplet, hide
+      the bracket, but still let the number be displayed.
+      Only do this if the user has not explicitly specified bracket-visibility = #t.
+  */
+  if (!to_boolean (bracket_vis_prop)
+      && (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))))
+    bracket_visibility = false;
+
+  Interval x_span = robust_scm2interval (scm_x_span, Interval (0.0, 0.0));
+  Interval positions = robust_scm2interval (scm_positions, Interval (0.0, 0.0));
+
+  Drul_array<Offset> points;
+  for (LEFT_and_RIGHT (d))
+    points[d] = Offset (x_span[d], positions[d]);
+
+  Output_def *pap = me->layout ();
+
+  Grob *number_grob = Grob::unsmob (me->get_object ("tuplet-number"));
+
+  /*
+    Don't print the bracket when it would be smaller than the number.
+    ...Unless the user has coded bracket-visibility = #t, that is.
+  */
+  Real gap = 0.;
+  if (bracket_visibility && number_grob)
     {
-      SCM properties = Font_interface::font_alist_chain (me);
-      Molecule num = Text_item::text2molecule (me, number, properties);
-      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_molecule (num);
+      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_prop)
+            bracket_visibility = false;
+        }
     }
-      
-  if (bracket_visibility)      
+
+  if (bracket_visibility)
     {
-      Real  lt =  me->paper_l ()->get_var ("linethickness");
-  
-      SCM thick = me->get_grob_property ("thickness");
-      if (gh_number_p (thick))
-       lt *= gh_scm2double (thick);
-      
-      SCM gap = me->get_grob_property ("gap");
-
-      Real prot_size = 0.7;    // magic.
-
-      Molecule brack = make_bracket (Y_AXIS,
-                                    w, ry-ly, lt,
-                                    -prot_size*dir, -prot_size*dir,
-                                    gh_scm2double (gap),
-                                    0.0, 0.0);
-      mol.add_molecule (brack);
+      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));
+
+      for (LEFT_and_RIGHT (d))
+        {
+          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 = Stencil::unsmob (t);
+                      edge_text->translate_axis (x_span[d] - x_span[LEFT],
+                                                 X_AXIS);
+                      edge_stencils[d] = *edge_text;
+                    }
+                }
+            }
+        }
+
+      Stencil brack = make_bracket (me, Y_AXIS,
+                                    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);
+
+      for (LEFT_and_RIGHT (d))
+        {
+          if (!edge_stencils[d].is_empty ())
+            brack.add_stencil (edge_stencils[d]);
+        }
+
+      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 ();
 }
 
 /*
   should move to lookup?
- */
-Molecule
-Tuplet_bracket::make_bracket (Axis protusion_axis,
-                             Real dx, Real dy, Real thick, Real lprotrusion,
-                             Real rprotrusion, Real gap, Real left_widen,
-                             Real right_widen)
+
+  TODO: this will fail for very short (shorter than the flare)
+  brackets.
+*/
+Stencil
+Tuplet_bracket::make_bracket (Grob *me, // for line properties.
+                              Axis protrusion_axis,
+                              Offset dz,
+                              Drul_array<Real> height,
+                              Interval gap,
+                              Drul_array<Real> flare,
+                              Drul_array<Real> shorten)
 {
-  Real len = Offset (dx,dy).length ();
-  Real gapx = dx*  (gap /  len);
-  Real gapy = dy*  (gap /  len);
-  Axis other = other_axis (protusion_axis);
-
-  Molecule l1 = Lookup::line (thick, Offset(0,0),
-                             Offset ( (dx - gapx)/2, (dy - gapy)/2 ));
-  Molecule l2 = Lookup::line (thick, Offset((dx + gapx) / 2,(dy + gapy) / 2),
-                             
-                             Offset (dx,dy));
-
-  Offset protusion;
-  protusion[other] = left_widen;
-  protusion[protusion_axis] = lprotrusion;
-  
-  Molecule p1 = Lookup::line (thick, Offset(0,0), protusion);
-
-  protusion[other] = right_widen;
-  protusion[protusion_axis] = rprotrusion;
-  Molecule p2 = Lookup::line (thick, Offset(dx,dy),Offset(dx,dy) + protusion);  
-
-
-  Molecule m;
-  m.add_molecule (p1);
-  m.add_molecule (p2);
-  m.add_molecule (l1);
-  m.add_molecule (l2);
-
-  return m;  
-}
+  Drul_array<Offset> corners (Offset (0, 0), dz);
 
+  Real length = dz.length ();
+  Drul_array<Offset> gap_corners;
 
-/*
-  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) 
-{
-  Link_array<Grob> column_arr=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Axis bracket_axis = other_axis (protrusion_axis);
 
+  Drul_array<Offset> straight_corners = corners;
 
-  Grob * commony = me->common_refpoint (me->get_grob_property ("note-columns"), Y_AXIS);
-  Grob * commonx = me->common_refpoint (me->get_grob_property ("note-columns"), X_AXIS);  
-  
-  Direction dir = Directional_element_interface::get (me);
+  for (LEFT_and_RIGHT (d))
+    straight_corners[d] += -d * shorten[d] / length * dz;
 
-  /*
-    Use outer non-rest columns to determine slope
-   */
-  int l = 0;
-  while (l <column_arr.size () && Note_column::rest_b (column_arr[l]))
-    l ++;
-
-  int r = column_arr.size ()- 1;
-  while (r >= l && Note_column::rest_b (column_arr[r]))
-    r--;
-  
-  if (l < r)
+  if (!gap.is_empty ())
     {
-      *dy = column_arr[r]->extent (commony, Y_AXIS) [dir]
-       - column_arr[l]->extent (commony, Y_AXIS) [dir] ;
+      for (LEFT_and_RIGHT (d))
+        gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
     }
-  else
-    * dy = 0;
-
-
-  *offset = - dir * infinity_f;
 
-  if (!column_arr.size ())
-    return;
-
-
-  
-  Grob * lgr = get_x_bound_grob (column_arr[0], dir);
-  Grob * rgr = get_x_bound_grob (column_arr.top(), dir);  
-  Real x0 = lgr->extent (commonx,X_AXIS)[LEFT];
-  Real x1 = rgr->extent (commonx,X_AXIS)[RIGHT];
-
-
-    /*
-      Slope.
-    */
-  Real factor = column_arr.size () > 1 ? 1/ (x1 - x0) : 1.0;
-  
-  for (int i = 0; i < column_arr.size ();  i++)
+  Drul_array<Offset> flare_corners = straight_corners;
+  for (LEFT_and_RIGHT (d))
     {
-      Real notey = column_arr[i]->extent (commony, Y_AXIS)[dir] 
-       - me->relative_coordinate (commony, Y_AXIS);
+      flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
+      flare_corners[d][protrusion_axis] += height[d];
+      straight_corners[d][bracket_axis] += -d * flare[d];
+    }
 
-      Real x = column_arr[i]->relative_coordinate (commonx, X_AXIS) - x0;
-      Real tuplety =  *dy * x * factor;
+  Stencil m;
+  if (!gap.is_empty ())
+    for (LEFT_and_RIGHT (d))
+      m.add_stencil (Line_interface::line (me, straight_corners[d],
+                                           gap_corners[d]));
+  else
+    m.add_stencil (Line_interface::line (me, straight_corners[LEFT],
+                                         straight_corners[RIGHT]));
+
+  if (scm_is_number (me->get_property ("dash-fraction")))
+    me->set_property ("dash-fraction", scm_from_double (1.0));
+  for (LEFT_and_RIGHT (d))
+    m.add_stencil (Line_interface::line (me, straight_corners[d],
+                                         flare_corners[d]));
+  return m;
+}
 
-      if (notey * dir > (*offset + tuplety) * dir)
-       *offset = notey - tuplety; 
-    }
+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++;
 
-  // padding
-  *offset +=  gh_scm2double (me->get_grob_property ("padding")) *dir;
+  vsize r = columns.size ();
+  while (r > l && Note_column::has_rests (columns[r - 1]))
+    r--;
 
-  
-  /*
-    horizontal brackets should not collide with staff lines.
-   */
-  if (*dy == 0)
-    {
-      // quantize, then do collision check.
-      Real ss= Staff_symbol_referencer::staff_space (me);
-      *offset *= 2 / ss;
-      
-      *offset = rint (*offset);
-      if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
-       *offset += dir;
+  *left = *right = 0;
 
-      *offset *= 0.5 * ss;
+  if (l < r)
+    {
+      *left = columns[l];
+      *right = columns[r - 1];
     }
-  
 }
 
 /*
-  use first -> last note for slope,
-*/
+  use first -> last note for slope, and then correct for disturbing
+  notes in between.  */
 void
-Tuplet_bracket::calc_dy (Grob*me,Real * dy)
+Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
 {
-  Link_array<Grob> column_arr=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Spanner *me = dynamic_cast<Spanner *> (me_grob);
 
-  /*
-    ugh. refps.
-   */
-  Direction d = Directional_element_interface::get (me);
-  *dy = column_arr.top ()->extent (column_arr.top (), Y_AXIS) [d]
-    - column_arr[0]->extent (column_arr[0], Y_AXIS) [d];
-}
+  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);
+  Real my_offset = me->relative_coordinate (commony, Y_AXIS);
 
-/*
-  We depend on the beams if there are any.
- */
-MAKE_SCHEME_CALLBACK (Tuplet_bracket,before_line_breaking,1);
-SCM
-Tuplet_bracket::before_line_breaking (SCM smob)
-{
-  Grob *me = unsmob_grob (smob);
-  Link_array<Grob> column_arr=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Grob *commonx = get_common_x (me);
+  commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
 
+  Interval staff;
+  Grob *st = Staff_symbol_referencer::get_staff_symbol (me);
 
-  for (int i = column_arr.size(); i--;)
+  /* staff-padding doesn't work correctly on cross-staff tuplets
+     because it only considers one staff symbol. Until this works,
+     disable it. */
+  if (st && !to_boolean (me->get_property ("cross-staff")))
     {
-      Grob * s =Note_column::stem_l (column_arr[i]);
-      Grob * b = s ? Stem::beam_l (s): 0;
-      if (b)
-       me->add_dependency (b);
+      Real pad = robust_scm2double (me->get_property ("staff-padding"), -1.0);
+      if (pad >= 0.0)
+        {
+          staff = st->extent (commony, Y_AXIS) - my_offset;
+          staff.widen (pad);
+        }
     }
-  return SCM_UNDEFINED;
-}
-
-MAKE_SCHEME_CALLBACK (Tuplet_bracket,after_line_breaking,1);
 
-SCM
-Tuplet_bracket::after_line_breaking (SCM smob)
-{
-  Grob * me = unsmob_grob (smob);
-  Link_array<Grob> column_arr=
-    Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-columns");
+  Direction dir = get_grob_direction (me);
 
-  if (!column_arr.size ())
-    {
-      me->suicide ();
-      return SCM_UNSPECIFIED;
-    }
-  if (dynamic_cast<Spanner*> (me)->broken_b ())
+  bool equally_long = false;
+  Grob *par_beam = parallel_beam (me, columns, &equally_long);
+
+  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];
+  bool follow_beam = par_beam
+                     && get_grob_direction (par_beam) == dir
+                     && !Beam::is_knee (par_beam);
+
+  vector<Offset> points;
+  if (columns.size ()
+      && follow_beam
+      && Note_column::get_stem (columns[0])
+      && Note_column::get_stem (columns.back ()))
     {
-      me->warning ( "Tuplet_bracket was across linebreak. Farewell cruel world.");
-      me->suicide();
-      return SCM_UNSPECIFIED;
+      Drul_array<Grob *> stems (Note_column::get_stem (columns[0]),
+                                Note_column::get_stem (columns.back ()));
+
+      Interval poss;
+      for (LEFT_and_RIGHT (side))
+        {
+          // Trigger setting of stem lengths if necessary.
+          if (Grob *beam = Stem::get_beam (stems[side]))
+            (void) beam->get_property ("quantized-positions");
+          poss[side] = stems[side]->extent (stems[side], Y_AXIS)[get_grob_direction (stems[side])]
+                       + stems[side]->get_parent (Y_AXIS)->relative_coordinate (commony, Y_AXIS);
+        }
+
+      *dy = poss[RIGHT] - poss[LEFT];
+      points.push_back (Offset (stems[LEFT]->relative_coordinate (commonx, X_AXIS) - x0, poss[LEFT]));
+      points.push_back (Offset (stems[RIGHT]->relative_coordinate (commonx, X_AXIS) - x0, poss[RIGHT]));
     }
-  
-  Direction dir = Directional_element_interface::get (me);
-  if (!dir)
+  else
     {
-      dir = Tuplet_bracket::get_default_dir (me);
-      Directional_element_interface::set (me, dir);
+      /*
+        Use outer non-rest columns to determine slope
+      */
+      Grob *left_col = 0;
+      Grob *right_col = 0;
+      get_bounds (me, &left_col, &right_col);
+      if (left_col && right_col)
+        {
+          Interval rv = Note_column::cross_staff_extent (right_col, commony);
+          Interval lv = Note_column::cross_staff_extent (left_col, commony);
+          rv.unite (staff);
+          lv.unite (staff);
+
+          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);
+
+          Interval musical_dy;
+          musical_dy[UP] = rs[UP] - ls[UP];
+          musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
+          if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
+            *dy = 0.0;
+          else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
+            *dy = 0.0;
+          else
+            *dy = graphical_dy;
+        }
+      else
+        *dy = 0;
+
+      for (vsize i = 0; i < columns.size (); i++)
+        {
+          Interval note_ext = Note_column::cross_staff_extent (columns[i],
+                                                               commony);
+          Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
+
+          points.push_back (Offset (x, note_ext[dir]));
+        }
     }
-  
-  bool equally_long = false;
-  Grob * par_beam = parallel_beam (me, column_arr, &equally_long);
 
-  Real dy, offset;
-  if (!par_beam)
+  if (!follow_beam)
     {
-      calc_position_and_height (me,&offset,&dy);
+      points.push_back (Offset (x0 - x0, staff[dir]));
+      points.push_back (Offset (x1 - x0, staff[dir]));
     }
-  else
+
+  /*
+    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 ss = Staff_symbol_referencer::staff_space (me);
+  for (vsize i = 0; i < tuplets.size (); i++)
     {
-      SCM ps =  par_beam->get_grob_property ("positions"); 
+      Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
+      Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
 
-      Real lp = gh_scm2double (gh_car (ps));
-      Real rp = gh_scm2double (gh_cdr (ps));
+      if (!tuplets[i]->is_live ())
+        continue;
 
-      /*
-       duh. magic.
-       */
-      offset = lp + dir * (0.5 + gh_scm2double (me->get_grob_property ("padding")));
-      dy = rp- lp;
+      Drul_array<Real> positions
+        = robust_scm2interval (tuplets[i]->get_property ("positions"),
+                               Interval (0, 0));
+
+      Real other_dy = positions[RIGHT] - positions[LEFT];
+
+      for (LEFT_and_RIGHT (d))
+        {
+          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?
+          */
+
+          points.push_back (Offset (tuplet_x[d] - x0, y));
+        }
+
+      // Check for number-on-bracket collisions
+      Grob *number = Grob::unsmob (tuplets[i]->get_object ("tuplet-number"));
+      if (number)
+        points.push_back (Offset (number->extent (commonx, X_AXIS).center () - x0,
+                                  number->extent (commony, Y_AXIS)[dir]));
     }
-  
-  
-  SCM lp =  me->get_grob_property ("left-position");
-  SCM rp = me->get_grob_property ("right-position");  
-  
-  if (gh_number_p (lp) && !gh_number_p (rp))
+
+  if (to_boolean (me->get_property ("avoid-scripts"))
+      && !scm_is_number (me->get_property ("outside-staff-priority")))
     {
-      rp = gh_double2scm (gh_scm2double (lp) + dy);
+      extract_grob_set (me, "scripts", scripts);
+      for (vsize i = 0; i < scripts.size (); i++)
+        {
+          if (!scripts[i]->is_live ())
+            continue;
+          if (scm_is_number (scripts[i]->get_property ("outside-staff-priority")))
+            continue;
+
+          // assume that if a script is avoiding slurs, it should not get placed
+          // under a tuplet bracket
+          if (Grob::unsmob (scripts[i]->get_object ("slur")))
+            continue;
+
+          Interval script_x (scripts[i]->extent (commonx, X_AXIS));
+          Interval script_y (scripts[i]->extent (commony, Y_AXIS));
+
+          points.push_back (Offset (script_x.center () - x0,
+                                    script_y[dir]));
+        }
     }
-  else if (gh_number_p (rp) && !gh_number_p (lp))
+
+  *offset = -dir * infinity_f;
+  Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
+  for (vsize i = 0; i < points.size (); i++)
     {
-      lp = gh_double2scm (gh_scm2double (rp) - dy);
+      Real x = points[i][X_AXIS];
+      Real tuplety = (*dy) * x * factor + my_offset;
+
+      if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
+        *offset = points[i][Y_AXIS] - tuplety;
     }
-  else if (!gh_number_p (rp) && !gh_number_p (lp))
+
+  *offset += scm_to_double (me->get_property ("padding")) * dir;
+
+  /*
+    horizontal brackets should not collide with staff lines.
+
+    This doesn't seem to support cross-staff tuplets atm.
+  */
+  if (*dy == 0)
     {
-      lp = gh_double2scm (offset);
-      rp = gh_double2scm (offset +dy);
-    }
+      // quantize, then do collision check.
+      *offset /= 0.5 * ss;
 
-  me->set_grob_property ("left-position", lp);
-  me->set_grob_property ("right-position", rp);
+      Interval staff_span = Staff_symbol_referencer::staff_span (me);
+      if (staff_span.contains (*offset))
+        {
+          *offset = rint (*offset);
+          if (Staff_symbol_referencer::on_line (me, int (*offset)))
+            *offset += dir;
+        }
 
-  return SCM_UNSPECIFIED;
+      *offset *= 0.5 * ss;
+    }
 }
 
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
+SCM
+Tuplet_bracket::calc_direction (SCM smob)
+{
+  Grob *me = Grob::unsmob (smob);
+  Direction dir = Tuplet_bracket::get_default_dir (me);
+  return scm_from_int (dir);
+}
+
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
+SCM
+Tuplet_bracket::calc_positions (SCM smob)
+{
+  Spanner *me = Spanner::unsmob (smob);
+
+  Real dy = 0.0;
+  Real offset = 0.0;
+  calc_position_and_height (me, &offset, &dy);
+
+  SCM x = scm_cons (scm_from_double (offset),
+                    scm_from_double (offset + dy));
+
+  return x;
+}
 
 /*
-  similar to slur.
- */
+  similar to beam ?
+*/
 Direction
-Tuplet_bracket::get_default_dir (Grob*me)
+Tuplet_bracket::get_default_dir (Grob *me)
 {
-  Direction d = UP;
-  for (SCM s = me->get_grob_property ("note-columns"); gh_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));
-      if (Note_column::dir (nc) < 0) 
-       {
-         d = DOWN;
-         break;
-       }
+      Grob *nc = columns[i];
+      if (Note_column::has_rests (nc))
+        continue;
+      Direction d = Note_column::dir (nc);
+      if (d)
+        dirs[d]++;
     }
-  return d;
+
+  if (dirs[UP] == dirs[DOWN])
+    {
+      if (dirs[UP] == 0)
+        return UP;
+      Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
+      if (!staff)
+        return UP;
+      Interval staff_extent = staff->extent (staff, Y_AXIS);
+      Interval extremal_positions;
+      extremal_positions.set_empty ();
+      for (vsize i = 0; i < columns.size (); i++)
+        {
+          Direction d = Note_column::dir (columns[i]);
+          extremal_positions[d] = minmax (d, 1.0 * Note_column::head_positions_interval (columns[i])[d], extremal_positions[d]);
+        }
+      for (LEFT_and_RIGHT (d))
+        extremal_positions[d] = -d * (staff_extent[d] - extremal_positions[d]);
+
+      return extremal_positions[UP] <= extremal_positions[DOWN] ? UP : DOWN;
+    }
+
+  return dirs[UP] > dirs[DOWN] ? UP : DOWN;
 }
 
 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_script (Grob *me, Item *s)
+{
+  Pointer_group_interface::add_grob (me, ly_symbol2scm ("scripts"), s);
+}
 
-bool
-Tuplet_bracket::has_interface (Grob*me)
+void
+Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
 {
-  return me->has_interface (ly_symbol2scm ("tuplet-bracket-interface"));
+  Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
 }
 
+MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_cross_staff, 1);
+SCM
+Tuplet_bracket::calc_cross_staff (SCM smob)
+{
+  Grob *me = Grob::unsmob (smob);
+  extract_grob_set (me, "note-columns", cols);
+  extract_grob_set (me, "tuplets", tuplets);
+
+  Grob *commony = common_refpoint_of_array (cols, 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);
+  if (me->check_cross_staff (commony))
+    return SCM_BOOL_T;
 
+  bool equally_long = false;
+  Grob *par_beam = parallel_beam (me, cols, &equally_long);
 
+  if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
+    return SCM_BOOL_T;
 
-ADD_INTERFACE (Tuplet_bracket,"tuplet-bracket-interface",
-  "A bracket with a number in the middle, used for tuplets.",
-  "note-columns padding gap left-position right-position bracket-visibility number-visibility thickness direction");
+  for (vsize i = 0; i < cols.size (); i++)
+    {
+      Grob *stem = Grob::unsmob (cols[i]->get_object ("stem"));
+      if (stem && to_boolean (stem->get_property ("cross-staff")))
+        return SCM_BOOL_T;
+    }
+
+  return SCM_BOOL_F;
+}
 
+ADD_INTERFACE (Tuplet_bracket,
+               "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 */
+               "avoid-scripts "
+               "bracket-flare "
+               "bracket-visibility "
+               "break-overshoot "
+               "connect-to-neighbor "
+               "direction "
+               "edge-height "
+               "edge-text "
+               "full-length-padding "
+               "full-length-to-extent "
+               "gap "
+               "positions "
+               "note-columns "
+               "padding "
+               "tuplet-number "
+               "shorten-pair "
+               "staff-padding "
+               "thickness "
+               "tuplets "
+               "X-positions "
+              );