lilypond-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: setting the number of pages for a score


From: Joe Neeman
Subject: Re: setting the number of pages for a score
Date: Tue, 14 Feb 2006 11:43:52 +1100
User-agent: Mozilla Thunderbird 1.0.7 (X11/20051121)

Oops, I sent this to myself rather than the list...

Anyway, I hope to have a (preliminary) version of the page/line breaker combination up later today, so people can at least see the design and ideas in it.

Ok, it took a bit longer than I thought, but here it is.

Known limitations:
- it won't let you start on the right-hand page
- it will choke badly (and probably crash) if it can't find line-breaking that satisfies constraints
- it is slow
- the loop-breaking in Optimal_breaking::calc_subproblem is particularly ugly and one reason for the slowness
- it prints a lot of debugging output

You use it by adding #(define page-breaking ly:optimal-breaking) in the book's \paper block. The (paper-block) variables "blank-page-force" and "blank-last-page-force" contain the penalties for leaving blank pages in the middle and end of the score (the blank page will always be the right-hand page). "system-height" specifies the height that the page-breaker assumes systems to be. It doesn't affect how the systems are actually spaced out at the end, just how they are broken into pages.

Any other questions, please ask.


Index: lily/book.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/book.cc,v
retrieving revision 1.55
diff -u -r1.55 book.cc
--- lily/book.cc        6 Feb 2006 01:13:58 -0000       1.55
+++ lily/book.cc        14 Feb 2006 00:27:16 -0000
@@ -125,10 +125,9 @@
                paper_book->add_performance (perf->self_scm ());
              else if (Paper_score *pscore = dynamic_cast<Paper_score *> 
(output))
                {
-                 SCM systems = pscore->get_paper_systems ();
                  if (ly_is_module (score->header_))
                    paper_book->add_score (score->header_);
-                 paper_book->add_score (systems);
+                 paper_book->add_score (pscore->self_scm ());
                }
 
              outputs = scm_cdr (outputs);
Index: lily/break-algorithm.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/break-algorithm.cc,v
retrieving revision 1.54
diff -u -r1.54 break-algorithm.cc
--- lily/break-algorithm.cc     11 Feb 2006 11:35:18 -0000      1.54
+++ lily/break-algorithm.cc     14 Feb 2006 00:27:16 -0000
@@ -89,7 +89,7 @@
 }
 
 vector<Column_x_positions>
-Break_algorithm::solve () const
+Break_algorithm::solve ()
 {
   vector<Column_x_positions> h= do_solve ();
 
Index: lily/gourlay-breaking.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/gourlay-breaking.cc,v
retrieving revision 1.93
diff -u -r1.93 gourlay-breaking.cc
--- lily/gourlay-breaking.cc    11 Feb 2006 11:35:18 -0000      1.93
+++ lily/gourlay-breaking.cc    14 Feb 2006 00:27:17 -0000
@@ -75,7 +75,7 @@
    inspiration.
 */
 vector<Column_x_positions>
-Gourlay_breaking::do_solve () const
+Gourlay_breaking::do_solve ()
 {
   vector<Break_node> optimal_paths;
   vector<Grob*> all
Index: lily/paper-book.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/paper-book.cc,v
retrieving revision 1.129
diff -u -r1.129 paper-book.cc
--- lily/paper-book.cc  11 Feb 2006 11:35:17 -0000      1.129
+++ lily/paper-book.cc  14 Feb 2006 00:27:19 -0000
@@ -365,7 +365,7 @@
 
   pages_ = SCM_EOL;
   SCM proc = paper_->c_variable ("page-breaking");
-  pages_ = scm_apply_0 (proc, scm_list_2 (systems (), self_scm ()));
+  pages_ = scm_apply_0 (proc, scm_list_1(self_scm ()));
   return pages_;
 }
 
Index: lily/paper-score.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/paper-score.cc,v
retrieving revision 1.96
diff -u -r1.96 paper-score.cc
--- lily/paper-score.cc 11 Feb 2006 11:35:17 -0000      1.96
+++ lily/paper-score.cc 14 Feb 2006 00:27:19 -0000
@@ -28,7 +28,7 @@
   layout_ = layout;
   system_ = 0;
   systems_ = SCM_EOL;
-  paper_systems_ = SCM_EOL;
+  paper_systems_ = SCM_BOOL_F;
 }
 
 Paper_score::Paper_score (Paper_score const &s)
@@ -91,11 +91,6 @@
   pc.back ()->set_property ("breakable", SCM_BOOL_T);
 
   system_->pre_processing ();
-
-  vector<Column_x_positions> breaking = calc_breaking ();
-  system_->break_into_pieces (breaking);
-
-  paper_systems_ = system_->get_paper_systems ();
 }
 
 System *
@@ -111,7 +106,13 @@
 }
 
 SCM
-Paper_score::get_paper_systems () const
+Paper_score::get_paper_systems ()
 {
+  if (paper_systems_ == SCM_BOOL_F)
+    {
+      vector<Column_x_positions> breaking = calc_breaking ();
+      system_->break_into_pieces (breaking);
+      paper_systems_ = system_->get_paper_systems ();
+    }
   return paper_systems_;
 }
Index: lily/score.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/score.cc,v
retrieving revision 1.177
diff -u -r1.177 score.cc
--- lily/score.cc       6 Feb 2006 01:13:58 -0000       1.177
+++ lily/score.cc       14 Feb 2006 00:27:19 -0000
@@ -135,8 +135,7 @@
       if (ly_is_module (header))
        paper_book->add_score (header);
 
-      SCM systems = pscore->get_paper_systems ();
-      paper_book->add_score (systems);
+      paper_book->add_score (pscore->self_scm ());
 
       paper_book->classic_output (outname);
       paper_book->unprotect ();
Index: lily/system.cc
===================================================================
RCS file: /sources/lilypond/lilypond/lily/system.cc,v
retrieving revision 1.138
diff -u -r1.138 system.cc
--- lily/system.cc      11 Feb 2006 11:35:17 -0000      1.138
+++ lily/system.cc      14 Feb 2006 00:27:24 -0000
@@ -205,7 +205,7 @@
   for (vsize i = 0; i < breaking.size (); i++)
     {
       System *system = dynamic_cast<System *> (clone (i));
-      system->rank_ = i;
+      system->rank_ = broken_intos_.size ();
 
       vector<Grob*> c (breaking[i].cols_);
       pscore_->typeset_system (system);
Index: lily/include/break-algorithm.hh
===================================================================
RCS file: /sources/lilypond/lilypond/lily/include/break-algorithm.hh,v
retrieving revision 1.31
diff -u -r1.31 break-algorithm.hh
--- lily/include/break-algorithm.hh     11 Feb 2006 11:35:17 -0000      1.31
+++ lily/include/break-algorithm.hh     14 Feb 2006 00:27:24 -0000
@@ -30,14 +30,14 @@
 
   Simple_spacer_wrapper *generate_spacing_problem (vector<Grob*> const &,
                                                   Interval) const;
-  virtual vector<Column_x_positions> do_solve () const = 0;
+  virtual vector<Column_x_positions> do_solve () = 0;
 
 public:
   virtual ~Break_algorithm ();
   Simple_spacer *(*get_line_spacer) ();
   Break_algorithm ();
   void set_pscore (Paper_score *);
-  vector<Column_x_positions> solve () const;
+  vector<Column_x_positions> solve ();
 };
 
 #endif // BREAK_HH
Index: lily/include/gourlay-breaking.hh
===================================================================
RCS file: /sources/lilypond/lilypond/lily/include/gourlay-breaking.hh,v
retrieving revision 1.21
diff -u -r1.21 gourlay-breaking.hh
--- lily/include/gourlay-breaking.hh    11 Feb 2006 11:35:16 -0000      1.21
+++ lily/include/gourlay-breaking.hh    14 Feb 2006 00:27:24 -0000
@@ -16,7 +16,7 @@
 */
 struct Gourlay_breaking : public Break_algorithm
 {
-  vector<Column_x_positions> do_solve () const;
+  vector<Column_x_positions> do_solve ();
   Gourlay_breaking ();
   Real combine_demerits (Column_x_positions const &, Column_x_positions const 
&) const;
 };
Index: lily/include/paper-book.hh
===================================================================
RCS file: /sources/lilypond/lilypond/lily/include/paper-book.hh,v
retrieving revision 1.37
diff -u -r1.37 paper-book.hh
--- lily/include/paper-book.hh  3 Feb 2006 01:32:16 -0000       1.37
+++ lily/include/paper-book.hh  14 Feb 2006 00:27:24 -0000
@@ -48,6 +48,9 @@
   void output (SCM output_channel);
 };
 
