]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/align-interface.cc
resolve merge
[lilypond.git] / lily / align-interface.cc
index 216323644f62dba718dc1c9c8b6139df65149265..2fd49f5162a2547c57606d76a5ddd4d0386e5cfd 100644 (file)
@@ -1,9 +1,20 @@
 /*
-  align-interface.cc -- implement Align_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--2009 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 "align-interface.hh"
@@ -12,6 +23,8 @@
 #include "hara-kiri-group-spanner.hh"
 #include "international.hh"
 #include "item.hh"
+#include "page-layout-problem.hh"
+#include "paper-book.hh"
 #include "paper-column.hh"
 #include "pointer-group-interface.hh"
 #include "spanner.hh"
 #include "system.hh"
 #include "warn.hh"
 
-/*
-  TODO: for vertical spacing, should also include a rod & spring
-  scheme of sorts into this: the alignment should default to a certain
-  distance between element refpoints, unless bbox force a bigger
-  distance.
- */
 
-MAKE_SCHEME_CALLBACK (Align_interface, calc_positioning_done, 1);
+MAKE_SCHEME_CALLBACK (Align_interface, align_to_minimum_distances, 1);
 SCM
-Align_interface::calc_positioning_done (SCM smob)
+Align_interface::align_to_minimum_distances (SCM smob)
 {
   Grob *me = unsmob_grob (smob);
 
@@ -37,53 +44,30 @@ Align_interface::calc_positioning_done (SCM smob)
   SCM axis = scm_car (me->get_property ("axes"));
   Axis ax = Axis (scm_to_int (axis));
 
-  Align_interface::align_elements_to_extents (me, ax);
+  Align_interface::align_elements_to_minimum_distances (me, ax);
 
   return SCM_BOOL_T;
 }
 
-/*
-  TODO: This belongs to the old two-pass spacing. Delete me.
-*/
-MAKE_SCHEME_CALLBACK (Align_interface, stretch_after_break, 1)
+MAKE_SCHEME_CALLBACK (Align_interface, align_to_ideal_distances, 1);
 SCM
-Align_interface::stretch_after_break (SCM grob)
+Align_interface::align_to_ideal_distances (SCM smob)
 {
-  Grob *me = unsmob_grob (grob);
+  Grob *me = unsmob_grob (smob);
 
-  Spanner *me_spanner = dynamic_cast<Spanner *> (me);
-  extract_grob_set (me, "elements", elems);
+  me->set_property ("positioning-done", SCM_BOOL_T);
 
-  if (me_spanner && elems.size ())
-    {
-      Grob *common = common_refpoint_of_array (elems, me, Y_AXIS);
-
-      /* force position callbacks */
-      for (vsize i = 0; i < elems.size (); i++)
-       elems[i]->relative_coordinate (common, Y_AXIS);
-
-      SCM details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
-      SCM extra_space_handle = scm_assoc (ly_symbol2scm ("fixed-alignment-extra-space"), details);
-      
-      Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
-                                           ? scm_cdr (extra_space_handle)
-                                           : SCM_EOL,
-                                           0.0);
-
-      Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
-                                              DOWN);
-      Real delta  = extra_space / elems.size () * stacking_dir;
-      for (vsize i = 0; i < elems.size (); i++)
-       elems[i]->translate_axis (i * delta, Y_AXIS);
-    }
-  
-  return SCM_UNSPECIFIED;
+  Align_interface::align_elements_to_ideal_distances (me);
+
+  return SCM_BOOL_T;
 }
 
 /* for each grob, find its upper and lower skylines. If the grob has
    an empty extent, delete it from the list instead. If the extent is
    non-empty but there is no skyline available (or pure is true), just
    create a flat skyline from the bounding box */
