]> git.donarmstrong.com Git - lilypond.git/commitdiff
Refactor page breaking to give a higher-level interface to the individual
authorJoe Neeman <joeneeman@gmail.com>
Sun, 25 Mar 2007 22:36:58 +0000 (08:36 +1000)
committerJoe Neeman <joeneeman@gmail.com>
Mon, 16 Apr 2007 23:25:34 +0000 (09:25 +1000)
page-breaking algorithms.

Conflicts:

lily/page-breaking.cc

lily/include/optimal-page-breaking.hh
lily/include/page-breaking.hh
lily/include/page-spacing.hh
lily/include/page-turn-page-breaking.hh
lily/optimal-page-breaking.cc
lily/page-breaking.cc
lily/page-spacing.cc
lily/page-turn-page-breaking.cc

index 79e160055cad76c2c4e87d7dfe6bda76a175063a..2ecfad8d9c5c01a79f3538397c7b5ac9174839da 100644 (file)
@@ -21,9 +21,6 @@ public:
 
   Optimal_page_breaking (Paper_book *pb);
   virtual ~Optimal_page_breaking ();
-
-private:
-  Spacing_result try_page_spacing (Line_division const&);
 };
 
 #endif /* OPTIMAL_PAGE_BREAKING_HH */
index b5b3e2411367b351a60499e492f91515a2e61ad9..23ffbd807ec0df559a3b13c56e6c13fb6da5ad43 100644 (file)
@@ -11,6 +11,7 @@
 #define PAGE_BREAKING_HH
 
 #include "constrained-breaking.hh"
+#include "page-spacing.hh"
 
 /* Either a paper-score, markup or header.
  */
@@ -79,27 +80,38 @@ public:
   Page_breaking (Paper_book *pb, Break_predicate);
   virtual ~Page_breaking ();
 
+  bool ragged () const;
+  bool ragged_last () const;
+  bool last () const;
+  Real page_height (int page_number, bool last) const;
+
 protected:
   Paper_book *book_;
 
-  Real page_height (int page_number, bool last);
   vsize next_system (Break_position const &break_pos) const;
 
   SCM make_pages (vector<vsize> lines_per_page, SCM lines);
 
   vsize min_system_count (vsize start, vsize end);
   vsize max_system_count (vsize start, vsize end);
-  vector<Line_details> line_details (vsize start, vsize end, Line_division const &div);
+
 
   void break_into_pieces (vsize start, vsize end, Line_division const &div);
   SCM systems ();
 
-
-  vector<Line_division> line_divisions (vsize start,
-                                       vsize end,
-                                       vsize system_count,
-                                       Line_division lower_bound = Line_division (),
-                                       Line_division upper_bound = Line_division ());
+  void set_current_breakpoints (vsize start,
+                               vsize end,
+                               vsize system_count,
+                               Line_division lower_bound = Line_division (),
+                               Line_division upper_bound = Line_division ());
+  vsize current_configuration_count () const;
+  Line_division current_configuration (vsize configuration_index) const;
+  Spacing_result space_systems_on_n_pages (vsize configuration_index, vsize n, vsize first_page_num);
+  Spacing_result space_systems_on_n_or_one_more_pages (vsize configuration_index, vsize n, vsize first_page_num);
+  Spacing_result space_systems_on_best_pages (vsize configuration_index, vsize first_page_num);
+  vsize min_page_count (vsize configuration_index, vsize first_page_num);
+  bool all_lines_stretched (vsize configuration_index);
+  Real blank_page_penalty () const;
 
   SCM breakpoint_property (vsize breakpoint, char const *str);
   vector<Break_position> breaks_;
@@ -108,6 +120,18 @@ private:
   vector<Break_position> chunks_;
   vector<System_spec> all_;
   vector<Constrained_breaking> line_breaking_;
+  bool ragged_;
+  bool ragged_last_;
+
+  vector<Line_division> current_configurations_;
+  vector<Break_position> current_chunks_;
+  vsize current_start_breakpoint_;
+  vsize current_end_breakpoint_;
+
+  void cache_line_details (vsize configuration_index);
+  void clear_line_details_cache ();
+  vsize cached_configuration_index_;
+  vector<Line_details> cached_line_details_;
 
   vector<Break_position> chunk_list (vsize start, vsize end);
   Line_division system_count_bounds (vector<Break_position> const &chunks, bool min);
@@ -120,9 +144,12 @@ private:
   void line_divisions_rec (vsize system_count,
                           Line_division const &min,
                           Line_division const &max,
-                          vector<Line_division> *result,
                           Line_division *cur);
 
+  vector<Line_details> line_details (vsize start, vsize end, Line_division const &div);
+  Spacing_result space_systems_on_1_page (vector<Line_details> const &lines, Real page_height, bool ragged);
+  Spacing_result space_systems_on_2_pages (vsize configuration_index, vsize first_page_num);
+  Spacing_result finalize_spacing_result (vsize configuration_index, Spacing_result);
   void create_system_list ();
   void find_chunks_and_breaks (Break_predicate);
 };
index e1fa582d922fa8013e7d9535945db9f33e0feba7..7dba158c84929bfd8fa182dd39afa5415d29ddbd 100644 (file)
@@ -30,10 +30,12 @@ struct Spacing_result {
    calculations so they can be reused for querying different page counts.
 */
 
+class Page_breaking;
+
 class Page_spacer
 {
 public:
-  Page_spacer (vector<Line_details> const &lines, Real page_height, bool ragged, bool ragged_last);
+  Page_spacer (vector<Line_details> const &lines, vsize first_page_num, Page_breaking const*);
   Spacing_result solve (vsize page_count);
 
 private:
@@ -53,7 +55,8 @@ private:
     vsize prev_;
   };
 
-  Real page_height_;
+  Page_breaking const *breaker_;
+  vsize first_page_num_;
   vector<Line_details> lines_;
   Matrix<Page_spacing_node> state_;
   vsize max_page_count_;
@@ -65,29 +68,28 @@ private:
   bool calc_subproblem (vsize page, vsize lines);
 };
 