+class Prob;
+void set_system_penalty (Prob *ps, SCM header);
+
 DECLARE_UNSMOB (Paper_book, paper_book)
 
 #endif /* PAPER_BOOK_HH */
Index: lily/include/paper-score.hh
===================================================================
RCS file: /sources/lilypond/lilypond/lily/include/paper-score.hh,v
retrieving revision 1.39
diff -u -r1.39 paper-score.hh
--- lily/include/paper-score.hh 11 Feb 2006 11:35:16 -0000      1.39
+++ lily/include/paper-score.hh 14 Feb 2006 00:27:24 -0000
@@ -30,7 +30,7 @@
   void typeset_system (System *);
   vector<Column_x_positions> calc_breaking ();
 
-  SCM get_paper_systems () const;
+  SCM get_paper_systems ();
 protected:
   virtual void process ();
   virtual void derived_mark () const;
Index: ly/paper-defaults.ly
===================================================================
RCS file: /sources/lilypond/lilypond/ly/paper-defaults.ly,v
retrieving revision 1.22
diff -u -r1.22 paper-defaults.ly
--- ly/paper-defaults.ly        10 Feb 2006 12:27:42 -0000      1.22
+++ ly/paper-defaults.ly        14 Feb 2006 00:27:24 -0000
@@ -57,6 +57,12 @@
     %%
     between-system-padding = #(* 4 mm)
 
+    %%
+    %% the assumed system height that we use for page-breaking
+    system-height = #(* 20 mm)
+    blank-page-force = 15
+    blank-last-page-force = 0
+
     after-title-space = 5 \mm
     before-title-space = 10 \mm
     between-title-space = 2 \mm
Index: scm/layout-page-layout.scm
===================================================================
RCS file: /sources/lilypond/lilypond/scm/layout-page-layout.scm,v
retrieving revision 1.14
diff -u -r1.14 layout-page-layout.scm
--- scm/layout-page-layout.scm  6 Feb 2006 01:13:59 -0000       1.14
+++ scm/layout-page-layout.scm  14 Feb 2006 00:27:24 -0000
@@ -136,6 +136,151 @@
   (if (ly:output-def-lookup layout 'write-page-layout #f)
       (write-page-breaks pages)))
 
