]> git.donarmstrong.com Git - lilypond.git/commitdiff
Rewrite the vertical layout of staves/systems.
authorJoe Neeman <joeneeman@gmail.com>
Thu, 30 Jul 2009 20:48:16 +0000 (13:48 -0700)
committerJoe Neeman <joeneeman@gmail.com>
Fri, 31 Jul 2009 01:02:24 +0000 (18:02 -0700)
This combines the positioning of systems and staves into
a single pass of a rod-and-spring problem. As a consequence,
the spacing of staves within a system is much more configurable.

Most properties related to vertical spacing have been removed
(eg. page-top-space, between-system-space, between-system-padding)
and replaced with more powerful versions (eg. first-system-spacing,
first-system-title-spacing, between-system-spacing).

40 files changed:
input/regression/alignment-vertical-manual-setting.ly
input/regression/alignment-vertical-spacing.ly [deleted file]
input/regression/page-layout-twopass.ly [deleted file]
input/regression/page-spacing-bottom-spring.ly [new file with mode: 0644]
input/regression/page-spacing-first-system-spacing.ly [new file with mode: 0644]
input/regression/page-spacing-first-system-title-spacing.ly [new file with mode: 0644]
input/regression/page-spacing-loose-lines-after.ly [new file with mode: 0644]
input/regression/page-spacing-loose-lines-before.ly [new file with mode: 0644]
input/regression/page-spacing-staff-group.ly [new file with mode: 0644]
input/regression/page-spacing-stretchability.ly [new file with mode: 0644]
lily/align-interface.cc
lily/axis-group-interface.cc
lily/constrained-breaking.cc
lily/grob.cc
lily/include/align-interface.hh
lily/include/axis-group-interface.hh
lily/include/grob.hh
lily/include/page-breaking.hh
lily/include/page-layout-problem.hh [new file with mode: 0644]
lily/include/page-spacing.hh
lily/include/staff-grouper-interface.hh [new file with mode: 0644]
lily/page-breaking.cc
lily/page-layout-problem.cc [new file with mode: 0644]
lily/page-spacing.cc
lily/staff-grouper-interface.cc [new file with mode: 0644]
lily/system-scheme.cc [deleted file]
lily/tuplet-bracket.cc
lily/vertical-align-engraver.cc
ly/engraver-init.ly
ly/paper-defaults-init.ly
python/convertrules.py
scm/define-context-properties.scm
scm/define-grob-properties.scm
scm/define-grobs.scm
scm/layout-page-dump.scm [deleted file]
scm/layout-page-layout.scm [deleted file]
scm/page.scm
scm/paper-system.scm
scm/paper.scm
scm/stencil.scm

index b176e931cec7ffe5dd1323d379e951e1694c23d2..b7f30c719c5b8ac862053f53ebe3e41efd732f42 100644 (file)
@@ -1,12 +1,12 @@
 \header {
 
   texidoc = "Alignments may be changed pre system by setting
-  @code{alignment-offsets} in the @code{line-break-system-details}
+  @code{alignment-distances} in the @code{line-break-system-details}
   property"
 
 }
 
-\version "2.12.0"
+\version "2.13.2"
 
 \book {
   \score {
        \overrideProperty
        #"Score.NonMusicalPaperColumn"
        #'line-break-system-details
-       #'((alignment-offsets . (#f #f -30)))
+       #'((alignment-distances . (#f 20)))
        c1 \break
        \overrideProperty
        #"Score.NonMusicalPaperColumn"
        #'line-break-system-details
-       #'((alignment-offsets . (0 -5 -20)))
+       #'((alignment-distances . (5 15)))
        c1 \break
        \overrideProperty
        #"Score.NonMusicalPaperColumn"
        #'line-break-system-details
-       #'((alignment-offsets . (0 -15 -20)))
+       #'((alignment-distances . (15 5)))
        c1 c
       }
     >>
diff --git a/input/regression/alignment-vertical-spacing.ly b/input/regression/alignment-vertical-spacing.ly
deleted file mode 100644 (file)
index 2140e37..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-
-\header {
-
-  texidoc = "By setting properties in @code{NonMusicalPaperColumn}, vertical
-spacing of alignments can be adjusted per system.
-
-By setting @code{alignment-extra-space} or
-@code{fixed-alignment-extra-space} an individual system may be
-stretched vertically.
-
-For technical reasons, @code{overrideProperty} has to be used for
-setting properties on individual object. @code{\override} in a
-@code{\context} block may still be used for global overrides.
-
-"
-
-}
-
-\version "2.12.0"
-
-#(set-global-staff-size 13)
-
-\relative c''
-\new StaffGroup <<
-  \new Staff {
-    c1\break 
-    c\break c\break
-  }
-  \new Staff { c1 c c }
-  \new PianoStaff <<
-    \new Voice  {
-      \set PianoStaff.instrumentName = #"piano"
-      \set PianoStaff.shortInstrumentName = #"pn"
-      c1_"normal"
-      
-      \overrideProperty
-      #"Score.NonMusicalPaperColumn"
-      #'line-break-system-details
-      #'((fixed-alignment-extra-space . 15))
-      c_"fixed-aligment-extra-space"
-
-      \overrideProperty
-      #"Score.NonMusicalPaperColumn"
-      #'line-break-system-details
-      #'((alignment-extra-space . 15))
-      c_"aligment-extra-space"
-    }
-    { c1 c c }
-  >>
->>
-
diff --git a/input/regression/page-layout-twopass.ly b/input/regression/page-layout-twopass.ly
deleted file mode 100644 (file)
index c1e93df..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-\header
-{
-  texidoc = "Page breaking details can be stored for later reference." 
-}
-
-\version "2.12.0"
-
-\paper  {
-  #(define write-page-layout #t)
-}
-bla = \new Staff {
-  c1 c1
-  \break
-  \grace { c16 } c1\break
-  \repeat unfold 5 \relative { c1 c1 c1 }
-}
-
-
-\book {
-  \score {
-    \bla
-    \layout {
-      #(define tweak-key "blabla")
-    }
-  }
-}
-
-tweakFileName = #(format "~a-page-layout.ly" (ly:parser-output-name parser))
-
-#(newline)
-
-#(ly:progress "Contents of: '~a'" (ly:gulp-file tweakFileName))
diff --git a/input/regression/page-spacing-bottom-spring.ly b/input/regression/page-spacing-bottom-spring.ly
new file mode 100644 (file)
index 0000000..f7802af
--- /dev/null
@@ -0,0 +1,20 @@
+\version "2.13.2"
+#(set-default-paper-size "a6")
+
+\book {
+  \header {
+    texidoc = "The spring at the bottom of a page is fairly flexible (much more so
+than the one at the top), so it does not drag the staff to the bottom of the
+page.  However, it is sufficiently stiff to cause stretching."
+  }
+
+  \paper {
+    ragged-last-bottom = ##f
+  }
+
+  \new StaffGroup
+  <<
+    \new Staff c'1
+    \new Staff c'1
+  >>
+}
\ No newline at end of file
diff --git a/input/regression/page-spacing-first-system-spacing.ly b/input/regression/page-spacing-first-system-spacing.ly
new file mode 100644 (file)
index 0000000..82f7b4e
--- /dev/null
@@ -0,0 +1,19 @@
+\version "2.13.2"
+
+#(set-default-paper-size "a6")
+
+\book {
+
+  \header {
+    texidoc = "first-system-spacing controls the spacing to the first
+non-title staff on every page."
+    title = "Title"
+  }
+
+  \paper {
+    first-system-spacing = #'((minimum-distance . 30))
+    ragged-bottom = ##t
+  }
+
+  { c'1 \pageBreak c'1 }
+}
\ No newline at end of file
diff --git a/input/regression/page-spacing-first-system-title-spacing.ly b/input/regression/page-spacing-first-system-title-spacing.ly
new file mode 100644 (file)
index 0000000..e6117fc
--- /dev/null
@@ -0,0 +1,19 @@
+\version "2.13.2"
+
+#(set-default-paper-size "a6")
+
+\book {
+
+  \header {
+    texidoc = "first-system-title-spacing controls the spacing to the title,
+provided that it is the first system on a page."
+    title = "Title"
+  }
+
+  \paper {
+    first-system-title-spacing = #'((minimum-distance . 30))
+    ragged-bottom = ##t
+  }
+
+  { c'1 \pageBreak c'1 }
+}
\ No newline at end of file
diff --git a/input/regression/page-spacing-loose-lines-after.ly b/input/regression/page-spacing-loose-lines-after.ly
new file mode 100644 (file)
index 0000000..8a7903d
--- /dev/null
@@ -0,0 +1,25 @@
+\header {
+    texidoc = "A loose line (eg. a lyric line) at the bottom of a system
+gets spaced as though it wasn't loose."
+}
+
+\layout {
+  ragged-right = ##t
+  \context {
+    \Lyrics
+    \override VerticalAxisGroup #'inter-loose-line-spacing #'space = #20
+  }
+}
+<<
+    \new Staff \relative {
+       d'2 d c4 bes a2 \break
+    }
+    \addlyrics {
+       My first Li -- ly song,
+    }
+    \addlyrics {
+       Not much can go wrong!
+    }
+>>
+
+\version "2.12.0"
diff --git a/input/regression/page-spacing-loose-lines-before.ly b/input/regression/page-spacing-loose-lines-before.ly
new file mode 100644 (file)
index 0000000..15a0a73
--- /dev/null
@@ -0,0 +1,26 @@
+\header {
+    texidoc = "A loose line (eg. a lyric line) at the top of a system
+gets spaced as though it wasn't loose."
+}
+
+\layout {
+  ragged-right = ##t
+  \context {
+    \Lyrics
+    \override VerticalAxisGroup #'inter-loose-line-spacing #'space = #20
+    \override VerticalAxisGroup #'staff-affinity = #DOWN
+  }
+}
+<<
+  \new Lyrics \lyricmode {
+    My2 first Li4 -- ly song,
+  }
+  \new Lyrics \lyricmode {
+    Not2 much can4 go wrong!
+  }
+  \context Voice = "voice" \relative {
+    d'2 d c4 bes a2
+  }
+>>
+
+\version "2.12.0"
diff --git a/input/regression/page-spacing-staff-group.ly b/input/regression/page-spacing-staff-group.ly
new file mode 100644 (file)
index 0000000..9f38cd6
--- /dev/null
@@ -0,0 +1,30 @@
+\version "2.13.2"
+
+#(set-default-paper-size "a6")
+
+\book {
+
+  \header {
+    texidoc = "By default, the staves within a StaffGroup are spaced more
+closely than staves not in a StaffGroup."
+  }
+
+  \paper {
+    ragged-last-bottom = ##f
+  }
+
+  <<
+    \new StaffGroup
+    <<
+      \new Staff c'1
+      \new Staff c'1
+      \new Staff c'1
+    >>
+    \new StaffGroup
+    <<
+      \new Staff c'1
+      \new Staff c'1
+      \new Staff c'1
+    >>
+  >>
+}
\ No newline at end of file
diff --git a/input/regression/page-spacing-stretchability.ly b/input/regression/page-spacing-stretchability.ly
new file mode 100644 (file)
index 0000000..563a12b
--- /dev/null
@@ -0,0 +1,19 @@
+\version "2.13.2"
+
+#(set-default-paper-size "a6")
+
+\book {
+
+  \header {
+    texidoc = "The stretchability property affects the amount that staves will
+move under extreme stretching, but it does not affect the default distance
+between staves."
+  }
+
+  <<
+    \new Staff {c'1 \pageBreak c'1}
+    \new Staff \with { \override VerticalAxisGroup #'default-next-staff-spacing #'stretchability = #50 } {c'1 c'1}
+    
+    \new Staff {c'1 c'1}
+  >>
+}
\ No newline at end of file
index 36aa81f11153d428eb9adafcfd7f2206766b51b4..2a479458d6c221fe4cceb8ad0bdca8cb24f8aad7 100644 (file)
@@ -12,6 +12,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,47 +33,22 @@ 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
@@ -108,23 +79,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. */
@@ -177,48 +131,37 @@ 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,
+                                          bool pure, int start, int end)
 {
-  Spanner *me_spanner = dynamic_cast<Spanner *> (me);
-
-
-  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");
-
-      if (!me->get_system () && !pure)
-       me->programming_error ("vertical alignment called before line-breaking");
-    }
+  if (!pure && a == Y_AXIS && dynamic_cast<Spanner*> (me) && !me->get_system ())
+    me->programming_error ("vertical alignment called before line-breaking");
   
   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);
 
+  SCM forced_distances = ly_assoc_get (ly_symbol2scm ("alignment-distances"),
+                                      Page_layout_problem::get_details (me),
+                                      SCM_EOL);
+
   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);
+  SCM last_spaceable_element_details = SCM_EOL;
+  Real last_spaceable_element_pos = 0;
+  bool found_spaceable_element = false;
   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 ();
       else
@@ -230,24 +173,40 @@ Align_interface::get_extents_aligned_translates (Grob *me,
       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 ());
+      if (Page_layout_problem::is_spaceable (elems[j]))
+       {
+         Real min_distance = 0;
+         Page_layout_problem::read_spacing_spec (last_spaceable_element_details,
+                                                 &padding,
+                                                 ly_symbol2scm ("padding"));
+         if (Page_layout_problem::read_spacing_spec (last_spaceable_element_details,
+                                                     &min_distance,
+                                                     ly_symbol2scm ("minimum-distance")))
+           dy = max (dy, min_distance + stacking_dir*(last_spaceable_element_pos - where));
+
+         if (found_spaceable_element && scm_is_pair (forced_distances))
+           {
+             SCM forced_dist = scm_car (forced_distances);
+             forced_distances = scm_cdr (forced_distances);
+
+             if (scm_is_number (forced_dist))
+               dy = scm_to_double (forced_dist) + stacking_dir * (last_spaceable_element_pos - where);
+           }
+         last_spaceable_element_details = elems[j]->get_property ("next-staff-padding");
+         found_spaceable_element = true;
+       }
+      else
+       {
+         // TODO: provide support for min-distance and padding for non-spaceable elements also.
+       }
+
+      dy = max (0.0, dy + padding);
       down_skyline.raise (-stacking_dir * dy);
       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 (scm_is_number (scm_car (s)))
-           translates[i] = scm_to_double (scm_car (s));
-       }
+      if (Page_layout_problem::is_spaceable (elems[j]))
+       last_spaceable_element_pos = where;
     }
 
   // So far, we've computed the translates for all the non-empty elements.
@@ -268,33 +227,23 @@ 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)
 {
-  extract_grob_set (me, "elements", all_grobs);
+  System *sys = me->get_system ();
+  Page_layout_problem layout (NULL, SCM_EOL, scm_list_1 (sys->self_scm ()));
 
-  vector<Real> translates = get_extents_aligned_translates (me, all_grobs, a, false, 0, 0);
-  if (translates.size ())
-    for (vsize j = 0; j < all_grobs.size (); j++)
-      all_grobs[j]->translate_axis (translates[j], a);
+  layout.solution (true);
 }
 