-vsize
-min_page_count (vector<Line_details> const &lines,
-               Real page_height, bool ragged, bool ragged_last);
-
-Spacing_result
-space_systems_on_n_pages (vector<Line_details> const&,
-                         vsize n,
-                         Real page_height,
-                         bool ragged,
-                         bool ragged_last);
-
-Spacing_result
-space_systems_on_n_or_one_more_pages (vector<Line_details> const&,
-                                     vsize n,
-                                     Real page_height,
-                                     Real odd_pages_penalty,
-                                     bool ragged,
-                                     bool ragged_last);
-Spacing_result
-space_systems_on_best_pages (vector<Line_details> const&,
-                            Real page_height,
-                            Real odd_pages_penalty,
-                            bool ragged,
-                            bool ragged_last);
+struct Page_spacing
+{
+  Real force_;
+  Real page_height_;
+  Real rod_height_;
+  Real spring_len_;
+  Real inverse_spring_k_;
+
+  Line_details last_line_;
+
+  Page_spacing (Real page_height)
+  {
+    page_height_ = page_height;
+    clear ();
+  }
+
+  void calc_force ();
+
+  void append_system (const Line_details &line);
+  void prepend_system (const Line_details &line);
+  void clear ();
+};
 
+Real line_space (Line_details const &line);
 #endif /* PAGE_SPACING_HH */
index db675202b6f8e138b139e745561fd0b972192d36..bdaec437061618456a3b3fa789e904faa47febc0 100644 (file)
@@ -55,9 +55,8 @@ protected:
   vsize final_page_num (Break_node const &b);
   Break_node put_systems_on_pages (vsize start,
                                   vsize end,
-                                  vector<Line_details> const &lines,
-                                  Line_division const &div,
-                                  int page_number);
+                                  vsize configuration,
+                                  vsize page_number);
 
   SCM make_lines (vector<Break_node> *breaks);
   SCM make_pages (vector<Break_node> const &breaks, SCM systems);
index d18ad2837e92ffe2104f0a4ae326bea06b0cc38e..282114d5c55fa8025c2013ba548ad5c9aaf93fa3 100644 (file)
@@ -32,45 +32,6 @@ Optimal_page_breaking::~Optimal_page_breaking ()
 {
 }
 