+(define-public (space-systems paper-book page-height lines ragged?)
+  (define paper (ly:paper-book-paper paper-book))
+  (let* ((global-inter-system-space
+          (ly:output-def-lookup paper 'between-system-space))
+         (top-space
+          (ly:output-def-lookup paper 'page-top-space))
+         (global-fixed-dist (ly:output-def-lookup paper 
'between-system-padding))
+         
+         (system-vector (list->vector
+                         (append lines
+                                 (if (<= (length lines) 1)
+                                     '(#f)
+                                     '()))))
+         (staff-extents
+          (list->vector
+           (append (map paper-system-staff-extents lines)
+                   (if (<= (length lines) 1)
+                       '((0 . 0))
+                       '()))))
+
+         (real-extents
+          (list->vector
+           (append
+            (map
+             (lambda (sys) (paper-system-extent sys Y)) lines)
+            (if (<= (length lines) 1)
+                '((0 .  0))
+                '()))))
+         
+         (system-count (vector-length real-extents))
+         (topskip (max
+                   (+
+                    top-space
+                    (interval-end (vector-ref staff-extents 0)))
+                   (interval-end (vector-ref real-extents 0))
+                   ))
+         (last-system (vector-ref system-vector (1- system-count)))
+         (bottom-space (if (ly:prob? last-system)
+                           (ly:prob-property last-system 'bottom-space 0.0)
+                           0.0))
+         (space-left (- page-height
+                        bottom-space
+                        (apply + (map interval-length
+                                      (vector->list real-extents)))))
+
+         (space (- page-height
+                   topskip
+                   bottom-space
+                   (-  (interval-start
+                        (vector-ref real-extents (1- system-count))))))
+
+         (calc-spring
+          (lambda (idx)
+            (let* (
+                   (upper-system (vector-ref system-vector idx))
+                   (between-space (ly:prob-property upper-system 'next-space
+                                                            
global-inter-system-space))
+                   (fixed-dist (ly:prob-property upper-system 'next-padding
+                                                         global-fixed-dist))
+                   
+                   (this-system-ext (vector-ref staff-extents idx))
+                   (next-system-ext (vector-ref staff-extents (1+ idx)))
+                   (fixed (max 0 (- (+ (interval-end next-system-ext)
+                                       fixed-dist)
+                                    (interval-start this-system-ext))))
+                   (title1? (and (vector-ref system-vector idx)
+                                 (paper-system-title? (vector-ref 
system-vector idx)
+                                                           )))
+                   (title2? (and
+                             (vector-ref system-vector (1+ idx))
+                             (paper-system-title? (vector-ref system-vector 
(1+ idx)))))
+                   (ideal (+
+                           (cond
+                            ((and title2? title1?)
+                             (ly:output-def-lookup paper 'between-title-space))
+                            (title1?
+                             (ly:output-def-lookup paper 'after-title-space))
+                            (title2?
+                             (ly:output-def-lookup paper 'before-title-space))
+                            (else between-space))
+                           fixed))
+                   (hooke (/ 1 (- ideal fixed))))
+              (list ideal hooke))))
+
+         (springs (map calc-spring (iota (1- system-count))))
+         (calc-rod
+          (lambda (idx)
+            (let* (
+                   (upper-system (vector-ref system-vector idx))
+                   (fixed-dist (ly:prob-property upper-system 'next-padding
+                                                         global-fixed-dist))
+                   (this-system-ext (vector-ref real-extents idx))
+                   (next-system-ext (vector-ref real-extents (1+ idx)))
+                   
+                   (distance (max  (- (+ (interval-end next-system-ext)
+                                         fixed-dist)
+                                      (interval-start this-system-ext)
+                                      ) 0))
+                   (entry (list idx (1+ idx) distance)))
+              entry)))
+         (rods (map calc-rod (iota (1- system-count))))
+
+         ;; we don't set ragged based on amount space left.
+         ;; ragged-bottomlast = ##T is much more predictable
+         (result (ly:solve-spring-rod-problem
+                  springs rods space
+                  ragged?))
+
+         (force (car result))
+         (positions
+          (map (lambda (y)
+                 (+ y topskip))
+               (cdr  result))))
+
+    (if #f ;; debug.
+        (begin
+          (display (list "\n# systems: " system-count
+                         "\nreal-ext" real-extents "\nstaff-ext" staff-extents
+                         "\ninterscore" global-inter-system-space
+                         "\nspace-left" space-left
+                         "\nspring,rod" springs rods
+                         "\ntopskip " topskip
+                         " space " space
+                         "\npage-height" page-height
+                         "\nragged" ragged?
+                         "\nforce" force
+                         "\nres" (cdr result)
+                         "\npositions" positions "\n"))))
+
+    (cons force positions)))
+
+(define-public (make-page-from-systems p-book lines p-num prev ragged? last?)
+  (let*
+    ((page (make-page
+            (layout->page-init (ly:paper-book-paper p-book))
+            'paper-book p-book
+            'lines lines
+            'page-number p-num
+            'prev prev
+            'is-last last?))
+     (height (page-printable-height page))
+     (posns (cdr (space-systems p-book height lines ragged?))))
+    (page-set-property! page 'configuration posns)
+    page))
+
 ;; Optimal distribution of
 ;; lines over pages; line breaks are a given.
 
@@ -145,11 +290,12 @@
 ;; - separate function for word-wrap style breaking?
 ;; - ragged-bottom? ragged-last-bottom?
 
-(define-public (optimal-page-breaks lines paper-book)
+(define-public (optimal-page-breaks paper-book)
   "Return pages as a list starting with 1st page. Each page is a 'page Prob."
 
   (define MAXPENALTY 1e9)
   (define paper (ly:paper-book-paper paper-book))
+  (define lines (ly:paper-book-systems paper-book))
 
   ;; ugh.
   (define page-alist (layout->page-init (ly:paper-book-paper paper-book))) 
@@ -181,136 +327,6 @@
                                         inter-system-space))
         user)))
 
-  (define (space-systems page-height lines ragged?)
-    (let* ((global-inter-system-space
-           (ly:output-def-lookup paper 'between-system-space))
-          (top-space
-           (ly:output-def-lookup paper 'page-top-space))
-          (global-fixed-dist (ly:output-def-lookup paper 
'between-system-padding))
-          
-          (system-vector (list->vector
-                          (append lines
-                                  (if (= (length lines) 1)
-                                      '(#f)
-                                      '()))))
-          (staff-extents
-           (list->vector
-            (append (map paper-system-staff-extents lines)
-                    (if (= (length lines) 1)
-                        '((0 . 0))
-                        '()))))
-
-          (real-extents
-           (list->vector
-            (append
-             (map
-              (lambda (sys) (paper-system-extent sys Y)) lines)
-             (if (= (length lines) 1)
-                 '((0 .  0))
-                 '()))))
-          
-          (system-count (vector-length real-extents))
-          (topskip (max
-                    (+
-                     top-space
-                     (interval-end (vector-ref staff-extents 0)))
-                    (interval-end (vector-ref real-extents 0))
-                    ))
-          (last-system (vector-ref system-vector (1- system-count)))
-          (bottom-space (if (ly:prob? last-system)
-                            (ly:prob-property last-system 'bottom-space 0.0)
-                            0.0))
-          (space-left (- page-height
-                         bottom-space
-                         (apply + (map interval-length
-                                       (vector->list real-extents)))))
-
-          (space (- page-height
-                    topskip
-                    bottom-space
-                    (-  (interval-start
-                         (vector-ref real-extents (1- system-count))))))
-
-          (calc-spring
-           (lambda (idx)
-             (let* (
-                    (upper-system (vector-ref system-vector idx))
-                    (between-space (ly:prob-property upper-system 'next-space
-                                                             
global-inter-system-space))
-                    (fixed-dist (ly:prob-property upper-system 'next-padding
-                                                          global-fixed-dist))
-                    
-                    (this-system-ext (vector-ref staff-extents idx))
-                    (next-system-ext (vector-ref staff-extents (1+ idx)))
-                    (fixed (max 0 (- (+ (interval-end next-system-ext)
-                                        fixed-dist)
-                                     (interval-start this-system-ext))))
-                    (title1? (and (vector-ref system-vector idx)
-                                  (paper-system-title? (vector-ref 
system-vector idx)
-                                                            )))
-                    (title2? (and
-                              (vector-ref system-vector (1+ idx))
-                              (paper-system-title? (vector-ref system-vector 
(1+ idx)))))
-                    (ideal (+
-                            (cond
-                             ((and title2? title1?)
-                              (ly:output-def-lookup paper 
'between-title-space))
-                             (title1?
-                              (ly:output-def-lookup paper 'after-title-space))
-                             (title2?
-                              (ly:output-def-lookup paper 'before-title-space))
-                             (else between-space))
-                            fixed))
-                    (hooke (/ 1 (- ideal fixed))))
-               (list ideal hooke))))
-
-          (springs (map calc-spring (iota (1- system-count))))
-          (calc-rod
-           (lambda (idx)
-             (let* (
-                    (upper-system (vector-ref system-vector idx))
-                    (fixed-dist (ly:prob-property upper-system 'next-padding
-                                                          global-fixed-dist))
-                    (this-system-ext (vector-ref real-extents idx))
-                    (next-system-ext (vector-ref real-extents (1+ idx)))
-                    
-                    (distance (max  (- (+ (interval-end next-system-ext)
-                                          fixed-dist)
-                                       (interval-start this-system-ext)
-                                       ) 0))
-                    (entry (list idx (1+ idx) distance)))
-               entry)))
-          (rods (map calc-rod (iota (1- system-count))))
-
-          ;; we don't set ragged based on amount space left.
-          ;; ragged-bottomlast = ##T is much more predictable
-          (result (ly:solve-spring-rod-problem
-                   springs rods space
-                   ragged?))
-
-          (force (car result))
-          (positions
-           (map (lambda (y)
-                  (+ y topskip))
-                (cdr  result))))
-
-      (if #f ;; debug.
-         (begin
-           (display (list "\n# systems: " system-count
-                          "\nreal-ext" real-extents "\nstaff-ext" staff-extents
-                          "\ninterscore" global-inter-system-space
-                          "\nspace-left" space-left
-                          "\nspring,rod" springs rods
-                          "\ntopskip " topskip
-                          " space " space
-                          "\npage-height" page-height
-                          "\nragged" ragged?
-                          "\nforce" force
-                          "\nres" (cdr result)
-                          "\npositions" positions "\n"))))
-
-      (cons force positions)))
-
   (define (walk-paths done-lines best-paths current-lines  last? current-best)
     "Return the best optimal-page-break-node that contains
 CURRENT-LINES.  DONE-LINES.reversed ++ CURRENT-LINES is a consecutive
@@ -335,7 +351,7 @@
                        (and ragged-last?
                             last?)))
            (height (page-printable-height this-page))
-          (vertical-spacing (space-systems height current-lines ragged?))
+          (vertical-spacing (space-systems paper-book height current-lines 
ragged?))
           (satisfied-constraints (car vertical-spacing))
            (force (if satisfied-constraints
                      (if (and last? ragged-last?)
Index: scm/page.scm
===================================================================
RCS file: /sources/lilypond/lilypond/scm/page.scm,v
retrieving revision 1.7
diff -u -r1.7 page.scm
--- scm/page.scm        10 Feb 2006 12:27:42 -0000      1.7
+++ scm/page.scm        14 Feb 2006 00:27:26 -0000
@@ -191,12 +191,31 @@
 
     head-stencil))
 
+;; this is basically calc-printable-page height, but it works just from the 
paper-book
+(define-public (calc-page-height p-book p-number last?)
+    (let*
+      ((layout (ly:paper-book-paper p-book))
+       (scopes (ly:paper-book-scopes p-book))
+       (head (page-headfoot layout scopes p-number 'make-header 
'head-separation UP last?))
+       (foot (page-headfoot layout scopes p-number 'make-footer 
'foot-separation DOWN last?))
+       (h (- (ly:output-def-lookup layout 'paper-height)
+              (ly:output-def-lookup layout 'top-margin)
+              (ly:output-def-lookup layout 'bottom-margin)))
+       (available
+       (- h (if (ly:stencil? head)
+                (interval-length (ly:stencil-extent head Y))
+                0)
+          (if (ly:stencil? foot)
+              (interval-length (ly:stencil-extent foot Y))
+              0))))
+    available))
+    
+
 (define (page-header-or-footer page dir)
     (let*
       ((p-book (page-property page 'paper-book))
        (layout (ly:paper-book-paper p-book))
        (scopes (ly:paper-book-scopes p-book))
-       (lines (page-lines page))
        (number (page-page-number page))
        (last? (page-property page 'is-last))
        )
@@ -379,9 +398,6 @@
   (let*
       ((p-book (page-property page 'paper-book))
        (layout (ly:paper-book-paper p-book))
-       (scopes (ly:paper-book-scopes p-book))
-       (number (page-page-number page))
-       (last? (page-property page 'is-last))
        (h (- (ly:output-def-lookup layout 'paper-height)
               (ly:output-def-lookup layout 'top-margin)
               (ly:output-def-lookup layout 'bottom-margin)))
Index: scm/paper.scm
===================================================================
RCS file: /sources/lilypond/lilypond/scm/paper.scm,v
retrieving revision 1.65
diff -u -r1.65 paper.scm
--- scm/paper.scm       6 Feb 2006 01:13:59 -0000       1.65
+++ scm/paper.scm       14 Feb 2006 00:27:26 -0000
@@ -7,6 +7,7 @@
 (define-public (set-paper-dimension-variables mod)
   (module-define! mod 'dimension-variables
                  '(pt mm cm in staff-height staff-space
+           system-height
                       page-top-space
                       between-system-space between-system-padding
                       line-width indent paper-width paper-height 
horizontal-shift

/*
  optimal-breaking-scheme.cc -- implement Optimal_breaking bindings

  source file of the GNU LilyPond music typesetter

  (c) 2006 Han-Wen Nienhuys <address@hidden>
*/

#include "paper-book.hh"
#include "optimal-breaking.hh"

LY_DEFINE (ly_optimal_breaking, "ly:optimal-breaking",
           1, 0, 0, (SCM pb),
           "Optimally break (pages and lines) the Paper_book PB, returning its 
pages.")
{
  Optimal_breaking b (unsmob_paper_book (pb));
  return b.solve();
}


/*
  optimal-breaking.cc -- implement Optimal_breakinr

  source file of the GNU LilyPond music typesetter

  (c) 2006 Han-Wen Nienhuys <address@hidden>
*/

#include "optimal-breaking.hh"

#include "main.hh"
#include "item.hh"
#include "paper-book.hh"
#include "output-def.hh"
#include "system.hh"
#include "paper-score.hh"
#include "paper-system.hh"
#include "text-interface.hh"
#include "warn.hh"

#include "ly-smobs.icc"

#include "gourlay-breaking.hh"
#include "international.hh"

System_spec::System_spec (Paper_score *ps, int n_brks)
{
  paper_score_ = true;
  pscore_ = ps;
  prob_ = 0;
  Output_def *l = ps->layout ();
  size_ = scm_to_double (l->c_variable ("system-height"));
  penalty_ = 0;
  between_size_ = scm_to_double (l->c_variable ("between-system-padding"));
  between_space_ = scm_to_double (l->c_variable ("between-system-space"));

  n_score_brk_ = n_brks;
}

System_spec::System_spec (Prob *pb)
{
  paper_score_ = false;
  pscore_ = 0;
  prob_ = pb;
  Stencil *s = unsmob_stencil (pb->get_property ("stencil"));
  size_ = s->extent (Y_AXIS).length ();
  // these get determined later
  penalty_ = 0;
  between_size_ = 0;
  between_space_ = 0;

  n_score_brk_ = 0;
}

System_spec::System_spec (SCM header, Paper_book *pb)
{
  Stencil title = pb->score_title (header);

  paper_score_ = false;
  pscore_ = 0;
  SCM props = pb->paper_->lookup_variable (ly_symbol2scm 
("score-title-properties"));
  prob_ = make_paper_system (props);
  paper_system_set_stencil (prob_, title);
  set_system_penalty (prob_, header);

  size_ = title.extent (Y_AXIS).length ();
  penalty_ = scm_to_double (prob_->get_property ("penalty"));
  // these get determined later
  between_size_ = 0;
  between_space_ = 0;

  n_score_brk_ = 0;
}

System_spec::System_spec ()
{
  paper_score_ = true;
  size_ = between_size_ = between_space_ = 0;
  pscore_ = 0;
  prob_ = 0;
  n_score_brk_ = 0;
}

// for Optimal_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
// could either be the same as the end of the previous page or one more than
// it.
//
// Turn a break index into the sys index that starts the next page
int Optimal_breaking::sys_idx (int start) const
{
  int sys = breaks_[start].sys_;

  if (sys < 0) // beginning of the piece
    return 0;
  if (all_[sys].paper_score_ && all_[sys].n_score_brk_ > 
breaks_[start].score_brk_)
    return sys; // the score overflows the previous page
  return sys + 1; // this page starts with a new sys
}

Optimal_breaking::Optimal_breaking (Paper_book *pb)
{
  book_ = pb;
  create_system_list ();
}

void
Optimal_breaking::calc_demerits (Optimal_break_node &me)
{
  Real prev_f = 0;
  Real prev_dem = 0;
  Real break_pen = 0;
  int prev_break = 0;
  if (me.prev_ >= 0)
    {
      prev_f = state_[me.prev_].force_;
      prev_dem = state_[me.prev_].demerits_;
      prev_break = state_[me.prev_].break_pos_;
    }

  int j = 0;
  int p1_sys_left = me.p1_sys_ - 1;
  int p2_sys_left = me.p1_sys_ + me.p2_sys_ - 1;
  int start = prev_break;
  int end = me.break_pos_;
  Real line_f = 0, line_pen = 0;
  for (vsize i = sys_idx (prev_break); (int)i <= breaks_[me.break_pos_].sys_; 
i++)
    {
      if (all_[i].paper_score_)
        {
          int nsys = me.div_[j++];
          if (p1_sys_left >= 0 && p1_sys_left < nsys)
            break_pen += get_page_penalty (i, nsys, start, end, p1_sys_left);
          if (p2_sys_left >= 0 && p2_sys_left < nsys)
            break_pen += get_page_penalty (i, nsys, start, end, p2_sys_left);
          p1_sys_left -= nsys;
          p2_sys_left -= nsys;
          line_f += get_line_force (i, nsys, start, end);
          line_pen += get_line_penalty (i, nsys, start, end);
        }
      else
        {
          p1_sys_left--;
          p2_sys_left--;
        }
    }
  assert (me.line_demerits_ == line_f);

  me.demerits_ = me.force_ * me.force_ + line_f * line_f * 4.0 + fabs 
(me.force_ - prev_f);
  if (isinf (line_f)) // make failed line-breaking very unattractive
    me.demerits_ = (me.force_ + 20000.0) * 10;

  me.demerits_ += prev_dem + break_pen + all_[breaks_[end].sys_].penalty_ + 
breaks_[end].penalty_ + line_pen;
  assert (!isinf (line_f) || me.demerits_ > 200);
}

void
Optimal_breaking::line_brk_args (vsize i, int &start, int &end)
{
  assert (all_[i].paper_score_);
  assert (sys_idx (start) <= (int)i && (int)i <= breaks_[end].sys_);

  int start_col = 0;
  int end_col = -1;

  if (breaks_[start].sys_ == (int)i)
    start_col = breaks_[start].score_brk_;
  if (breaks_[end].sys_ == (int)i)
    end_col = breaks_[end].score_brk_;

  assert (end_col && end_col <= all_[breaks_[end].sys_].n_score_brk_);
  start = start_col;
  end = end_col;
}

Real
Optimal_breaking::get_page_penalty (vsize i, int nsys, int start, int end, int 
sys_num)
{
  line_brk_args (i, start, end);
  return line_breaking_[i].get_page_penalty (start, end, nsys, sys_num);
}

vector<Column_x_positions>
Optimal_breaking::get_line_breaks (vsize i, int nsys, int start, int end)
{
  line_brk_args (i, start, end);
  return line_breaking_[i].get_soln (start, end, nsys);
}

Real
Optimal_breaking::get_line_break_dems (vsize i, int nsys, int start, int end)
{
  line_brk_args (i, start, end);
  return line_breaking_[i].get_demerits (start, end, nsys);
}

Real
Optimal_breaking::get_line_force (vsize i, int nsys, int start, int end)
{
  line_brk_args (i, start, end);
  return line_breaking_[i].get_force (start, end, nsys);
}

Real
Optimal_breaking::get_line_penalty (vsize i, int nsys, int start, int end)
{
  line_brk_args (i, start, end);
  return line_breaking_[i].get_penalty (start, end, nsys);
}

// TODO: allow starting on the right-hand page
// this is quite long, but I can't see any obvious places to split it
Real
Optimal_breaking::best_page_force (vector<System_spec> const &sys,
                                   int nsys,
                                   vector<int> const &div,
                                   int p_num,
                                   bool last,
                                   int &p1_sys)
{
  SCM ph = ly_lily_module_constant("calc-page-height");
  Real p1_h, p1_last_h, p2_h;
  vector<System_spec const*> rep_sys;

  bool ragged_all = to_boolean (book_->paper_->c_variable ("ragged-bottom"));
  bool ragged_last = to_boolean (book_->paper_->c_variable 
("ragged-last-bottom"));
  bool ragged = ragged_all || (last && ragged_last);

  SCM b = book_->self_scm ();
  p1_h = scm_to_double (
          scm_apply_0 (ph, scm_list_3 (b, scm_from_int (p_num), SCM_BOOL_F)));
  if (last)
    p1_last_h = scm_to_double (
          scm_apply_0 (ph, scm_list_3 (b, scm_from_int (p_num), SCM_BOOL_T)));
  p2_h = scm_to_double (
          scm_apply_0 (ph, scm_list_3 (b, scm_from_int (p_num), 
scm_from_bool(last))));

  Real p1_sys_h = 0, p2_sys_h = 0;
  Real p1_spr_len = 0, p2_spr_len = 0; // spring lengths
  Real p1_inv_k = 0, p2_inv_k = 0;     // inverse spring contants
  Real p1_spr_space = p1_h, p2_spr_space = p2_h;

  // repeat each score for the number of systems it has
  vsize score_idx = 0;
  for (vsize i = 0; i < sys.size (); i++)
    {
      if (sys[i].paper_score_)
        {
          for (vsize j = 0; (int)j < div[score_idx]; j++)
            rep_sys.push_back (&sys[i]);
          score_idx++;
        }
      else
        rep_sys.push_back (&sys[i]);
    }
  if (rep_sys.size () < (vsize)nsys)
    return infinity_f;  // we don't have any scores so we can't increase nsys

  // start with everything on the first page
  for (vsize i = 0; i < rep_sys.size (); i++)
    {
      p1_sys_h += rep_sys[i]->size_ + rep_sys[i]->between_size_;
      p1_spr_len += rep_sys[i]->between_space_;
      p1_spr_space -= rep_sys[i]->between_size_;
      p1_inv_k++;
    }
  p1_sys_h -= rep_sys.back ()->between_size_;
  p1_spr_space += rep_sys.back ()->between_size_;

  Real force = (p1_sys_h < (last ? p1_last_h : p1_h)) ?
                (p1_spr_space - p1_spr_len) / p1_inv_k : infinity_f;
  Real best = force;
  if (ragged && !isinf(force))
    force = max (0.0, force);
  else
    force = fabs (force);
  if (!last)
    force += robust_scm2double (book_->paper_->c_variable ("blank-page-force"), 
0.0);
  else
    force += robust_scm2double (book_->paper_->c_variable 
("blank-last-page-force"), 0.0);
  p1_sys = nsys;

  // move things over one system at a time
  System_spec const *me;
  System_spec const *prev;
  for (vsize i = rep_sys.size () - 1; i > 0; i--)
    {
      me = rep_sys[i];
      prev = rep_sys[i-1];
      p1_sys_h -= me->size_ + prev->between_size_;
      p1_spr_len -= me->between_space_;
      p1_spr_space += prev->between_size_;
      p1_inv_k--;
      p2_sys_h += me->size_;
      p2_spr_len += me->between_space_;
      if (i < rep_sys.size () - 1)
        {
          p2_sys_h += me->between_size_;
          p2_spr_space -= me->between_size_;
        }
      p2_inv_k++;

      if (p2_sys_h >= p2_h) // it's only going to get worse if we add more 
systems
        return force;
      if (p1_sys_h >= p1_h)
        continue;
      Real p1_f = (p1_spr_space - p1_spr_len) / p1_inv_k;
      Real p2_f = (p2_spr_space - p2_spr_len) / p2_inv_k;
      p1_f = ragged_all ? max (0.0, p1_f) : fabs (p1_f);
      p2_f = ragged     ? max (0.0, p2_f) : fabs (p2_f);
      if (p1_f + p2_f + prev->penalty_ < best)
        {
          best = p1_f + p2_f + prev->penalty_;
          force = p1_f + p2_f;
          p1_sys = i;
        }
    }

  return force;
}


void
Optimal_breaking::calc_subproblem(int i)
{
  vsize end = i + 1;
  Optimal_break_node best;
  Optimal_break_node cur;

  cur.break_pos_ = end;
  bool bad_page_and_lines = false;
  for (int start = end - 1; start >= 0; start--)
    {
      int p_num = (start == 0) ? 1 : state_[start-1].page_number_ + 2;
      int n_scores = 0;
      int n_probs = 0;
      vector<System_spec> systems (all_.begin () + sys_idx (start),
                                   all_.begin () + breaks_[end].sys_ + 1);

      Optimal_break_node this_break_best;
      cur.page_number_ = p_num;
      cur.prev_ = start - 1;

      for (vsize j = sys_idx (start); (int)j <= breaks_[end].sys_; j++)
        {
          if (all_[j].paper_score_)
            n_scores++;
          else
            n_probs++;
        }

      bool ok_page = true;
      bool each_nsys_bad_lines = true;
      bool bad_lines = false;
      for (int nsys = n_scores + n_probs; ok_page; nsys++)
        {
          vector<vector<int> > div;
          divide_systems (nsys - n_probs, n_scores, div, vector<int> ());

          bool each_div_bad_lines = true;
          for (vsize d = 0; d < div.size (); d++)
            {
              Real line_f = 0;
              cur.force_ = best_page_force (systems, nsys, div[d], p_num,
                                            end == breaks_.size () - 1, 
cur.p1_sys_);

              vsize k = 0;
              for (vsize j = 0; j < systems.size (); j++)
                if (systems[j].paper_score_)
                  line_f += get_line_force (j + sys_idx(start), div[d][k++], 
start, end);

              bad_lines = isinf (line_f);
              if (!bad_lines)
                each_div_bad_lines = false;

              if (isinf (cur.force_))
                {
                  ok_page = false;
                  break;
                }

              cur.div_ = div[d];
              cur.p2_sys_ = nsys - cur.p1_sys_;
              cur.line_demerits_ = line_f;
              calc_demerits (cur);
              if (isinf(this_break_best.demerits_) || cur.demerits_ < 
this_break_best.demerits_)
                {
                  this_break_best = cur;
                  this_break_best.div_ = div[d];
                }
            }
          if (!each_div_bad_lines)
            each_nsys_bad_lines = false;
          if (each_div_bad_lines && !each_nsys_bad_lines)
            break; // lines are too sparse, no point adding more systems
          else if (each_div_bad_lines && !ok_page) // lines and pages are too 
dense
            {
              bad_page_and_lines = true;
              break;
            }
        }
      message (_f ("the page between %d and %d has prev=%d, force=%f, dem=%f, 
line_dem=%f", start, end,
            this_break_best.prev_+1, this_break_best.force_, 
this_break_best.demerits_, this_break_best.line_demerits_));
      // if both page and lines are too dense, there's no point making them 
denser
      if (isinf (best.demerits_) || this_break_best.demerits_ < best.demerits_)
        best = this_break_best;
      if (bad_page_and_lines)
        break;
    }
  state_.push_back (best);
}

SCM
Optimal_breaking::solve ()
{
  state_.clear ();
  message (_f ("Calculating page and line breaks (%d possible page breaks)...",
               (int)breaks_.size () - 1) + " ");
  for (vsize i = 0; i < breaks_.size () - 1; i++)
    {
      calc_subproblem (i);
      progress_indication (string ("[") + to_string (i + 1) + "]");
    }
  progress_indication ("\n");

  vector<Optimal_break_node> soln;
  int i = state_.size () - 1;
  while (i >= 0)
    {
      soln.push_back (state_[i]);
      i = state_[i].prev_;
    }
  reverse (soln);
  SCM systems = do_line_breaking (soln);
  return make_pages (soln, systems);
}

/* do the line breaking in all the scores and return a big list of systems */
SCM
Optimal_breaking::do_line_breaking (vector<Optimal_break_node> const &soln)
{
  int total_n_systems = 0;
  for (vsize n = 0; n < soln.size (); n++)
    {
      vsize start = n > 0 ? soln[n-1].break_pos_ : 0;
      vsize end = soln[n].break_pos_;
      int n_systems = 0;

      vsize pscore_idx = 0;

      /* break each score into pieces */
      for (vsize i = sys_idx (start); (int) i <= breaks_[end].sys_; i++)
        {
          if (all_[i].paper_score_)
            {
              vector<Column_x_positions> line_brk;
              Real dems = get_line_force (i, soln[n].div_[pscore_idx], start, 
end);
              if (isinf (dems))
                  ::message (_("WTF? I chose an infinite-demerits line 
breaking"));

              line_brk = get_line_breaks (i, soln[n].div_[pscore_idx], start, 
end);
              all_[i].pscore_->root_system ()->break_into_pieces (line_brk);
              pscore_idx++;
              n_systems += line_brk.size ();
            }
          else
            n_systems++;
        }
      assert (n_systems == soln[n].p1_sys_ + soln[n].p2_sys_);
      total_n_systems += n_systems;
    }
  SCM ret = SCM_EOL;
  SCM *tail = &ret;
  for (vsize i = 0; i < all_.size (); i++)
    {
      if (all_[i].paper_score_)
        for (SCM s = scm_vector_to_list (all_[i].pscore_->root_system 
()->get_paper_systems ());
              scm_is_pair (s); s = scm_cdr (s))
          {
            *tail = scm_cons (scm_car (s), SCM_EOL);
            tail = SCM_CDRLOC (*tail);
          }
      else
        {
          *tail = scm_cons (all_[i].prob_->self_scm (), SCM_EOL);
          tail = SCM_CDRLOC (*tail);
          all_[i].prob_->unprotect ();
        }
    }
  assert (scm_to_int (scm_length (ret)) == total_n_systems);
  return ret;
}

SCM
Optimal_breaking::make_pages (vector<Optimal_break_node> const &soln, SCM 
systems)
{
  SCM prev_page = SCM_BOOL_F;
  SCM make_page = ly_lily_module_constant ("make-page-from-systems");
  SCM book = book_->self_scm ();
  bool ragged_all = to_boolean (book_->paper_->c_variable ("ragged-bottom"));
  bool ragged_last = to_boolean (book_->paper_->c_variable 
("ragged-last-bottom"));
  SCM ret = SCM_EOL;

  for (vsize i = 0; i < soln.size (); i++)
    {
      SCM page_num = scm_from_int (soln[i].page_number_);
      SCM last = scm_from_bool (i == soln.size () - 1 && soln[i].p2_sys_ == 0);
      SCM ragged = scm_from_bool (ragged_all || (to_boolean (last) && 
ragged_last));
      SCM lines = SCM_EOL;
      for (int j = 0; j < soln[i].p1_sys_; j++, systems = scm_cdr(systems))
        lines = scm_cons (scm_car (systems), lines);
      lines = scm_reverse (lines);

      SCM page = scm_apply_0 (make_page,
                  scm_list_n (book, lines, page_num, prev_page, ragged, last, 
SCM_UNDEFINED));
      ret = scm_cons (page, ret);

      if (i < soln.size () - 1 || soln[i].p2_sys_)
        {
          page_num = scm_from_int (soln[i].page_number_ + 1);
          last = scm_from_bool (i == soln.size () - 1);
          lines = SCM_EOL;
          ragged = scm_from_bool (ragged_all || (to_boolean (last) && 
ragged_last));
          for (int j = 0; j < soln[i].p2_sys_; j++, systems = scm_cdr(systems))
            lines = scm_cons (scm_car (systems), lines);
          lines = scm_reverse (lines);

          SCM page = scm_apply_0 (make_page,
                      scm_list_n (book, lines, page_num, prev_page, ragged, 
last, SCM_UNDEFINED));
          ret = scm_cons (page, ret);
        }
    }
  return scm_reverse (ret);
}

/* unlike the default page breaker, we store our penalties in the system
 * before the current system */
void
Optimal_breaking::add_break_penalty (SCM header)
{
  int sys = all_.size () - 2;
  if (sys < 0)
    return;

  Real pen = 0;
  if (ly_is_module (header))
    {
      SCM force = ly_module_lookup (header, ly_symbol2scm ("breakbefore"));
      if (SCM_VARIABLEP (force)
          && scm_is_bool (SCM_VARIABLE_REF (force)))
        {
          pen = to_boolean (SCM_VARIABLE_REF (force)) ? -10000 : 10000;
        }
    }

  if (all_[sys].paper_score_)
    {
      // check if there's already a breakpoint there
      if (breaks_.back ().sys_ == sys
          && breaks_.back ().score_brk_ == all_[sys].n_score_brk_)
        breaks_.back ().penalty_ += pen;
      else
        breaks_.push_back (Break_position (sys, all_[sys].n_score_brk_, pen));
    }
  else
    {
      // there shouldn't already be a breakpoint there
      breaks_.push_back (Break_position (sys, -1, pen));
    }
}

bool
Optimal_breaking::add_prob (SCM props, Stencil sten)
{
  if (sten.is_empty ())
    return false;

  Prob *ps = make_paper_system (props);
  paper_system_set_stencil (ps, sten);
  all_.push_back (System_spec (ps));
  all_.back ().penalty_ = 10000; // by default, we don't want to break
                                 // after a title

  // we don't actually use this -- it's a dummy to make indices easier
  line_breaking_.push_back (Restrained_breaking ());
  return true;
}

void
Optimal_breaking::create_system_list()
{
  nscores_ = 0;

  breaks_.push_back (Break_position ());

  SCM props = book_->paper_->lookup_variable (ly_symbol2scm 
("book-title-properties"));
  if (add_prob (props, book_->book_title ()))
    add_break_penalty (book_->header_);

  SCM page_properties
    = scm_call_1 (ly_lily_module_constant ("layout-extract-page-properties"),
                  book_->paper_->self_scm ());

  SCM header = SCM_EOL;
  for (SCM s = scm_reverse (book_->scores_); s != SCM_EOL; s = scm_cdr (s))
    {
      if (ly_is_module (scm_car (s)))
        {
          header = scm_car (s);
        }
      else if (Music_output *mop = unsmob_music_output (scm_car (s)))
        {
          if (Paper_score *pscore = dynamic_cast<Paper_score *> (mop))
            {
              SCM props = book_->paper_->lookup_variable (ly_symbol2scm 
("score-title-properties"));
              if (add_prob (props, book_->score_title (header)))
                add_break_penalty (header);

              /* add a breakpoint at the end of the last score, if necessary */
              if (all_.size () && all_.back ().paper_score_)
                breaks_.push_back (
                    Break_position (all_.size () - 1, all_.back 
().n_score_brk_));

              vector<int> score_brk = find_page_break_indices (pscore);
              all_.push_back (System_spec (pscore, score_brk.size () + 1));
              nscores_++;

              for (vsize i = 0; i < score_brk.size(); i++)
                breaks_.push_back (Break_position (all_.size () - 1, i + 1));

              // include a page breaker at the start of the score
              score_brk.insert (score_brk.begin (), 0);
              line_breaking_.push_back (Restrained_breaking (score_brk));
              line_breaking_.back ().set_pscore (pscore);
            }
        }
      else if (Text_interface::is_markup (scm_car (s)))
        {
          SCM t = Text_interface::interpret_markup (book_->paper_->self_scm (),
                                                    page_properties,
                                                    scm_car (s));
          
          // TODO: init props
          if (add_prob (SCM_EOL, *unsmob_stencil (t)))
            all_.back ().prob_->set_property ("is-title", SCM_BOOL_T); 

          // FIXME: figure out penalty.
          //set_system_penalty (ps, scores_[i].header_);
        }
      else
        assert (0);
    }
  // add the ending breakpoint
  if (all_.back ().paper_score_)
    breaks_.push_back (Break_position (all_.size () - 1, all_.back 
().n_score_brk_));
  else
    breaks_.push_back (Break_position (all_.size () - 1));

  // add extra title space
  Real bet = scm_to_double (book_->paper_->c_variable ("between-title-space"));
  Real after = scm_to_double (book_->paper_->c_variable ("after-title-space"));
  Real before = scm_to_double (book_->paper_->c_variable 
("before-title-space"));
  for (vsize i = 0; i < all_.size () - 1; i++)
    {
      bool title1 = !all_[i].paper_score_;
      bool title2 = !all_[i+1].paper_score_;
      if (title1 && title2)
        all_[i].between_size_ += bet;
      else if (title1)
        all_[i].between_size_ += before;
      else if (title2)
        all_[i+1].between_size_ += after;
    }
}

vector<int>
Optimal_breaking::find_page_break_indices(Paper_score *pscore)
{
  vector<Grob*> all = pscore->root_system ()->columns ();
  vector<int> ret;

  // we don't include breaks at the beginning and end
  for (vsize i = 0; i < all.size () - 1; i++)
    if (Item::is_breakable (all[i]) &&
        scm_is_number (all[i]->get_property ("page-penalty")) &&
        robust_scm2double (all[i]->get_property ("page-penalty"), 0) < 0)
      ret.push_back(i);

  return ret;
}

void
Optimal_breaking::divide_systems(int nsystems, int nscores,
                                 vector<vector<int> > &result,
                                 const vector<int> &cur)
{
  assert (nscores <= nsystems);

  if (nscores == 0)
    {
      result.push_back (cur);
    }
  else if (nscores == 1)
    {
      vector<int> v = cur;
      v.push_back (nsystems);
      result.push_back (v);
    }
  else
    {
      nscores--;
      for (int i = 1; i <= nsystems - nscores; i++)
        {
          vector<int> v = cur;
          v.push_back (i);
          divide_systems (nsystems-i, nscores, result, v);
        }
    }
}

/*
  restrained-breaking.cc -- implement a line breaker that
  support limits on the number of systems

  source file of the GNU LilyPond music typesetter

  (c) 1997--2005 Han-Wen Nienhuys <address@hidden>
*/

#include "restrained-breaking.hh"

#include <cmath>                // rint
#include <cstdio>
using namespace std;

#include "warn.hh"
#include "main.hh"
#include "paper-column.hh"
#include "paper-score.hh"
#include "output-def.hh"
#include "simple-spacer.hh"
#include "system.hh"

#include "international.hh"

/// How often to print operator pacification marks?
const int HAPPY_DOTS_I = 3;

void
print_restrained_break_nodes (vector<Restrained_break_node> const &arr)
{
  for (vsize i = 0; i < arr.size (); i++)
    {
      printf ("node %d: ", (int)i);
      arr[i].print ();
    }
}

/**
   We use the following optimal substructure. Let W(A) be our weight function.

   Let A_{k,n} = (a_{k,n,1}, ... a_{k,n,k}) be the optimal set of line breaks
   for k systems and n potential breakpoints. a_{k,n,k} = n (it is the end of
   the piece)

   Then A_{k+1, m} is contructed from
                        min_ {k < j < m} ( W(A_{k,j} :: m) )
   where by A::m we denote appending m to the list A
*/

/* start_idx and sys here are indexed from 0.
 * max_brk is indexed from start_brk_idx_[start_idx]
 * (for max_brk, start_brk_idx_[start_idx] is the beginning
 * of the piece; the smallest value we should ever see here is
 * start_brk_idx_[start_idx] + 1) */
bool
Restrained_breaking::calc_subproblem (int start_idx, int sys, int max_brk)
{
  assert (sys < systems_);
  assert (start_idx < (int)start_.size ());
  assert (max_brk < (int)breaks_.size ());

  bool found_something = false;
  int start_col = start_brk_idx_[start_idx];
  vector<Restrained_break_node> &st = state_[start_idx];
  int rank = breaks_.size () - start_col;
  int max_idx = max_brk - start_col;
  for (int j=sys; j < max_idx; j++)
    {
      if (0 == sys && j > 0)
        break; // the first line cannot have its first break after the beginning

      Column_x_positions const &cur = cols_[(j + start_col)*cols_rank_ + 
max_brk];
      Column_x_positions prev;
      Real prev_dem = 0;

      if (sys > 0)
        {
          prev = st[(sys-1) * rank + j].line_config_;
          prev_dem = st[(sys-1) * rank + j].demerits_;
        }
      if (isinf (prev_dem))
        break;

      Real dem, force, pen;
      combine_demerits(prev, cur, force, pen, dem);
      dem += prev_dem;
      if (isinf (dem))
        continue;

      if (isinf (st[sys*rank + max_idx].demerits_)
          || dem < st[sys*rank + max_idx].demerits_)
        {
          found_something = true;
          st[sys*rank + max_idx].demerits_ = dem;
          st[sys*rank + max_idx].force_ = force;
          st[sys*rank + max_idx].penalty_ = pen;
          st[sys*rank + max_idx].prev_ = j;
          st[sys*rank + max_idx].line_config_ = cur;
        }
    }
  return found_something;
}

vector<Column_x_positions>
Restrained_breaking::do_solve ()
{
  resize ();
  return get_soln(0, systems_, -1);
}

void
Restrained_breaking::resize ()
{
  static int num_spacings = 0;
  if (!breaks_.size ())
    {
      // do all the rod/spring problems
      breaks_ = find_break_indices ();
      cols_rank_ = breaks_.size ();
      all_ = pscore_->root_system ()->columns ();
      cols_.resize (breaks_.size () * breaks_.size ());
      for (vsize i = 0; i < breaks_.size () - 1; i++)
          for (vsize j = i + 1; j < breaks_.size (); j++)
            {
              vector<Grob*> line (all_.begin () + breaks_[i],
                                  all_.begin() + breaks_[j] + 1);

              line[0] = dynamic_cast<Item *> (line[0])->find_prebroken_piece 
(RIGHT);
              line.back () = dynamic_cast<Item *> (line.back 
())->find_prebroken_piece (LEFT);

              cols_[i*cols_rank_ + j].cols_ = line;

              // we have no idea what line this will be -- only whether it is 
the first
              Interval line_dims = line_dimensions_int (pscore_->layout (), i);
              Simple_spacer_wrapper *sp = generate_spacing_problem (line, 
line_dims);

              // TODO: support raggedness
              sp->solve (&cols_[i*cols_rank_ + j], false);
              num_spacings++;
              if (!cols_[i*cols_rank_ + j].satisfies_constraints_)
                break;
              delete sp;
            }
      ::message (_f("calculated %d spacing problems\n", num_spacings));
      // work out all the starting indices
      for (vsize i = 0; i < start_.size (); i++)
        {
          vsize j;
          for (j = 0; j < breaks_.size () - 1 && breaks_[j] < start_[i]; j++)
            ;
          start_brk_idx_.push_back (j);
          start_[i] = breaks_[j];
        }
      state_.resize (start_.size ());
    }

  if (!systems_) systems_ = 4; // just to make this compatible with Gourlay

  for (vsize i = 0; i < state_.size (); i++)
    state_[i].resize((breaks_.size () - start_brk_idx_[i]) * systems_);

  // fill out the matrices
  for (vsize i = 0; i < state_.size (); i++)
    for (int j = valid_systems_; j < systems_; j++)
      for (vsize k = start_brk_idx_[i] + j + 1; k < breaks_.size (); k++)
        if (!calc_subproblem (i, j, k))
          break; // if we couldn't break this, it is too cramped already
  valid_systems_ = systems_;
}

vector<Column_x_positions>
Restrained_breaking::get_soln (int start_idx, int end_idx, int nsys)
{
  int rank, brk;
  prepare_solution (start_idx, end_idx, nsys, rank, brk);

  vector<Restrained_break_node> const &st = state_[start_idx];
  vector<Column_x_positions> ret;

  for (int sys = nsys-1; sys >= 0; sys--)
    {
      assert (brk > 0);
      ret.push_back( st[sys*rank + brk].line_config_ );
      brk = st[sys*rank + brk].prev_;
    }
  assert (brk == 0);
  reverse (ret);
  return ret;
}

Real
Restrained_breaking::get_demerits (int start_idx, int end_idx, int nsys)
{
  int rank, brk;
  prepare_solution (start_idx, end_idx, nsys, rank, brk);

  return state_[start_idx][(nsys-1)*rank + brk].demerits_;
}

Real
Restrained_breaking::get_force (int start_idx, int end_idx, int nsys)
{
  int rank, brk;
  prepare_solution (start_idx, end_idx, nsys, rank, brk);
  vector<Restrained_break_node> const &st = state_[start_idx];
  Real f = 0;

  for (int sys = nsys-1; sys >= 0 && brk >= 0; sys--)
    {
      f += fabs (st[sys*rank + brk].force_);
      brk = st[sys*rank + brk].prev_;
    }
  if (brk < 0)
    f = infinity_f;

  return f;
}

Real
Restrained_breaking::get_penalty (int start_idx, int end_idx, int nsys)
{
  int rank, brk;
  prepare_solution (start_idx, end_idx, nsys, rank, brk);

  return state_[start_idx][(nsys-1)*rank + brk].penalty_;
}

Real
Restrained_breaking::get_page_penalty (int start_idx, int end_idx, int nsys, 
int sys_num)
{
  int rank, brk;
  prepare_solution (start_idx, end_idx, nsys, rank, brk);

  int sys;
  for (sys = nsys-1; sys > sys_num; sys--)
      brk = state_[start_idx][sys*rank + brk].prev_;

  if (brk < 0) // we didn't satisfy constraints
    return 0;
  vector<Grob*> &cols = state_[start_idx][sys*rank + brk].line_config_.cols_;
  if (cols.empty ())
    return 0;

  Grob *pc = cols.back ();
  if (pc->original ())
    {
      SCM pen = pc->get_property ("page-penalty");
      if (scm_is_number (pen) && fabs (scm_to_double (pen)) < 10000)
        return scm_to_double (pen);
    }
  return 0;
}

void
Restrained_breaking::prepare_solution (int start_idx, int end_idx, int nsys, 
int &rank, int &brk)
{
  assert (start_idx < (int)start_.size () && end_idx <= (int)start_.size ());
  assert (end_idx < 0 || start_idx < end_idx);
  assert (nsys > 0);

  if (nsys >= valid_systems_)
    {
      systems_ = nsys;
      resize ();
    }
  if (end_idx == (int)start_.size ())
    end_idx = -1;

  rank = breaks_.size () - start_brk_idx_[start_idx];
  brk = end_idx < 0 ? breaks_.size () - 1 : start_brk_idx_[end_idx];
  brk -= start_brk_idx_[start_idx];
}

Restrained_breaking::Restrained_breaking ()
{
  valid_systems_ = systems_ = 0;
  start_.push_back (0);
}

Restrained_breaking::Restrained_breaking (vector<int> const &start):
  start_ (start)
{
  valid_systems_ = systems_ = 0;
}

void
Restrained_breaking::combine_demerits (Column_x_positions const &prev,
                                       Column_x_positions const &col,
                                       Real &force,
                                       Real &penalty,
                                       Real &demerits) const
{
  penalty = 0;
  if (col.cols_.empty () || !col.satisfies_constraints_)
    force = infinity_f;
  else
    {
      force = col.force_;

      Grob *pc = col.cols_.back ();
      if (pc->original ())
        {
          SCM pen = pc->get_property ("penalty");
          if (scm_is_number (pen) && fabs (scm_to_double (pen)) < 10000)
            penalty += scm_to_double (pen);
        }
    }

  demerits = force * force + abs (prev.force_ - force) + penalty;
}


/*
  optimal-breaking.hh -- break lines and pages optimally
  for a whole Paper_book.

  source file of the GNU LilyPond music typesetter

  (c) 2006 Han-Wen Nienhuys <address@hidden>
*/

#ifndef OPTIMAL_PAGE_BREAKING_HH
#define OPTIMAL_PAGE_BREAKING_HH

#include "restrained-breaking.hh"
#include "lily-guile.hh"

class Prob;
class Paper_score;

/* Either a paper-score, markup or header. For paper scores, we record the
 * size of a single system. For the others, we record the size of the entire
 * thing. */
struct System_spec
{
  System_spec (Paper_score*, int);
  System_spec (SCM header, Paper_book*);
  System_spec (Prob*);
  System_spec ();

  bool paper_score_;

  Real size_;
  Real between_size_; // just like size_ but we ignore it for the last system
  Real between_space_;
  Real penalty_;      // we need a break penalty here as well as in
                      // Break_position because the break positions are only at
                      // possible page turns -- this could be a page break w/o
                      // a page turn
  Paper_score *pscore_;
  Prob *prob_;

  int n_score_brk_; // for scores, this is the number of start-points our line-
                    // breaker has
};

struct Break_position
{
  int sys_; /* the index in our all_ list */
  int score_brk_; // if sys_ is a score, then we start at the score_brk_'th
                  // possible page-break in the score
  Real penalty_;

  Break_position(int s=-1, int brk=-1, Real pen=0)
  {
    sys_ = s;
    score_brk_ = brk;
    penalty_ = pen;
  }
};

/**
   Helper to trace back an optimal path
*/
struct Optimal_break_node
{
  int prev_;
  int page_number_;

  Real force_;

  /** unlike the Gourlay breaker, this is the sum of all demerits up to,
   * and including, this page */
  Real demerits_;
  Real line_demerits_;
  int break_pos_; // index into breaks_

  vector<int> div_;  // our division of system between scores on this page
  int p1_sys_, p2_sys_; // number of systems on each page

  Optimal_break_node ()
  {
    prev_ = -1;
    p1_sys_ = p2_sys_ = -1;
    demerits_ = infinity_f;
    page_number_ = 0;
  }

  void print () const
  {
    printf ("prev break %d, demerits %f\n",
            prev_, demerits_);
  }
};

/**
   A dynamic programming solution to breaking pages
*/
class Optimal_breaking
{
  public:
    // return a list of pages
    SCM solve ();

    Optimal_breaking (Paper_book *pb);

  private:
    // the ith entry is for distributing the first i page-breakable-chunks
    std::vector<Optimal_break_node> state_;
    std::vector<Break_position> breaks_;

    // list of Paper_score and Prob objects
    std::vector<System_spec> all_;

    // One line breaker for each paper score
    std::vector<Restrained_breaking> line_breaking_;

    Paper_book *book_;
    int nscores_;

    int sys_idx (int starting_brk_idx) const;

    Real page_force (std::vector<System_spec> const &sys,
                     int score_systems,
                     Real size);

    void split_systems (std::vector<System_spec> const &sys,
                        std::vector<System_spec> &p1,
                        std::vector<System_spec> &p2,
                        int &n_p1_psys,
                        int &n_p2_psys,
                        int nsys,
                        int n_p1_sys,
                        vector<int> const &score_sys_div);

    /* given a group of systems and a scheme for dividing the
     * score_systems between available scores, find the optimum way
     * of dividing the systems between two pages (and return the force).
     * Also set the number of systems for the first page. */
    Real best_page_force (std::vector<System_spec> const &sys,
                    int nsys,
                    vector<int> const &score_sys_div,
                    int p_num,
                    bool last,
                    int &p1_sys);

    void create_system_list ();
    std::vector<int> find_page_break_indices (Paper_score *pscore);
    bool add_prob (SCM props, Stencil sten);
    void add_break_penalty (SCM header);

    /* return a vector of all the systems */
    SCM do_line_breaking (vector<Optimal_break_node> const &soln);

    /* return a list of pages */
    SCM make_pages (vector<Optimal_break_node> const &soln, SCM systems);

    /* calculate all possible ways of dividing nsystems between nscores */
    void divide_systems (int nsystems, int nscores,
                         vector<vector<int> > &result,
                         const vector<int> &cur);

    void calc_demerits (Optimal_break_node &me);

    void calc_subproblem (int);

    void line_brk_args (vsize i, int &break_st, int &break_end);
    Real get_page_penalty (vsize sys_idx, int nsys, int break_st, int 
break_end, int sys_num);
    Real get_line_break_dems (vsize sys_idx, int nsys, int break_st, int 
break_end);
    Real get_line_force (vsize sys_idx, int nsys, int break_st, int break_end);
    Real get_line_penalty (vsize sys_idx, int nsys, int break_st, int 
break_end);
    std::vector<Column_x_positions> get_line_breaks (vsize sys_idx, int nsys, 
int st, int end);
};
#endif // OPTIMAL_BREAKING_HH

/*
  restrained-breaking.hh -- declare a line breaker that
  supports limits on the number of systems

  source file of the GNU LilyPond music typesetter

  (c) 1997--2005 Han-Wen Nienhuys <address@hidden>
*/

#ifndef RESTRAINED_BREAKING_HH
#define RESTRAINED_BREAKING_HH

#include "break-algorithm.hh"

/**
   Helper to trace back an optimal path
*/
struct Restrained_break_node
{
  /** the number of bars in all the systems before this one
  */
  int prev_;

  /** unlike the Gourlay breaker, this is the sum of all demerits up to,
   * and including, this line */
  Real demerits_;
  Real force_;
  Real penalty_;
  Column_x_positions line_config_;

  Restrained_break_node ()
  {
    prev_ = -1;
    demerits_ = infinity_f;
    force_ = infinity_f;
    penalty_ = 0;
    line_config_.satisfies_constraints_ = false;
  }

  void print () const
  {
    printf ("prev break %d, demerits %f\n",
            prev_, demerits_);
  }
};

/**
   A dynamic programming solution to breaking scores into lines
*/
class Restrained_breaking : public Break_algorithm
{
  public:
    std::vector<Column_x_positions> do_solve ();
    Restrained_breaking ();
    Restrained_breaking (std::vector<int> const &start_col_posns);

    // return an optimal solution with n_sys systems
    // (n_sys must be less than systems_) and with
    // max column index no greater than max_col
    std::vector<Column_x_positions> get_soln(int start_idx, int end_idx, int 
nsys);
    Real get_demerits (int start_idx, int end_idx, int nsys);
    Real get_force (int start_idx, int end_idx, int nsys);
    Real get_penalty (int start_idx, int end_idx, int nsys);

    // get the page penalty of system number sys_idx with the given breaking
    Real get_page_penalty (int start_idx, int end_idx, int nsys, int sys_idx);

  private:
    int valid_systems_;
    int systems_;

    // the (i,j)th entry is the column configuration for breaking between
    // columns i and j
    std::vector<Column_x_positions> cols_;
    int cols_rank_;

    // the [i](j,k)th entry is the score for fitting the first k bars onto the
    // first j systems, starting at the i'th allowed starting column
    std::vector<std::vector<Restrained_break_node> > state_;

    vector<int> start_;         // the columns at which we might be asked to 
start breaking
    vector<int> start_brk_idx_; // the corresponding index in breaks_

    vector<Grob*> all_;
    std::vector<int> breaks_;

    void prepare_solution (int start_idx, int end_idx, int nsys, int &rank, int 
&brk);

    void combine_demerits (Column_x_positions const &, Column_x_positions const 
&,
                           Real &force, Real &pen, Real &dem) const;

    // return false if we could not break this subproblem at all
    bool calc_subproblem(int start_idx, int systems, int max_brk);
    void resize ();
};
#endif // RESTRAINED_BREAKING_HH


reply via email to

[Prev in Thread] Current Thread [Next in Thread]