]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/page-layout-problem.cc
Doc-es: various updates.
[lilypond.git] / lily / page-layout-problem.cc
index c9301ff3843f6812c1c39a129df9511c01817aca..479ac72bf72ec1152a3b4bd78affeae26fe78262 100644 (file)
@@ -1,7 +1,7 @@
 /*
   This file is part of LilyPond, the GNU music typesetter.
 
-  Copyright (C) 2009--2011 Joe Neeman <joeneeman@gmail.com>
+  Copyright (C) 2009--2015 Joe Neeman <joeneeman@gmail.com>
 
   LilyPond is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 #include "skyline-pair.hh"
 #include "system.hh"
 #include "text-interface.hh"
+#include "lily-imports.hh"
 
 /*
- Returns the number of footntoes associated with a given line.
+ Returns the number of footnotes associated with a given line.
 */
 
-vsize
-Page_layout_problem::get_footnote_count (SCM lines)
+vector<Grob *>
+Page_layout_problem::get_footnote_grobs (SCM lines)
 {
-  vsize fn_count = 0;
+  vector<Grob *> footnotes;
   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
     {
-      if (Grob *g = unsmob_grob (scm_car (s)))
+      if (Grob *g = unsmob<Grob> (scm_car (s)))
         {
           System *sys = dynamic_cast<System *> (g);
           if (!sys)
@@ -52,31 +53,75 @@ Page_layout_problem::get_footnote_count (SCM lines)
               programming_error ("got a grob for footnotes that wasn't a System");
               continue;
             }
-          fn_count += sys->num_footnotes ();
+          extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
+          footnotes.insert (footnotes.end (), footnote_grobs.begin (), footnote_grobs.end ());
         }
-      else if (Prob *p = unsmob_prob (scm_car (s)))
+      else if (Prob *p = unsmob<Prob> (scm_car (s)))
         {
           SCM stencils = p->get_property ("footnotes");
-          if (stencils == SCM_EOL)
+          if (scm_is_null (stencils))
             continue;
           for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
-            fn_count++;
+            footnotes.push_back (0);
         }
     }
 
-  return fn_count;
+  return footnotes;
+}
+
+vsize
+Page_layout_problem::get_footnote_count (SCM lines)
+{
+  vector<Grob *> notes = get_footnote_grobs (lines);
+  return notes.size ();
+}
+
+SCM
+Page_layout_problem::get_footnotes_from_lines (SCM lines)
+{
+  if (!scm_is_pair (lines))
+    return SCM_EOL;
+
+  bool footnotes_added;
+  if (Grob *g = unsmob<Grob> (scm_car (lines)))
+    footnotes_added = !scm_is_null (g->get_property ("footnote-stencil"));
+  else if (Prob *p = unsmob<Prob> (scm_car (lines)))
+    footnotes_added = !scm_is_null (p->get_property ("footnote-stencil"));
+  else
+    {
+      programming_error ("Systems on a page must be a prob or grob.");
+      return SCM_EOL;
+    }
+  if (!footnotes_added)
+    {
+      programming_error ("Footnotes must be added to lines before they are retrieved.");
+      return SCM_EOL;
+    }
+
+  SCM out = SCM_EOL;
+  for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
+    {
+      if (Grob *g = unsmob<Grob> (scm_car (s)))
+        out = scm_cons (g->get_property ("footnote-stencil"), out);
+      else if (Prob *p = unsmob<Prob> (scm_car (s)))
+        out = scm_cons (p->get_property ("footnote-stencil"), out);
+      else
+        programming_error ("Systems on a page must be a prob or grob.");
+    }
+
+  return scm_reverse_x (out, SCM_EOL);
 }
 
 /*
-   Returns a stencil for the footnote of each system.  This stencil may
+   Adds a footnote stencil to each system.  This stencil may
    itself be comprised of several footnotes.
 
    This is a long function, but it seems better to keep it intact rather than
    splitting it into parts.
 */
 