-/* 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)
+Align_interface::align_elements_to_minimum_distances (Grob *me, 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);
+  extract_grob_set (me, "elements", all_grobs);
+
+  vector<Real> translates = get_minimum_translations (me, all_grobs, a, false, 0, 0);
+  if (translates.size ())
+    for (vsize j = 0; j < all_grobs.size (); j++)
+      all_grobs[j]->translate_axis (translates[j], a);
 }
 
 Real
@@ -318,7 +267,7 @@ Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, in
     }
   else
     {
-      vector<Real> translates = get_extents_aligned_translates (me, all_grobs, Y_AXIS, true, start, end);
+      vector<Real> translates = get_minimum_translations (me, all_grobs, Y_AXIS, true, start, end);
 
       if (translates.size ())
        {
@@ -330,7 +279,7 @@ Align_interface::get_pure_child_y_translation (Grob *me, Grob *ch, int start, in
        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;
 }
 
@@ -366,46 +315,6 @@ 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"
index 07ed23f64ff025f26e4e5464d9078694b8f3fad8..4ce0a01a65918d486bb1d7be9dca9603eb8ef9be 100644 (file)
 
 #include "align-interface.hh"
 #include "directional-element-interface.hh"
-#include "pointer-group-interface.hh"
 #include "grob-array.hh"
 #include "hara-kiri-group-spanner.hh"
 #include "international.hh"
 #include "lookup.hh"
 #include "paper-column.hh"
 #include "paper-score.hh"
+#include "pointer-group-interface.hh"
 #include "separation-item.hh"
 #include "skyline-pair.hh"
+#include "staff-grouper-interface.hh"
 #include "stencil.hh"
 #include "system.hh"
 #include "warn.hh"
@@ -579,7 +580,7 @@ add_grobs_of_one_priority (Skyline_pair *const skylines,
                  b.translate (Offset (0, dir*dist));
                  elements[i]->translate_axis (dir*dist, Y_AXIS);
                }
-             (*skylines)[dir].insert (b, 0, X_AXIS);
+             skylines->insert (b, 0, X_AXIS);
              elements[i]->set_property ("outside-staff-priority", SCM_BOOL_F);
              last_affected_position[dir] = b[X_AXIS][RIGHT];
            }
@@ -653,21 +654,6 @@ Axis_group_interface::skyline_spacing (Grob *me, vector<Grob*> elements)
   return skylines;
 }
 
-MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_max_stretch, 1)
-SCM
-Axis_group_interface::calc_max_stretch (SCM smob)
-{
-  Grob *me = unsmob_grob (smob);
-  Real ret = 0;
-  extract_grob_set (me, "elements", elts);
-
-  for (vsize i = 0; i < elts.size (); i++)
-    if (Axis_group_interface::has_interface (elts[i]))
-      ret += robust_scm2double (elts[i]->get_property ("max-stretch"), 0.0);
-
-  return scm_from_double (ret);
-}
-
 MAKE_SCHEME_CALLBACK (Axis_group_interface, print, 1)
 SCM
 Axis_group_interface::print (SCM smob)
@@ -687,20 +673,47 @@ Axis_group_interface::print (SCM smob)
   return ret.smobbed_copy ();
 }
 
+MAKE_SCHEME_CALLBACK (Axis_group_interface, calc_next_staff_spacing, 1)
+SCM
+Axis_group_interface::calc_next_staff_spacing (SCM smob)
+{
+  Grob *me = unsmob_grob (smob);
+  Grob *grouper = unsmob_grob (me->get_object ("staff-grouper"));
+
+  if (grouper)
+    {
+      Grob *last_in_group = Staff_grouper_interface::get_last_grob (grouper);
+      if (me == last_in_group)
+       return grouper->get_property ("after-last-staff-spacing");
+      else
+       return grouper->get_property ("between-staff-spacing");
+    }
+  return me->get_property ("default-next-staff-spacing");
+}
+
 ADD_INTERFACE (Axis_group_interface,
               "An object that groups other layout objects.",
 
+              // TODO: some of these properties are specific to
+              // VerticalAxisGroup. We should split off a
+              // vertical-axis-group-interface.
               /* properties */
               "X-common "
               "Y-common "
               "adjacent-pure-heights "
               "axes "
+              "default-next-staff-spacing "
               "elements "
+              "inter-loose-line-spacing "
+              "inter-staff-spacing "
               "keep-fixed-while-stretching "
               "max-stretch "
+              "next-staff-spacing "
               "no-alignment "
               "pure-Y-common "
               "pure-relevant-items "
               "pure-relevant-spanners "
+              "staff-affinity "
+              "staff-grouper "
               "vertical-skylines "
               );
index f7f832b57b0cadc4261c85f5e1e97cab15f6e7ee..2d081e91ad65d2499ec6a6e7da7bea7428f2e19a 100644 (file)
@@ -12,6 +12,7 @@
 #include "international.hh"
 #include "main.hh"
 #include "output-def.hh"
+#include "page-layout-problem.hh"
 #include "paper-column.hh"
 #include "paper-score.hh"
 #include "simple-spacer.hh"
@@ -333,11 +334,15 @@ Constrained_breaking::initialize ()
       
   Output_def *l = pscore_->layout ();
   System *sys = pscore_->root_system ();
-  Real space = robust_scm2double (l->c_variable ("ideal-system-space"), 0);
-  SCM padding_scm = l->c_variable ("page-breaking-between-system-padding");
-  if (!scm_is_number (padding_scm))
-    padding_scm = l->c_variable ("between-system-padding");
-  Real padding = robust_scm2double (padding_scm, 0.0);
+
+  // TODO: add support for minimum-distance and stretchability here and
+  // to the page-breaker.
+  SCM spacing_spec = l->c_variable ("between-system-spacing");
+  SCM page_breaking_spacing_spec = l->c_variable ("page-breaking-between-system-spacing");
+  Real space = 0;
+  Real padding = 0;
+  Page_layout_problem::read_spacing_spec (spacing_spec, &padding, ly_symbol2scm ("padding"));
+  Page_layout_problem::read_spacing_spec (page_breaking_spacing_spec, &padding, ly_symbol2scm ("padding"));
 
   Interval first_line = line_dimensions_int (pscore_->layout (), 0);
   Interval other_lines = line_dimensions_int (pscore_->layout (), 1);
index 5660b688b81fe477be9a4a538a5d69f02f1f074d..ce3e41c76dbcbf826d9191c9e27511edd23f1209 100644 (file)
@@ -747,3 +747,17 @@ robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
   return ext;
 }
 
+// Checks whether there is a vertical alignment in the chain of
+// parents between this and commony.
+bool
+Grob::check_cross_staff (Grob *commony)
+{
+  if (Align_interface::has_interface (commony))
+    return true;
+
+  for (Grob *g = this; g && g != commony; g = g->get_parent (Y_AXIS))
+    if (Align_interface::has_interface (g))
+      return true;
+
+  return false;
+}
index 149ef321c1b2f449270621b9c4bbcc0802e9279c..467f1248f260cfa2b8d30b0ee82223674460a102 100644 (file)
 
 struct Align_interface
 {
-  DECLARE_SCHEME_CALLBACK (calc_positioning_done, (SCM));
-  DECLARE_SCHEME_CALLBACK (stretch_after_break, (SCM element));
-  DECLARE_SCHEME_CALLBACK (calc_max_stretch, (SCM));
-  static void stretch (Grob *, Real amount, Axis a);
-  static void align_elements_to_extents (Grob *, Axis a);
-  static vector<Real> get_extents_aligned_translates (Grob *, vector<Grob*> const&,
-                                                     Axis a,
-                                                     bool safe, int start, int end);
-  static int stretchable_children_count (Grob const*);
+  DECLARE_SCHEME_CALLBACK (align_to_minimum_distances, (SCM));
+  DECLARE_SCHEME_CALLBACK (align_to_ideal_distances, (SCM));
+  static void align_elements_to_minimum_distances(Grob *, Axis a);
+  static void align_elements_to_ideal_distances(Grob *);
+  static vector<Real> get_minimum_translations (Grob *, vector<Grob*> const&,
+                                               Axis a,
+                                               bool safe, int start, int end);
   static void set_ordered (Grob *);
   static Axis axis (Grob *);
   static void add_element (Grob *, Grob *);
index f852c92a34165e98bc2b59a8ad0d22b01d2f86db..b147896b05dab1c132e8bb2bc6e36b7d959652ea 100644 (file)
@@ -25,9 +25,9 @@ struct Axis_group_interface
   DECLARE_SCHEME_CALLBACK (pure_height, (SCM smob, SCM start, SCM end));
   DECLARE_SCHEME_CALLBACK (calc_skylines, (SCM smob));
   DECLARE_SCHEME_CALLBACK (combine_skylines, (SCM smob));
-  DECLARE_SCHEME_CALLBACK (calc_max_stretch, (SCM smob));
   DECLARE_SCHEME_CALLBACK (print, (SCM smob));
   DECLARE_SCHEME_CALLBACK (adjacent_pure_heights, (SCM));
+  DECLARE_SCHEME_CALLBACK (calc_next_staff_spacing, (SCM));
   static Interval relative_group_extent (vector<Grob*> const &list,
                                         Grob *common, Axis);
   static Interval relative_pure_height (Grob *me, int start, int end);
index 66cc0530b73a0221f8f74cb93489bea5c80e0737..3a724c8d95ac5141e233584d2752d50bdc51a840 100644 (file)
@@ -128,6 +128,7 @@ public:
   void fixup_refpoint ();
 
   virtual Interval_t<int> spanned_rank_interval () const;
+  bool check_cross_staff (Grob *common);
 };
 
 /* smob utilities */
index c6c1bc02c80b88c32b0fa2b0275a70fcbb6d1fca..ffeaebc5a19c320d6c76a485b74ea8e0e287f58e 100644 (file)
@@ -105,12 +105,13 @@ public:
   int max_systems_per_page () const;
   int min_systems_per_page () const;
   Real page_height (int page_number, bool last) const;
-  Real page_top_space () const;
   vsize system_count () const;
   Real line_count_penalty (int line_count) const;
   int line_count_status (int line_count) const;
   bool too_many_lines (int line_count) const;
   bool too_few_lines (int line_count) const;
+  Real min_whitespace_at_top_of_page (Line_details const&) const;
+  Real min_whitespace_at_bottom_of_page (Line_details const&) const;
 
 protected:
   Paper_book *book_;
@@ -162,7 +163,6 @@ private:
   int systems_per_page_;
   int max_systems_per_page_;
   int min_systems_per_page_;
-  Real page_top_space_;
   vsize system_count_;
 
   vector<Line_division> current_configurations_;
@@ -195,5 +195,8 @@ private:
   Page_spacing_result finalize_spacing_result (vsize configuration_index, Page_spacing_result);
   void create_system_list ();
   void find_chunks_and_breaks (Break_predicate);
+  SCM make_page (int page_num, bool last) const;
+  SCM get_page_configuration (SCM systems, int page_num, bool ragged, bool last);
+  SCM draw_page (SCM systems, SCM config, int page_num, bool last);
 };
 #endif /* PAGE_BREAKING_HH */
diff --git a/lily/include/page-layout-problem.hh b/lily/include/page-layout-problem.hh
new file mode 100644 (file)
index 0000000..c8da010
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+  page-layout-problem.hh -- space systems nicely on a page. If systems can
+  be stretched, do that too.
+
+  source file of the GNU LilyPond music typesetter
+
+  (c) 2009 Joe Neeman <joeneeman@gmail.com>
+*/
+
+#ifndef PAGE_LAYOUT_HH
+#define PAGE_LAYOUT_HH
+
+#include "simple-spacer.hh"
+#include "skyline.hh"
+
+class Page_layout_problem
+{
+public:
+  Page_layout_problem (Paper_book*, SCM page, SCM systems);
+
+  SCM solution (bool ragged);
+  void set_header_height (Real);
+  void set_footer_height (Real);
+  static bool read_spacing_spec (SCM spec, Real* dest, SCM sym);
+  static bool is_spaceable (Grob *g);
+  static SCM get_details (Grob *g);
+
+protected:
+  void append_system (System*, Spring const&, Real padding);
+  void append_prob (Prob*, Spring const&, Real padding);
+
+  void solve_rod_spring_problem (bool ragged);
+  SCM find_system_offsets ();
+  void distribute_loose_lines (Grob*, Real, vector<Grob*> const&, vector<Real> const&, Grob*, Real);
+  void add_loose_lines_as_spaceable_lines (vector<Grob*> const&,
+                                          vector<Real> const&,
+                                          vsize start, vsize end);
+
+  static Grob* find_vertical_alignment (System*);
+  static void build_system_skyline (vector<Grob*> const&, vector<Real> const&, Skyline* up, Skyline* down);
+
+  // This is a union (in spirit).
+  // Either staves must be empty or prob must be null.
+  typedef struct Element {
+    Prob *prob;
+    vector<Grob*> staves;
+    vector<Real> min_offsets;
+
+    Element (vector<Grob*> const& a, vector<Real> const& o)
+    {
+      staves = a;
+      min_offsets = o;
+      prob = 0;
+    }
+
+    Element (Prob *p)
+    {
+      prob = p;
+    }
+  } Element;
+
+  static Interval first_staff_extent (Element const&);
+  static Interval last_staff_extent (Element const&);
+  static Interval prob_extent (Prob*);
+  static SCM get_details (Element const&);
+  static SCM details_get_property (SCM details, const char*);
+  static void alter_spring_from_spacing_spec (SCM spec, Spring* spring);
+  static void mark_as_spaceable (Grob*);
+
+  vector<Spring> springs_;
+  vector<Element> elements_;
+  vector<Real> solution_;
+  Skyline bottom_skyline_;
+  Real page_height_;
+  Real header_height_;
+  Real footer_height_;
+};
+
+#endif /* PAGE_LAYOUT_HH */
index 958a1b21ff08b2bffd51b5746db2f29abaf91daf..bf8a7e1826c3f8a3e2e06f9b2d27874a7fbf1650 100644 (file)
@@ -83,15 +83,15 @@ struct Page_spacing
   Real rod_height_;
   Real spring_len_;
   Real inverse_spring_k_;
-  Real page_top_space_;
 
   Line_details last_line_;
   Line_details first_line_;
+  Page_breaking const *breaker_;
 
-  Page_spacing (Real page_height, Real page_top_space)
+  Page_spacing (Real page_height, Page_breaking const *breaker)
   {
     page_height_ = page_height;
-    page_top_space_ = page_top_space;
+    breaker_ = breaker;
     clear ();
   }
 
diff --git a/lily/include/staff-grouper-interface.hh b/lily/include/staff-grouper-interface.hh
new file mode 100644 (file)
index 0000000..f5feaeb
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+  staff-grouper-interface.hh -- declare Staff_grouper_interface
+
+  source file of the GNU LilyPond music typesetter
+
+  (c) 2009 Joe Neeman <joeneeman@gmail.com>
+*/
+
+#ifndef STAFF_GROUPER_INTERFACE_HH
+#define STAFF_GROUPER_INTERFACE_HH
+
+#include "grob.hh"
+
+class Staff_grouper_interface
+{
+public:
+  DECLARE_GROB_INTERFACE ();
+
+  static Grob *get_last_grob (Grob *);
+};
+
+#endif /* STAFF_GROUPER_INTERFACE_HH */
index 668231f63aa95f6ab8092f0c2c547189a4861ab1..e03a5c97c154f6a2576378fe1cb4e3b74fae4fd3 100644 (file)
@@ -68,6 +68,7 @@
 #include "international.hh"
 #include "item.hh"
 #include "output-def.hh"
+#include "page-layout-problem.hh"
 #include "page-spacing.hh"
 #include "paper-book.hh"
 #include "paper-score.hh"
@@ -97,7 +98,9 @@ compress_lines (const vector<Line_details> &orig)
          compressed.compressed_nontitle_lines_count_ =
            old.compressed_nontitle_lines_count_ + (compressed.title_ ? 0 : 1);
 
-         compressed.title_ = compressed.title_ && old.title_;
+         // compressed.title_ is true if and only if the first of its
+         // compressed lines was a title.
+         compressed.title_ = old.title_;
          ret.back () = compressed;
        }
       else
@@ -156,7 +159,6 @@ Page_breaking::Page_breaking (Paper_book *pb, Break_predicate is_break)
   system_count_ = 0;
   ragged_ = to_boolean (pb->paper_->c_variable ("ragged-bottom"));
   ragged_last_ = to_boolean (pb->paper_->c_variable ("ragged-last-bottom"));
-  page_top_space_ = robust_scm2double (pb->paper_->c_variable ("page-top-space"), 0);
   systems_per_page_ = max (0, robust_scm2int (pb->paper_->c_variable ("systems-per-page"), 0));
   max_systems_per_page_ = max (0, robust_scm2int (pb->paper_->c_variable ("max-systems-per-page"), 0));
   min_systems_per_page_ = max (0, robust_scm2int (pb->paper_->c_variable ("min-systems-per-page"), 0));
@@ -210,12 +212,6 @@ Page_breaking::min_systems_per_page () const
   return min_systems_per_page_;
 }
 
-Real
-Page_breaking::page_top_space () const
-{
-  return page_top_space_;
-}
-
 vsize
 Page_breaking::system_count () const
 {
@@ -331,25 +327,33 @@ Page_breaking::systems ()
   return scm_append (scm_reverse (ret));
 }
 
+SCM
+Page_breaking::make_page (int page_num, bool last) const
+{
+  bool last_part = ly_scm2bool (book_->paper_->c_variable ("is-last-bookpart"));
+  SCM mod = scm_c_resolve_module ("scm page");
+  SCM make_page_scm = scm_c_module_lookup (mod, "make-page");
+
+  make_page_scm = scm_variable_ref (make_page_scm);
+
+  return scm_apply_0 (make_page_scm,
+                     scm_list_n (book_->self_scm (),
+                                 ly_symbol2scm ("page-number"), scm_from_int (page_num),
+                                 ly_symbol2scm ("is-last-bookpart"), scm_from_bool (last_part),
+                                 ly_symbol2scm ("is-bookpart-last-page"), scm_from_bool (last),
+                                 SCM_UNDEFINED));
+}
+
 Real
 Page_breaking::page_height (int page_num, bool last) const
 {
-  bool last_part = ly_scm2bool (book_->paper_->c_variable ("is-last-bookpart"));
   SCM mod = scm_c_resolve_module ("scm page");
+  SCM page = make_page (page_num, last);
   SCM calc_height = scm_c_module_lookup (mod, "calc-printable-height");
-  SCM make_page = scm_c_module_lookup (mod, "make-page");
-
   calc_height = scm_variable_ref (calc_height);
-  make_page = scm_variable_ref (make_page);
-
-  SCM page = scm_apply_0 (make_page, scm_list_n (
-                  book_->self_scm (),
-                  ly_symbol2scm ("page-number"), scm_from_int (page_num),
-                  ly_symbol2scm ("is-last-bookpart"), scm_from_bool (last_part),
-                  ly_symbol2scm ("is-bookpart-last-page"), scm_from_bool (last),
-                  SCM_UNDEFINED));
+
   SCM height = scm_apply_1 (calc_height, page, SCM_EOL);
-  return scm_to_double (height) - page_top_space_;
+  return scm_to_double (height);
 }
 
 SCM