-Spacing_result
-Optimal_page_breaking::try_page_spacing (Line_division const &line_count)
-{
-  vector<Line_details> lines = line_details (0, breaks_.size () - 1, line_count);
-  Real page_h = page_height (1, false); // FIXME
-  SCM force_sym = ly_symbol2scm ("blank-last-page-force");
-  Real blank_force = robust_scm2double (book_->paper_->lookup_variable (force_sym), 0);
-  bool ragged_all = to_boolean (book_->paper_->c_variable ("ragged-bottom"));
-  bool ragged_last = to_boolean (book_->paper_->c_variable ("ragged-last-bottom"));
-  Spacing_result ret = space_systems_on_best_pages (lines,
-                                                   page_h,
-                                                   blank_force,
-                                                   ragged_all,
-                                                   ragged_last);
-
-  /* add in the line penalties */
-  Real line_force = 0;
-  Real line_penalty = 0;
-  Real page_weighting = robust_scm2double (book_->paper_->c_variable ("page-spacing-weight"), 10);
-
-  for (vsize i = 0; i < lines.size (); i++)
-    {
-      line_force += lines[i].force_ * lines[i].force_;
-      line_penalty += lines[i].break_penalty_;
-    }
-
-  ret.demerits_ = ret.force_[0] * ret.force_[0] * page_weighting;
-  for (vsize i = 1; i < ret.force_.size (); i++)
-    ret.demerits_ += ret.force_[i] * ret.force_[i] * page_weighting;
-
-  /* for a while we tried averaging page and line forces instead of summing
-     them, but it caused the following problem. If there is a single page
-     with a very bad page force (for example because of a forced page break),
-     the page breaker will put in a _lot_ of pages so that the bad force
-     becomes averaged out over many pages. */
-  ret.demerits_ += line_force + line_penalty;
-  return ret;
-}
-
 SCM
 Optimal_page_breaking::solve ()
 {
@@ -82,36 +43,32 @@ Optimal_page_breaking::solve ()
   Spacing_result best;
   Line_division best_division;
   Line_division lower_bound;
+  vsize first_page_num = robust_scm2int (book_->paper_->c_variable ("first-page-number"), 1);
 
   for (vsize sys_count = min_sys_count;
        cur_page_count <= max_page_count && sys_count <= max_sys_count;
        sys_count++)
     {
-      Real this_best_demerits = infinity_f;
-      vector<Line_division> div = line_divisions (0, end, sys_count, lower_bound);
-      for (vsize d = 0; d < div.size (); d++)
+      Real best_demerits_for_this_sys_count = infinity_f;
+      set_current_breakpoints (0, end, sys_count, lower_bound);
+
+      for (vsize i = 0; i < current_configuration_count (); i++)
        {
-         Spacing_result cur = try_page_spacing (div[d]);
+         Spacing_result cur = space_systems_on_best_pages (i, first_page_num);
          cur_page_count = cur.systems_per_page_.size ();
          if (cur.demerits_ < best.demerits_ || isinf (best.demerits_))
            {
              best = cur;
-             best_division = div[d];
+             best_division = current_configuration (i);
            }
 
-         if (cur.demerits_ < this_best_demerits || isinf (best.demerits_))
+         if (cur.demerits_ < best_demerits_for_this_sys_count || isinf (best.demerits_))
            {
-             this_best_demerits = cur.demerits_;
-             lower_bound = div[d];
+             best_demerits_for_this_sys_count = cur.demerits_;
+             lower_bound = current_configuration (i);
            }
 
-         vector<Line_details> det = line_details (0, end, div[d]);
-         bool all_lines_stretched = true;
-         for (vsize i = 0; i < det.size (); i++)
-           if (det[i].force_ < 0)
-             all_lines_stretched = false;
-
-         if (all_lines_stretched)
+         if (all_lines_stretched (i))
            max_page_count = min (max_page_count, cur_page_count + 1);
        }
     }
index c80e0aa22317694669667fce5ed13c9cdb397a10..2646b24664973393f19e19451235c6341e4317ad 100644 (file)
 #include "system.hh"
 #include "warn.hh"
 
+/* for each forbidden page break, merge the systems around it into one system. */
+static vector<Line_details>
+compress_lines (const vector<Line_details> &orig)
+{
+  vector<Line_details> ret;
+
+  for (vsize i = 0; i < orig.size (); i++)
+    {
+      if (ret.size () && !scm_is_symbol (ret.back ().page_permission_))
+       {
+         Line_details const &old = ret.back ();
+         Line_details compressed = orig[i];
+         compressed.extent_[DOWN] = old.extent_[DOWN];
+         compressed.extent_[UP] = old.extent_[UP] + orig[i].extent_.length () + old.padding_;
+         compressed.space_ += old.space_;
+         compressed.inverse_hooke_ += old.inverse_hooke_;
+
+         /* we don't need the force_ field for the vertical spacing,
+            so we use force_ = n to signal that the line was compressed,
+            reducing the number of lines by n (and force_ = 0 otherwise).
+            This makes uncompression much easier. */
+         compressed.force_ = old.force_ + 1;
+         ret.back () = compressed;
+       }
+      else
+       {
+         ret.push_back (orig[i]);
+         ret.back ().force_ = 0;
+       }
+    }
+  return ret;
+}
+
+/* translate the number of systems-per-page into something meaningful for
+   the uncompressed lines.
+*/
+static vector<vsize>
+uncompress_solution (vector<vsize> const &systems_per_page,
+                    vector<Line_details> const &compressed)
+{
+  vector<vsize> ret;
+  vsize start_sys = 0;
+
+  for (vsize i = 0; i < systems_per_page.size (); i++)
+    {
+      int compressed_count = 0;
+      for (vsize j = start_sys; j < start_sys + systems_per_page[i]; j++)
+       compressed_count += (int)compressed[j].force_;
+
+      ret.push_back (systems_per_page[i] + compressed_count);
+      start_sys += systems_per_page[i];
+    }
+  return ret;
+}
+
 /* for Page_breaking, the start index (when we are dealing with the stuff
    between a pair of breakpoints) refers to the break_ index of the end of
    the previous page. So the system index of the start of the current page
@@ -41,6 +96,8 @@ Page_breaking::next_system (Break_position const &break_pos) const
 Page_breaking::Page_breaking (Paper_book *pb, Break_predicate is_break)
 {
   book_ = pb;
+  ragged_ = to_boolean (pb->paper_->c_variable ("ragged"));
+  ragged_last_ = to_boolean (pb->paper_->c_variable ("ragged-last"));
   create_system_list ();
   find_chunks_and_breaks (is_break);
 }
@@ -49,6 +106,18 @@ Page_breaking::~Page_breaking ()
 {
 }
 
+bool
+Page_breaking::ragged () const
+{
+  return ragged_;
+}
+
+bool
+Page_breaking::ragged_last () const
+{
+  return ragged_last_;
+}
+
 /* translate indices into breaks_ into start-end parameters for the line breaker */
 void
 Page_breaking::line_breaker_args (vsize sys,
@@ -121,42 +190,8 @@ Page_breaking::systems ()
   return scm_append (scm_reverse (ret));
 }
 
-vector<Line_details>
-Page_breaking::line_details (vsize start_break, vsize end_break, Line_division const &div)
-{
-  vector<Break_position> chunks = chunk_list (start_break, end_break);
-  vector<Line_details> ret;
-  assert (chunks.size () == div.size () + 1);
-
-  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);
-
-  for (vsize i = 0; i + 1 < chunks.size (); i++)
-    {
-      vsize sys = next_system (chunks[i]);
-      if (all_[sys].pscore_)
-       {
-         vsize start;
-         vsize end;
-         line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end);
-
-         vector<Line_details> details = line_breaking_[sys].line_details (start, end, div[i]);
-         ret.insert (ret.end (), details.begin (), details.end ());
-       }
-      else
-       {
-         assert (div[i] == 1);
-         ret.push_back (Line_details (all_[sys].prob_));
-         ret.back ().padding_ = padding;
-       }
-    }
-  return ret;
-}
-
 Real