-SCM
-Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_book *pb)
+void
+Page_layout_problem::add_footnotes_to_lines (SCM lines, int counter, Paper_book *pb)
 {
   /*
     first, we have to see how many footnotes are on this page.
@@ -88,7 +133,7 @@ Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_boo
   if (!paper)
     {
       programming_error ("Cannot get footnotes because there is no valid paper block.");
-      return SCM_EOL;
+      return;
     }
 
   SCM number_footnote_table = pb->top_paper ()->c_variable ("number-footnote-table");
@@ -96,18 +141,16 @@ Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_boo
     number_footnote_table = SCM_EOL;
   SCM numbering_function = paper->c_variable ("footnote-numbering-function");
   SCM layout = paper->self_scm ();
-  SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
-                          paper->self_scm ());
+  SCM props = Lily::layout_extract_page_properties (layout);
   Real padding = robust_scm2double (paper->c_variable ("footnote-padding"), 0.0);
   Real number_raise = robust_scm2double (paper->c_variable ("footnote-number-raise"), 0.0);
 
-  vsize fn_count = get_footnote_count (lines);
+  vector<Grob *> fn_grobs = get_footnote_grobs (lines);
+  vsize fn_count = fn_grobs.size ();
 
   // now, make the footnote stencils with the numbering function
   SCM numbers = SCM_EOL;
   SCM in_text_numbers = SCM_EOL;
-  bool do_numbering = to_boolean (paper->c_variable ("footnote-auto-numbering"));
-  // if there's no numbering, skip all this
   /*
     TODO: This recalculates numbering every time this function is called, including once
     after the balloon prints are called.  Although it is not a huge computational drain,
@@ -118,54 +161,59 @@ Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_boo
     in duplicated work, either by making this process less complicated or (preferably)
     by passing its results downstream.
   */
-  if (do_numbering)
+
+  // find the maximum X_AXIS length
+  Real max_length = -infinity_f;
+
+  for (vsize i = 0; i < fn_count; i++)
     {
-      vector<SCM> footnote_number_markups; // Holds the numbering markups.
-      vector<Stencil *> footnote_number_stencils; // Holds translated versions of the stencilized numbering markups.
-      for (vsize i = 0; i < fn_count; i++)
+      if (fn_grobs[i])
         {
-          SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
-          Stencil *s = unsmob_stencil (Text_interface::interpret_markup (layout, props, markup));
-          if (!s)
-            {
-              programming_error ("Your numbering function needs to return a stencil.");
-              markup = SCM_EOL;
-              s = new Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL);
-            }
-          footnote_number_markups.push_back (markup);
-          footnote_number_stencils.push_back (s);
-          counter++;
+          SCM assertion_function = fn_grobs[i]->get_property ("numbering-assertion-function");
+          if (ly_is_procedure (assertion_function))
+            (void) scm_call_1 (assertion_function, scm_from_int (counter));
         }
+      SCM markup = scm_call_1 (numbering_function, scm_from_int (counter));
+      SCM stencil = Text_interface::interpret_markup (layout, props, markup);
+      Stencil *st = unsmob<Stencil> (stencil);
+      if (!st)
+        {
+          programming_error ("Your numbering function needs to return a stencil.");
+          markup = SCM_EOL;
+          stencil = Stencil (Box (Interval (0, 0), Interval (0, 0)), SCM_EOL).smobbed_copy ();
+          st = unsmob<Stencil> (stencil);
+        }
+      in_text_numbers = scm_cons (markup, in_text_numbers);
+      numbers = scm_cons (stencil, numbers);
 
-      // find the maximum X_AXIS length
-      Real max_length = -infinity_f;
-      for (vsize i = 0; i < fn_count; i++)
-        max_length = max (max_length, footnote_number_stencils[i]->extent (X_AXIS).length ());
+      if (!st->extent (X_AXIS).is_empty ())
+        max_length = max (max_length, st->extent (X_AXIS)[RIGHT]);
 
-      /*
-        translate each stencil such that it attains the correct maximum length and bundle the
-        footnotes into a scheme object.
-      */
-      SCM *tail = &numbers;
-      SCM *in_text_tail = &in_text_numbers;
+      counter++;
+    }
 
-      for (vsize i = 0; i < fn_count; i++)
-        {
-          *in_text_tail = scm_cons (footnote_number_markups[i], SCM_EOL);
-          in_text_tail = SCM_CDRLOC (*in_text_tail);
-          footnote_number_stencils[i]->translate_axis (max_length - footnote_number_stencils[i]->extent (X_AXIS).length (), X_AXIS);
-          *tail = scm_cons (footnote_number_stencils[i]->smobbed_copy (), SCM_EOL);
-          tail = SCM_CDRLOC (*tail);
-        }
+  in_text_numbers = scm_reverse_x (in_text_numbers, SCM_EOL);
+  numbers = scm_reverse_x (numbers, SCM_EOL);
+
+  /*
+    translate each stencil such that it attains the correct maximum length and bundle the
+    footnotes into a scheme object.
+  */
+
+  for (SCM p = numbers; scm_is_pair (p); p = scm_cdr (p))
+    {
+      Stencil *st = unsmob<Stencil> (scm_car (p));
+      if (!st->extent (X_AXIS).is_empty ())
+        st->translate_axis ((max_length - st->extent (X_AXIS)[RIGHT]),
+                            X_AXIS);
     }