+// TODO(jneem): the pure and non-pure parts seem to share very little
+// code. Split them into 2 functions, perhaps?
 static void
 get_skylines (Grob *me,
              vector<Grob*> *const elements,
@@ -106,23 +90,6 @@ get_skylines (Grob *me,
          if (skys)
            skylines = *skys;
 
-         /* this is perhaps an abuse of minimum-?-extent: maybe we should create
-            another property? But it seems that the only (current) use of
-            minimum-Y-extent is to separate vertically-aligned elements */
-         SCM min_extent = g->get_property (a == X_AXIS
-                                           ? ly_symbol2scm ("minimum-X-extent")
-                                           : ly_symbol2scm ("minimum-Y-extent"));
-
-         if (is_number_pair (min_extent))
-           {
-             Box b;
-             Interval other_extent = g->extent (other_common, other_axis (a));
-             b[a] = ly_scm2interval (min_extent);
-             b[other_axis (a)] = other_extent;
-             if (!other_extent.is_empty ())
-               skylines.insert (b, 0, other_axis (a));
-           }
-
          /* This skyline was calculated relative to the grob g. In order to compare it to
             skylines belonging to other grobs, we need to shift it so that it is relative
             to the common reference. */
@@ -133,11 +100,37 @@ get_skylines (Grob *me,
        {
          assert (a == Y_AXIS);
          Interval extent = g->pure_height (g, start, end);
+
+         // This is a hack to get better accuracy on the pure-height of VerticalAlignment.
+         // It's quite common for a treble clef to be the highest element of one system
+         // and for a low note (or lyrics) to be the lowest note on another. The two will
+         // never collide, but the pure-height stuff only works with bounding boxes, so it
+         // doesn't know that. The result is a significant over-estimation of the pure-height,
+         // especially on systems with many staves. To correct for this, we build a skyline
+         // in two parts: the part we did above contains most of the grobs (note-heads, etc.)
+         // while the bit we're about to do only contains the breakable grobs at the beginning
+         // of the system. This way, the tall treble clefs are only compared with the treble
+         // clefs of the other staff and they will be ignored if the staff above is, for example,
+         // lyrics.
+         if (Axis_group_interface::has_interface (g)
+             && !Hara_kiri_group_spanner::request_suicide (g, start, end))
+           {
+             extent = Axis_group_interface::rest_of_line_pure_height (g, start, end);
+             Interval begin_of_line_extent = Axis_group_interface::begin_of_line_pure_height (g, start);
+             if (!begin_of_line_extent.is_empty ())
+               {
+                 Box b;
+                 b[a] = begin_of_line_extent;
+                 b[other_axis (a)] = Interval (-infinity_f, -1);
+                 skylines.insert (b, 0, other_axis (a));
+               }
+           }
+
          if (!extent.is_empty ())
            {
              Box b;
              b[a] = extent;
-             b[other_axis (a)] = Interval (-infinity_f, infinity_f);
+             b[other_axis (a)] = Interval (0, infinity_f);
              skylines.insert (b, 0, other_axis (a));
            }
        }
@@ -151,76 +144,130 @@ get_skylines (Grob *me,
 }
 
 vector<Real>
-Align_interface::get_extents_aligned_translates (Grob *me,
-                                                vector<Grob*> const &all_grobs,
-                                                Axis a,
-                                                bool pure, int start, int end)
+Align_interface::get_minimum_translations (Grob *me,
+                                          vector<Grob*> const &all_grobs,
+                                          Axis a)
 {
-  Spanner *me_spanner = dynamic_cast<Spanner *> (me);
+  return internal_get_minimum_translations (me, all_grobs, a, true, false, 0, 0);
+}
 
+vector<Real>
+Align_interface::get_pure_minimum_translations (Grob *me,
+                                               vector<Grob*> const &all_grobs,
+                                               Axis a, int start, int end)
+{
+  return internal_get_minimum_translations (me, all_grobs, a, true, true, start, end);
+}
 
-  SCM line_break_details = SCM_EOL;
-  if (a == Y_AXIS && me_spanner)
-    {
-      if (pure)
-       line_break_details = get_root_system (me)->column (start)->get_property ("line-break-system-details");
-      else
-       line_break_details = me_spanner->get_bound (LEFT)->get_property ("line-break-system-details");
+vector<Real>
+Align_interface::get_minimum_translations_without_min_dist (Grob *me,
+                                                           vector<Grob*> const &all_grobs,
+                                                           Axis a)
+{
+  return internal_get_minimum_translations (me, all_grobs, a, false, false, 0, 0);
+}
 
-      if (!me->get_system () && !pure)
-       me->programming_error ("vertical alignment called before line-breaking");
-    }
+// If include_fixed_spacing is false, the only constraints that will be measured
+// here are those that result from collisions (+ padding) and minimum-distance
+// between adjacent staves.
+// If include_fixed_spacing is true, constraints from line-break-system-details,
+// basic-distance+stretchable=0, and staff-staff-spacing of spaceable staves
+// with loose lines in between, are included as well.
+// - If you want to find the minimum height of a system, include_fixed_spacing should be true.
+// - If you're going to actually lay out the page, then it should be false (or
+//   else centered dynamics will break when there is a fixed alignment).
+vector<Real>
+Align_interface::internal_get_minimum_translations (Grob *me,
+                                                   vector<Grob*> const &all_grobs,
+                                                   Axis a,
+                                                   bool include_fixed_spacing,
+                                                   bool pure, int start, int end)
+{
+  if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
+    me->programming_error ("vertical alignment called before line-breaking");
+
+  // If include_fixed_spacing is true, we look at things like system-system-spacing
+  // and alignment-distances, which only make sense for the toplevel VerticalAlignment.
+  // If we aren't toplevel, we're working on something like BassFigureAlignment
+  // and so we definitely don't want to include alignment-distances!
+  if (!dynamic_cast<System*> (me->get_parent (Y_AXIS)))
+    include_fixed_spacing = false;
   
   Direction stacking_dir = robust_scm2dir (me->get_property ("stacking-dir"),
                                           DOWN);
-
   vector<Grob*> elems (all_grobs); // writable copy
   vector<Skyline_pair> skylines;
 
   get_skylines (me, &elems, a, pure, start, end, &skylines);
 
   Real where = 0;
-  /* TODO: extra-space stuff belongs to two-pass spacing. Delete me */
-  SCM extra_space_handle = scm_assq (ly_symbol2scm ("alignment-extra-space"), line_break_details);
-  Real extra_space = robust_scm2double (scm_is_pair (extra_space_handle)
-                                       ? scm_cdr (extra_space_handle)
-                                       : SCM_EOL,
-                                       0.0);
-
-  Real padding = robust_scm2double (me->get_property ("padding"), 0.0);
+  Real default_padding = robust_scm2double (me->get_property ("padding"), 0.0);
   vector<Real> translates;
   Skyline down_skyline (stacking_dir);
+  Real last_spaceable_element_pos = 0;
+  Grob *last_spaceable_element = 0;
+  Skyline last_spaceable_skyline (stacking_dir);
+  int spaceable_count = 0;
   for (vsize j = 0; j < elems.size (); j++)
     {
       Real dy = 0;
+      Real padding = default_padding;
+
       if (j == 0)
-       dy = skylines[j][-stacking_dir].max_height ();
+       dy = skylines[j][-stacking_dir].max_height () + padding;
       else
        {
-         down_skyline.merge (skylines[j-1][stacking_dir]);
-         dy = down_skyline.distance (skylines[j][-stacking_dir]);
+         SCM spec = Page_layout_problem::get_spacing_spec (elems[j-1], elems[j], pure, start, end);
+         Page_layout_problem::read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
+
+         dy = down_skyline.distance (skylines[j][-stacking_dir]) + padding;
+
+         Real min_distance = 0;
+         if (Page_layout_problem::read_spacing_spec (spec, &min_distance, ly_symbol2scm ("minimum-distance")))
+           dy = max (dy, min_distance);
+
+         if (include_fixed_spacing)
+           dy = max (dy, Page_layout_problem::get_fixed_spacing (elems[j-1], elems[j], spaceable_count, pure, start, end));
+
+         if (include_fixed_spacing && Page_layout_problem::is_spaceable (elems[j]) && last_spaceable_element)
+           {
+             // Spaceable staves may have
+             // constraints coming from the previous spaceable staff
+             // as well as from the previous staff.
+             spec = Page_layout_problem::get_spacing_spec (last_spaceable_element, elems[j], pure, start, end);
+             Real spaceable_padding = 0;
+             Page_layout_problem::read_spacing_spec (spec,
+                                                     &spaceable_padding,
+                                                     ly_symbol2scm ("padding"));
+             dy = max(dy, (last_spaceable_skyline.distance (skylines[j][-stacking_dir])
+                           + stacking_dir*(last_spaceable_element_pos - where) + spaceable_padding));
+
+             Real spaceable_min_distance = 0;
+             if (Page_layout_problem::read_spacing_spec (spec,
+                                                         &spaceable_min_distance,
+                                                         ly_symbol2scm ("minimum-distance")))
+               dy = max (dy, spaceable_min_distance + stacking_dir*(last_spaceable_element_pos - where));
+
+             dy = max (dy, Page_layout_problem::get_fixed_spacing (last_spaceable_element, elems[j], spaceable_count,
+                                                                   pure, start, end));
+           }
        }
 
       if (isinf (dy)) /* if the skyline is empty, maybe max_height is infinity_f */
        dy = 0.0;
 
-      dy = max (0.0, dy + padding + extra_space / elems.size ());
+      dy = max (0.0, dy);
       down_skyline.raise (-stacking_dir * dy);
+      down_skyline.merge (skylines[j][stacking_dir]);
       where += stacking_dir * dy;
       translates.push_back (where);
-    }
 
-  SCM offsets_handle = scm_assq (ly_symbol2scm ("alignment-offsets"),
-                                line_break_details);
-  if (scm_is_pair (offsets_handle))
-    {
-      vsize i = 0;
-      for (SCM s = scm_cdr (offsets_handle);
-          scm_is_pair (s) && i < translates.size (); s = scm_cdr (s), i++)
+      if (Page_layout_problem::is_spaceable (elems[j]))
        {
-         if (scm_is_number (scm_car (s)))
-           translates[i] = scm_to_double (scm_car (s));
+         spaceable_count++;
+         last_spaceable_element = elems[j];
+         last_spaceable_element_pos = where;
+         last_spaceable_skyline = down_skyline;
        }
     }
 
@@ -242,69 +289,45 @@ Align_interface::get_extents_aligned_translates (Grob *me,
 }
 
 void
-Align_interface::align_elements_to_extents (Grob *me, Axis a)
+Align_interface::align_elements_to_ideal_distances (Grob *me)
+{
+  System *sys = me->get_system ();
+  if (sys)
+    {
+      Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
+      layout.solution (true);
+    }
+  else
+    programming_error ("vertical alignment called before line breaking");
+}
+
+void
+Align_interface::align_elements_to_minimum_distances (Grob *me, Axis a)
 {
   extract_grob_set (me, "elements", all_grobs);
 
-  vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
+  vector<Real> translates = get_minimum_translations (me, all_grobs, a);
   if (translates.size ())
     for (vsize j = 0; j < all_grobs.size (); j++)
       all_grobs[j]->translate_axis (translates[j], a);
 }
 
-/* After we have already determined the y-offsets of our children, we may still
-   want to stretch them a little. */
-void
-Align_interface::stretch (Grob *me, Real amount, Axis a)
-{
-  extract_grob_set (me, "elements", elts);
-  Real non_empty_elts = stretchable_children_count (me);
-  Real offset = 0.0;
-  Direction dir = robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
-  for (vsize i = 1; i < elts.size (); i++)
-    {
-      if (!elts[i]->extent (me, a).is_empty ()
-         && !to_boolean (elts[i]->get_property ("keep-fixed-while-stretching")))
-       offset += amount / non_empty_elts;
-      elts[i]->translate_axis (dir * offset, a);
-    }
-  me->flush_extent_cache (Y_AXIS);
-}
-
 Real
 Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, int end)
 {
   extract_grob_set (me, "elements", all_grobs);
-  SCM dy_scm = me->get_property ("forced-distance");
+  vector<Real> translates = get_pure_minimum_translations (me, all_grobs, Y_AXIS, start, end);
 
-  if (scm_is_number (dy_scm))
+  if (translates.size ())
     {
-      Real dy = scm_to_double (dy_scm) * robust_scm2dir (me->get_property ("stacking-dir"), DOWN);
-      Real pos = 0;
       for (vsize i = 0; i < all_grobs.size (); i++)
-       {
-         if (all_grobs[i] == ch)
-           return pos;
-         if (!Hara_kiri_group_spanner::has_interface (all_grobs[i])
-             || !Hara_kiri_group_spanner::request_suicide (all_grobs[i], start, end))
-           pos += dy;
-       }
+       if (all_grobs[i] == ch)
+         return translates[i];
     }
   else
-    {
-      vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
+    return 0;
 
-      if (translates.size ())
-       {
-         for (vsize i = 0; i < all_grobs.size (); i++)
-           if (all_grobs[i] == ch)
-             return translates[i];
-       }
-      else
-       return 0;
-    }
-
-  programming_error (_ ("tried to get a translation for something that is no child of mine"));
+  programming_error ("tried to get a translation for something that is no child of mine");
   return 0;
 }
 
@@ -340,55 +363,12 @@ Align_interface::set_ordered (Grob *me)
   ga->set_ordered (true);
 }
 
-int
-Align_interface::stretchable_children_count (Grob const *me)
-{
-  extract_grob_set (me, "elements", elts);
-  int ret = 0;
-
-  /* start at 1: we will never move the first child while stretching */
-  for (vsize i = 1; i < elts.size (); i++)
-    if (!to_boolean (elts[i]->get_property ("keep-fixed-while-stretching"))
-       && !elts[i]->extent (elts[i], Y_AXIS).is_empty ())
-      ret++;
-
-  return ret;
-}
-
-MAKE_SCHEME_CALLBACK (Align_interface, calc_max_stretch, 1)
-SCM
-Align_interface::calc_max_stretch (SCM smob)
-{
-  Grob *me = unsmob_grob (smob);
-  Spanner *spanner_me = dynamic_cast<Spanner*> (me);
-  Real ret = 0;
-
-  if (spanner_me && stretchable_children_count (me) > 0)
-    {
-      Paper_column *left = dynamic_cast<Paper_column*> (spanner_me->get_bound (LEFT));
-      Real height = me->extent (me, Y_AXIS).length ();
-      SCM line_break_details = left->get_property ("line-break-system-details");
-      SCM fixed_offsets = scm_assq (ly_symbol2scm ("alignment-offsets"),
-                                   line_break_details);
-
-      /* if there are fixed offsets, we refuse to stretch */
-      if (fixed_offsets != SCM_BOOL_F)
-       ret = 0;
-      else
-       ret = height * height / 80.0; /* why this, exactly? -- jneem */
-    }
-  return scm_from_double (ret);
-}
-
 ADD_INTERFACE (Align_interface,
               "Order grobs from top to bottom, left to right, right to left"
               " or bottom to top.  For vertical alignments of staves, the"
               " @code{break-system-details} of the left"
               " @rinternals{NonMusicalPaperColumn} may be set to tune"
-              " vertical spacing.  Set @code{alignment-extra-space} to add"
-              " extra space for staves.  Set"
-              " @code{fixed-alignment-extra-space} to force staves in"
-              " @code{PianoStaff}s further apart.",
+              " vertical spacing.",
               
               /* properties */
               "align-dir "
@@ -397,5 +377,4 @@ ADD_INTERFACE (Align_interface,
               "padding "
               "positioning-done "
               "stacking-dir "
-              "threshold "
               );