@@ -365,38 +369,86 @@ Page_breaking::breakpoint_property (vsize breakpoint, char const *str)
 }
 
 SCM
-Page_breaking::make_pages (vector<vsize> lines_per_page, SCM systems)
+Page_breaking::get_page_configuration (SCM systems, int page_num, bool ragged, bool last)
 {
-  SCM layout_module = scm_c_resolve_module ("scm layout-page-layout");
-  SCM page_module = scm_c_resolve_module ("scm page");
+  SCM dummy_page = make_page (page_num, last);
+  Page_layout_problem layout (book_, dummy_page, systems);
+  return scm_is_pair (systems) ? layout.solution (ragged) : SCM_EOL;
+}
+
+SCM
+Page_breaking::draw_page (SCM systems, SCM configuration, int page_num, bool last)
+{
+  // Create a stencil for each system.
+  SCM paper_systems = SCM_EOL;
+  for (SCM s = scm_reverse (systems); scm_is_pair (s); s = scm_cdr (s))
+    {
+      SCM paper_system = scm_car (s);
+      if (Grob *g = unsmob_grob (scm_car (s)))
+       {
+         System *sys = dynamic_cast<System*> (g);
+         paper_system = sys->get_paper_system ();
+       }
+
+      paper_systems = scm_cons (paper_system, paper_systems);
+    }
 
-  SCM make_page = scm_c_module_lookup (layout_module, "stretch-and-draw-page");
+  // Create the page and draw it.
+  SCM page = make_page (page_num, last);
+  SCM page_module = scm_c_resolve_module ("scm page");
   SCM page_stencil = scm_c_module_lookup (page_module, "page-stencil");
-  make_page = scm_variable_ref (make_page);
   page_stencil = scm_variable_ref (page_stencil);
 
-  SCM book = book_->self_scm ();
+  Prob *p = unsmob_prob (page);
+  p->set_property ("lines", paper_systems);
+  p->set_property ("configuration", configuration);
+  scm_apply_1 (page_stencil, page, SCM_EOL);
+
+  return page;
+}
+
+SCM
+Page_breaking::make_pages (vector<vsize> lines_per_page, SCM systems)
+{
   int first_page_number
     = robust_scm2int (book_->paper_->c_variable ("first-page-number"), 1);
-  bool last_bookpart = ly_scm2bool (book_->paper_->c_variable ("is-last-bookpart"));
   SCM ret = SCM_EOL;
   SCM label_page_table = book_->top_paper ()->c_variable ("label-page-table");
   if (label_page_table == SCM_UNDEFINED)
     label_page_table = SCM_EOL;
 
+  // Build a list of (systems . configuration) pairs. Note that we lay out
+  // the staves and find the configurations before drawing anything. Some
+  // grobs (like tuplet brackets) look at their neighbours while drawing
+  // themselves. If this happens before the neighbouring staves have
+  // been laid out, bad side-effects could happen (in particular,
+  // Align_interface::align_to_ideal_distances might be called).
+  SCM systems_and_configs = SCM_EOL;
+
   for (vsize i = 0; i < lines_per_page.size (); i++)
     {
-      SCM page_num = scm_from_int (i + first_page_number);
-      bool partbook_last_page = (i == lines_per_page.size () - 1);
-      SCM rag = scm_from_bool (ragged () || ( partbook_last_page && ragged_last ()));
+      int page_num = i + first_page_number;
+      bool bookpart_last_page = (i == lines_per_page.size () - 1);
+      bool rag = ragged () || (bookpart_last_page && ragged_last ());
       SCM line_count = scm_from_int (lines_per_page[i]);
       SCM lines = scm_list_head (systems, line_count);
-      SCM page = scm_apply_0 (make_page,
-                             scm_list_n (book, lines, page_num, rag,
-                                         scm_from_bool (last_bookpart),
-                                         scm_from_bool (partbook_last_page),
-                                         SCM_UNDEFINED));
+      SCM config = get_page_configuration (lines, page_num, rag, bookpart_last_page);
+
+      systems_and_configs = scm_cons (scm_cons (lines, config), systems_and_configs);
+      systems = scm_list_tail (systems, line_count);
+    }
+
+  // Now it's safe to make the pages.
+  int page_num = first_page_number + lines_per_page.size () - 1;
+  for (SCM s = systems_and_configs; scm_is_pair (s); s = scm_cdr (s))
+    {
+      SCM lines = scm_caar (s);
+      SCM config = scm_cdar (s);
+      bool bookpart_last_page = (s == systems_and_configs);
+      SCM page = draw_page (lines, config, page_num, bookpart_last_page);
+
       /* collect labels */
+      SCM page_num_scm = scm_from_int (page_num);
       for (SCM l = lines ; scm_is_pair (l)  ; l = scm_cdr (l))
        {
          SCM labels = SCM_EOL;
@@ -409,16 +461,14 @@ Page_breaking::make_pages (vector<vsize> lines_per_page, SCM systems)
            labels = prob->get_property ("labels");
 
          for (SCM lbls = labels ; scm_is_pair (lbls) ; lbls = scm_cdr (lbls))
-           label_page_table = scm_cons (scm_cons (scm_car (lbls), page_num),
+           label_page_table = scm_cons (scm_cons (scm_car (lbls), page_num_scm),
                                         label_page_table);
        }
 
-      scm_apply_1 (page_stencil, page, SCM_EOL);
       ret = scm_cons (page, ret);
-      systems = scm_list_tail (systems, line_count);
+      --page_num;
     }
   book_->top_paper ()->set_variable (ly_symbol2scm ("label-page-table"), label_page_table);
-  ret = scm_reverse (ret);
   return ret;
 }
 
@@ -673,10 +723,11 @@ Page_breaking::cache_line_details (vsize configuration_index)
   if (cached_configuration_index_ != configuration_index)
     {
       cached_configuration_index_ = configuration_index;
-      SCM padding_scm = book_->paper_->c_variable ("page-breaking-between-system-padding");
-      if (!scm_is_number (padding_scm))
-       padding_scm = book_->paper_->c_variable ("between-system-padding");
-      Real padding = robust_scm2double (padding_scm, 0.0);
+      Real padding = 0;
+      SCM spacing_spec = book_->paper_->c_variable ("between-system-spacing");
+      SCM page_breaking_spacing_spec = book_->paper_->c_variable ("page-breaking-between-system-spacing");
+      Page_layout_problem::read_spacing_spec (spacing_spec, &padding, ly_symbol2scm ("padding"));
+      Page_layout_problem::read_spacing_spec (page_breaking_spacing_spec, &padding, ly_symbol2scm ("padding"));
 
       Line_division &div = current_configurations_[configuration_index];
       uncompressed_line_details_.clear ();
@@ -756,6 +807,7 @@ vsize
 Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
 {
   vsize ret = 1;
+  vsize page_starter = 0;
   Real cur_rod_height = 0;
   Real cur_spring_height = 0;
   Real cur_page_height = page_height (first_page_num, false);
@@ -763,10 +815,8 @@ Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
 
   cache_line_details (configuration);
 
-  // If the first line on a page has titles, allow them some extra space.
-  if (cached_line_details_.size ()
-      && cached_line_details_[0].compressed_nontitle_lines_count_ < cached_line_details_[0].compressed_lines_count_)
-    cur_page_height += page_top_space ();
+  if (cached_line_details_.size ())
+    cur_page_height -= min_whitespace_at_top_of_page (cached_line_details_[0]);
 
   for (vsize i = 0; i < cached_line_details_.size (); i++)
     {
@@ -774,7 +824,8 @@ Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
       Real next_rod_height = cur_rod_height + ext_len
        + ((cur_rod_height > 0) ? cached_line_details_[i].padding_: 0);
       Real next_spring_height = cur_spring_height + cached_line_details_[i].space_;
-      Real next_height = next_rod_height + (ragged () ? next_spring_height : 0);
+      Real next_height = next_rod_height + (ragged () ? next_spring_height : 0)
+       + min_whitespace_at_bottom_of_page (cached_line_details_[i]);
       int next_line_count = line_count + cached_line_details_[i].compressed_nontitle_lines_count_;
 
       if ((!too_few_lines (line_count) && (next_height > cur_page_height && cur_rod_height > 0))
@@ -785,10 +836,11 @@ Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
          line_count = cached_line_details_[i].compressed_nontitle_lines_count_;
          cur_rod_height = ext_len;
          cur_spring_height = cached_line_details_[i].space_;
+         page_starter = i;
+
          cur_page_height = page_height (first_page_num + ret, false);
+         cur_page_height -= min_whitespace_at_top_of_page (cached_line_details_[i]);
 
-         if (cached_line_details_[i].compressed_nontitle_lines_count_ < cached_line_details_[i].compressed_lines_count_)
-           cur_page_height += page_top_space ();
          ret++;
        }
       else
@@ -813,6 +865,9 @@ Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
   */
 
   cur_page_height = page_height (first_page_num + ret - 1, true);
+  cur_page_height -= min_whitespace_at_top_of_page (cached_line_details_[page_starter]);
+  cur_page_height -= min_whitespace_at_bottom_of_page (cached_line_details_.back ());
+
   Real cur_height = cur_rod_height + ((ragged_last () || ragged ()) ? cur_spring_height : 0);
   if (!too_few_lines (line_count - cached_line_details_.back ().compressed_nontitle_lines_count_)
       && cur_height > cur_page_height
@@ -962,7 +1017,7 @@ Page_breaking::space_systems_with_fixed_number_per_page (vsize configuration,
                                                         vsize first_page_num)
 {
   Page_spacing_result res;
-  Page_spacing space (page_height (first_page_num, false), page_top_space_);
+  Page_spacing space (page_height (first_page_num, false), this);
   vsize line = 0;
   vsize page = 0;
   vsize page_first_line = 0;
@@ -1015,7 +1070,7 @@ Page_breaking::pack_systems_on_least_pages (vsize configuration, vsize first_pag
   Page_spacing_result res;
   vsize page = 0;
   vsize page_first_line = 0;
-  Page_spacing space (page_height (first_page_num, false), page_top_space_);
+  Page_spacing space (page_height (first_page_num, false), this);
 
   cache_line_details (configuration);
   for (vsize line = 0; line < cached_line_details_.size (); line++)
@@ -1117,7 +1172,7 @@ Page_breaking::finalize_spacing_result (vsize configuration, Page_spacing_result
 Page_spacing_result
 Page_breaking::space_systems_on_1_page (vector<Line_details> const &lines, Real page_height, bool ragged)
 {
-  Page_spacing space (page_height, page_top_space_);
+  Page_spacing space (page_height, this);
   Page_spacing_result ret;
   int line_count = 0;
 
@@ -1173,8 +1228,8 @@ Page_breaking::space_systems_on_2_pages (vsize configuration, vsize first_page_n
   vector<int> page1_status;
   vector<int> page2_status;
 
-  Page_spacing page1 (page1_height, page_top_space_);
-  Page_spacing page2 (page2_height, page_top_space_);
+  Page_spacing page1 (page1_height, this);
+  Page_spacing page2 (page2_height, this);
   int page1_line_count = 0;
   int page2_line_count = 0;
 
@@ -1276,3 +1331,45 @@ Page_breaking::last_break_position () const
 {
   return breaks_.size () - 1;  
 }
+
+// This gives the minimum distance between the top of the
+// printable area (ie. the bottom of the top-margin) and
+// the extent box of the topmost system.
+Real
+Page_breaking::min_whitespace_at_top_of_page (Line_details const &line) const
+{
+  SCM first_system_spacing = book_->paper_->c_variable ("first-system-spacing");
+  if (line.title_)
+    first_system_spacing = book_->paper_->c_variable ("first-system-title-spacing");
+
+  Real min_distance = -infinity_f;
+  Real padding = 0;
+
+  Page_layout_problem::read_spacing_spec (first_system_spacing,
+                                         &min_distance,
+                                         ly_symbol2scm ("minimum-distance"));
+  Page_layout_problem::read_spacing_spec (first_system_spacing,
+                                         &padding,
+                                         ly_symbol2scm ("padding"));
+
+  // FIXME: take into account the height of the header
+  return max (0.0, max (padding, min_distance - line.extent_[UP]));
+}
+
+Real
+Page_breaking::min_whitespace_at_bottom_of_page (Line_details const &line) const
+{
+  SCM last_system_spacing = book_->paper_->c_variable ("last-system-spacing");
+  Real min_distance = -infinity_f;
+  Real padding = 0;
+
+  Page_layout_problem::read_spacing_spec (last_system_spacing,
+                                         &min_distance,
+                                         ly_symbol2scm ("minimum-distance"));
+  Page_layout_problem::read_spacing_spec (last_system_spacing,
+                                         &padding,
+                                         ly_symbol2scm ("padding"));
+
+  // FIXME: take into account the height of the footer
+  return max (0.0, max (padding, min_distance + line.extent_[DOWN]));
+}
diff --git a/lily/page-layout-problem.cc b/lily/page-layout-problem.cc
new file mode 100644 (file)
index 0000000..c04e6be
--- /dev/null
@@ -0,0 +1,690 @@
+/*
+  page-layout-problem.cc -- space systems nicely on a page. If systems can
+  be stretched, do that too.
+
+  source file of the GNU LilyPond music typesetter
+
+  (c) 2009 Joe Neeman <joeneeman@gmail.com>
+*/
+
+#include "page-layout-problem.hh"
+
+#include "align-interface.hh"
+#include "international.hh"
+#include "item.hh"
+#include "output-def.hh"
+#include "paper-book.hh"
+#include "pointer-group-interface.hh"
+#include "prob.hh"
+#include "skyline-pair.hh"
+#include "system.hh"
+
+Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
+  : bottom_skyline_ (DOWN)
+{
+  Prob *page = unsmob_prob (page_scm);
+  header_height_ = 0;
+  footer_height_ = 0;
+  page_height_ = 100;
+
+  if (page)
+    {
+      Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
+      Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
+
+      header_height_ = head ? head->extent (Y_AXIS).length () : 0;
+      footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
+      page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
+    }
+
+  // Initially, bottom_skyline_ represents the top of the page. Make
+  // it solid, so that the top of the first system will be forced
+  // below the top of the printable area.
+  bottom_skyline_.set_minimum_height (-header_height_);
+
+  SCM between_system_spacing = SCM_EOL;
+  SCM after_title_spacing = SCM_EOL;
+  SCM before_title_spacing = SCM_EOL;
+  SCM between_title_spacing = SCM_EOL;
+
+  // first_system_spacing controls the spring from the top of the printable
+  // area to the first staff. It allows the user to control the offset of
+  // the first staff (as opposed to the top of the first system) from the
+  // top of the page. Similarly for last_system_spacing.
+  SCM first_system_spacing = SCM_EOL;
+  SCM last_system_spacing = SCM_EOL;
+  if (pb && pb->paper_)
+    {
+      Output_def *paper = pb->paper_;
+      between_system_spacing = paper->c_variable ("between-system-spacing");
+      after_title_spacing = paper->c_variable ("after-title-spacing");
+      before_title_spacing = paper->c_variable ("before-title-spacing");
+      between_title_spacing = paper->c_variable ("between-title-spacing");
+      last_system_spacing = paper->c_variable ("last-system-spacing");
+      first_system_spacing = paper->c_variable ("first-system-spacing");
+      if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
+       first_system_spacing = paper->c_variable ("first-system-title-spacing");
+
+      // Note: the page height here does _not_ reserve space for headers and
+      // footers. This is because we want to anchor the first-system-spacing
+      // spring at the _top_ of the header.
+      page_height_ -= robust_scm2double (paper->c_variable ("top-margin"), 0)
+       + robust_scm2double (paper->c_variable ("bottom-margin"), 0);
+    }
+  bool last_system_was_title = false;
+
+
+  for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
+    {
+      bool first = (s == systems);
+
+      if (Grob *g = unsmob_grob (scm_car (s)))
+       {
+         System *sys = dynamic_cast<System*> (g);
+         if (!sys)
+           {
+             programming_error ("got a grob for vertical spacing that wasn't a System");
+             continue;
+           }
+
+         SCM spec = first ? first_system_spacing
+           : (last_system_was_title ? after_title_spacing : between_system_spacing);
+         Spring spring (first ? 0 : 1, 0.0);
+         Real padding = 0.0;
+         alter_spring_from_spacing_spec (spec, &spring);
+         read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
+
+         append_system (sys, spring, padding);
+         last_system_was_title = false;
+       }
+      else if (Prob *p = unsmob_prob (scm_car (s)))
+       {
+         SCM spec = first ? first_system_spacing
+           : (last_system_was_title ? between_title_spacing : before_title_spacing);
+         Spring spring (first ? 0 : 1, 0.0);
+         Real padding = 0.0;
+         alter_spring_from_spacing_spec (spec, &spring);
+         read_spacing_spec (spec, &padding, ly_symbol2scm ("padding"));
+
+         append_prob (p, spring, padding);
+         last_system_was_title = true;
+       }
+      else
+       programming_error ("got a system that was neither a Grob nor a Prob");
+    }
+
+  Spring last_spring (0, 0);
+  Real last_padding = 0;
+  alter_spring_from_spacing_spec (last_system_spacing, &last_spring);
+  read_spacing_spec (last_system_spacing, &last_padding, ly_symbol2scm ("padding"));
+  last_spring.ensure_min_distance (last_padding - bottom_skyline_.max_height () + footer_height_);
+  springs_.push_back (last_spring);
+}
+
+void
+Page_layout_problem::set_header_height (Real height)
+{
+  header_height_ = height;
+}
+
+void
+Page_layout_problem::set_footer_height (Real height)
+{
+  footer_height_ = height;
+}
+
+Grob*
+Page_layout_problem::find_vertical_alignment (System *sys)
+{
+  extract_grob_set (sys, "elements", elts);
+  for (vsize i = 0; i < elts.size (); ++i)
+    if (Align_interface::has_interface (elts[i]))
+      return elts[i];
+
+  return 0;
+}
+
+void
+Page_layout_problem::append_system (System *sys, Spring const& spring, Real padding)
+{
+  Grob *align = find_vertical_alignment (sys);
+  if (!align)
+    {
+      sys->programming_error ("no VerticalAlignment in system: can't do vertical spacing");
+      return;
+    }
+
+  align->set_property ("positioning-done", SCM_BOOL_T);
+
+  extract_grob_set (align, "elements", elts);
+  vector<Real> minimum_offsets = Align_interface::get_minimum_translations (align, elts, Y_AXIS,
+                                                                           false, 0, 0);
+
+  Skyline up_skyline (UP);
+  Skyline down_skyline (DOWN);
+  build_system_skyline (elts, minimum_offsets, &up_skyline, &down_skyline);
+
+  Real minimum_distance = up_skyline.distance (bottom_skyline_) + padding;
+
+  Spring spring_copy = spring;
+  spring_copy.ensure_min_distance (minimum_distance);
+  springs_.push_back (spring_copy);
+
+  bottom_skyline_ = down_skyline;
+  elements_.push_back (Element (elts, minimum_offsets));
+
+  // Add the springs for the VerticalAxisGroups in this system.
+
+  // If the user has specified the offsets of the individual staves, fix the
+  // springs at the given distances. Otherwise, use stretchable springs.
+  SCM details = get_details (elements_.back ());
+  SCM manual_dists = ly_assoc_get (ly_symbol2scm ("alignment-distances"), details, SCM_EOL);
+  vsize last_spaceable_staff = 0;
+  bool first_live_element = true;
+  for (vsize i = 0; i < elts.size (); ++i)
+    {
+      if (elts[i]->is_live () && is_spaceable (elts[i]))
+       {
+         // We don't add a spring for the first live element, since
+         // we are only adding springs _between_ staves here.
+         if (first_live_element)
+           {
+             if (i > 0)
+               add_loose_lines_as_spaceable_lines (elts, minimum_offsets, 0, i-1);
+
+             first_live_element = false;
+             last_spaceable_staff = i;
+             continue;
+           }
+
+         Spring spring (0.5, 0.0);
+         SCM spec = elts[last_spaceable_staff]->get_property ("next-staff-spacing");
+         alter_spring_from_spacing_spec (spec, &spring);
+
+         springs_.push_back (spring);
+         Real min_distance = minimum_offsets[last_spaceable_staff] - minimum_offsets[i];
+         springs_.back ().ensure_min_distance (min_distance);
+
+         if (scm_is_pair (manual_dists))
+           {
+             if (scm_is_number (scm_car (manual_dists)))
+               {
+                 Real dy = scm_to_double (scm_car (manual_dists));
+
+                 springs_.back ().set_distance (dy);
+                 springs_.back ().set_min_distance (dy);
+                 springs_.back ().set_inverse_stretch_strength (0);
+               }
+             manual_dists = scm_cdr (manual_dists);
+           }
+         last_spaceable_staff = i;
+       }
+    }
+  // Any loose lines hanging off the end are treated as spaceable
+  // lines.  This might give slightly weird results if the hanging
+  // systems have staff-affinity != UP. It's not quite clear what
+  // should happen in that case, though.
+  if (last_spaceable_staff + 1 < elts.size ())
+    add_loose_lines_as_spaceable_lines (elts, minimum_offsets,
+                                       first_live_element ? 0 : last_spaceable_staff + 1,
+                                       elts.size () - 1);
+}
+
+void
+Page_layout_problem::add_loose_lines_as_spaceable_lines (vector<Grob*> const& elts,
+                                                        vector<Real> const& minimum_offsets,
+                                                        vsize first, vsize last)
+{
+  Direction last_affinity = UP;
+  SCM last_spec = SCM_EOL;
+  bool found_live_line = false;
+  for (vsize i = first; i <= last; ++i)
+    {
+      if (elts[i]->is_live ())
+       {
+         Direction affinity = robust_scm2dir (elts[i]->get_property ("staff-affinity"), CENTER);
+         if (affinity > last_affinity)
+           {
+             warning (_ ("staff-affinities should only decrease"));
+             affinity = last_affinity;
+           }
+
+         SCM spec = elts[i]->get_property ("inter-staff-spacing");
+         if ((affinity != DOWN && found_live_line)
+             || (affinity == DOWN && i != last)) // FIXME: we want to know if we are the last _live_ line.
+           spec = elts[i]->get_property ("inter-loose-line-spacing");
+
+         Spring spring (1.0, 0.0);
+         // When affinity == DOWN, the spacing spec specifies the
+         // spacing to the staff below.
+         if (affinity == DOWN)
+           alter_spring_from_spacing_spec (last_spec, &spring);
+         else
+           alter_spring_from_spacing_spec (spec, &spring);
+
+         Real min_distance = (i > 0 ? minimum_offsets[i-1] : 0) - minimum_offsets[i];
+         spring.ensure_min_distance (min_distance);
+
+         // The first line in a system doesn't get a spring.
+         if (first > 0 || found_live_line)
+           springs_.push_back (spring);
+
+         mark_as_spaceable (elts[i]);
+         last_spec = spec;
+         last_affinity = affinity;
+         found_live_line = true;
+       }
+    }
+
+  if (found_live_line && last + 1 < elts.size ())
+    {
+      Spring spring (1.0, 0.0);
+      if (last_affinity == UP)
+       warning (_ ("a system with staff-affinity UP is stranded above the system"));
+      alter_spring_from_spacing_spec (last_spec, &spring);
+
+      Real min_distance = minimum_offsets[last] - minimum_offsets[last+1];
+      spring.ensure_min_distance (min_distance);
+      springs_.push_back (spring);
+    }
+}
+
+void
+Page_layout_problem::append_prob (Prob *prob, Spring const& spring, Real padding)
+{
+  Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
+  Real minimum_distance = 0;
+  if (sky)
+    {
+      minimum_distance = (*sky)[UP].distance (bottom_skyline_);
+      bottom_skyline_ = (*sky)[DOWN];
+    }
+  else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
+    {
+      Interval iv = sten->extent (Y_AXIS);
+      minimum_distance = iv[UP] - bottom_skyline_.max_height ();
+
+      bottom_skyline_.clear ();
+      bottom_skyline_.set_minimum_height (iv[DOWN]);
+    }
+  minimum_distance += padding;
+
+  Spring spring_copy = spring;
+  spring_copy.ensure_min_distance (minimum_distance);
+  springs_.push_back (spring_copy);
+  elements_.push_back (Element (prob));
+}
+
+void
+Page_layout_problem::solve_rod_spring_problem (bool ragged)
+{
+  Simple_spacer spacer;
+
+  for (vsize i = 0; i < springs_.size (); ++i)
+    spacer.add_spring (springs_[i]);
+
+  Real bottom_padding = 0;
+  Interval first_staff_iv (0, 0);
+  Interval last_staff_iv (0, 0);
+  if (elements_.size ())
+    {
+      first_staff_iv = first_staff_extent (elements_[0]);
+      last_staff_iv = last_staff_extent (elements_.back ());
+
+      // TODO: junk bottom-space now that we have last-spring-spacing?
+      // bottom-space has the flexibility that one can do it per-system.
+      // NOTE: bottom-space is misnamed since it is not stretchable space.
+      if (Prob *p = elements_.back ().prob)
+       bottom_padding = robust_scm2double (p->get_property ("bottom-space"), 0);
+      else if (elements_.back ().staves.size ())
+       {
+         SCM details = get_details (elements_.back ());
+         bottom_padding = robust_scm2double (ly_assoc_get (ly_symbol2scm ("bottom-space"),
+                                                           details,
+                                                           SCM_BOOL_F),
+                                             0.0);
+       }
+    }
+
+  spacer.solve (page_height_ - bottom_padding, ragged);
+  solution_ = spacer.spring_positions ();
+}
+
+// The solution_ vector stores the position of every live VerticalAxisGroup
+// and every title. From that information,
+// 1) within each system, stretch the staves so they land at the right position
+// 2) find the offset of each system (relative to the printable area of the page).
+// TODO: this function is getting too long, maybe split it up?
+SCM
+Page_layout_problem::find_system_offsets ()
+{
+  SCM system_offsets = SCM_EOL;
+  SCM *tail = &system_offsets;
+
+  // spring_idx 0 is the top of the page. Interesting values start from 1.
+  vsize spring_idx = 1;
+  for (vsize i = 0; i < elements_.size (); ++i)
+    {
+      if (elements_[i].prob)
+       {
+         *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
+         tail = SCM_CDRLOC (*tail);
+         spring_idx++;
+       }
+      else
+       {
+         // Getting this signs right here is a little tricky. The configuration
+         // we return has zero at the top of the page and positive numbers further
+         // down, as does the solution_ vector.  Within a staff, however, positive
+         // numbers are up.
+         // TODO: perhaps change the way the page 'configuration variable works so
+         // that it is consistent with the usual up/down sign conventions in
+         // Lilypond. Then this would be less confusing.
+
+         // These two positions are relative to the page (with positive numbers being
+         // down).
+         Real first_staff_position = solution_[spring_idx];
+         Real first_staff_min_translation = elements_[i].min_offsets.size () ? elements_[i].min_offsets[0] : 0;
+         Real system_position = first_staff_position + first_staff_min_translation;
+
+         // Position the staves within this system.
+         Real translation = 0;
+         bool found_live_staff = false;
+         vector<Grob*> loose_lines;
+         vector<Real> const& min_offsets = elements_[i].min_offsets;
+         vector<Real> loose_line_min_distances;
+         Grob *last_spaceable_line = 0;
+         Real last_spaceable_line_translation = 0;
+         vsize last_live_staff = 0;
+         for (vsize staff_idx = 0; staff_idx < elements_[i].staves.size (); ++staff_idx)
+           {
+             Grob *staff = elements_[i].staves[staff_idx];
+
+             // Dead VerticalAxisGroups didn't participate in the
+             // rod-and-spring problem, but they still need to be
+             // translated. We translate them by the same amount as
+             // the VerticalAxisGroup directly before.  (but we don't
+             // increment spring_idx!)
+             if (is_spaceable (staff) || !staff->is_live ())
+               {
+                 if (staff->is_live ())
+                   {
+                     // this is relative to the system: negative numbers are down.
+                     translation = system_position - solution_[spring_idx];
+                     found_live_staff = true;
+                     spring_idx++;
+
+                     // Lay out any non-spaceable lines between this line and
+                     // the last one.
+                     if (loose_lines.size ())
+                       {
+                         loose_line_min_distances.push_back (min_offsets[last_live_staff] - min_offsets[staff_idx]);
+                         distribute_loose_lines (last_spaceable_line, last_spaceable_line_translation,
+                                                 loose_lines, loose_line_min_distances,
+                                                 staff, translation);
+                         loose_lines.clear ();
+                         loose_line_min_distances.clear ();
+                       }
+                     last_spaceable_line = staff;
+                     last_spaceable_line_translation = translation;
+                     last_live_staff = staff_idx;
+                   }
+
+                 staff->translate_axis (translation, Y_AXIS);
+               }
+             // We only need to deal with loose lines if we've
+             // already found a live staff (because any loose lines
+             // that occur before our first live staff are treated as
+             // spaced lines anyway).
+             else if (found_live_staff)
+               {
+                 loose_lines.push_back (staff);
+                 loose_line_min_distances.push_back (min_offsets[last_live_staff] - min_offsets[staff_idx]);
+                 last_live_staff = staff_idx;
+               }
+           }
+
+         // Corner case: even if a system has no live staves, it still takes up
+         // one spring (a system with one live staff also takes up one spring),
+         // which we need to increment past.
+         if (!found_live_staff)
+           spring_idx++;
+
+         *tail = scm_cons (scm_from_double (system_position), SCM_EOL);
+         tail = SCM_CDRLOC (*tail);
+       }
+    }
+
+  assert (spring_idx == solution_.size () - 1);
+  return system_offsets;
+}
+
+// Given two lines that are already spaced (line_before and line_after), distribute
+// some unspaced lines between them.  If line_before is null, the unspaced lines
+// will be packed as closely as possible to line_after.  If line_after is null, the
+// unspaced lines will be packed as closely as possible to line_before.  If both are
+// null, the first loose_line will be translated to before_offset and the rest
+// of the loose_lines will be packed as closely as possible to it.
+//
+// min_distances has one more element than loose_lines; the first element of
+// min_distances contains the minimum skyline distance between line_before
+// and loose_lines[0].
+void
+Page_layout_problem::distribute_loose_lines (Grob *line_before, Real before_offset,
+                                            vector<Grob*> const &loose_lines,
+                                            vector<Real> const &min_distances,
+                                            Grob *line_after, Real after_offset)
+{
+  vector<Real> offsets;
+  assert (line_before && line_after);
+
+  Simple_spacer spacer;
+  Direction last_affinity = UP;
+  for (vsize i = 0; i < loose_lines.size (); ++i)
+    {
+      Direction affinity = robust_scm2dir (loose_lines[i]->get_property ("staff-affinity"), CENTER);
+      if (affinity > last_affinity)
+       {
+         warning (_ ("staff-affinities should only decrease"));
+         affinity = last_affinity;
+       }
+
+      SCM staff_spec = loose_lines[i]->get_property ("inter-staff-spacing");
+      SCM loose_spec = loose_lines[i]->get_property ("inter-loose-line-spacing");
+      SCM spec = loose_spec;
+      if ((i == 0 && affinity == UP)
+         || (i + 1 == loose_lines.size () && affinity != UP))
+       spec = staff_spec;
+
+      Spring spring (1.0, 0.0);
+      alter_spring_from_spacing_spec (spec, &spring);
+
+      if (affinity != last_affinity)
+       {
+         if (affinity == CENTER)
+           {
+             Spring up_spring (1.0, 0.0);
+             SCM up_spec = (i == 0) ? staff_spec : loose_spec;
+             alter_spring_from_spacing_spec (up_spec, &up_spring);
+             up_spring.ensure_min_distance (min_distances[i]);
+
+             spacer.add_spring (up_spring);
+           }
+         else if (affinity == DOWN && last_affinity == UP)
+           {
+             // Insert a very flexible spring, so it doesn't mess things up too much.
+             Spring extra_spr (1.0, min_distances[i]);
+             extra_spr.set_inverse_stretch_strength (100000);
+             extra_spr.set_inverse_compress_strength (100000);
+             spacer.add_spring (extra_spr);
+           }
+       }
+      if (affinity == UP)
+       spring.ensure_min_distance (min_distances[i]);
+      else
+       spring.ensure_min_distance (min_distances[i+1]);
+
+      spacer.add_spring (spring);
+      last_affinity = affinity;
+    }
+
+  if (last_affinity == UP)
+    {
+      Spring extra_spr (1.0, min_distances.back ());
+      extra_spr.set_inverse_stretch_strength (100000);
+      extra_spr.set_inverse_compress_strength (100000);
+      spacer.add_spring (extra_spr);
+    }
+
+  // Remember: offsets are decreasing, since we're going from UP to DOWN!
+  spacer.solve (before_offset - after_offset, false);
+
+  vector<Real> solution = spacer.spring_positions ();
+  for (vsize i = 1; i + 1 < solution.size (); ++i)
+    offsets.push_back (before_offset - solution[i]);
+
+  assert (offsets.size () == loose_lines.size ());
+  for (vsize i = 0; i < offsets.size (); ++i)
+    loose_lines[i]->translate_axis (offsets[i], Y_AXIS);
+}
+
+SCM
+Page_layout_problem::solution (bool ragged)
+{
+  solve_rod_spring_problem (ragged);
+  return find_system_offsets ();
+}
+
+// Build upper and lower skylines for a system. We don't yet know the positions
+// of the staves within the system, so we make the skyline as conservative as
+// possible. That is, for the upper skyline, we pretend that all of the staves
+// in the system are packed together close to the top system; for the lower
+// skyline, we pretend that all of the staves are packed together close to
+// the bottom system.
+//
+// The upper skyline is relative to the top staff; the lower skyline is relative to
+// the bottom staff.
+void
+Page_layout_problem::build_system_skyline (vector<Grob*> const& staves,
+                                          vector<Real> const& minimum_translations,
+                                          Skyline *up,
+                                          Skyline *down)
+{
+  if (minimum_translations.empty ())
+    return;
+
+  assert (staves.size () == minimum_translations.size ());
+  Real first_translation = minimum_translations[0];
+  Real last_dy = 0;
+
+  for (vsize i = 0; i < staves.size (); ++i)
+    {
+      Real dy = minimum_translations[i] - first_translation;
+      Grob *g = staves[i];
+      Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
+      if (sky)
+       {
+         up->raise (-dy);
+         up->merge ((*sky)[UP]);
+         up->raise (dy);
+
+         down->raise (-dy);
+         down->merge ((*sky)[DOWN]);
+         down->raise (dy);
+
+         last_dy = dy;
+       }
+    }
+
+  // Leave the down skyline at a position
+  // relative to the bottom staff.
+  down->raise (-last_dy);
+}
+
+Interval
+Page_layout_problem::prob_extent (Prob *p)
+{
+  Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
+  return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
+}
+
+Interval
+Page_layout_problem::first_staff_extent (Element const& e)
+{
+  if (e.prob)
+    return prob_extent (e.prob);
+  else if (e.staves.size ())
+    return e.staves[0]->extent (e.staves[0], Y_AXIS);
+
+  return Interval (0, 0);
+}
+
+Interval
+Page_layout_problem::last_staff_extent (Element const& e)
+{
+  if (e.prob)
+    return prob_extent (e.prob);
+  else if (e.staves.size ())
+    return e.staves.back ()->extent (e.staves.back (), Y_AXIS);
+
+  return Interval (0, 0);
+}
+
+SCM
+Page_layout_problem::get_details (Element const& elt)
+{
+  if (elt.staves.empty ())
+    return SCM_EOL;
+
+  return get_details (elt.staves.back ()->get_system ());
+}
+
+SCM
+Page_layout_problem::get_details (Grob *g)
+{
+  Grob *left_bound = dynamic_cast<Spanner*> (g)->get_bound (LEFT);
+  return left_bound->get_property ("line-break-system-details");
+}
+
+bool
+Page_layout_problem::is_spaceable (Grob *g)
+{
+  return !scm_is_number (g->get_property ("staff-affinity"));
+}
+
+void
+Page_layout_problem::mark_as_spaceable (Grob *g)
+{
+  g->set_property ("staff-affinity", SCM_BOOL_F);
+}
+
+bool
+Page_layout_problem::read_spacing_spec (SCM spec, Real* dest, SCM sym)
+{
+  SCM pair = scm_sloppy_assq (sym, spec);
+  if (scm_is_pair (pair) && scm_is_number (scm_cdr (pair)))
+    {
+      *dest = scm_to_double (scm_cdr (pair));
+      return true;
+    }
+  return false;
+}
+
+void
+Page_layout_problem::alter_spring_from_spacing_spec (SCM spec, Spring* spring)
+{
+  Real space;
+  Real stretch;
+  Real min_dist;
+  if (read_spacing_spec (spec, &space, ly_symbol2scm ("space")))
+    spring->set_distance (space);
+  if (read_spacing_spec (spec, &min_dist, ly_symbol2scm ("minimum-distance")))
+    spring->set_min_distance (min_dist);
+  spring->set_default_strength ();
+
+  if (read_spacing_spec (spec, &stretch, ly_symbol2scm ("stretchability")))
+    {
+      spring->set_inverse_stretch_strength (stretch);
+      spring->set_inverse_compress_strength (stretch);
+    }
+}
index cc84cfd9432ef19dd3bda51f881dbb79e07705c1..ab8085c1d59b92900fd46cd7ac72f59f425e3f91 100644 (file)
@@ -16,9 +16,9 @@
 void
 Page_spacing::calc_force ()
 {
-  /* If the first system contains a title, we add back in the page-top-space. */
-  bool starts_with_title = first_line_.compressed_nontitle_lines_count_ < first_line_.compressed_lines_count_;
-  Real height = starts_with_title ? page_height_ + page_top_space_ : page_height_;
+  Real height = page_height_
+    - breaker_->min_whitespace_at_top_of_page (first_line_)
+    - breaker_->min_whitespace_at_bottom_of_page (last_line_);
 
   if (rod_height_ + last_line_.bottom_padding_ >= height)
     force_ = infinity_f;
@@ -194,7 +194,7 @@ Page_spacer::calc_subproblem (vsize page, vsize line)
 {
   bool last = line == lines_.size () - 1;
   Page_spacing space (breaker_->page_height (page + first_page_num_, last),
-                     breaker_->page_top_space ());
+                     breaker_);
   Page_spacing_node &cur = state_.at (line, page);
   bool ragged = ragged_ || (ragged_last_ && last);
   int line_count = 0;
diff --git a/lily/staff-grouper-interface.cc b/lily/staff-grouper-interface.cc
new file mode 100644 (file)
index 0000000..0eae7e5
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+  staff-grouper-interface.cc -- implement Staff_grouper_interface
+
+  source file of the GNU LilyPond music typesetter
+
+  (c) 2009 Joe Neeman <joeneeman@gmail.com>
+*/
+
+#include "staff-grouper-interface.hh"
+
+#include "pointer-group-interface.hh"
+
+Grob*
+Staff_grouper_interface::get_last_grob (Grob *me)
+{
+  extract_grob_set (me, "elements", elts);
+  for (vsize i = elts.size (); i--;)
+    if (elts[i]->is_live ())
+      return elts[i];
+
+  return 0;
+}
+
+ADD_INTERFACE (Staff_grouper_interface,
+              "A grob that collects staves together.",
+
+              /* properties */
+              "between-staff-spacing "
+              "after-last-staff-spacing "
+              );
+
diff --git a/lily/system-scheme.cc b/lily/system-scheme.cc
deleted file mode 100644 (file)
index 37ec8b0..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-  system-scheme.cc -- implement scheme bindings to System
-
-  source file of the GNU LilyPond music typesetter
-
-  (c) 2007--2009 Joe Neeman <joeneeman@gmail.com>
-*/
-
-#include "align-interface.hh"
-#include "pointer-group-interface.hh"
-#include "system.hh"
-
-
-LY_DEFINE (ly_system_print, "ly:system-print",
-          1, 0, 0, (SCM system),
-          "Draw the system and return the prob containing its"
-          " stencil.")
-{
-  Grob *me = unsmob_grob (system);
-  System *me_system = dynamic_cast<System*> (me);
-  SCM_ASSERT_TYPE (me, system, SCM_ARG1, __FUNCTION__, "grob");
-
-  return me_system->get_paper_system ();
-}
-
-LY_DEFINE (ly_system_stretch, "ly:system-stretch",
-          2, 0, 0, (SCM system, SCM amount_scm),
-          "Stretch the system vertically by the given amount."
-          "  This must be called before the system is drawn (for example"
-          " with @code{ly:system-print}).")
-{
-  Grob *me = unsmob_grob (system);
-  Real amount = robust_scm2double (amount_scm, 0.0);
-  
-  extract_grob_set (me, "elements", elts);
-  for (vsize i = 0; i < elts.size (); i++)
-    if (Align_interface::has_interface (elts[i]))
-      {
-       Align_interface::stretch (elts[i], amount, Y_AXIS);
-       break;
-      }
-  return SCM_UNDEFINED;
-}
index 4ddb09245ee236f98a17c5e7cb359f8b8711ffa2..c633f49810f2409c6a4ff320fd3e20d662eee3d8 100644 (file)
@@ -744,28 +744,29 @@ SCM
 Tuplet_bracket::calc_cross_staff (SCM smob)
 {
   Grob *me = unsmob_grob (smob);
-  Grob *staff_symbol = 0;
   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)
-    return par_beam->get_property ("cross-staff");
+  if (par_beam && to_boolean (par_beam->get_property ("cross-staff")))
+    return SCM_BOOL_T;
 
   for (vsize i = 0; i < cols.size (); i++)
     {
       Grob *stem = unsmob_grob (cols[i]->get_object ("stem"));
-      if (!stem)
-       continue;
-      
-      if (to_boolean (stem->get_property ("cross-staff")))
+      if (stem && to_boolean (stem->get_property ("cross-staff")))
        return SCM_BOOL_T;
-
-      Grob *stem_staff = Staff_symbol_referencer::get_staff_symbol (stem);
-      if (staff_symbol && (stem_staff != staff_symbol))
-        return SCM_BOOL_T;
-      staff_symbol = stem_staff;
     }
+
   return SCM_BOOL_F;
 }
 
index 365c35cb4870659be13a50f311e06dcd1a0b351c..14f67333cf54c5f98f8d737fd62f80354062e937 100644 (file)
@@ -33,6 +33,8 @@ protected:
   void process_music ();
   virtual void finalize ();
   virtual void initialize ();
+
+  bool top_level_;
 };
 
 ADD_ACKNOWLEDGER (Vertical_align_engraver, axis_group);
@@ -56,6 +58,7 @@ Vertical_align_engraver::Vertical_align_engraver ()
 {
   valign_ = 0;
   id_to_group_hashtab_ = SCM_EOL;
+  top_level_ = false;
 }
 
 void
@@ -75,7 +78,9 @@ Vertical_align_engraver::process_music ()
 {
   if (!valign_)
     {
-      valign_ = make_spanner ("VerticalAlignment", SCM_EOL);
+      top_level_ = to_boolean (get_property ("topLevelAlignment"));
+
+      valign_ = make_spanner (top_level_ ? "VerticalAlignment" : "StaffGrouper", SCM_EOL);
       valign_->set_bound (LEFT, unsmob_grob (get_property ("currentCommandColumn")));
       Align_interface::set_ordered (valign_);
     }
@@ -105,7 +110,7 @@ Vertical_align_engraver::qualifies (Grob_info i) const
 void
 Vertical_align_engraver::acknowledge_axis_group (Grob_info i)
 {
-  if (qualifies (i))
+  if (top_level_ && qualifies (i))
     {
       string id = i.context ()->id_string ();
 
@@ -145,4 +150,9 @@ Vertical_align_engraver::acknowledge_axis_group (Grob_info i)
            }
        }
     }
+  else if (!top_level_)
+    {
+      Pointer_group_interface::add_grob (valign_, ly_symbol2scm ("elements"), i.grob ());
+      i.grob ()->set_object ("staff-grouper", valign_->self_scm ());
+    }
 }
index 55da3ff6f61ddd5575bd1639f9157b8811c098e8..81bdf0a4a656a780c8e08345fbd6392f5427f693 100644 (file)
@@ -10,6 +10,7 @@
   \grobdescriptions #all-grob-descriptions
 }
 
+% FIXME: replace minimum-Y-extents with proper spacing commands.
 
 \context {
   \type "Engraver_group"
@@ -64,7 +65,7 @@
 
   localKeySignature = #'()
   createSpacing = ##t
-  ignoreFiguredBassRest = ##t 
+  ignoreFiguredBassRest = ##t
   \override VerticalAxisGroup #'minimum-Y-extent = #'(-4 . 4)
   
   %% explicitly set instrument, so we don't get 
 \context {
   \type "Engraver_group"
   \name "ChoirStaff"
+  \consists "Vertical_align_engraver"
+  topLevelAlignment = ##f
+
   \consists "System_start_delimiter_engraver"
   systemStartDelimiter = #'SystemStartBracket
   vocalName = #'()
@@ -291,6 +295,7 @@ contained staves are connected vertically."
   \defaultchild "Staff"
   \accepts "Staff"
   \accepts "FiguredBass"
+  \accepts "Dynamics"
 }
 
 \context{
@@ -302,6 +307,10 @@ contained staves are connected vertically."
 instrument names at the start of each system."
 
   \consists "Instrument_name_engraver"
+  \consists "Vertical_align_engraver"
+  topLevelAlignment = ##f
+
+  \override StaffGrouper #'between-staff-spacing #'stretchability = #5
   
   instrumentName = #'()
   shortInstrumentName = #'()
@@ -311,6 +320,9 @@ instrument names at the start of each system."
   \type "Engraver_group"
   \name "StaffGroup"
 
+  \consists "Vertical_align_engraver"
+  topLevelAlignment = ##f
+
   \consists "Span_bar_engraver"
   \consists "Span_arpeggio_engraver"
   \consists "Output_property_engraver" 
@@ -337,6 +349,31 @@ staves are connected vertically.  @code{StaffGroup} only consists of
 a collection of staves, with a bracket in front and spanning bar lines."
 }
 
+\context {
+  \type "Engraver_group"
+  \name Dynamics
+  \alias Voice
+  \consists "Output_property_engraver"
+  \consists "Piano_pedal_engraver"
+  \consists "Script_engraver"
+  \consists "New_dynamic_engraver"
+  \consists "Dynamic_align_engraver"
+  \consists "Text_engraver"
+  \consists "Skip_event_swallow_translator"
+  \consists "Axis_group_engraver"
+
+  pedalSustainStrings = #'("Ped." "*Ped." "*")
+  pedalUnaCordaStrings = #'("una corda" "" "tre corde")
+  \override VerticalAxisGroup #'staff-affinity = #CENTER
+  \override DynamicLineSpanner #'Y-offset = #0
+  \override TextScript #'font-size = #2
+  \override TextScript #'font-shape = #'italic
+
+  \description "Holds a single line of dynamics, which will be
+centered between the staves surrounding this context."
+}
+
+
 \context{
   \type "Engraver_group"
   \override VerticalAxisGroup #'minimum-Y-extent = #'(-0.75 . 2.0)
@@ -359,7 +396,9 @@ printing of a single line of lyrics."
 
   \override VerticalAxisGroup #'remove-first = ##t
   \override VerticalAxisGroup #'remove-empty = ##t
-  \override VerticalAxisGroup #'keep-fixed-while-stretching = ##t
+  \override VerticalAxisGroup #'staff-affinity = #UP
+  \override VerticalAxisGroup #'inter-staff-spacing = #'((space . 5.5) (stretchability . 1) (padding . 0.5))
+  \override VerticalAxisGroup #'inter-loose-line-spacing = #'((space . 2) (stretchability . 0.5) (padding . 0.2))
   \override SeparationItem #'padding = #0.2
   \override InstrumentName #'self-alignment-Y = ##f
 
@@ -379,6 +418,8 @@ printing of a single line of lyrics."
   \consists "Axis_group_engraver"
 
   \override VerticalAxisGroup #'minimum-Y-extent = ##f
+  % FIXME: not sure what the default should be here.
+  \override VerticalAxisGroup #'staff-affinity = #DOWN
 
   
   \consists "Rest_swallow_translator" 
@@ -403,6 +444,7 @@ printing of a single line of lyrics."
   \override VerticalAxisGroup #'minimum-Y-extent = #'(0 . 2)
   \override VerticalAxisGroup #'remove-first = ##t
   \override VerticalAxisGroup #'remove-empty = ##t
+  \override VerticalAxisGroup #'staff-affinity = #DOWN
 }
 
 
@@ -618,6 +660,7 @@ automatically when an output definition (a @code{\score} or
   instrumentTransposition = #(ly:make-pitch 0 0 0)
 
   verticallySpacedContexts = #'(Staff)
+  topLevelAlignment = ##t
   
   timing = ##t
 }
@@ -638,6 +681,7 @@ automatically when an output definition (a @code{\score} or
 
   \override VerticalAxisGroup #'remove-empty = ##t
   \override VerticalAxisGroup #'remove-first = ##t
+  \override VerticalAxisGroup #'staff-affinity = #UP
   \override VerticalAxisGroup #'minimum-Y-extent = #'(0 . 2)
 }
 
index 4bd187cbbe7cf6795d2882f5db6cafe0932dfd4e..07ab1ae02fbc315e8a5266ac9ef84e7560157c39 100644 (file)
@@ -1,5 +1,5 @@
 \version "2.12.0"
-#(use-modules (scm layout-page-layout))
+
 \paper {
 
     %%% WARNING
        (is-book-title . #t)
        ))
     
-    %%
-    %% this dimension includes the extent of the
-    %% staves themselves.
-    %%
-    between-system-space = #(* 20 mm)
-    
-    
-    %%
-    %% fixed space between systems.
-    %%
-    between-system-padding = #(* 4 mm)
+    %% Note: these are not scaled; they are in staff-spaces.
+    between-system-spacing = #'((space . 12) (minimum-distance . 8) (padding . 1))
+    after-title-spacing = #'((space . 2) (padding . 0.5))
+    before-title-spacing = #'((space . 5) (padding . 0.5))
+    between-title-spacing = #'((space . 1) (padding . 0.5))
+    first-system-spacing = #'((space . 1) (padding . 0) (min-distance . 0))
+    first-system-title-spacing = #'((space . 1) (padding . 1) (min-distance . 0))
+    last-system-spacing = #'((space . 1) (padding . 0) (min-distance . 0) (stretchability . 5))
 
-    after-title-space = 5 \mm
-    before-title-space = 10 \mm
-    between-title-space = 2 \mm
-
-
-    %%
-    %% Small staves are aligned so they come out on the same place on
-    %% across different pages.
-    %%
-    page-top-space = #(* 12 mm)
-
-    
     ragged-bottom = ##f
 
     %%
@@ -97,7 +82,6 @@
        (word-space . 0.6)))
 
     #(define page-breaking ly:optimal-breaking)
-    #(define page-post-process post-process-pages)
 
     #(define write-page-layout (ly:get-option 'dump-tweaks))
     #(define system-maximum-stretch-procedure
index 9aac7314d94959844bc4fe4f178f68f41f931388..558ccec430e00d6cfea9fffddd441a0c51560b77 100644 (file)
@@ -2907,6 +2907,15 @@ def conv(str):
         stderr_write (UPDATE_MANUALLY)
     return str
 
+@rule ((2, 13, 2), _("different settings for vertical layout"))
+def conv(str):
+    if re.search(r'alignment-offsets', str):
+        stderr_write("\n")
+        stderr_write(NOT_SMART % _("alignment-offsets has been changed to alignment-distances: \
+you must now specify the distances between staves rather than the offset of staves.\n"))
+        stderr_write(UPDATE_MANUALLY)
+    return str
+
 # Guidelines to write rules (please keep this at the end of this file)
 #
 # - keep at most one rule per version; if several conversions should be done,
index fad961821e494181ea3a1b5cc7501a5039d3536d..63a447c99a36b5600d5660fc75b74c4318e76507 100644 (file)
@@ -458,6 +458,9 @@ signifying the time signature.  For example, @code{#'(4 . 4)} is a
      (timing ,boolean? "Keep administration of measure length,
 position, bar number, etc.?  Switch off for cadenzas.")
      (tonic ,ly:pitch? "The tonic of the current scale.")
+     (topLevelAlignment ,boolean? "If true, the @var{Vertical_align_engraver}
+will create a @var{VerticalAlignment}; otherwise, it will create a
+@var{StaffGrouper}")
      (trebleStaffProperties ,list? "An alist of property settings to
 apply for the up staff of @code{PianoStaff}.  Used by
 @code{\\autochange}.")
index 65fb09c4b4cf0722dac160814f9a121964825a08..98eb2c022c31f21b82af2c3ff7f7d481c33df53a 100644 (file)
 ;;
      (add-stem-support ,boolean? "If set, the @code{Stem} object is
 included in this script's support.")
+     (after-last-staff-spacing ,list? "An alist of spacing variables
+that controls the spacing after the last staff in this staff group.
+See @var{next-staff-spacing} for a description of the elements of
+this alist.")
      (after-line-breaking ,boolean? "Dummy property, used to trigger
 callback for @code{after-line-breaking}.")
      (align-dir ,ly:dir? "Which side to align? @code{-1}: left side,
@@ -95,6 +99,10 @@ beamlet, as a proportion of the distance between two adjacent stems.")
      (before-line-breaking ,boolean? "Dummy property, used to trigger
 a callback function.")
      (between-cols ,pair? "Where to attach a loose column to.")
+     (between-staff-spacing ,list? "An alist of spacing variables
+that controls the spacing between staves within this staff group.
+See @var{next-staff-spacing} for a description of the elements of
+this alist.")
      (bound-details ,list? "An alist of properties for determining
 attachments of spanners to edges.")
      (bound-padding ,number? "The amount of padding to insert around
@@ -188,6 +196,10 @@ an ending t-value, a @code{dash-fraction}, and a @code{dash-period}.")
 whitespace.  If negative, no line is drawn at all.")
      (default-direction ,ly:dir? "Direction determined by note head
 positions.")
+     (default-next-staff-spacing ,list? "An alist of spacing variables
+that controls the spacing between this staff and the next.
+See @var{next-staff-spacing} for a description of the elements of
+this alist.")
      (details ,list? "Alist of parameters for detailed grob behavior.
 More information on the allowed parameters for a grob can be found by
 looking at the top of the Internals Reference page for each interface
@@ -451,6 +463,13 @@ resolution on this @code{NoteColumn}.")
 configuration to this index, and print the respective scores.")
      (inspect-quants ,number-pair? "If debugging is set, set beam and
 slur quants to this position, and print the respective scores.")
+     (inter-loose-line-spacing ,list? "Specifies how to vertically
+position a non-spaced line relative to the other non-spaced lines
+around it.  See @var{next-staff-spacing} for the format of this list.")
+     (inter-staff-spacing ,list? "Specifies how to vertically
+position a non-spaced line relative to the staff for which it
+has affinity.  See @var{next-staff-spacing} for the format of this list.")
+
 
 
 ;;
@@ -556,6 +575,25 @@ center of the staff.")
 to flip the direction of custos stem.")
      (next ,ly:grob? "Object that is next relation (e.g., the lyric
 syllable following an extender).")
+     (next-staff-spacing ,list? "An alist of properties used to position
+the next staff in the system.  The symbols that can be defined in the alist
+are
+@itemize @bullet
+@item @var{space} -- the amount of stretchable space between the center
+of this staff and the center of the next staff;
+@item @var{padding} -- the minimum amount of whitespace that must be
+present between this staff and the next staff;
+@item @var{stretchability} -- the ease with which the stretchable
+space increases when the system to which this staff belongs is stretched.
+If this is zero, the distance to the next staff will be fixed either at
+@var{space} or at @var{padding} plus the minimum distance to ensure
+there is no overlap, whichever is larger;
+@item @var{minimum-distance} -- the minimum distance to place between
+the center of this staff and the center of the next. This differs
+from @var{padding} in that the height of a staff has no effect on
+the application of @var{minimum-distance} (whereas the height of a
+staff is crucial for @var{padding}).
+@end itemize")
      (no-alignment ,boolean? "If set, don't place this grob in a
 @code{VerticalAlignment}; rather, place it using its own
 @code{Y-offset} callback.")
@@ -703,6 +741,8 @@ duration.  Typically, the width of a note head.  See also
      (springs-and-rods ,boolean? "Dummy variable for triggering
 spacing routines.")
      (stacking-dir ,ly:dir? "Stack objects in which direction?")
+     (staff-affinity ,ly:dir? "The direction of the staff to which this
+line should stick.")
      (staff-padding ,ly:dimension? "Maintain this much space between
 reference points and the staff.  Its effect is to align objects of
 differing sizes (like the dynamics @b{p} and @b{f}) on their
@@ -921,6 +961,7 @@ layout.")
      (spacing ,ly:grob? "The spacing spanner governing this section.")
      (spacing-wishes ,ly:grob-array? "An array of note spacing or staff spacing
 objects.")
+     (staff-grouper ,ly:grob? "The staff grouper we belong to.")
      (staff-symbol ,ly:grob? "The staff symbol grob that we are in.")
      (stem ,ly:grob? "A pointer to a @code{Stem} object.")
      (stems ,ly:grob-array? "An array of stem objects.")
index 259a4be74f9acb65dcf27adece1126f422663c11..a20dc54cb00403e919a157ecfe554e163c8932e9 100644 (file)
      . (
        (axes . (,Y))
        (padding . 0.2)
-       (positioning-done . ,ly:align-interface::calc-positioning-done)
+       (positioning-done . ,ly:align-interface::align-to-minimum-distances)
        (stacking-dir . ,DOWN)
        (threshold . (2 . 1000))
        (Y-extent . ,ly:axis-group-interface::height)
                                font-interface
                                span-bar-interface))))))
 
+    (StaffGrouper
+     . (
+       (between-staff-spacing . ((space . 9) (minimum-distance . 7)))
+       (after-last-staff-spacing . ((space . 10.5) (minimum-distance . 8)))
+       (meta . ((class . Spanner)
+                (interfaces . (staff-grouper-interface))))))
+
     (StaffSpacing
      . (
        (non-musical . #t)
     (System
      . (
        (axes . (,X ,Y))
-       (max-stretch . ,ly:axis-group-interface::calc-max-stretch)
        (vertical-skylines . ,ly:axis-group-interface::calc-skylines)
        (X-extent . ,ly:axis-group-interface::width)
        (Y-extent . ,ly:axis-group-interface::height)
 
     (VerticalAlignment
      . (
-       (after-line-breaking . ,ly:align-interface::stretch-after-break)
        (axes . (,Y))
-       (max-stretch . 0)
-       (padding . 0.5)
-       (positioning-done . ,ly:align-interface::calc-positioning-done)
+       (positioning-done . ,ly:align-interface::align-to-ideal-distances)
        (stacking-dir . -1)
        (vertical-skylines . ,ly:axis-group-interface::combine-skylines)
        (X-extent . ,ly:axis-group-interface::width)
      . (
        (adjacent-pure-heights . ,ly:axis-group-interface::adjacent-pure-heights)
        (axes . (,Y))
-       (max-stretch . ,ly:axis-group-interface::calc-max-stretch)
+       (default-next-staff-spacing . ((space . 9) (minimum-distance . 8)))
+       (next-staff-spacing . ,ly:axis-group-interface::calc-next-staff-spacing)
        (stencil . ,ly:axis-group-interface::print)
        (vertical-skylines . ,ly:hara-kiri-group-spanner::calc-skylines)
        (X-extent . ,ly:axis-group-interface::width)
diff --git a/scm/layout-page-dump.scm b/scm/layout-page-dump.scm
deleted file mode 100644 (file)
index 387a038..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-;;;; layout-page-dump.scm -- page breaking and page layout
-;;;;
-;;;;  source file of the GNU LilyPond music typesetter
-;;;;
-;;;; (c) 2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
-;;;;    2006 Nicolas Sceaux <nicolas.sceaux@free.fr>
-
-(define-module (scm layout-page-dump)
-  #:use-module (srfi srfi-1)
-  #:use-module (ice-9 pretty-print)
-  #:use-module (scm paper-system)
-  #:use-module (scm page)
-  #:use-module (scm layout-page-layout)
-  #:use-module (lily)
-  #:export (write-page-breaks
-           ;; utilities for writing other page dump functions
-           record-tweaks dump-all-tweaks))
-
-(define (record-tweaks what property-pairs tweaks)
-  (let ((key (ly:output-def-lookup (ly:grob-layout what)
-                                  'tweak-key
-                                  "tweaks"))
-       (when (ly:grob-property what 'when)))
-    (if (not (hash-ref tweaks key))
-       (hash-set! tweaks key '()))
-    (hash-set! tweaks key
-              (acons when property-pairs
-                     (hash-ref tweaks key)))))
-
-(define (graceless-moment mom)
-  (ly:make-moment (ly:moment-main-numerator mom)
-                 (ly:moment-main-denominator mom)
-                 0 0))
-
-(define (moment->skip mom)
-  (let ((main (if (> (ly:moment-main-numerator mom) 0)
-                 (format "\\skip 1*~a/~a"
-                         (ly:moment-main-numerator mom)
-                         (ly:moment-main-denominator mom))
-                   ""))
-       (grace (if (< (ly:moment-grace-numerator mom) 0)
-                  (format "\\grace { \\skip 1*~a/~a }"
-                          (- (ly:moment-grace-numerator mom))
-                          (ly:moment-grace-denominator mom))
-                  "")))
-    (format "~a~a" main grace)))
-
-(define (dump-tweaks out-port tweak-list last-moment)
-  (if (not (null? tweak-list))
-      (let* ((now (caar tweak-list))
-            (diff (ly:moment-sub now last-moment))
-            (these-tweaks (cdar tweak-list))
-            (skip (moment->skip diff))
-            (line-break-str (if (assoc-get 'line-break these-tweaks #f)
-                                "\\break\n"
-                                ""))
-            (page-break-str (if (assoc-get 'page-break these-tweaks #f)
-                                "\\pageBreak\n"
-                                ""))
-            (space-tweaks (format "\\spacingTweaks #'~a\n"
-                                  (with-output-to-string
-                                    (lambda ()
-                                      (pretty-print
-                                       (assoc-get 'spacing-parameters
-                                                  these-tweaks '()))))))
-            (base (format "~a~a~a"
-                          line-break-str
-                          page-break-str
-                          space-tweaks)))
-       (format out-port "~a\n~a\n" skip base)
-       (dump-tweaks out-port (cdr tweak-list) (graceless-moment now)))))
-
-(define (dump-all-tweaks pages tweaks output-name)
-  (let* ((paper (ly:paper-book-paper (page-property (car pages) 'paper-book)))
-        (name (format "~a-page-layout.ly" output-name))
-        (out-port (open-output-file name)))
-    
-    (ly:message "Writing page layout to ~a" name)
-    (hash-for-each
-     (lambda (key val)
-       (format out-port "~a = {" key)
-       (dump-tweaks out-port (reverse val) (ly:make-moment 0 1))
-       (display "}" out-port))
-     tweaks)
-    (close-port out-port)))
-
-(define (write-page-breaks pages output-name)
-  "Dump page breaks and tweaks"
-  (let ((tweaks (make-hash-table 60)))
-    (define (handle-page page)
-      "Computes vertical stretch for each music line of `page' (starting by
-      the smallest lines), then record the tweak parameters  of each line to
-      the `tweaks' hash-table."
-      (let* ((lines (page-property page 'lines))
-            (line-count (length lines))
-            (compute-max-stretch (ly:output-def-lookup
-                                  (ly:paper-book-paper (page-property page
-                                                                      'paper-book))
-                                   'system-maximum-stretch-procedure))
-            (page-number (page-property page 'page-number)))
-       (let set-line-stretch! ((sorted-lines (sort lines
-                                                   (lambda (l1 l2)
-                                                     (< (line-height l1)
-                                                        (line-height l2)))))
-                               (rest-height ;; sum of stretchable line heights
-                                (reduce + 0.0
-                                        (map line-height
-                                             (filter stretchable-line? lines))))
-                               (space-left (page-maximum-space-left page)))
-         (if (not (null? sorted-lines))
-             (let* ((line (first sorted-lines))
-                    (height (line-height line))
-                    (stretch (min (compute-max-stretch line)
-                                  (if (and (stretchable-line? line)
-                                           (positive? rest-height))
-                                      (/ (* height space-left) rest-height)
-                                      0.0))))
-               (set! (ly:prob-property line 'stretch) stretch)
-               (set-line-stretch! (cdr sorted-lines)
-                                  (if (stretchable-line? line)
-                                      (- rest-height height)
-                                      rest-height)
-                                  (- space-left stretch)))))
-       (let record-line-tweak ((lines lines)
-                               (is-first-line #t)
-                               (index 0))
-         (if (not (null? lines))
-             (let ((line (first lines)))
-               (if (not (ly:prob-property? line 'is-title))
-                   (record-tweaks
-                    (ly:spanner-bound (ly:prob-property line 'system-grob) LEFT)
-                    `((line-break . #t)
-                      (page-break . ,is-first-line)
-                      (spacing-parameters
-                       . ((page-number . ,page-number)
-                          (system-index . ,index)
-                          (system-stretch . ,(ly:prob-property line 'stretch))
-                          (system-Y-extent . ,(paper-system-extent line Y))
-                          (system-refpoint-Y-extent . ,(paper-system-staff-extents line))
-                          (page-system-count . ,line-count)
-                          (page-printable-height . ,(page-printable-height page))
-                          (page-space-left . ,(page-property page 'space-left)))))
-                    tweaks))
-               (record-line-tweak (cdr lines) #f (1+ index)))))))
-    ;; Compute tweaks for each page, then dump them to the page-layout file
-    (for-each handle-page pages)
-    (dump-all-tweaks pages tweaks output-name)))
diff --git a/scm/layout-page-layout.scm b/scm/layout-page-layout.scm
deleted file mode 100644 (file)
index d1c18ab..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-;;;; layout-page-layout.scm -- page breaking and page layout
-;;;;
-;;;;  source file of the GNU LilyPond music typesetter
-;;;;
-;;;; (c) 2004--2009 Jan Nieuwenhuizen <janneke@gnu.org>
-;;;;         Han-Wen Nienhuys <hanwen@xs4all.nl>
-
-(define-module (scm layout-page-layout)
-  #:use-module (srfi srfi-1)
-  #:use-module (oop goops describe)
-  #:use-module (oop goops)
-  #:use-module (scm paper-system)
-  #:use-module (scm page)
-  #:use-module (scm layout-page-dump)
-  #:use-module (lily)
-  #:export (post-process-pages optimal-page-breaks make-page-from-systems
-           page-breaking-wrapper
-           stretchable-line? ; delete me
-           ;; utilities for writing custom page breaking functions
-            line-height line-next-space line-next-padding
-           line-minimum-distance line-ideal-distance
-           first-line-position
-           line-ideal-relative-position line-minimum-relative-position
-           page-maximum-space-to-fill page-maximum-space-left space-systems))
-
-; this is for 2-pass spacing. Delete me.
-(define (stretchable-line? line)
-  "Say whether a system can be stretched."
-  (not (or (ly:prob-property? line 'is-title)
-          (let ((system-extent (paper-system-staff-extents line)))
-            (= (interval-start system-extent)
-               (interval-end   system-extent))))))
-
-(define (stretch-and-draw-page paper-book systems page-number ragged
-                               is-last-bookpart is-bookpart-last-page)
-  (define (max-stretch sys)
-    (if (ly:grob? sys)
-       (ly:grob-property sys 'max-stretch)
-       0.0))
-
-  (define (stretchable? sys)
-    (and (ly:grob? sys)
-        (> (max-stretch sys) 0.0)))
-
-  (define (height-estimate sys)
-    (interval-length
-     (if (ly:grob? sys)
-        (ly:grob-property sys 'pure-Y-extent)
-        (paper-system-extent sys Y))))
-
-  (define (print-system sys)
-    (if (ly:grob? sys)
-       (ly:system-print sys)
-       sys))
-
-  (define (set-line-stretch! sorted-lines rest-height space-left)
-    (if (not (null? sorted-lines))
-       (let* ((line (first sorted-lines))
-              (height (height-estimate line))
-              (stretch (min (max-stretch line)
-                            (if (positive? rest-height)
-                                (/ (* height space-left) rest-height)
-                                  0.0))))
-         (if (stretchable? line)
-             (ly:system-stretch line stretch))
-         (set-line-stretch! (cdr sorted-lines)
-                            (if (stretchable? line)
-                                (- rest-height height)
-                                rest-height)
-                            (- space-left stretch)))))
-
-  (define (total-padding systems)
-    (let ((layout (ly:paper-book-paper paper-book)))
-      (if (or (null? systems)
-             (null? (cdr systems)))
-         0.0
-         (+ (line-next-padding (car systems) (cadr systems) layout)
-            (total-padding (cdr systems))))))
-
-  (let* ((page (make-page paper-book
-                         'page-number page-number
-                         'is-last-bookpart is-last-bookpart
-                         'is-bookpart-last-page is-bookpart-last-page))
-        (paper (ly:paper-book-paper paper-book))
-        (height (page-printable-height page))
-        ; there is a certain amount of impreciseness going on here:
-        ; the system heights are estimated, we aren't using skyline distances
-        ; yet, etc. If we overstretch because of underestimation, the result
-        ; is very bad. So we stick in some extra space, just to be sure.
-        (buffer (/ height 10.0))
-        (total-system-height (+ (apply + (map height-estimate systems))
-                                (total-padding systems)))
-        (height-left (- height total-system-height buffer)))
-
-    (if (and
-        (not ragged)
-        (> height-left 0))
-       (set-line-stretch! (sort systems
-                                (lambda (s1 s2)
-                                  (< (height-estimate s1)
-                                     (height-estimate s2))))
-                          (apply + (map height-estimate
-                                        (filter stretchable? systems)))
-                          height-left))
-
-    (let ((lines (map print-system systems)))
-      (page-set-property! page 'lines lines)
-      (page-set-property!
-       page 'configuration 
-       (if (null? lines)
-           (list)
-           (let* ((paper (ly:paper-book-paper paper-book))
-                  (max-space-to-fill (page-maximum-space-to-fill page lines paper))
-                  (space-to-fill (if (ly:output-def-lookup
-                                      paper 'page-limit-inter-system-space #f)
-                                     (min max-space-to-fill
-                                          (* (ly:output-def-lookup
-                                              paper 'page-limit-inter-system-space-factor 1.4)
-                                             (- max-space-to-fill
-                                                (or (page-ideal-space-left page) 0))))
-                                     max-space-to-fill))
-                  (spacing (space-systems space-to-fill lines ragged paper #f)))
-             (if (and (> (length lines) 1)
-                      (or (not (car spacing)) (inf? (car spacing))))
-                 (begin
-                   (ly:warning (_ "Can't fit systems on page -- ignoring between-system-padding"))
-                   (cdr (space-systems space-to-fill lines ragged paper #t)))
-                 (cdr spacing)))))
-      page)))
-
-(define (post-process-pages layout pages)
-  "If the write-page-layout paper variable is true, dumps page breaks
-  and tweaks."
-
-  (let*
-      ((parser (ly:modules-lookup (list (current-module)) 'parser))
-       (output-name (ly:parser-output-name parser)) 
-       )
-
-    (if (ly:output-def-lookup layout 'write-page-layout #f)
-       (write-page-breaks pages output-name))))
-
-;;;
-;;; Utilities for computing line distances and positions
-;;;
-(define (line-extent line)
-  "Return the extent of the line (its lowest and highest Y-coordinates)."
-  (paper-system-extent line Y))
-
-(define (line-height line)
-  "Return the system height, that is the length of its vertical extent."
-  (interval-length (line-extent line)))
-
-(define (line-next-space line next-line layout)
-  "Return space to use between `line' and `next-line'.
-  `next-line' can be #f, meaning that `line' is the last line."
-  (let* ((title (paper-system-title? line))
-        (next-title (and next-line (paper-system-title? next-line))))
-    (ly:prob-property
-     line 'next-space
-     (ly:output-def-lookup layout 
-                          (cond ((and title next-title) 'between-title-space)
-                                (title 'after-title-space)
-                                (next-title 'before-title-space)
-                                (else 'between-system-space))))))
-
-(define (line-next-padding line next-line layout)
-  "Return padding to use between `line' and `next-line'.
-  `next-line' can be #f, meaning that `line' is the last line."
-  (let ((default (ly:output-def-lookup layout 'between-system-padding)))
-    (if (ly:grob? line)
-       (let* ((details (ly:grob-property line 'line-break-system-details))
-              (padding (assq 'next-padding details)))
-         (if padding
-             (cdr padding)
-             default))
-       (ly:prob-property line 'next-padding default))))
-
-
-(define (line-minimum-distance line next-line layout ignore-padding)
-  "Minimum distance between `line' reference position and `next-line'
- reference position. If next-line is #f, return #f."
-  (and next-line
-       (let ((padding (if ignore-padding
-                         0
-                         (line-next-padding line next-line layout))))
-        (if (or (ly:grob? line) (ly:grob? next-line))
-            (max 0 (+ padding
-                      (- (interval-start (line-extent line))
-                         (interval-end (line-extent next-line)))))
-            (max 0 (+ padding
-                      (ly:paper-system-minimum-distance line next-line)))))))
-
-(define (line-ideal-distance line next-line layout ignore-padding)
-  "Ideal distance between `line' reference position and `next-line'
- reference position. If next-line is #f, return #f."
-  (and next-line
-       (max 0
-           (+ (- (+ (interval-end (paper-system-staff-extents next-line))
-                    (if ignore-padding 0 (line-next-padding line next-line layout)))
-                 (interval-start (paper-system-staff-extents line)))
-              (line-next-space line next-line layout)))))
-
-(define (first-line-position line layout)
-  "Position of the first line on page"
-  (max (+ (if (ly:prob-property? line 'is-title)
-             ;; do not use page-top-space if first line is a title
-             0.0
-           (ly:output-def-lookup layout 'page-top-space))
-         (interval-end (paper-system-staff-extents line)))
-       (interval-end (line-extent line))))
-
-(define (line-ideal-relative-position line prev-line layout ignore-padding)
-  "Return ideal position of `line', relative to `prev-line' position.
-  `prev-line' can be #f, meaning that `line' is the first line."
-  (if (not prev-line)
-      ;; first line on page
-      (first-line-position line layout)
-      ;; not the first line on page
-      (max (line-minimum-distance prev-line line layout ignore-padding)
-          (line-ideal-distance prev-line line layout ignore-padding))))
-
-(define (line-minimum-relative-position line prev-line layout ignore-padding)
-  "Return position of `line', relative to `prev-line' position.
-  `prev-line' can be #f, meaning that `line' is the first line."
-  (if (not prev-line)
-      ;; first line on page
-      (first-line-position line layout)
-      ;; not the first line on page
-      (line-minimum-distance prev-line line layout ignore-padding)))
-
-(define (line-position-on-page line prev-line prev-position page relative-positionning-fn)
-  "If `line' fits on `page' after `prev-line', which position on page is
-  `prev-position', then return the line's postion on page, otherwise #f.
-  `prev-line' can be #f, meaning that `line' is the first line."
-  (let* ((layout (ly:paper-book-paper (page-property page 'paper-book)))
-         (position (+ (relative-positionning-fn line prev-line layout #f)
-                      (if prev-line prev-position 0.0)))
-         (bottom-position (- position
-                             (interval-start (line-extent line)))))
-    position))
-
-(define (page-maximum-space-to-fill page lines paper)
-  "Return the space between the first line top position and the last line
-  bottom position. This constitutes the maximum space to fill on `page'
-  with `lines'."
-  (let ((last-line (car (last-pair lines))))
-    (- (page-printable-height page)
-       (first-line-position (first lines) paper)
-       (ly:prob-property last-line
-                        'bottom-space 0.0)
-       (- (interval-start (line-extent last-line))))))
-
-(define (page-space-left page relative-positionning-fn)
-  (let ((paper (ly:paper-book-paper (page-property page 'paper-book))))
-    (let bottom-position ((lines (page-property page 'lines))
-                          (prev-line #f)
-                          (prev-position #f))
-      (if (null? lines)
-          (page-printable-height page)
-          (let* ((line (first lines))
-                 (position (line-position-on-page
-                            line prev-line prev-position page relative-positionning-fn)))
-            (if (null? (cdr lines))
-                (max 0
-                     (- (page-printable-height page)
-                        (- position
-                           (interval-start (line-extent line)))))
-                (bottom-position (cdr lines) line position)))))))
-
-(define (page-maximum-space-left page)
-  (page-space-left page line-minimum-relative-position))
-
-(define (page-ideal-space-left page)
-  (page-space-left page line-ideal-relative-position))
-
-;;;
-;;; Utilities for distributing systems on a page
-;;;
-
-(define (space-systems space-to-fill lines ragged paper ignore-padding)
-  "Compute lines positions on page: return force and line positions as a pair.
- force is #f if lines do not fit on page."
-  (let* ((empty-stencil (ly:make-stencil '() '(0 . 0) '(0 . 0)))
-        (empty-prob (ly:make-prob 'paper-system (list `(stencil . ,empty-stencil))))
-        (cdr-lines (append (cdr lines)
-                           (if (<= (length lines) 1)
-                               (list empty-prob)
-                               '())))
-        (springs (map (lambda (prev-line line)
-                        (list (line-ideal-distance prev-line line paper ignore-padding)
-                              (line-next-space prev-line line paper)))
-                      lines
-                      cdr-lines))
-        (rods (map (let ((i -1))
-                     (lambda (prev-line line)
-                       (set! i (1+ i))
-                       (list i (1+ i)
-                             (line-minimum-distance prev-line line paper ignore-padding))))
-                      lines
-                      cdr-lines))
-        (space-result
-         (ly:solve-spring-rod-problem springs rods space-to-fill ragged)))
-    (cons (car space-result)
-         (map (let ((topskip (first-line-position (first lines) paper)))
-                (lambda (y)
-                  (+ y topskip)))
-              (cdr space-result)))))
-
-
-;;;
-;;; Page breaking function
-;;;
-
-;; Optimal distribution of
-;; lines over pages; line breaks are a given.
-
-;; TODO:
-;;
-;; - density scoring
-;; - separate function for word-wrap style breaking?
-;; - ragged-bottom? ragged-last-bottom?
-
-(define (get-path node done)
-  "Follow NODE.PREV, and return as an ascending list of pages. DONE
-is what have collected so far, and has ascending page numbers."
-  (if (page? node)
-      (get-path (page-prev node) (cons node done))
-      done))
-
-(define (combine-penalties force user best-paths
-                          inter-system-space force-equalization-factor)
-  (let* ((prev-force (if (null? best-paths)
-                        0.0
-                        (page-force (car best-paths))))
-        (prev-penalty (if (null? best-paths)
-                          0.0
-                          (page-penalty (car best-paths))))
-        (relative-force (/ force inter-system-space))
-        (abs-relative-force (abs relative-force)))
-    (+ (* abs-relative-force (+ abs-relative-force 1))
-       prev-penalty
-       (* force-equalization-factor (/ (abs (- prev-force force))
-                                      inter-system-space))
-       user)))
-
-(define (walk-paths done-lines best-paths current-lines is-last-bookpart
-                   is-bookpart-last-page current-best paper-book page-alist)
-  "Return the best optimal-page-break-node that contains
-CURRENT-LINES. DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
-ascending range of lines, and BEST-PATHS contains the optimal breaks
-corresponding to DONE-LINES.
-
-CURRENT-BEST is the best result sofar, or #f."
-  (let* ((paper (ly:paper-book-paper paper-book))
-        (this-page (make-page
-                    paper-book
-                    'is-last-bookpart is-last-bookpart
-                    'is-bookpart-last-page is-bookpart-last-page
-                    'page-number (if (null? best-paths)
-                                     (ly:output-def-lookup paper 'first-page-number)
-                                     (1+ (page-page-number (first best-paths))))))
-        (ragged-all (eq? #t (ly:output-def-lookup paper 'ragged-bottom)))
-        (ragged-last (eq? #t (ly:output-def-lookup paper 'ragged-last-bottom)))
-        (ragged (or ragged-all (and ragged-last is-bookpart-last-page)))
-        (space-to-fill (page-maximum-space-to-fill this-page current-lines paper))
-        (vertical-spacing (space-systems space-to-fill current-lines ragged paper #f))
-        (satisfied-constraints (car vertical-spacing))
-        (force (if satisfied-constraints
-                   (if (and is-bookpart-last-page ragged-last)
-                       0.0
-                       satisfied-constraints)
-                   10000))
-        (positions (cdr vertical-spacing))
-        (get-break-penalty (lambda (sys)
-                             (ly:prob-property sys 'penalty 0.0)))
-        (user-nobreak-penalties (- (apply + (filter negative?
-                                                    (map get-break-penalty
-                                                         (cdr current-lines))))))
-        (user-penalty (+ (max (get-break-penalty (car current-lines)) 0.0)
-                         user-nobreak-penalties))
-        (total-penalty (combine-penalties
-                        force user-penalty best-paths
-                        (ly:output-def-lookup paper 'between-system-space)
-                        (ly:output-def-lookup paper 'verticalequalizationfactor 0.3)))
-        (new-best (if (or (not current-best)
-                          (and satisfied-constraints
-                               (< total-penalty (page-penalty current-best))))
-                      (begin
-                        (map (lambda (x)
-                               (page-set-property! this-page
-                                                   (car x)
-                                                   (cdr x)))
-                             (list (cons 'prev (if (null? best-paths)
-                                                   #f
-                                                   (car best-paths)))
-                                   (cons 'lines current-lines)
-                                   (cons 'force force)
-                                   (cons 'configuration positions)
-                                   (cons 'penalty total-penalty)))
-                        this-page)
-                      current-best)))
-    (if #f ;; debug
-       (display
-        (list
-         "\nuser pen " user-penalty
-         "\nsatisfied-constraints" satisfied-constraints
-         "\nlast? " is-bookpart-last-page "ragged?" ragged
-         "\nis-better " is-better " total-penalty " total-penalty "\n"
-         "\nconfig " positions
-         "\nforce " force
-         "\nlines: " current-lines "\n")))
-    (if #f ; debug
-       (display (list "\nnew-best is " (page-lines new-best)
-                      "\ncontinuation of "
-                      (if (null? best-paths)
-                          "start"
-                          (page-lines (car best-paths))))))
-    (if (and (pair? done-lines)
-            ;; if this page is too full, adding another line won't help
-            satisfied-constraints)
-       (walk-paths (cdr done-lines) (cdr best-paths)
-                   (cons (car done-lines) current-lines)
-                   is-last-bookpart is-bookpart-last-page new-best
-                   paper-book page-alist)
-       new-best)))
-
-(define (walk-lines done best-paths todo paper-book page-alist is-last-bookpart)
-  "Return the best page breaking as a single
-page node for optimally breaking TODO ++
-DONE.reversed. BEST-PATHS is a list of break nodes corresponding to
-DONE."
-  (if (null? todo)
-      (car best-paths)
-      (let* ((this-line (car todo))
-            (is-bookpart-last-page (null? (cdr todo)))
-            (next (walk-paths done best-paths (list this-line) is-last-bookpart
-                              is-bookpart-last-page #f paper-book page-alist)))
-       (walk-lines (cons this-line done)
-                   (cons next best-paths)
-                   (cdr todo)
-                   paper-book
-                   page-alist
-                   is-last-bookpart))))
-
-(define-public (optimal-page-breaks paper-book)
-  "Return pages as a list starting with 1st page. Each page is a 'page Prob."
-  (let* ((paper (ly:paper-book-paper paper-book))
-        (lines (ly:paper-book-systems paper-book))
-        (page-alist (layout->page-init paper)) 
-        (force-equalization-factor (ly:output-def-lookup
-                                    paper 'verticalequalizationfactor 0.3))
-         (is-last-bookpart (ly:output-def-lookup paper 'is-last-bookpart)))
-    (ly:message (_ "Calculating page breaks..."))
-    (let* ((best-break-node (walk-lines '() '() lines paper-book page-alist is-last-bookpart))
-          (break-nodes (get-path best-break-node '())))
-      (if #f; (ly:get-option 'verbose)
-         (begin
-           (display (list
-                     "\nbreaks: " (map (lambda (node)
-                                         (ly:prob-property (car (page-lines node))
-                                                           'number))
-                                       break-nodes)
-                     "\nsystems " (map page-lines break-nodes)
-                     "\npenalties " (map page-penalty break-nodes)
-                     "\nconfigs " (map page-configuration break-nodes)))))
-      ;; construct page stencils.
-      (for-each page-stencil break-nodes)
-      break-nodes)))
index a4862198010c206a36b72febae29b8d3ee3aeefa..3d897c1465919af7bdd1359334460978d4eba690 100644 (file)
    (zip (page-property page 'lines)
        (page-property page 'configuration))))
 
+(define (annotate-top-space first-system layout header-stencil stencil)
+  (let* ((top-margin (ly:output-def-lookup layout 'top-margin))
+        (sym (if (paper-system-title? first-system)
+                 'first-system-title-spacing
+                 'first-system-spacing))
+        (spacing-spec (ly:output-def-lookup layout sym))
+        (X-offset (ly:prob-property first-system 'X-offset 5))
+        (header-extent (ly:stencil-extent header-stencil Y)))
+
+    (set! stencil
+         (ly:stencil-add stencil
+                         (ly:stencil-translate-axis
+                          (annotate-spacing-spec layout
+                                                 spacing-spec
+                                                 (- top-margin)
+                                                 (car header-extent)
+                                                 #:base-color red)
+                          X-offset X)))
+    stencil))
+
+
 (define (annotate-page layout stencil)
   (let ((top-margin (ly:output-def-lookup layout 'top-margin))
        (paper-height (ly:output-def-lookup layout 'paper-height))
 
     arrow))
 
-\f
-
-
-(define (page-headfoot layout scopes number sym separation-symbol dir
-                      is-last-bookpart is-bookpart-last-page)
-  
-  "Create a stencil including separating space."
-
-  (let* ((header-proc (ly:output-def-lookup layout sym))
-        (sep (ly:output-def-lookup layout separation-symbol))
-        (stencil (ly:make-stencil "" '(0 . 0) '(0 . 0)))
-        (head-stencil
-         (if (procedure? header-proc)
-             (header-proc layout scopes number is-last-bookpart is-bookpart-last-page)
-             #f)))
-    
-    (if (and (number? sep)
-            (ly:stencil? head-stencil)
-            (not (ly:stencil-empty? head-stencil)))
-
-       (begin
-         (set! head-stencil
-               (ly:stencil-combine-at-edge
-                stencil Y dir head-stencil
-                sep))
-
-         
-         ;; add arrow markers 
-         (if (or (annotate? layout)
-                 (ly:output-def-lookup layout 'annotate-headers #f)) 
-             (set! head-stencil
-                   (ly:stencil-add
-                    (ly:stencil-translate-axis
-                     (annotate-y-interval layout 
-                                          (symbol->string separation-symbol)
-                                          (cons (min 0 (* dir sep))
-                                                (max 0 (* dir sep)))
-                                          #t)
-                     (/ (ly:output-def-lookup layout 'line-width) 2)
-                     X)
-                    (if (= dir UP)
-                        (ly:stencil-translate-axis
-                         (annotate-y-interval layout
-                                             "page-top-space"
-                                             (cons
-                                              (- (min 0 (* dir sep))
-                                                 (ly:output-def-lookup layout 'page-top-space))
-                                              (min 0 (* dir sep)))
-                                             #t)
-                         (+ 7 (interval-center (ly:stencil-extent head-stencil X))) X)
-                        empty-stencil
-                        )
-                    head-stencil
-                    ))
-             )))
-
-    head-stencil))
 
 (define (page-header-or-footer page dir)
     (let*
        (scopes (ly:paper-book-scopes paper-book))
        (number (page-page-number page))
        (is-last-bookpart (page-property page 'is-last-bookpart))
-       (is-bookpart-last-page (page-property page 'is-bookpart-last-page)))
-       
-      (page-headfoot layout scopes number
-               (if (= dir UP)
-                   'make-header
-                   'make-footer)
-               (if (= dir UP)
-                   'head-separation
-                   'foot-separation)
-               dir is-last-bookpart is-bookpart-last-page)))
+       (is-bookpart-last-page (page-property page 'is-bookpart-last-page))
+       (sym (if (= dir UP)
+               'make-header
+               'make-footer))
+       (header-proc (ly:output-def-lookup layout sym)))
+
+      (if (procedure? header-proc)
+         (header-proc layout scopes number is-last-bookpart is-bookpart-last-page)
+         #f)))
+
 
 (define (page-header page)
   (page-header-or-footer page UP))
                                            (ly:stencil-translate stencil
                                                                  (cons
                                                                   (+ system-xoffset x)
-                                                                  (- 0 head-height y (prop 'top-margin)))
+                                                                  (- 0 y (prop 'top-margin)))
                                                                  
                                                                  )))))
        (add-system
        )
 
     (if (and
-        (or (annotate? layout)
-            (ly:output-def-lookup layout 'annotate-systems #f))
+        (ly:stencil? head)
+        (not (ly:stencil-empty? head)))
+       (begin
+         (set! head (ly:stencil-translate-axis head
+                                               (- 0 head-height (prop 'top-margin)) Y))
+         (set! page-stencil (ly:stencil-add page-stencil head))))
+
+    (if (and
+        (annotate? layout)
         (pair? lines))
 
        (begin
+         (set! page-stencil (annotate-top-space (car lines) layout head page-stencil))
+
          (for-each (lambda (sys next-sys)
                      (paper-system-annotate sys next-sys layout))
                    lines
                    (append (cdr lines) (list #f)))
          (paper-system-annotate-last (car (last-pair lines)) layout)))
 
-    (if (and
-        (ly:stencil? head)
-        (not (ly:stencil-empty? head)))
-       
-       (set! page-stencil (ly:stencil-add page-stencil 
-                                          (ly:stencil-translate-axis head
-                                                                     (- 0 head-height (prop 'top-margin)) Y))))
-                                          
     (map add-system lines)
 
 
          (ly:stencil-translate page-stencil (cons (prop 'left-margin) 0)))
 
     ;; annotation.
-    (if (or (annotate? layout)
-           (ly:output-def-lookup layout 'annotate-page #f))
+    (if (annotate? layout)
        (set! page-stencil (annotate-page layout page-stencil)))
 
-
     page-stencil))
               
 
index 6a391a3a0f41099739022ab74027e33b287e8f66..d7436a224fef055c6ce80953e55eede70d064f1c 100644 (file)
     (set! (ly:prob-property system 'stencil)
          stencil)
   ))
-  
+
+; TODO: annotate the spacing for every spaceable staff within the system.
 (define-public (paper-system-annotate system next-system layout)
   "Add arrows and texts to indicate which lengths are set."
   (let* ((annotations (list))
-        (annotate-extent-and-space
-         (lambda (extent-accessor next-space
-                                  extent-name next-space-name after-space-name)
-           (let* ((extent-annotations (list))
-                  (this-extent (extent-accessor system))
-                  (next-extent (and next-system (extent-accessor next-system)))
-                  (push-annotation (lambda (stil)
-                                     (set! extent-annotations
-                                           (cons stil extent-annotations))))
-                  (color (if (paper-system-title? system) darkblue blue))
-                  (space-color (if (paper-system-title? system) darkred red)))
-             (if (and (number-pair? this-extent)
-                      (not (= (interval-start this-extent)
-                              (interval-end this-extent))))
-                 (push-annotation (annotate-y-interval
-                                   layout extent-name this-extent #f
-                                   #:color color)))
-             (if next-system
-                 (push-annotation (annotate-y-interval
-                                   layout next-space-name
-                                   (interval-translate (cons (- next-space) 0)
-                                                       (if (number-pair? this-extent)
-                                                           (interval-start this-extent)
-                                                           0))
-                                   #t
-                                   #:color color)))
-             (if (and next-system
-                      (number-pair? this-extent)
-                      (number-pair? next-extent))
-                 (let ((space-after
-                        (- (+ (ly:prob-property next-system 'Y-offset)
-                              (interval-start this-extent))
-                           (ly:prob-property system 'Y-offset)
-                           (interval-end next-extent)
-                           next-space)))
-                   (if (> space-after 0.01)
-                       (push-annotation (annotate-y-interval
-                                         layout
-                                         after-space-name
-                                         (interval-translate
-                                          (cons (- space-after) 0)
-                                          (- (interval-start this-extent)
-                                             next-space))
-                                         #t
-                                         #:color space-color)))))
-             (if (not (null? extent-annotations))
-                 (set! annotations
-                       (stack-stencils X RIGHT 0.5
-                                       (list annotations
-                                             (ly:make-stencil '() (cons 0 1) (cons 0 0))
-                                             (apply ly:stencil-add
-                                                    extent-annotations))))))))
-
         (grob (ly:prob-property system 'system-grob))
         (estimate-extent (if (ly:grob? grob)
                              (annotate-y-interval layout
                                                   (ly:grob-property grob 'pure-Y-extent)
                                                   #f)
                              #f)))
-    (let ((next-space (ly:prob-property
-                      system 'next-space
-                      (cond ((and next-system
-                                  (paper-system-title? system)
-                                  (paper-system-title? next-system))
-                             (ly:output-def-lookup layout 'between-title-space))
-                            ((paper-system-title? system)
-                             (ly:output-def-lookup layout 'after-title-space))
-                            ((and next-system
-                                  (paper-system-title? next-system))
-                             (ly:output-def-lookup layout 'before-title-space))
-                            (else
-                             (ly:output-def-lookup layout 'between-system-space)))))
-         (next-padding (ly:prob-property
-                        system 'next-padding
-                        (ly:output-def-lookup layout 'between-system-padding))))
-      (annotate-extent-and-space (lambda (sys)
-                                  (paper-system-extent sys Y))
-                                next-padding
-                                "Y-extent" "next-padding" "space after next-padding")
-      (annotate-extent-and-space paper-system-staff-extents
-                                (+ next-space next-padding)
-                                "staff-refpoint-extent" "next-space+padding"
-                                "space after next-space+padding"))
+    (let* ((spacing-spec (cond ((and next-system
+                                    (paper-system-title? system)
+                                    (paper-system-title? next-system))
+                               (ly:output-def-lookup layout 'between-title-spacing))
+                              ((paper-system-title? system)
+                               (ly:output-def-lookup layout 'after-title-spacing))
+                              ((and next-system
+                                    (paper-system-title? next-system))
+                               (ly:output-def-lookup layout 'before-title-spacing))
+                              (else
+                               (ly:output-def-lookup layout 'between-system-spacing))))
+          (last-staff-Y (car (paper-system-staff-extents system))))
+
+      (set! annotations
+           (annotate-spacing-spec layout spacing-spec last-staff-Y (car (paper-system-extent system Y)))))
     (if estimate-extent
        (set! annotations
              (stack-stencils X RIGHT 0.5
index d3e299ddd80c4bd4d0646b16101067f9e014620d..48f4a46cb1a5645ccf7808cf36d64ec7f70b7d65 100644 (file)
@@ -6,12 +6,7 @@
 
 (define-public (set-paper-dimension-variables mod)
   (module-define! mod 'dimension-variables
-                 '(after-title-space
-                   before-title-space
-                   between-system-padding
-                   between-system-space
-                   between-title-space
-                   blot-diameter
+                 '(blot-diameter
                    bottom-margin
                    cm
                    foot-separation
@@ -24,7 +19,6 @@
                    line-thickness
                    line-width
                    mm
-                   page-top-space
                    paper-height
                    paper-width
                    pt
index 105f6f5893555deade31e7f9b5e5e97aed13d8ff..0f02a55505e1d871c4c4fea03db85aab560409d9 100644 (file)
@@ -261,12 +261,15 @@ encloses the contents.
      stencil)
     ))
 
-(define-public (dimension-arrows destination) 
+(define-public (dimension-arrows destination max-size
   "Draw twosided arrow from here to @var{destination}"
   
   (let*
       ((e_x 1+0i)
        (e_y 0+1i)
+       (distance (sqrt (+ (* (car destination) (car destination))
+                         (* (cdr destination) (cdr destination)))))
+       (size (min max-size (/ distance 3)))
        (rotate (lambda (z ang)
                 (* (make-polar 1 ang)
                    z)))
@@ -275,9 +278,10 @@ encloses the contents.
        
        (z-dest (+ (* e_x (car destination)) (* e_y (cdr destination))))
        (e_z (/ z-dest (magnitude z-dest)))
-       (triangle-points '(-1+0.25i
-                         0
-                         -1-0.25i))
+       (triangle-points (list
+                        (* size -1+0.25i)
+                        0
+                        (* size -1-0.25i)))
        (p1s (map (lambda (z)
                   (+ z-dest (rotate z (angle z-dest))))
                 triangle-points))
@@ -295,8 +299,8 @@ encloses the contents.
         `(polygon (quote ,(concatenate (map complex-to-offset p2s)))
                   0.0
                   #t) null null ) )
-       (thickness 0.1)
-       (shorten-line 0.5)
+       (thickness (min (/ distance 12) 0.1))
+       (shorten-line (min (/ distance 3) 0.5))
        (start (complex-to-offset (/ (* e_z shorten-line) 2)))
        (end (complex-to-offset (- z-dest (/ (* e_z shorten-line) 2))))
        
@@ -352,7 +356,7 @@ encloses the contents.
                                                (ly:format "(~$,~$)"
                                                        (car extent) (cdr extent)))))))
              (arrows (ly:stencil-translate-axis 
-                      (dimension-arrows (cons 0 (interval-length extent)))
+                      (dimension-arrows (cons 0 (interval-length extent)) 1.0)
                       (interval-start extent) Y)))
          (set! annotation
                 (center-stencil-on-extent text-stencil))
@@ -369,6 +373,38 @@ encloses the contents.
     annotation))
 
 
+(define*-public (annotate-spacing-spec layout spacing-spec start-Y-offset prev-system-end
+                                     #:key (base-color blue))
+  (let* ((get-spacing-var (lambda (sym) (assoc-get sym spacing-spec 0.0)))
+        (space (get-spacing-var 'space))
+        (padding (get-spacing-var 'padding))
+        (min-dist (get-spacing-var 'minimum-distance))
+        (contrast-color (append (cdr base-color) (list (car base-color)))))
+    (stack-stencils X RIGHT 0.0
+                   (list
+                    (annotate-y-interval layout
+                                         "space"
+                                         (cons (- start-Y-offset space) start-Y-offset)
+                                         #t
+                                         #:color (map (lambda (x) (* x 0.25)) base-color))
+                    (annotate-y-interval layout
+                                         "min-dist"
+                                         (cons (- start-Y-offset min-dist) start-Y-offset)
+                                         #t
+                                         #:color (map (lambda (x) (* x 0.5)) base-color))
+                    (ly:stencil-add
+                     (annotate-y-interval layout
+                                          "bottom-of-extent"
+                                          (cons prev-system-end start-Y-offset)
+                                          #t
+                                          #:color base-color)
+                     (annotate-y-interval layout
+                                          "padding"
+                                          (cons (- prev-system-end padding) prev-system-end)
+                                          #t
+                                          #:color contrast-color))))))
+
+
 (define-public (eps-file->stencil axis size file-name)
   (let*
       ((contents (ly:gulp-file file-name))