-Page_breaking::page_height (int page_num, bool last)
+Page_breaking::page_height (int page_num, bool last) const
 {
   SCM mod = scm_c_resolve_module ("scm page");
   SCM calc_height = scm_c_module_lookup (mod, "calc-printable-height");
@@ -372,39 +407,86 @@ Page_breaking::system_count_bounds (vector<Break_position> const &chunks, bool m
   return ret;
 }
 
-vector<Page_breaking::Line_division>
-Page_breaking::line_divisions (vsize start,
-                              vsize end,
-                              vsize system_count,
-                              Line_division lower_bound,
-                              Line_division upper_bound)
+void
+Page_breaking::set_current_breakpoints (vsize start,
+                                       vsize end,
+                                       vsize system_count,
+                                       Line_division lower_bound,
+                                       Line_division upper_bound)
 {
-  vector<Break_position> chunks = chunk_list (start, end);
+  current_chunks_ = chunk_list (start, end);
+  current_start_breakpoint_ = start;
+  current_end_breakpoint_ = end;
+  clear_line_details_cache ();
 
   if (!lower_bound.size ())
-    lower_bound = system_count_bounds (chunks, true);
+    lower_bound = system_count_bounds (current_chunks_, true);
   if (!upper_bound.size ())
-    upper_bound = system_count_bounds (chunks, false);
+    upper_bound = system_count_bounds (current_chunks_, false);
 
-  assert (lower_bound.size () == chunks.size () - 1);
-  assert (upper_bound.size () == chunks.size () - 1);
+  assert (lower_bound.size () == current_chunks_.size () - 1);
+  assert (upper_bound.size () == current_chunks_.size () - 1);
 
-  vector<Line_division> ret;
   Line_division work_in_progress;
-
+  current_configurations_.clear ();
   line_divisions_rec (system_count,
                      lower_bound,
                      upper_bound,
-                     &ret,
                      &work_in_progress);
-  return ret;
+}
+
+vsize
+Page_breaking::current_configuration_count () const
+{
+  return current_configurations_.size ();
+}
+
+void
+Page_breaking::cache_line_details (vsize configuration_index)
+{
+  if (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);
+
+      Line_division &div = current_configurations_[configuration_index];
+      cached_line_details_.clear ();
+      for (vsize i = 0; i + 1 < current_chunks_.size (); i++)
+       {
+         vsize sys = next_system (current_chunks_[i]);
+         if (all_[sys].pscore_)
+           {
+             vsize start;
+             vsize end;
+             line_breaker_args (sys, current_chunks_[i], current_chunks_[i+1], &start, &end);
+
+             vector<Line_details> details = line_breaking_[sys].line_details (start, end, div[i]);
+             cached_line_details_.insert (cached_line_details_.end (), details.begin (), details.end ());
+           }
+         else
+           {
+             assert (div[i] == 1);
+             cached_line_details_.push_back (Line_details (all_[sys].prob_));
+             cached_line_details_.back ().padding_ = padding;
+           }
+       }
+      cached_line_details_ = compress_lines (cached_line_details_);
+    }
+}
+
+void
+Page_breaking::clear_line_details_cache ()
+{
+  cached_configuration_index_ = VPOS;
+  cached_line_details_.clear ();
 }
 
 void
 Page_breaking::line_divisions_rec (vsize system_count,
                                   Line_division const &min_sys,
                                   Line_division const &max_sys,
-                                  vector<Line_division > *result,
                                   Line_division *cur_division)
 {
   vsize my_index = cur_division->size ();
@@ -433,9 +515,284 @@ Page_breaking::line_divisions_rec (vsize system_count,
     {
       cur_division->push_back (i);
       if (my_index == min_sys.size () - 1)
-        result->push_back (*cur_division);
+       current_configurations_.push_back (*cur_division);
       else
-        line_divisions_rec (system_count - i, min_sys, max_sys, result, cur_division);
+        line_divisions_rec (system_count - i, min_sys, max_sys, cur_division);
       cur_division->pop_back ();
     }
 }
+
+vsize
+Page_breaking::min_page_count (vsize configuration, vsize first_page_num)
+{
+  vsize ret = 1;
+  Real cur_rod_height = 0;
+  Real cur_spring_height = 0;
+  Real cur_page_height = page_height (first_page_num, false);
+
+  cache_line_details (configuration);
+  for (vsize i = 0; i < cached_line_details_.size (); i++)
+    {
+      Real ext_len = cached_line_details_[i].extent_.length ();
+      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 + line_space (cached_line_details_[i]);
+      Real next_height = next_rod_height + (ragged () ? next_spring_height : 0);
+
+
+      if ((next_height > cur_page_height && cur_rod_height > 0)
+         || (i + 1 < cached_line_details_.size ()
+             && cached_line_details_[i].page_permission_ == ly_symbol2scm ("force")))
+       {
+         cur_rod_height = ext_len;
+         cur_spring_height = line_space (cached_line_details_[i]);
+         cur_page_height = page_height (first_page_num + ret, false);
+         ret++;
+       }
+      else
+       {
+         cur_rod_height = next_rod_height;
+         cur_spring_height = next_spring_height;
+       }
+    }
+
+  /* there are two potential problems with the last page (because we didn't know
+     it was the last page until after we managed to fit all the systems to it):
+     - we are ragged-last but the last page has a compressed spring
+     - the value returned by page_height (num, true) is smaller than the
+       value returned by page_height (num, false) and it causes the page not to
+       fit.
+
+     In either case, we just need to add one more page. This is because the last
+     line will always fit on the extra page and by adding one more page to the
+     end, the previous page is no longer the last page, so our previous
+     calculations that treated it as a non-last page were ok.
+  */
+
+  cur_page_height = page_height (first_page_num + ret - 1, true);
+  Real cur_height = cur_rod_height + ((ragged_last () || ragged ()) ? cur_spring_height : 0);
+  if (cur_height > cur_page_height)
+    ret++;
+  return ret;
+}
+
+Spacing_result
+Page_breaking::space_systems_on_n_pages (vsize configuration, vsize n, vsize first_page_num)
+{
+  Spacing_result ret;
+  assert (n >= min_page_count (configuration, first_page_num));
+
+  cache_line_details (configuration);
+  if (n > cached_line_details_.size ())
+    return Spacing_result ();
+  if (n == 1)
+    ret = space_systems_on_1_page (cached_line_details_,
+                                  page_height (first_page_num, last ()),
+                                  ragged () || (last () && ragged_last ()));
+  else if (n == 2)
+    ret = space_systems_on_2_pages (configuration, first_page_num);
+  else
+    {
+      Page_spacer ps (cached_line_details_, first_page_num, this);
+      ret = ps.solve (n);
+    }
+
+  return finalize_spacing_result (configuration, ret);
+}
+
+Real
+Page_breaking::blank_page_penalty () const
+{
+  SCM penalty_sym = last () ? ly_symbol2scm ("blank-last-page-force") : ly_symbol2scm ("blank-page-force");
+  return robust_scm2double (book_->paper_->lookup_variable (penalty_sym), 0.0);
+}
+
+Spacing_result
+Page_breaking::space_systems_on_n_or_one_more_pages (vsize configuration, vsize n, vsize first_page_num)
+{
+  Spacing_result n_res = space_systems_on_n_pages (configuration, n, first_page_num);
+  Spacing_result m_res = space_systems_on_n_pages (configuration, n+1, first_page_num);
+  Real penalty = blank_page_penalty ();
+  n_res.demerits_ += penalty;
+  n_res.force_.back () += penalty;
+
+  if (n_res.demerits_ < m_res.demerits_)
+    return n_res;
+  return m_res;
+}
+
+Spacing_result
+Page_breaking::space_systems_on_best_pages (vsize configuration, vsize first_page_num)
+{
+  vsize min_p_count = min_page_count (configuration, first_page_num);
+  Real odd_pages_penalty = blank_page_penalty ();
+
+  cache_line_details (configuration);
+  Page_spacer ps (cached_line_details_, first_page_num, this);
+  Spacing_result best = ps.solve (min_p_count);
+  best.force_.back () += (min_p_count % 2) ? odd_pages_penalty : 0;
+  best.demerits_ += (min_p_count % 2) ? odd_pages_penalty : 0;
+
+  for (vsize i = min_p_count+1; i <= cached_line_details_.size (); i++)
+    {
+      Spacing_result cur = ps.solve (i);
+      cur.demerits_ += (i % 2) ? odd_pages_penalty : 0;
+      if (cur.demerits_ < best.demerits_)
+       best = cur;
+    }
+
+  return finalize_spacing_result (configuration, best);
+}
+
+/* Calculate demerits and fix res.systems_per_page_ so that
+   it refers to the original line numbers, not the ones given by compress_lines (). */
+Spacing_result
+Page_breaking::finalize_spacing_result (vsize configuration, Spacing_result res)
+{
+  cache_line_details (configuration);
+  res.systems_per_page_ = uncompress_solution (res.systems_per_page_, cached_line_details_);
+
+  Real line_force = 0;
+  Real line_penalty = 0;
+  Real page_force = 0;
+  Real page_weighting = robust_scm2double (book_->paper_->c_variable ("page-spacing-weight"), 10);
+
+  for (vsize i = 0; i < cached_line_details_.size (); i++)
+    {
+      line_force += cached_line_details_[i].force_ * cached_line_details_[i].force_;
+      line_penalty += cached_line_details_[i].break_penalty_;
+    }
+
+  for (vsize i = 0; i < res.force_.size (); i++)
+    page_force += res.force_[i] * res.force_[i];
+
+  /* for a while we tried averaging page and line forces across pages instead
+     of summing them, but it caused a problem: if there is a single page
+     with a very bad page force (for example because of a forced page break),
+     the page breaker will put in a _lot_ of pages so that the bad force
+     becomes averaged out over many pages. */
+  res.demerits_ = line_force + line_penalty + (page_force + res.penalty_) * page_weighting;
+  return res;
+
+}
+
+/* the cases for page_count = 1 or 2 can be done in O (n) time. Since they
+   are by far the most common cases, we have special functions for them.
+
+   space_systems_on_1_page has a different calling convention than most of the
+   space_systems functions. This is because space_systems_on_1_page is (unlike
+   the other space_systems functions) sometimes called on subsets of a full
+   configuration. */
+Spacing_result
+Page_breaking::space_systems_on_1_page (vector<Line_details> const &lines, Real page_height, bool ragged)
+{
+  Page_spacing space (page_height);
+  Spacing_result ret;
+
+  for (vsize i = 0; i < lines.size (); i++)
+    space.append_system (lines[i]);
+
+  ret.systems_per_page_.push_back (lines.size ());
+  ret.force_.push_back (ragged ? min (space.force_, 0.0) : space.force_);
+  ret.penalty_ = lines.back ().page_penalty_ + lines.back ().turn_penalty_;
+
+  /* don't do finalize_spacing_result () because we are only an internal function */
+  return ret;
+}
+
+Spacing_result
+Page_breaking::space_systems_on_2_pages (vsize configuration, vsize first_page_num)
+{
+  Real page1_height = page_height (first_page_num, false);
+  Real page2_height = page_height (first_page_num+1, last ());
+  bool ragged1 = ragged ();
+  bool ragged2 = ragged () || (last () && ragged_last ());
+
+  /* if there is a forced break, this reduces to 2 1-page problems */
+  cache_line_details (configuration);
+  for (vsize i = 0; i + 1 < cached_line_details_.size (); i++)
+    if (cached_line_details_[i].page_permission_ == ly_symbol2scm ("force"))
+      {
+       vector<Line_details> lines1 (cached_line_details_.begin (), cached_line_details_.begin () + i + 1);
+       vector<Line_details> lines2 (cached_line_details_.begin () + i + 1, cached_line_details_.end ());
+       Spacing_result p1 = space_systems_on_1_page (lines1, page1_height, ragged1);
+       Spacing_result p2 = space_systems_on_1_page (lines2, page2_height, ragged2);
+
+       p1.systems_per_page_.push_back (p2.systems_per_page_[0]);
+       p1.force_.push_back (p2.force_[0]);
+       p1.penalty_ += p2.penalty_ - cached_line_details_[i].turn_penalty_;
+       return p1;
+      }
+
+  vector<Real> page1_force;
+  vector<Real> page2_force;
+  Page_spacing page1 (page1_height);
+  Page_spacing page2 (page2_height);
+
+  page1_force.resize (cached_line_details_.size () - 1, infinity_f);
+  page2_force.resize (cached_line_details_.size () - 1, infinity_f);
+
+  /* find the page 1 and page 2 forces for each page-breaking position */
+  for (vsize i = 0; i < page1_force.size (); i++)
+    {
+      page1.append_system (cached_line_details_[i]);
+      page2.prepend_system (cached_line_details_[cached_line_details_.size () - 1 - i]);
+      page1_force[i] = (ragged1 && page1.force_ < 0 && i > 0) ? infinity_f : page1.force_;
+
+      if (ragged2)
+       page2_force[page2_force.size () - 1 - i] =
+         (page2.force_ < 0 && i + 1 < page1_force.size ()) ? infinity_f : 0;
+      else
+       page2_force[page2_force.size () - 1 - i] = page2.force_;
+    }
+
+  /* find the position that minimises the sum of the page forces */
+  vsize best_sys_count = 1;
+  Real best_demerits = infinity_f;
+  for (vsize i = 0; i < page1_force.size (); i++)
+    {
+      Real dem = page1_force[i] * page1_force[i]
+       + page2_force[i] * page2_force[i]
+       + cached_line_details_[i+1].page_penalty_
+       + cached_line_details_.back ().page_penalty_ + cached_line_details_.back ().turn_penalty_;
+      if (dem < best_demerits)
+       {
+         best_demerits = dem;
+         best_sys_count = i+1;
+       }
+    }
+
+  Spacing_result ret;
+  ret.systems_per_page_.push_back (best_sys_count);
+  ret.systems_per_page_.push_back (cached_line_details_.size () - best_sys_count);
+  ret.force_.push_back (page1_force[best_sys_count-1]);
+  ret.force_.push_back (page2_force[best_sys_count-1]);
+  ret.penalty_ = cached_line_details_[best_sys_count-1].page_penalty_
+    + cached_line_details_.back ().page_penalty_
+    + cached_line_details_.back ().turn_penalty_;
+
+  /* don't do finalize_spacing_result () because we are only an internal function */
+  return ret;
+}
+
+bool
+Page_breaking::all_lines_stretched (vsize configuration)
+{
+  cache_line_details (configuration);
+  for (vsize i = 0; i < cached_line_details_.size (); i++)
+    if (cached_line_details_[i].force_ < 0)
+      return false;
+
+  return true;
+}
+
+Page_breaking::Line_division
+Page_breaking::current_configuration (vsize configuration_index) const
+{
+  return current_configurations_[configuration_index];
+}
+
+bool Page_breaking::last () const
+{
+  return current_end_breakpoint_ == breaks_.size () - 1;
+}
index c3a56b098724094efa89f602e631489b24f6fd2a..028a67a9fde4d73e383f1f5fc09f32d5b7d53146 100644 (file)
 #include "page-spacing.hh"
 
 #include "matrix.hh"
+#include "page-breaking.hh"
 #include "warn.hh"
 
-/*
-  A much simplified rods-and-springs problem.
- */
-struct Page_spacing
-{
-  Real force_;
-  Real page_height_;
-  Real rod_height_;
-  Real spring_len_;
-  Real inverse_spring_k_;
-
-  Line_details last_line_;
-
-  Page_spacing (Real page_height)
-  {
-    page_height_ = page_height;
-    clear ();
-  }
-
-  void calc_force ();
-
-  void append_system (const Line_details &line);
-  void prepend_system (const Line_details &line);
-  void clear ();
-};
-
 /* In order to prevent possible division by zero, we require every line
    to have a spring of non-zero length. */
-static Real
+Real
 line_space (const Line_details &line)
 {
   return max (0.1, line.space_);
@@ -91,158 +66,15 @@ Page_spacing::clear ()
   inverse_spring_k_ = 0;
 }
 
-/* for each forbidden page break, merge the systems around it into one system. */
-static vector<Line_details>
-compress_lines (const vector<Line_details> &orig)
-{
-  vector<Line_details> ret;
-
-  for (vsize i = 0; i < orig.size (); i++)
-    {
-      if (ret.size () && !scm_is_symbol (ret.back ().page_permission_))
-       {
-         Line_details const &old = ret.back ();
-         Line_details compressed = orig[i];
-         compressed.extent_[DOWN] = old.extent_[DOWN];
-         compressed.extent_[UP] = old.extent_[UP] + orig[i].extent_.length () + old.padding_;
-         compressed.space_ += old.space_;
-         compressed.inverse_hooke_ += old.inverse_hooke_;
-
-         /* we don't need the force_ field for the vertical spacing,
-            so we use force_ = n to signal that the line was compressed,
-            reducing the number of lines by n (and force_ = 0 otherwise).
-            This makes uncompression much easier. */
-         compressed.force_ = old.force_ + 1;
-         ret.back () = compressed;
-       }
-      else
-       {
-         ret.push_back (orig[i]);
-         ret.back ().force_ = 0;
-       }
-    }
-  return ret;
-}
-
-/* translate the number of systems-per-page into something meaningful for
-   the uncompressed lines.
-*/
-static vector<vsize>
-uncompress_solution (vector<vsize> const &systems_per_page,
-                    vector<Line_details> const &compressed)
-{
-  vector<vsize> ret;
-  vsize start_sys = 0;
-
-  for (vsize i = 0; i < systems_per_page.size (); i++)
-    {
-      int compressed_count = 0;
-      for (vsize j = start_sys; j < start_sys + systems_per_page[i]; j++)
-       compressed_count += (int)compressed[j].force_;
-
-      ret.push_back (systems_per_page[i] + compressed_count);
-      start_sys += systems_per_page[i];
-    }
-  return ret;
-}
-
-/* the cases for page_count = 1 or 2 can be done in O (n) time. Since they
-   are by far the most common cases, we have special functions for them */
-static Spacing_result
-space_systems_on_1_page (vector<Line_details> const &lines, Real page_height, bool ragged)
-{
-  Page_spacing space (page_height);
-  Spacing_result ret;
-
-  for (vsize i = 0; i < lines.size (); i++)
-    space.append_system (lines[i]);
-
-  ret.systems_per_page_.push_back (lines.size ());
-  ret.force_.push_back (ragged ? min (space.force_, 0.0) : space.force_);
-  ret.penalty_ = lines.back ().page_penalty_ + lines.back ().turn_penalty_;
-  ret.demerits_ = ret.force_.back () * ret.force_.back () + ret.penalty_;
 
-  return ret;
-}
-
-static Spacing_result
-space_systems_on_2_pages (vector<Line_details> const &lines,
-                         Real page_height,
-                         bool ragged,
-                         bool ragged_last)
-{
-  /* if there is a forced break, this reduces to 2 1-page problems */
-  for (vsize i = 0; i + 1 < lines.size (); i++)
-    if (lines[i].page_permission_ == ly_symbol2scm ("force"))
-      {
-       vector<Line_details> lines1 (lines.begin (), lines.begin () + i + 1);
-       vector<Line_details> lines2 (lines.begin () + i + 1, lines.end ());
-       Spacing_result p1 = space_systems_on_1_page (lines1, page_height, ragged);
-       Spacing_result p2 = space_systems_on_1_page (lines2, page_height, ragged || ragged_last);
-
-       p1.systems_per_page_.push_back (p2.systems_per_page_[0]);
-       p1.force_.push_back (p2.force_[0]);
-       p1.penalty_ += p2.penalty_ - lines[i].turn_penalty_;
-       p1.demerits_ += p2.demerits_ - lines[i].turn_penalty_;
-       return p1;
-      }
-
-  vector<Real> page1_force;
-  vector<Real> page2_force;
-  Page_spacing page1 (page_height);
-  Page_spacing page2 (page_height);
-
-  page1_force.resize (lines.size () - 1, infinity_f);
-  page2_force.resize (lines.size () - 1, infinity_f);
-
-  for (vsize i = 0; i < page1_force.size (); i++)
-    {
-      page1.append_system (lines[i]);
-      page2.prepend_system (lines[lines.size () - 1 - i]);
-      page1_force[i] = (ragged && page1.force_ < 0 && i > 0) ? infinity_f : page1.force_;
-
-      if (ragged || ragged_last)
-       page2_force[page2_force.size () - 1 - i] =
-         (page2.force_ < 0 && i + 1 < page1_force.size ()) ? infinity_f : 0;
-      else
-       page2_force[page2_force.size () - 1 - i] = page2.force_;
-    }
-
-  vsize best_sys_count = 1;
-  Real best_demerits = infinity_f;
-  for (vsize i = 0; i < page1_force.size (); i++)
-    {
-      Real dem = page1_force[i] * page1_force[i]
-       + page2_force[i] * page2_force[i]
-       + lines[i+1].page_penalty_
-       + lines.back ().page_penalty_ + lines.back ().turn_penalty_;
-      if (dem < best_demerits)
-       {
-         best_demerits = dem;
-         best_sys_count = i+1;
-       }
-    }
-
-  Spacing_result ret;
-  ret.systems_per_page_.push_back (best_sys_count);
-  ret.systems_per_page_.push_back (lines.size () - best_sys_count);
-  ret.force_.push_back (page1_force[best_sys_count-1]);
-  ret.force_.push_back (page2_force[best_sys_count-1]);
-  ret.penalty_ = lines[best_sys_count-1].page_penalty_
-    + lines.back ().page_penalty_
-    + lines.back ().turn_penalty_;
-  ret.demerits_ = best_demerits;
-
-  return ret;
-}
-
-Page_spacer::Page_spacer (vector<Line_details> const &lines, Real page_height, bool ragged, bool ragged_last)
+Page_spacer::Page_spacer (vector<Line_details> const &lines, vsize first_page_num, Page_breaking const *breaker)
   : lines_ (lines)
 {
-  page_height_ = page_height;
+  first_page_num_ = first_page_num;
+  breaker_ = breaker;
   max_page_count_ = 0;
-  ragged_ = ragged;
-  ragged_last_ = ragged_last;
+  ragged_ = breaker->ragged ();
+  ragged_last_ = breaker->last () && breaker->ragged_last ();
 }
 
 Spacing_result
@@ -320,9 +152,10 @@ Page_spacer::resize (vsize page_count)
 bool
 Page_spacer::calc_subproblem (vsize page, vsize line)
 {
-  Page_spacing space (page_height_);
+  bool last = line == lines_.size () - 1;
+  Page_spacing space (breaker_->page_height (page + first_page_num_, last));
   Page_spacing_node &cur = state_.at (line, page);
-  bool ragged = ragged_ || (ragged_last_ && line == lines_.size () - 1);
+  bool ragged = ragged_ || (ragged_last_ && last);
 
   for (vsize page_start = line+1; page_start > page && page_start--;)
     {
@@ -364,102 +197,3 @@ Page_spacer::calc_subproblem (vsize page, vsize line)
   return !isinf (cur.demerits_);
 }
 
-vsize
-min_page_count (vector<Line_details> const &uncompressed_lines,
-               Real page_height, bool ragged, bool ragged_last)
-{
-  vsize ret = 1;
-  Real cur_rod_height = 0;
-  vector<Line_details> lines = compress_lines (uncompressed_lines);
-
-  assert (lines.size ());
-  for (vsize i = lines.size (); i--;)
-    {
-      bool rag = ragged || (ragged_last && ret == 1);
-      Real ext_len = lines[i].extent_.length ();
-      Real next_height = cur_rod_height + ext_len
-       + (rag ? line_space (lines[i]) : 0)
-       + ((cur_rod_height > 0) ? lines[i].padding_: 0);
-
-      if ((next_height > page_height && cur_rod_height > 0)
-         || (i + 1 < lines.size () && lines[i].page_permission_ == ly_symbol2scm ("force")))
-       {
-         ret++;
-         cur_rod_height = ext_len + (rag ? line_space (lines[i]) : 0);
-       }
-      else
-       cur_rod_height = next_height;
-    }
-
-  return ret;
-}
-
-Spacing_result
-space_systems_on_n_pages (vector<Line_details> const &lines,
-                         vsize n,
-                         Real page_height,
-                         bool ragged,
-                         bool ragged_last)
-{
-  vector<Line_details> compressed_lines = compress_lines (lines);
-  Spacing_result ret;
-  assert (n >= min_page_count (lines, page_height, ragged, ragged_last));
-
-  if (n > compressed_lines.size ())
-    return Spacing_result ();
-  if (n == 1)
-    ret = space_systems_on_1_page (compressed_lines, page_height, ragged || ragged_last);
-  else if (n == 2)
-    ret = space_systems_on_2_pages (compressed_lines, page_height, ragged, ragged_last);
-
-  Page_spacer ps (compressed_lines, page_height, ragged, ragged_last);
-  ret = ps.solve (n);
-
-  ret.systems_per_page_ = uncompress_solution (ret.systems_per_page_, compressed_lines);
-  return ret;
-}
-
-Spacing_result
-space_systems_on_n_or_one_more_pages (vector<Line_details> const &lines,
-                                     vsize n,
-                                     Real page_height,
-                                     Real odd_pages_penalty,
-                                     bool ragged,
-                                     bool ragged_last)
-{
-  Spacing_result n_res = space_systems_on_n_pages (lines, n, page_height, ragged, ragged_last);
-  Spacing_result m_res = space_systems_on_n_pages (lines, n+1, page_height, ragged, ragged_last);
-  n_res.demerits_ += odd_pages_penalty;
-  n_res.force_.back () += odd_pages_penalty;
-
-  if (n_res.demerits_ < m_res.demerits_)
-    return n_res;
-  return m_res;
-}
-
-Spacing_result
-space_systems_on_best_pages (vector<Line_details> const &lines,
-                            Real page_height,
-                            Real odd_pages_penalty,
-                            bool ragged,
-                            bool ragged_last)
-{
-  vector<Line_details> compressed_lines = compress_lines (lines);
-  vsize min_p_count = min_page_count (compressed_lines, page_height, ragged, ragged_last);
-
-  Page_spacer ps (compressed_lines, page_height, ragged, ragged_last);
-  Spacing_result best = ps.solve (min_p_count);
-  best.force_.back () += (min_p_count % 2) ? odd_pages_penalty : 0;
-  best.demerits_ += (min_p_count % 2) ? odd_pages_penalty : 0;
-
-  for (vsize i = min_p_count+1; i <= compressed_lines.size (); i++)
-    {
-      Spacing_result cur = ps.solve (i);
-      cur.demerits_ += (i % 2) ? odd_pages_penalty : 0;
-      if (cur.demerits_ < best.demerits_)
-       best = cur;
-    }
-
-  best.systems_per_page_ = uncompress_solution (best.systems_per_page_, compressed_lines);
-  return best;
-}
index 028856229bc00cb236f1d1003427a8b30572f367..782399542c0ab01b47ba53c5283c4d6b80e50f0b 100644 (file)
@@ -36,18 +36,10 @@ Page_turn_page_breaking::~Page_turn_page_breaking ()
 Page_turn_page_breaking::Break_node
 Page_turn_page_breaking::put_systems_on_pages (vsize start,
                                               vsize end,
-                                              vector<Line_details> const &lines,
-                                              Line_division const &div,
-                                              int page_number)
+                                              vsize configuration,
+                                              vsize page_number)
 {
-  bool last = end == breaks_.size () - 1;
-  bool ragged_all = to_boolean (book_->paper_->c_variable ("ragged-bottom"));
-  bool ragged_last = last && to_boolean (book_->paper_->c_variable ("ragged-last-bottom"));
-  Real page_h = page_height (1, false); // FIXME
-  SCM force_sym = last ? ly_symbol2scm ("blank-last-page-force") : ly_symbol2scm ("blank-page-force");
-  Real blank_force = robust_scm2double (book_->paper_->lookup_variable (force_sym), 0);
-  Real page_weighting = robust_scm2double (book_->paper_->c_variable ("page-spacing-weight"), 10);
-  int min_p_count = min_page_count (lines, page_h, ragged_all, ragged_last);
+  vsize min_p_count = min_page_count (configuration, page_number);
   bool auto_first = to_boolean (book_->paper_->c_variable ("auto-first-page-number"));
 
   /* If [START, END] does not contain an intermediate
@@ -73,14 +65,14 @@ Page_turn_page_breaking::put_systems_on_pages (vsize start,
   if (start == 0 && auto_first)
     {
       if (min_p_count % 2)
-       result = space_systems_on_n_or_one_more_pages (lines, min_p_count, page_h, 0, ragged_all, ragged_last);
+       result = space_systems_on_n_or_one_more_pages (configuration, min_p_count, page_number);
       else
-       result = space_systems_on_n_pages (lines, min_p_count, page_h, ragged_all, ragged_last);
+       result = space_systems_on_n_pages (configuration, min_p_count, page_number);
     }
   else if (page_number % 2 == min_p_count % 2)
-    result = space_systems_on_n_pages (lines, min_p_count, page_h, ragged_all, ragged_last);
+    result = space_systems_on_n_pages (configuration, min_p_count, page_number);
   else
-    result = space_systems_on_n_or_one_more_pages (lines, min_p_count, page_h, blank_force, ragged_all, ragged_last);
+    result = space_systems_on_n_or_one_more_pages (configuration, min_p_count, page_number);
 
   Break_node ret;
   ret.prev_ = start - 1;
@@ -90,22 +82,14 @@ Page_turn_page_breaking::put_systems_on_pages (vsize start,
   if (auto_first && start == 0)
     ret.first_page_number_ += 1 - (ret.page_count_ % 2);
 
-  ret.div_ = div;
+  ret.div_ = current_configuration (configuration);
   ret.system_count_ = result.systems_per_page_;
 
-  ret.too_many_lines_ = true;
-  ret.demerits_ = result.penalty_;
+  ret.too_many_lines_ = all_lines_stretched (configuration);
+  ret.demerits_ = result.demerits_;
   if (start > 0)
     ret.demerits_ += state_[start-1].demerits_;
-  for (vsize i = 0; i < lines.size (); i++)
-    {
-      ret.demerits_ += lines[i].force_ * lines[i].force_;
-      ret.demerits_ += lines[i].break_penalty_;
-      if (lines[i].force_ < 0)
-       ret.too_many_lines_ = false;
-    }
-  for (vsize i = 0; i < result.force_.size (); i++)
-    ret.demerits_ += result.force_[i] * result.force_[i] * page_weighting;
+
   return ret;
 }
 
@@ -166,14 +150,12 @@ Page_turn_page_breaking::calc_subproblem (vsize ending_breakpoint)
       min_sys_count = max (min_sys_count, prev_best_system_count);
       for (vsize sys_count = min_sys_count; sys_count <= max_sys_count && ok_page; sys_count++)
         {
-         vector<Line_division> div = line_divisions (start, end, sys_count, min_division, max_division);
+         set_current_breakpoints (start, end, sys_count, min_division, max_division);
           bool found = false;
 
-          for (vsize d = 0; d < div.size (); d++)
+          for (vsize i = 0; i < current_configuration_count (); i++)
             {
-             vector<Line_details> line = line_details (start, end, div[d]);
-
-              cur = put_systems_on_pages (start, end, line, div[d], p_num);
+              cur = put_systems_on_pages (start, end, i, p_num);
 
               if (isinf (cur.demerits_)
                  || (cur.page_count_  + (p_num % 2) > 2
@@ -195,7 +177,7 @@ Page_turn_page_breaking::calc_subproblem (vsize ending_breakpoint)
 
                  /* heuristic: if we increase the number of systems, we can bound the
                     division from below by our current best division */
-                 min_division = div[d];
+                 min_division = current_configuration (i);
                 }
             }
           if (!found && this_start_best.too_many_lines_)