-  // build the footnotes
 
-  SCM footnotes = SCM_EOL;
+  // build the footnotes
 
   for (SCM s = lines; scm_is_pair (s); s = scm_cdr (s))
     {
       // Take care of musical systems.
-      if (Grob *g = unsmob_grob (scm_car (s)))
+      if (Grob *g = unsmob<Grob> (scm_car (s)))
         {
           System *sys = dynamic_cast<System *> (g);
           if (!sys)
@@ -174,25 +222,30 @@ Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_boo
               continue;
             }
           Stencil mol;
-
-          for (vsize i = 0; i < sys->footnote_grobs ()->size (); i++)
+          Stencil in_note_mol;
+          extract_grob_set (sys, "footnotes-after-line-breaking", footnote_grobs);
+          for (vsize i = 0; i < footnote_grobs.size (); i++)
             {
-              Grob *footnote = sys->footnote_grobs ()->at (i);
+              Grob *footnote = footnote_grobs[i];
               SCM footnote_markup = footnote->get_property ("footnote-text");
               if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
                 if (orig->is_broken ())
                   footnote_markup = orig->broken_intos_[0]->get_property ("footnote-text");
 
-              if (!Text_interface::is_markup (footnote_markup))
-                continue;
-
-              SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
-                                      paper->self_scm ());
+              SCM props = Lily::layout_extract_page_properties (paper->self_scm ());
 
               SCM footnote_stl = Text_interface::interpret_markup (paper->self_scm (),
                                                                    props, footnote_markup);
 
-              Stencil *footnote_stencil = unsmob_stencil (footnote_stl);
+              Stencil footnote_stencil = *unsmob<Stencil> (footnote_stl);
+              bool do_numbering = to_boolean (footnote->get_property ("automatically-numbered"));
+              if (Spanner *orig = dynamic_cast<Spanner *>(footnote))
+                {
+                  if (orig->is_broken ())
+                    for (vsize i = 0; i < orig->broken_intos_.size (); i++)
+                      do_numbering = do_numbering
+                                     || to_boolean (orig->broken_intos_[i]->get_property ("automatically-numbered"));
+                }
               if (do_numbering)
                 {
                   SCM annotation_scm = scm_car (in_text_numbers);
@@ -205,78 +258,90 @@ Page_layout_problem::get_footnotes_from_lines (SCM lines, int counter, Paper_boo
                           orig->broken_intos_[i]->set_property ("text", annotation_scm);
                     }
 
-                  Stencil *annotation = unsmob_stencil (scm_car (numbers));
-                  annotation->translate_axis (footnote_stencil->extent (Y_AXIS)[UP] + number_raise - annotation->extent (Y_AXIS)[UP], Y_AXIS);
-                  footnote_stencil->add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
+                  Stencil annotation = *unsmob<Stencil> (scm_car (numbers));
+                  annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
+                                              + number_raise
+                                              - annotation.extent (Y_AXIS)[UP]),
+                                             Y_AXIS);
+                  footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
                   numbers = scm_cdr (numbers);
                   in_text_numbers = scm_cdr (in_text_numbers);
                 }
-              mol.add_at_edge (Y_AXIS, DOWN, *footnote_stencil, padding);
+              if (!footnote_stencil.is_empty ())
+                {
+                  if (to_boolean (footnote->get_property ("footnote")))
+                    mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
+                  else
+                    in_note_mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
+                }
             }
-          footnotes = scm_cons (mol.smobbed_copy (), footnotes);
+          sys->set_property ("in-note-stencil", in_note_mol.smobbed_copy ());
+          sys->set_property ("footnote-stencil", mol.smobbed_copy ());
         }
       // Take care of top-level markups
-      else if (Prob *p = unsmob_prob (scm_car (s)))
+      else if (Prob *p = unsmob<Prob> (scm_car (s)))
         {
           SCM stencils = p->get_property ("footnotes");
-          if (stencils == SCM_EOL)
-            continue;
-          Stencil footnote_stencil;
+          Stencil mol;
 
           for (SCM st = stencils; scm_is_pair (st); st = scm_cdr (st))
             {
-              Stencil mol;
-              Stencil *footnote = unsmob_stencil (scm_cadar (st));
-              mol.add_stencil (*footnote);
+              Stencil footnote_stencil = *unsmob<Stencil> (scm_caddar (st));
+              bool do_numbering = to_boolean (scm_cadar (st));
+              SCM in_text_stencil = Stencil ().smobbed_copy ();
               if (do_numbering)
                 {
-                  Stencil *annotation = unsmob_stencil (scm_car (numbers));
+                  Stencil annotation = *unsmob<Stencil> (scm_car (numbers));
                   SCM in_text_annotation = scm_car (in_text_numbers);
-                  SCM in_text_stencil = Text_interface::interpret_markup (layout, props, in_text_annotation);
-                  if (!unsmob_stencil (in_text_stencil))
+                  in_text_stencil = Text_interface::interpret_markup (layout,
+                                                                      props,
+                                                                      in_text_annotation);
+                  if (!unsmob<Stencil> (in_text_stencil))
                     in_text_stencil = SCM_EOL;
-                  number_footnote_table = scm_cons (scm_cons (scm_caar (st), in_text_stencil), number_footnote_table);
-                  annotation->translate_axis (mol.extent (Y_AXIS)[UP] + number_raise - annotation->extent (Y_AXIS)[UP], Y_AXIS);
-                  mol.add_at_edge (X_AXIS, LEFT, *annotation, 0.0);
+                  annotation.translate_axis ((footnote_stencil.extent (Y_AXIS)[UP]
+                                              + number_raise
+                                              - annotation.extent (Y_AXIS)[UP]),
+                                             Y_AXIS);
+                  footnote_stencil.add_at_edge (X_AXIS, LEFT, annotation, 0.0);
                   numbers = scm_cdr (numbers);
                   in_text_numbers = scm_cdr (in_text_numbers);
                 }
-              footnote_stencil.add_at_edge (Y_AXIS, DOWN, mol, padding);
+              number_footnote_table = scm_cons (scm_cons (scm_caar (st),
+                                                          in_text_stencil),
+                                                number_footnote_table);
+              if (!footnote_stencil.is_empty ())
+                mol.add_at_edge (Y_AXIS, DOWN, footnote_stencil, padding);
             }
-          footnotes = scm_cons (footnote_stencil.smobbed_copy (), footnotes);
+          p->set_property ("footnote-stencil", mol.smobbed_copy ());
         }
     }
 
   // note that this line of code doesn't do anything if numbering isn't turned on
   pb->top_paper ()->set_variable (ly_symbol2scm ("number-footnote-table"), number_footnote_table);
-  if (!scm_is_pair (footnotes))
-    return SCM_EOL;
-
-  return scm_reverse (footnotes);
 }
 
-Stencil *
+Stencil
 Page_layout_problem::get_footnote_separator_stencil (Output_def *paper)
 {
-  SCM props = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
-                          paper->self_scm ());
+  SCM props = Lily::layout_extract_page_properties (paper->self_scm ());
 
   SCM markup = paper->c_variable ("footnote-separator-markup");
 
   if (!Text_interface::is_markup (markup))
-    return NULL;
+    return Stencil ();
 
   SCM footnote_stencil = Text_interface::interpret_markup (paper->self_scm (),
                                                            props, markup);
 
-  Stencil *footnote_separator = unsmob_stencil (footnote_stencil);
+  Stencil *footnote_separator = unsmob<Stencil> (footnote_stencil);
 
-  return footnote_separator;
+  return footnote_separator ? *footnote_separator : Stencil ();
 }
 
-void
-Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Paper_book *pb)
+Stencil
+Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil foot, Paper_book *pb)
 {
+
   bool footnotes_found = false;
   Real footnote_padding = robust_scm2double (pb->paper_->c_variable ("footnote-padding"), 0.0);
   Real footnote_footer_padding = robust_scm2double (pb->paper_->c_variable ("footnote-footer-padding"), 0.0);
@@ -285,53 +350,57 @@ Page_layout_problem::add_footnotes_to_footer (SCM footnotes, Stencil *foot, Pape
 
   for (SCM s = footnotes; scm_is_pair (s); s = scm_cdr (s))
     {
-      Stencil *stencil = unsmob_stencil (scm_car (s));
+      Stencil *stencil = unsmob<Stencil> (scm_car (s));
 
       if (!stencil)
         continue;
 
       if (!stencil->is_empty ())
         {
-          foot->add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
+          foot.add_at_edge (Y_AXIS, UP, *stencil, (!footnotes_found ? footnote_footer_padding : footnote_padding));
           footnotes_found = true;
         }
     }
 
   if (footnotes_found)
     {
-      Stencil *separator = get_footnote_separator_stencil (pb->paper_);
-      if (separator)
-        foot->add_at_edge (Y_AXIS, UP, *separator, footnote_padding);
+      Stencil separator = get_footnote_separator_stencil (pb->paper_);
+      if (!separator.is_empty ())
+        foot.add_at_edge (Y_AXIS, UP, separator, footnote_padding);
     }
+
+  return foot;
 }
 
-Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems, int footnote_count)
+Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM systems)
   : bottom_skyline_ (DOWN)
 {
-  Prob *page = unsmob_prob (page_scm);
+  Prob *page = unsmob<Prob> (page_scm);
+  bottom_loose_baseline_ = 0;
   header_height_ = 0;
   footer_height_ = 0;
   header_padding_ = 0;
   footer_padding_ = 0;
   page_height_ = 100;
+  force_ = 0;
 
   if (page)
     {
-      Stencil *head = unsmob_stencil (page->get_property ("head-stencil"));
-      Stencil *foot = unsmob_stencil (page->get_property ("foot-stencil"));
+      Stencil *head = unsmob<Stencil> (page->get_property ("head-stencil"));
+      Stencil *foot = unsmob<Stencil> (page->get_property ("foot-stencil"));
+
+      Stencil foot_stencil = foot ? *foot : Stencil ();
 
       if (pb && pb->paper_)
         {
-          if (to_boolean (pb->paper_->c_variable ("reset-footnotes-on-new-page")))
-            footnote_count = 0;
-          SCM footnotes = get_footnotes_from_lines (systems, footnote_count, pb);
-          add_footnotes_to_footer (footnotes, foot, pb);
+          SCM footnotes = get_footnotes_from_lines (systems);
+          foot_stencil = add_footnotes_to_footer (footnotes, foot_stencil, pb);
         }
       else
-        warning ("A page layout problem has been initiated that cannot accommodate footnotes.");
+        warning (_ ("A page layout problem has been initiated that cannot accommodate footnotes."));
 
       header_height_ = head ? head->extent (Y_AXIS).length () : 0;
-      footer_height_ = foot ? foot->extent (Y_AXIS).length () : 0;
+      footer_height_ = foot_stencil.extent (Y_AXIS).length ();
       page_height_ = robust_scm2double (page->get_property ("paper-height"), 100);
     }
 
@@ -362,7 +431,7 @@ Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM syst
       markup_markup_spacing = paper->c_variable ("markup-markup-spacing");
       last_bottom_spacing = paper->c_variable ("last-bottom-spacing");
       top_system_spacing = paper->c_variable ("top-system-spacing");
-      if (scm_is_pair (systems) && unsmob_prob (scm_car (systems)))
+      if (scm_is_pair (systems) && unsmob<Prob> (scm_car (systems)))
         top_system_spacing = paper->c_variable ("top-markup-spacing");
 
       // Note: the page height here does _not_ reserve space for headers and
@@ -373,14 +442,16 @@ Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM syst
 
       read_spacing_spec (top_system_spacing, &header_padding_, ly_symbol2scm ("padding"));
       read_spacing_spec (last_bottom_spacing, &footer_padding_, ly_symbol2scm ("padding"));
+      in_note_padding_ = robust_scm2double (paper->c_variable ("in-note-padding"), 0.5);
+      in_note_direction_ = robust_scm2dir (paper->c_variable ("in-note-direction"), UP);
     }
   bool last_system_was_title = false;
 
   for (SCM s = systems; scm_is_pair (s); s = scm_cdr (s))
     {
-      bool first = (s == systems);
+      bool first = scm_is_eq (s, systems);
 
-      if (Grob *g = unsmob_grob (scm_car (s)))
+      if (Grob *g = unsmob<Grob> (scm_car (s)))
         {
           System *sys = dynamic_cast<System *> (g);
           if (!sys)
@@ -406,7 +477,7 @@ Page_layout_problem::Page_layout_problem (Paper_book *pb, SCM page_scm, SCM syst
           append_system (sys, spring, indent, padding);
           last_system_was_title = false;
         }
-      else if (Prob *p = unsmob_prob (scm_car (s)))
+      else if (Prob *p = unsmob<Prob> (scm_car (s)))
         {
           SCM spec = first ? top_system_spacing
                      : (last_system_was_title ? markup_markup_spacing : score_markup_spacing);
@@ -465,7 +536,7 @@ Page_layout_problem::set_footer_height (Real height)
 void
 Page_layout_problem::append_system (System *sys, Spring const &spring, Real indent, Real padding)
 {
-  Grob *align = sys->get_vertical_alignment ();
+  Grob *align = unsmob<Grob> (sys->get_object ("vertical-alignment"));
   if (!align)
     return;
 
@@ -481,6 +552,18 @@ Page_layout_problem::append_system (System *sys, Spring const &spring, Real inde
   build_system_skyline (elts, minimum_offsets_with_min_dist, &up_skyline, &down_skyline);
   up_skyline.shift (indent);
   down_skyline.shift (indent);
+  Stencil *in_note_stencil = unsmob<Stencil> (sys->get_property ("in-note-stencil"));
+
+  if (in_note_stencil && in_note_stencil->extent (Y_AXIS).length () > 0)
+    {
+      sys->set_property ("in-note-padding", scm_from_double (in_note_padding_));
+      sys->set_property ("in-note-direction", scm_from_int (in_note_direction_));
+      Skyline *sky = in_note_direction_ == UP ? &up_skyline : &down_skyline;
+      sky->set_minimum_height (sky->max_height ()
+                               + in_note_direction_
+                               * (in_note_padding_
+                                  + in_note_stencil->extent (Y_AXIS).length ()));
+    }
 
   /*
     We need to call distance with skyline-horizontal-padding because
@@ -488,14 +571,28 @@ Page_layout_problem::append_system (System *sys, Spring const &spring, Real inde
     of an individual staff.  So we add the padding for the distance check
     at the time of adding in the system.
   */
-  Real minimum_distance = up_skyline.distance (bottom_skyline_, robust_scm2double (sys->get_property ("skyline-horizontal-padding"), 0)) + padding;
+  Real minimum_distance = up_skyline.distance (bottom_skyline_,
+                                               robust_scm2double (sys->get_property ("skyline-horizontal-padding"),
+                                                   0))
+                          + padding;
 
   Spring spring_copy = spring;
   spring_copy.ensure_min_distance (minimum_distance);
   springs_.push_back (spring_copy);
 
+  if (elts.size () && !is_spaceable (elts[0]))
+    {
+      // store the minimum distance, considering relative indents,
+      // for a loose line
+      Skyline first_skyline (UP);
+      Skyline_pair *sky = unsmob<Skyline_pair> (elts[0]->get_property ("vertical-skylines"));
+      if (sky)
+        first_skyline.merge ((*sky)[UP]);
+      first_skyline.shift (indent);
+      minimum_distance = first_skyline.distance (bottom_skyline_) - bottom_loose_baseline_;
+    }
   bottom_skyline_ = down_skyline;
-  elements_.push_back (Element (elts, minimum_offsets, padding));
+  elements_.push_back (Element (elts, minimum_offsets, minimum_distance, padding));
 
   // Add the springs for the VerticalAxisGroups in this system.
 
@@ -509,12 +606,17 @@ Page_layout_problem::append_system (System *sys, Spring const &spring, Real inde
     {
       if (is_spaceable (elts[i]))
         {
-          // We don't add a spring for the first staff, since
-          // we are only adding springs _between_ staves here.
           if (!found_spaceable_staff)
             {
+              // Ensure space for any loose lines above this system
+              if (i > 0)
+                springs_.back ().ensure_min_distance (bottom_loose_baseline_
+                                                      - minimum_offsets_with_min_dist[i]
+                                                      + padding);
               found_spaceable_staff = true;
               last_spaceable_staff = i;
+              // We don't add a spring for the first staff, since
+              // we are only adding springs _between_ staves here.
               continue;
             }
 
@@ -542,9 +644,12 @@ Page_layout_problem::append_system (System *sys, Spring const &spring, Real inde
         }
     }
 
+  bottom_loose_baseline_ = found_spaceable_staff
+                           ? ( minimum_offsets_with_min_dist[last_spaceable_staff]
+                               - minimum_offsets_with_min_dist.back ())
+                           : 0;
+
   // Corner case: there was only one staff, and it wasn't spaceable.
-  // Mark it spaceable, because we do not allow non-spaceable staves
-  // to be at the top or bottom of a system.
   if (!found_spaceable_staff && elts.size ())
     mark_as_spaceable (elts[0]);
 }
@@ -552,16 +657,17 @@ Page_layout_problem::append_system (System *sys, Spring const &spring, Real inde
 void
 Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding)
 {
-  Skyline_pair *sky = Skyline_pair::unsmob (prob->get_property ("vertical-skylines"));
+  Skyline_pair *sky = unsmob<Skyline_pair> (prob->get_property ("vertical-skylines"));
   Real minimum_distance = 0;
   bool tight_spacing = to_boolean (prob->get_property ("tight-spacing"));
 
   if (sky)
     {
-      minimum_distance = (*sky)[UP].distance (bottom_skyline_);
+      minimum_distance = max ((*sky)[UP].distance (bottom_skyline_),
+                              bottom_loose_baseline_);
       bottom_skyline_ = (*sky)[DOWN];
     }
-  else if (Stencil *sten = unsmob_stencil (prob->get_property ("stencil")))
+  else if (Stencil *sten = unsmob<Stencil> (prob->get_property ("stencil")))
     {
       Interval iv = sten->extent (Y_AXIS);
       minimum_distance = iv[UP] - bottom_skyline_.max_height ();
@@ -569,6 +675,7 @@ Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding
       bottom_skyline_.clear ();
       bottom_skyline_.set_minimum_height (iv[DOWN]);
     }
+  bottom_loose_baseline_ = 0.0;
 
   Spring spring_copy = spring;
   if (tight_spacing)
@@ -584,28 +691,47 @@ Page_layout_problem::append_prob (Prob *prob, Spring const &spring, Real padding
   elements_.push_back (Element (prob, padding));
 }
 
+/**
+   For ragged-last pages, we usually want to stretch the page so that it
+   is not much more compressed than the previous page.  Here, if ragged is
+   true and you pass a value of fixed_force that !isinf, then I will try
+   to space this page using the given force.  If it does not fit, I will
+   resort to just filling the page (non-raggedly).
+*/
 void
-Page_layout_problem::solve_rod_spring_problem (bool ragged)
+Page_layout_problem::solve_rod_spring_problem (bool ragged, Real fixed_force)
 {
   Simple_spacer spacer;
 
   for (vsize i = 0; i < springs_.size (); ++i)
     spacer.add_spring (springs_[i]);
 
-  spacer.solve (page_height_, ragged);
+  if (ragged && !isinf (fixed_force))
+    {
+      // We need to tell the spacer it isn't ragged.  Otherwise, it will
+      // refuse to stretch.
+      spacer.solve (page_height_, false);
+
+      if (spacer.configuration_length (fixed_force) <= page_height_)
+        spacer.set_force (fixed_force);
+    }
+  else
+    spacer.solve (page_height_, ragged);
+
   solution_ = spacer.spring_positions ();
+  force_ = spacer.force ();
 
   if (!spacer.fits ())
     {
       Real overflow = spacer.configuration_length (spacer.force ())
                       - page_height_;
       if (ragged && overflow < 1e-6)
-        warning (_ ("cannot fit music on page: ragged-spacing was requested, but page was compressed"));
+        warning (_ ("ragged-bottom was specified, but page must be compressed"));
       else
         {
-          warning (_f ("cannot fit music on page: overflow is %f",
+          warning (_f ("compressing over-full page by %.1f staff-spaces",
                        overflow));
-          warning (_ ("compressing music to fit"));
+          force_ = -infinity_f;
           vsize space_count = solution_.size ();
           Real spacing_increment = overflow / (space_count - 2);
           for (vsize i = 2; i < space_count; i++)
@@ -614,6 +740,12 @@ Page_layout_problem::solve_rod_spring_problem (bool ragged)
     }
 }
 
+Real
+Page_layout_problem::force () const
+{
+  return force_;
+}
+
 // 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
@@ -638,7 +770,7 @@ Page_layout_problem::find_system_offsets ()
         {
           *tail = scm_cons (scm_from_double (solution_[spring_idx]), SCM_EOL);
           tail = SCM_CDRLOC (*tail);
-          Interval prob_extent = unsmob_stencil (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
+          Interval prob_extent = unsmob<Stencil> (elements_[i].prob->get_property ("stencil"))->extent (Y_AXIS);
 
           // Lay out any non-spaceable lines between this line and
           // the last one.
@@ -698,7 +830,12 @@ Page_layout_problem::find_system_offsets ()
                       if (staff_idx)
                         loose_line_min_distances.push_back (min_offsets[staff_idx - 1] - min_offsets[staff_idx]);
                       else
-                        loose_line_min_distances.push_back (elements_[i].padding - min_offsets[staff_idx]);
+                        {
+                          // A null line to break any staff-affinity from the previous system
+                          loose_line_min_distances.push_back (0.0);
+                          loose_lines.push_back (0);
+                          loose_line_min_distances.push_back (elements_[i].padding - min_offsets[0]);
+                        }
                       loose_lines.push_back (staff);
 
                       distribute_loose_lines (loose_lines, loose_line_min_distances,
@@ -711,8 +848,11 @@ Page_layout_problem::find_system_offsets ()
                   found_spaceable_staff = true;
                   spring_idx++;
                 }
-              else
+              else // ! is_spaceable
                 {
+                  if (staff->extent (staff, Y_AXIS).is_empty ())
+                    continue;
+
                   if (loose_lines.empty ())
                     loose_lines.push_back (last_spaceable_line);
 
@@ -727,15 +867,17 @@ Page_layout_problem::find_system_offsets ()
                       // this is the first line in a system
                       Real min_dist = 0;
                       if (loose_lines.back ())
-                        // distance to the final line in the preceding system,
-                        // including 'system-system-spacing 'padding
-                        min_dist = (Axis_group_interface::minimum_distance (loose_lines.back (),
-                                                                            staff,
-                                                                            Y_AXIS)
-                                    + elements_[i].padding);
+                        {
+                          // distance to the final line in the preceding system,
+                          // including 'system-system-spacing 'padding
+                          min_dist = elements_[i].min_distance + elements_[i].padding;
+                          // A null line to break any staff-affinity for the previous system
+                          loose_line_min_distances.push_back (0.0);
+                          loose_lines.push_back (0);
+                        }
                       else if (!last_title_extent.is_empty ())
                         // distance to the preceding title,
-                        //  including 'markup-system-spacing 'padding
+                        //  including 'markup-system-wg 'padding
                         min_dist = (staff->extent (staff, Y_AXIS)[UP] - last_title_extent[DOWN]
                                     + elements_[i].padding);
                       else // distance to the top margin
@@ -798,16 +940,24 @@ Page_layout_problem::distribute_loose_lines (vector<Grob *> const &loose_lines,
 
   vector<Real> solution = spacer.spring_positions ();
   for (vsize i = 1; i + 1 < solution.size (); ++i)
-    {
-      Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
-      loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
-    }
+    if (loose_lines[i])
+      {
+        Real system_offset = scm_to_double (loose_lines[i]->get_property ("system-Y-offset"));
+        loose_lines[i]->translate_axis (first_translation - solution[i] - system_offset, Y_AXIS);
+      }
+}
+
+SCM
+Page_layout_problem::fixed_force_solution (Real force)
+{
+  solve_rod_spring_problem (true, force);
+  return find_system_offsets ();
 }
 
 SCM
 Page_layout_problem::solution (bool ragged)
 {
-  solve_rod_spring_problem (ragged);
+  solve_rod_spring_problem (ragged, -infinity_f);
   return find_system_offsets ();
 }
 
@@ -839,7 +989,7 @@ Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
     {
       Real dy = minimum_translations[i] - first_translation;
       Grob *g = staves[i];
-      Skyline_pair *sky = Skyline_pair::unsmob (g->get_property ("vertical-skylines"));
+      Skyline_pair *sky = unsmob<Skyline_pair> (g->get_property ("vertical-skylines"));
       if (sky)
         {
           up->raise (-dy);
@@ -873,7 +1023,7 @@ Page_layout_problem::build_system_skyline (vector<Grob *> const &staves,
 Interval
 Page_layout_problem::prob_extent (Prob *p)
 {
-  Stencil *sten = unsmob_stencil (p->get_property ("stencil"));
+  Stencil *sten = unsmob<Stencil> (p->get_property ("stencil"));
   return sten ? sten->extent (Y_AXIS) : Interval (0, 0);
 }
 
@@ -1082,7 +1232,7 @@ Page_layout_problem::filter_dead_elements (vector<Grob *> const &input)
   vector<Grob *> output;
   for (vsize i = 0; i < input.size (); ++i)
     {
-      if (Hara_kiri_group_spanner::has_interface (input[i]))
+      if (has_interface<Hara_kiri_group_spanner> (input[i]))
         Hara_kiri_group_spanner::consider_suicide (input[i]);
 
       if (input[i]->is_live ())