From: Joe Neeman Date: Wed, 23 Aug 2006 23:31:00 +0000 (+0000) Subject: * input/regression/optimal-page-breaking-hstretch.ly: test for X-Git-Tag: cvs/HEAD~136 X-Git-Url: https://git.donarmstrong.com/?a=commitdiff_plain;h=837a30f66f73ed048a82bef3a3ff1551d06a96da;p=lilypond.git * input/regression/optimal-page-breaking-hstretch.ly: test for ragged-last-bottom also * lily/paper-column-engraver.cc (finalize): make the end of a score breakable by default. This is to balance out a change in behaviour of the page-turn-breaker which no longer makes the end of a score breakable. * lily/paper-book.cc (pages): set the systems_ once the pages are broken * lily/page-turn-page-breaking.cc (calc_subproblem): use the new Page_breaking interface. * lily/page-breaking.cc (class Page_breaking): make the interface more consistent and provide abstractions for dealing with Line_divisions. * lily/optimal-page-breaking.cc (solve): use a more straightforward algorithm. Use the new interface to Page_breaking. * lily/page-spacing.cc: better support for ragged-bottom and ragged-last-bottom --- diff --git a/ChangeLog b/ChangeLog index d7367f184e..44c9d8d84b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,32 @@ * input/mutopia/F.Schubert/morgenlied.ly (pianoLH): update tempo. +2006-08-23 Joe Neeman + + * input/regression/optimal-page-breaking-hstretch.ly: test for + ragged-last-bottom also + + * lily/paper-column-engraver.cc (finalize): make the end of a score + breakable by default. This is to balance out a change in behaviour + of the page-turn-breaker which no longer makes the end of a score + breakable. + + * lily/paper-book.cc (pages): set the systems_ once the pages are + broken + + * lily/page-turn-page-breaking.cc (calc_subproblem): use the new + Page_breaking interface. + + * lily/page-breaking.cc (class Page_breaking): make the interface + more consistent and provide abstractions for dealing with + Line_divisions. + + * lily/optimal-page-breaking.cc (solve): use a more straightforward + algorithm. Use the new interface to Page_breaking. + + * lily/page-spacing.cc: better support for ragged-bottom and + ragged-last-bottom + 2006-08-22 Han-Wen Nienhuys * python/convertrules.py (conv): warning on \tempo{} diff --git a/input/regression/optimal-page-breaking-hstretch.ly b/input/regression/optimal-page-breaking-hstretch.ly index 64eef4e6d1..bf8bf1fed6 100644 --- a/input/regression/optimal-page-breaking-hstretch.ly +++ b/input/regression/optimal-page-breaking-hstretch.ly @@ -5,17 +5,19 @@ systems horizontally so that the vertical spacing will be more acceptable. The page-spacing-weight parameter controls the relative importance of vertical/horizontal -spacing. +spacing. Because ragged-last-bottom is on, only the +first page should be horizontally stretched. " } \paper { #(define page-breaking ly:optimal-breaking) page-spacing-weight = #10 - ragged-last-bottom = ##f + ragged-last-bottom = ##t } \relative c' { + \repeat unfold 5 {a b c d} \pageBreak \repeat unfold 5 {a b c d} } diff --git a/lily/include/optimal-page-breaking.hh b/lily/include/optimal-page-breaking.hh index 8e312233ad..8737e7e5a8 100644 --- a/lily/include/optimal-page-breaking.hh +++ b/lily/include/optimal-page-breaking.hh @@ -23,8 +23,7 @@ public: virtual ~Optimal_page_breaking (); private: - SCM make_lines (vector line_count); - Spacing_result try_page_spacing (vector line_count); + Spacing_result try_page_spacing (Line_division const&); }; #endif /* OPTIMAL_PAGE_BREAKING_HH */ diff --git a/lily/include/page-breaking.hh b/lily/include/page-breaking.hh index 4930c99d9e..548100ca8b 100644 --- a/lily/include/page-breaking.hh +++ b/lily/include/page-breaking.hh @@ -17,66 +17,114 @@ */ struct System_spec { - System_spec (Paper_score*, int); - System_spec (Prob*); - System_spec (); + System_spec (Paper_score *ps) + { + pscore_ = ps; + prob_ = NULL; + } + + System_spec (Prob *pb) + { + prob_ = pb; + pscore_ = NULL; + } + + System_spec () + { + pscore_ = NULL; + prob_ = NULL; + } Paper_score *pscore_; Prob *prob_; - - vsize score_break_count_; /* for scores, this is the number of start-points our line- - breaker has */ }; struct Break_position { - vsize sys_; /* the index in our all_ list */ + vsize sys_; /* our index in the all_ list */ vsize score_break_; /* if sys_ is a score, then we start at the score_brk_'th possible page-break in the score */ + Grob *col_; /* if sys_ is a score, this points to the broken column */ + bool score_ender_; - Break_position (vsize s=VPOS, vsize brk=VPOS) + Break_position (vsize s=VPOS, vsize brk=VPOS, Grob *g=NULL, bool end=false) { sys_ = s; score_break_ = brk; + col_ = g; + score_ender_ = end; + } + + bool operator< (const Break_position &other) + { + return (sys_ == VPOS && other.sys_ != VPOS) + || (sys_ < other.sys_) + || (sys_ == other.sys_ && score_break_ < other.score_break_); + } + + bool operator<= (const Break_position &other) + { + return (sys_ == VPOS) + || (sys_ < other.sys_ && other.sys_ != VPOS) + || (sys_ == other.sys_ && score_break_ <= other.score_break_); } }; class Page_breaking { public: + typedef bool (*Break_predicate) (Grob *); + typedef vector Line_division; virtual SCM solve () = 0; - Page_breaking (Paper_book *pb, bool allow_intra_score_breaks); + Page_breaking (Paper_book *pb, Break_predicate); virtual ~Page_breaking (); protected: - std::vector breaks_; - std::vector all_; - std::vector line_breaking_; - Paper_book *book_; Real page_height (int page_number, bool last); - vsize next_system (vsize starting_break_index) const; - - void create_system_list (bool allow_intra_score_breaks); - std::vector find_page_break_indices (Paper_score *pscore); + vsize next_system (Break_position const &break_pos) const; SCM make_pages (vector lines_per_page, SCM lines); - void calc_system_count_bounds (vsize start, vsize end, - vector *min, - vector *max); + vsize min_system_count (vsize start, vsize end); + vsize max_system_count (vsize start, vsize end); + vector 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_divisions (vsize start, + vsize end, + vsize system_count, + Line_division lower_bound = Line_division (), + Line_division upper_bound = Line_division ()); + + SCM breakpoint_property (vsize breakpoint, char const *str); + vector breaks_; + +private: + vector chunks_; + vector all_; + vector line_breaking_; + + vector chunk_list (vsize start, vsize end); + Line_division system_count_bounds (vector const &chunks, bool min); + void line_breaker_args (vsize i, + Break_position const &start, + Break_position const &end, + vsize *line_breaker_start, + vsize *line_breaker_end); - void divide_systems (vsize system_count, vector const &min, - vector const &max, - vector > *result, - vector *cur); + void line_divisions_rec (vsize system_count, + Line_division const &min, + Line_division const &max, + vector *result, + Line_division *cur); - void line_breaker_args (vsize i, vsize *break_st, vsize *break_end); - vsize get_min_systems (vsize sys, vsize break_start, vsize break_end); - vsize get_max_systems (vsize sys, vsize break_start, vsize break_end); - vector get_line_breaks (vsize sys, vsize sys_count, vsize st, vsize end); - vector get_line_details (vsize start_break, vsize end_break, vector const &div); + void create_system_list (); + void find_chunks_and_breaks (Break_predicate); }; #endif /* PAGE_BREAKING_HH */ diff --git a/lily/include/page-spacing.hh b/lily/include/page-spacing.hh index e7e3b4888a..b91540a03f 100644 --- a/lily/include/page-spacing.hh +++ b/lily/include/page-spacing.hh @@ -21,17 +21,61 @@ struct Spacing_result { Spacing_result () { penalty_ = 0; - demerits_ = 0; + demerits_ = infinity_f; } }; +/* for page_count > 2, we use a dynamic algorithm similar to + constrained-breaking -- we have a class that stores the intermediate + calculations so they can be reused for querying different page counts. +*/ + +class Page_spacer +{ +public: + Page_spacer (vector const &lines, Real page_height, bool ragged, bool ragged_last); + Spacing_result solve (vsize page_count); + +private: + struct Page_spacing_node + { + Page_spacing_node () + { + demerits_ = infinity_f; + force_ = infinity_f; + penalty_ = infinity_f; + prev_ = VPOS; + } + + Real demerits_; + Real force_; + Real penalty_; + vsize prev_; + }; + + Real page_height_; + vector lines_; + Matrix state_; + vsize max_page_count_; + + bool ragged_; + bool ragged_last_; + + void resize (vsize page_count); + bool calc_subproblem (vsize page, vsize lines); +}; + Spacing_result space_systems_on_min_pages (vector const&, Real page_height, - Real odd_pages_penalty); + Real odd_pages_penalty, + bool ragged, + bool ragged_last); Spacing_result space_systems_on_best_pages (vector const&, Real page_height, - Real odd_pages_penalty); + Real odd_pages_penalty, + bool ragged, + bool ragged_last); #endif /* PAGE_SPACING_HH */ diff --git a/lily/include/page-turn-page-breaking.hh b/lily/include/page-turn-page-breaking.hh index e386cc85da..dc52bcb03a 100644 --- a/lily/include/page-turn-page-breaking.hh +++ b/lily/include/page-turn-page-breaking.hh @@ -45,7 +45,7 @@ protected: Real demerits_; vsize break_pos_; /* index into breaks_ */ - vector div_; /* our division of systems between scores on this page */ + Line_division div_; vector system_count_; /* systems per page */ Break_node () @@ -59,12 +59,12 @@ protected: } }; - std::vector state_; + vector state_; Break_node put_systems_on_pages (vsize start, vsize end, vector const &lines, - vector const &system_div, + Line_division const &div, int page_number); SCM make_lines (vector *breaks); diff --git a/lily/optimal-page-breaking.cc b/lily/optimal-page-breaking.cc index 800c524591..6f3fcc5683 100644 --- a/lily/optimal-page-breaking.cc +++ b/lily/optimal-page-breaking.cc @@ -1,5 +1,5 @@ /* - optimal-page-breaking.hh -- implement a page-breaker that + optimal-page-breaking.cc -- implement a page-breaker that will break pages in such a way that both horizontal and vertical spacing will be acceptable @@ -16,8 +16,15 @@ #include "prob.hh" #include "system.hh" +static bool +is_break (Grob *g) +{ + (void) g; /* shutup warning */ + return false; +} + Optimal_page_breaking::Optimal_page_breaking (Paper_book *pb) - : Page_breaking (pb, false) + : Page_breaking (pb, is_break) { } @@ -26,16 +33,19 @@ Optimal_page_breaking::~Optimal_page_breaking () } Spacing_result -Optimal_page_breaking::try_page_spacing (vector line_count) +Optimal_page_breaking::try_page_spacing (Line_division const &line_count) { - vector lines = get_line_details (0, breaks_.size () - 1, line_count); + vector 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); - Spacing_result ret = space_systems_on_best_pages (lines, page_h, blank_force); - - 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_all = book_->paper_->c_variable ("ragged-bottom"); + bool ragged_last = 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; @@ -48,12 +58,6 @@ Optimal_page_breaking::try_page_spacing (vector line_count) line_penalty += lines[i].break_penalty_; } - if (ragged_all) - for (vsize i = 0; i < ret.force_.size () - 1; i++) - ret.force_[i] = min (0.0, ret.force_[i]); - if (ragged_all || ragged_last) - ret.force_.back () = min (0.0, ret.force_.back ()); - ret.demerits_ = ret.force_[0] * ret.force_[0] * page_weighting; for (vsize i = 1; i < ret.force_.size (); i++) { @@ -65,108 +69,53 @@ Optimal_page_breaking::try_page_spacing (vector line_count) return ret; } -/* The algorithm is as follows: - 1) break everything into its preferred number of lines - 2) decrease the number of lines until we've decreased - the number of pages - 3) increase the number of lines until we've increased - the number of pages - Take the best score we've found -*/ SCM Optimal_page_breaking::solve () { - vector ideal_line_count; - vector max_line_count; - vector min_line_count; - vector last_best_line_count; - vector best_line_count; - vsize last_line_total = 0; - - calc_system_count_bounds (0, breaks_.size () - 1, &min_line_count, &max_line_count); - ideal_line_count.resize (all_.size (), 1); - for (vsize i = 0; i < all_.size (); i++) + vsize end = breaks_.size () - 1; + vsize min_sys_count = min_system_count (0, end); + vsize max_sys_count = max_system_count (0, end); + vsize max_page_count = INT_MAX; + vsize cur_page_count = 0; + Spacing_result best; + Line_division best_division; + Line_division lower_bound; + + for (vsize sys_count = min_sys_count; + cur_page_count <= max_page_count && sys_count <= max_sys_count; + sys_count++) { - if (all_[i].pscore_) - ideal_line_count[i] = line_breaking_[i].get_best_solution (0, VPOS).size (); - last_line_total += ideal_line_count[i]; - } - - Spacing_result best_result = try_page_spacing (ideal_line_count); - vsize original_page_count = best_result.systems_per_page_.size (); - best_line_count = ideal_line_count; - last_best_line_count = ideal_line_count; - - Direction d = original_page_count > 1 ? DOWN : UP; - vector > div; - Spacing_result this_best_result; - do { - do { - vector blank; - - vector this_best_line_count; - this_best_result.demerits_ = infinity_f; - - last_line_total += d; - div.clear (); - divide_systems (last_line_total, - d == DOWN ? min_line_count : last_best_line_count, - d == DOWN ? last_best_line_count : max_line_count, - &div, &blank); - - for (vsize i = 0; i < div.size (); i++) + Real this_best_demerits = infinity_f; + vector div = line_divisions (0, end, sys_count, lower_bound); + for (vsize d = 0; d < div.size (); d++) { - Spacing_result cur = try_page_spacing (div[i]); - if (cur.demerits_ < this_best_result.demerits_) + Spacing_result cur = try_page_spacing (div[d]); + cur_page_count = cur.systems_per_page_.size (); + if (cur.demerits_ < best.demerits_) { - this_best_result = cur; - this_best_line_count = div[i]; + best = cur; + best_division = div[d]; } - } - last_best_line_count = this_best_line_count; - - if (this_best_result.demerits_ < best_result.demerits_) - { - best_line_count = this_best_line_count; - best_result = this_best_result; - } - } while (div.size () && this_best_result.systems_per_page_.size () == original_page_count); - - /* we're finished decreasing system count, let's try raising it */ - last_best_line_count = ideal_line_count; - last_line_total = 0; - for (vsize i = 0; i < ideal_line_count.size (); i++) - last_line_total += ideal_line_count[i]; - - } while (flip (&d) != DOWN); - SCM lines = make_lines (best_line_count); - SCM pages = make_pages (best_result.systems_per_page_, lines); - return pages; -} + if (cur.demerits_ < this_best_demerits) + { + this_best_demerits = cur.demerits_; + lower_bound = div[d]; + } -SCM -Optimal_page_breaking::make_lines (vector line_count) -{ - assert (line_count.size () == all_.size ()); + vector 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; - SCM ret = SCM_EOL; - for (vsize i = 0; i < all_.size (); i++) - { - if (all_[i].pscore_) - { - vector line_brk = line_breaking_[i].get_solution (0, VPOS, line_count[i]); - System *sys = all_[i].pscore_->root_system (); - - sys->break_into_pieces (line_brk); - ret = scm_append (scm_list_2 (ret, scm_vector_to_list (sys->get_paper_systems ()))); - } - else - { - SCM l = scm_cons (all_[i].prob_->self_scm (), SCM_EOL); - ret = scm_append (scm_list_2 (ret, l)); - all_[i].prob_->unprotect (); + if (all_lines_stretched) + max_page_count = cur_page_count + 1; } } - return ret; + + break_into_pieces (0, end, best_division); + SCM lines = systems (); + return make_pages (best.systems_per_page_, lines); } + diff --git a/lily/page-breaking.cc b/lily/page-breaking.cc index e944aa601f..1a32d7e7f1 100644 --- a/lily/page-breaking.cc +++ b/lily/page-breaking.cc @@ -1,6 +1,6 @@ /* page-breaking.cc -- implement a superclass and utility - functions for use by various page-breaking algorithms + functions shared by various page-breaking algorithms source file of the GNU LilyPond music typesetter @@ -19,27 +19,6 @@ #include "system.hh" #include "warn.hh" -System_spec::System_spec (Paper_score *ps, int break_count) -{ - pscore_ = ps; - prob_ = 0; - score_break_count_ = break_count; -} - -System_spec::System_spec (Prob *pb) -{ - pscore_ = 0; - prob_ = pb; - score_break_count_ = 0; -} - -System_spec::System_spec () -{ - pscore_ = 0; - prob_ = 0; - score_break_count_ = 0; -} - /* 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 @@ -47,21 +26,23 @@ System_spec::System_spec () it. */ /* Turn a break index into the sys index that starts the next page */ -vsize Page_breaking::next_system (vsize start) const +vsize +Page_breaking::next_system (Break_position const &break_pos) const { - vsize sys = breaks_[start].sys_; + vsize sys = break_pos.sys_; - if (sys == VPOS) /* beginning of the piece */ + if (sys == VPOS) /* beginning of the book */ return 0; - if (all_[sys].pscore_ && all_[sys].score_break_count_ > breaks_[start].score_break_) + if (all_[sys].pscore_ && !break_pos.score_ender_) return sys; /* the score overflows the previous page */ return sys + 1; /* this page starts with a new sys */ } -Page_breaking::Page_breaking (Paper_book *pb, bool allow_intra_score_breaks) +Page_breaking::Page_breaking (Paper_book *pb, Break_predicate is_break) { book_ = pb; - create_system_list (allow_intra_score_breaks); + create_system_list (); + find_chunks_and_breaks (is_break); } Page_breaking::~Page_breaking () @@ -70,69 +51,96 @@ Page_breaking::~Page_breaking () /* translate indices into breaks_ into start-end parameters for the line breaker */ void -Page_breaking::line_breaker_args (vsize i, vsize *start, vsize *end) +Page_breaking::line_breaker_args (vsize sys, + Break_position const &start, + Break_position const &end, + vsize *line_breaker_start, + vsize *line_breaker_end) { - assert (all_[i].pscore_); - assert (next_system (*start) <= i && i <= breaks_[*end].sys_); + assert (all_[sys].pscore_); + assert (next_system (start) <= sys && sys <= end.sys_); + + if (start.sys_ == sys) + *line_breaker_start = start.score_break_; + else + *line_breaker_start = 0; + + if (end.sys_ == sys) + *line_breaker_end = end.score_break_; + else + *line_breaker_end = VPOS; +} - vsize start_col = 0; - vsize end_col = VPOS; +void +Page_breaking::break_into_pieces (vsize start_break, vsize end_break, Line_division const &div) +{ + vector chunks = chunk_list (start_break, end_break); + assert (chunks.size () == div.size () + 1); - if (breaks_[*start].sys_ == i) - start_col = breaks_[*start].score_break_; - if (breaks_[*end].sys_ == i) - end_col = breaks_[*end].score_break_; + for (vsize i = 0; i < chunks.size () - 1; 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); - assert (end_col && (end_col == VPOS || end_col <= all_[breaks_[*end].sys_].score_break_count_)); - *start = start_col; - *end = end_col; + vector pos = line_breaking_[sys].get_solution (start, end, div[i]); + all_[sys].pscore_->root_system ()->break_into_pieces (pos); + } + } } -vector -Page_breaking::get_line_breaks (vsize i, vsize sys_count, vsize start, vsize end) +SCM +Page_breaking::systems () { - assert (all_[i].pscore_); - line_breaker_args (i, &start, &end); - return line_breaking_[i].get_solution (start, end, sys_count); + SCM ret = SCM_EOL; + for (vsize sys = 0; sys < all_.size (); sys++) + { + if (all_[sys].pscore_) + { + SCM lines = all_[sys].pscore_->root_system ()->get_paper_systems (); + ret = scm_cons (scm_vector_to_list (lines), ret); + } + else + { + Prob *pb = all_[sys].prob_; + ret = scm_cons (scm_list_1 (pb->self_scm ()), ret); + pb->unprotect (); + } + } + return scm_append (scm_reverse (ret)); } vector -Page_breaking::get_line_details (vsize start_break, vsize end_break, vector const &div) +Page_breaking::line_details (vsize start_break, vsize end_break, Line_division const &div) { + vector chunks = chunk_list (start_break, end_break); vector ret; + assert (chunks.size () == div.size () + 1); - for (vsize i = next_system (start_break); i <= breaks_[end_break].sys_; i++) + for (vsize i = 0; i < chunks.size () - 1; i++) { - if (all_[i].pscore_) + vsize sys = next_system (chunks[i]); + if (all_[sys].pscore_) { - vsize div_index = i - next_system (start_break); - vsize start = start_break; - vsize end = end_break; + vsize start; + vsize end; + line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end); - line_breaker_args (i, &start, &end); - vector l = line_breaking_[i].get_details (start, end, div[div_index]); - ret.insert (ret.end (), l.begin (), l.end ()); + vector details = line_breaking_[sys].get_details (start, end, div[i]); + ret.insert (ret.end (), details.begin (), details.end ()); } else - ret.push_back (Line_details (all_[i].prob_)); + { + assert (div[i] == 1); + ret.push_back (Line_details (all_[sys].prob_)); + } } return ret; } -vsize -Page_breaking::get_min_systems (vsize i, vsize start, vsize end) -{ - line_breaker_args (i, &start, &end); - return line_breaking_[i].get_min_systems (start, end); -} - -vsize -Page_breaking::get_max_systems (vsize i, vsize start, vsize end) -{ - line_breaker_args (i, &start, &end); - return line_breaking_[i].get_max_systems (start, end); -} - Real Page_breaking::page_height (int page_num, bool last) { @@ -152,6 +160,18 @@ Page_breaking::page_height (int page_num, bool last) return scm_to_double (height) - scm_to_double (book_->paper_->c_variable ("page-top-space")); } +SCM +Page_breaking::breakpoint_property (vsize breakpoint, char const *str) +{ + Break_position const &pos = breaks_[breakpoint]; + + if (pos.sys_ == VPOS) + return SCM_EOL; + if (all_[pos.sys_].pscore_) + return pos.col_->get_property (str); + return all_[pos.sys_].prob_->get_property (str); +} + SCM Page_breaking::make_pages (vector lines_per_page, SCM systems) { @@ -179,105 +199,183 @@ Page_breaking::make_pages (vector lines_per_page, SCM systems) return scm_reverse (ret); } -/* if allow_intra_score_breaks is false, that doesn't necessarily mean that there will - be no page turns in the middle of a score, only that we don't give special - consideration to any internal part of a score. +/* The page-turn-page-breaker needs to have a line-breaker between any two + columns with non-NULL page-turn-permission. - Corollary: if allow_intra_score_breaks is false, any \pageTurn or \noPageTurn commands - in the middle of a score will be ignored. + The optimal-breaker needs to have a line-breaker between any two columns + with page-break-permission = 'force. + + By using a grob predicate, we can accommodate both of these uses. */ void -Page_breaking::create_system_list (bool allow_intra_score_breaks) +Page_breaking::create_system_list () { - breaks_.push_back (Break_position ()); - SCM specs = book_->get_system_specs (); for (SCM s = specs; s != SCM_EOL; s = scm_cdr (s)) { if (Paper_score *ps = dynamic_cast (unsmob_music_output (scm_car (s)))) - { - /* add a breakpoint at the end of the last score, if necessary */ - if (all_.size () && all_.back ().pscore_) - breaks_.push_back (Break_position (all_.size () - 1, - all_.back ().score_break_count_)); - - vector score_brk; - if (allow_intra_score_breaks) - score_brk = find_page_break_indices (ps); - - all_.push_back (System_spec (ps, score_brk.size () + 1)); - - for (vsize i = 0; i < score_brk.size(); i++) - breaks_.push_back (Break_position (all_.size () - 1, i + 1)); - - /* include a line breaker at the start of the score */ - score_brk.insert (score_brk.begin (), 0); - line_breaking_.push_back (Constrained_breaking (score_brk)); - line_breaking_.back ().set_pscore (ps); - } + all_.push_back (System_spec (ps)); else { Prob *pb = unsmob_prob (scm_car (s)); assert (pb); pb->protect (); - // ignore penalties (from break-before) in favour of using \pageBreak at the - // end of the score - if (all_.size () && all_.back ().pscore_) - breaks_.push_back (Break_position (all_.size () - 1, all_.back ().score_break_count_)); all_.push_back (System_spec (pb)); - line_breaking_.push_back (Constrained_breaking ()); } } +} - /* add the ending breakpoint */ - if (all_.back ().pscore_) - breaks_.push_back (Break_position (all_.size () - 1, all_.back ().score_break_count_)); - else - breaks_.push_back (Break_position (all_.size () - 1)); +void +Page_breaking::find_chunks_and_breaks (Break_predicate is_break) +{ + SCM force_sym = ly_symbol2scm ("force"); + + chunks_.push_back (Break_position ()); + breaks_.push_back (Break_position ()); + + for (vsize i = 0; i < all_.size (); i++) + { + if (all_[i].pscore_) + { + vector cols = all_[i].pscore_->root_system ()->columns (); + vector line_breaker_columns; + line_breaker_columns.push_back (0); + + for (vsize j = 0; j < cols.size (); j++) + { + bool last = j == cols.size () - 1; + bool break_point = is_break (cols[j]); + bool chunk_end = cols[j]->get_property ("page-break-permission") == force_sym; + Break_position cur_pos = Break_position (i, + line_breaker_columns.size (), + cols[j], + last); + + if (break_point || (i == all_.size () - 1 && last)) + breaks_.push_back (cur_pos); + if (chunk_end || last) + chunks_.push_back (cur_pos); + + if ((break_point || chunk_end) && !last) + line_breaker_columns.push_back (j); + } + line_breaking_.push_back (Constrained_breaking (line_breaker_columns)); + line_breaking_.back ().set_pscore (all_[i].pscore_); + } + else + { + /* TODO: we want some way of applying Break_p to a prob? */ + if (i == all_.size () - 1) + breaks_.push_back (Break_position (i)); + + chunks_.push_back (Break_position (i)); + line_breaking_.push_back (Constrained_breaking ()); + } + } } -vector -Page_breaking::find_page_break_indices (Paper_score *pscore) +vector +Page_breaking::chunk_list (vsize start_index, vsize end_index) { - vector all = pscore->root_system ()->columns (); - vector ret; + Break_position start = breaks_[start_index]; + Break_position end = breaks_[end_index]; + + vsize i; + for (i = 0; i < chunks_.size () && chunks_[i] <= start; i++) + ; + + vector ret; + ret.push_back (start); + for (; i < chunks_.size () && chunks_[i] < end; i++) + ret.push_back (chunks_[i]); + ret.push_back (end); + return ret; +} - /* we don't include breaks at the beginning and end */ - for (vsize i = 0; i < all.size () - 1; i++) - if (scm_is_symbol (all[i]->get_property ("page-turn-permission"))) - ret.push_back(i); +vsize +Page_breaking::min_system_count (vsize start, vsize end) +{ + vector chunks = chunk_list (start, end); + Line_division div = system_count_bounds (chunks, true); + vsize ret = 0; + for (vsize i = 0; i < div.size (); i++) + ret += div[i]; return ret; } -void -Page_breaking::calc_system_count_bounds (vsize start, vsize end, - vector *min, - vector *max) +vsize +Page_breaking::max_system_count (vsize start, vsize end) { - for (vsize i = next_system (start); i <= breaks_[end].sys_; i++) + vector chunks = chunk_list (start, end); + Line_division div = system_count_bounds (chunks, false); + vsize ret = 0; + + for (vsize i = 0; i < div.size (); i++) + ret += div[i]; + return ret; +} + +Page_breaking::Line_division +Page_breaking::system_count_bounds (vector const &chunks, bool min) +{ + assert (chunks.size () >= 2); + + Line_division ret; + ret.resize (chunks.size () - 1, 1); + + for (vsize i = 0; i < chunks.size () - 1; i++) { - if (all_[i].pscore_) - { - min->push_back (get_min_systems (i, start, end)); - max->push_back (get_max_systems (i, start, end)); - } - else - { - min->push_back (1); - max->push_back (1); - } + 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); + ret[i] = min + ? line_breaking_[sys].get_min_systems (start, end) + : line_breaking_[sys].get_max_systems (start, end); + } } + + return ret; +} + +vector +Page_breaking::line_divisions (vsize start, + vsize end, + vsize system_count, + Line_division lower_bound, + Line_division upper_bound) +{ + vector chunks = chunk_list (start, end); + + if (!lower_bound.size ()) + lower_bound = system_count_bounds (chunks, true); + if (!upper_bound.size ()) + upper_bound = system_count_bounds (chunks, false); + + assert (lower_bound.size () == chunks.size () - 1); + assert (upper_bound.size () == chunks.size () - 1); + + vector ret; + Line_division work_in_progress; + + line_divisions_rec (system_count, + lower_bound, + upper_bound, + &ret, + &work_in_progress); + return ret; } -/* calculate all possible ways of dividing system_count between the System_specs */ void -Page_breaking::divide_systems (vsize system_count, - vector const &min_sys, - vector const &max_sys, - vector > *result, - vector *cur_division) +Page_breaking::line_divisions_rec (vsize system_count, + Line_division const &min_sys, + Line_division const &max_sys, + vector *result, + Line_division *cur_division) { vsize my_index = cur_division->size (); vsize others_min = 0; @@ -307,8 +405,7 @@ Page_breaking::divide_systems (vsize system_count, if (my_index == min_sys.size () - 1) result->push_back (*cur_division); else - divide_systems (system_count - i, min_sys, max_sys, result, cur_division); + line_divisions_rec (system_count - i, min_sys, max_sys, result, cur_division); cur_division->pop_back (); } } - diff --git a/lily/page-spacing.cc b/lily/page-spacing.cc index 32025a7078..fc6c5b3ddf 100644 --- a/lily/page-spacing.cc +++ b/lily/page-spacing.cc @@ -140,7 +140,7 @@ uncompress_solution (vector const &systems_per_page, /* 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 const &lines, Real page_height) +space_systems_on_1_page (vector const &lines, Real page_height, bool ragged) { Page_spacing space (page_height); Spacing_result ret; @@ -149,7 +149,7 @@ space_systems_on_1_page (vector const &lines, Real page_height) space.append_system (lines[i]); ret.systems_per_page_.push_back (lines.size ()); - ret.force_.push_back (space.force_); + 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_; @@ -157,7 +157,10 @@ space_systems_on_1_page (vector const &lines, Real page_height) } static Spacing_result -space_systems_on_2_pages (vector const &lines, Real page_height) +space_systems_on_2_pages (vector 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 < lines.size () - 1; i++) @@ -165,8 +168,8 @@ space_systems_on_2_pages (vector const &lines, Real page_height) { vector lines1 (lines.begin (), lines.begin () + i + 1); vector lines2 (lines.begin () + i + 1, lines.end ()); - Spacing_result p1 = space_systems_on_1_page (lines1, page_height); - Spacing_result p2 = space_systems_on_1_page (lines2, page_height); + 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]); @@ -187,8 +190,13 @@ space_systems_on_2_pages (vector const &lines, Real page_height) { page1.append_system (lines[i]); page2.prepend_system (lines[lines.size () - 1 - i]); - page1_force[i] = page1.force_; - page2_force[page2_force.size () - 1 - i] = page2.force_; + 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 < page1_force.size () - 1) ? infinity_f : 0; + else + page2_force[page2_force.size () - 1 - i] = page2.force_; } vsize best_sys_count = 1; @@ -219,48 +227,13 @@ space_systems_on_2_pages (vector const &lines, Real page_height) return ret; } -/* for page_count > 2, we use a dynamic algorithm similar to - constrained-breaking -- we have a class that stores the intermediate - calculations so they can be reused for querying different page counts. -*/ - -class Page_spacer -{ -public: - Page_spacer (vector const &lines, Real page_height); - Spacing_result solve (vsize page_count); - -private: - struct Page_spacing_node - { - Page_spacing_node () - { - demerits_ = infinity_f; - force_ = infinity_f; - penalty_ = infinity_f; - prev_ = VPOS; - } - - Real demerits_; - Real force_; - Real penalty_; - vsize prev_; - }; - - Real page_height_; - vector lines_; - Matrix state_; - vsize max_page_count_; - - void resize (vsize page_count); - bool calc_subproblem (vsize page, vsize lines); -}; - -Page_spacer::Page_spacer (vector const &lines, Real page_height) +Page_spacer::Page_spacer (vector const &lines, Real page_height, bool ragged, bool ragged_last) : lines_ (lines) { page_height_ = page_height; max_page_count_ = 0; + ragged_ = ragged; + ragged_last_ = ragged_last; } Spacing_result @@ -275,6 +248,12 @@ Page_spacer::solve (vsize page_count) vsize system = lines_.size () - 1; + if (isinf (state_.at (system, page_count-1).demerits_)) + return Spacing_result (); /* bad number of pages */ + + if (isinf (state_.at (system, page_count-1).demerits_)) + return Spacing_result (); /* bad number of pages */ + ret.penalty_ = state_.at (system, page_count-1).penalty_ + lines_.back ().page_penalty_ + lines_.back ().turn_penalty_; @@ -317,52 +296,62 @@ Page_spacer::calc_subproblem (vsize page, vsize line) { Page_spacing space (page_height_); Page_spacing_node &cur = state_.at (line, page); + bool ragged = ragged_ || (ragged_last_ && line == lines_.size () - 1); for (vsize page_start = line+1; page_start > page && page_start--;) { Page_spacing_node const *prev = page > 0 ? &state_.at (page_start-1, page-1) : 0; space.prepend_system (lines_[page_start]); - if (isinf (space.force_)) + if (page_start < line && (isinf (space.force_) || (space.force_ < 0 && ragged))) break; - if (page == 0 && page_start > 0) - continue; + if (page > 0 || page_start == 0) + { + if (line == lines_.size () - 1 && ragged_last_ && space.force_ > 0) + space.force_ = 0; - Real dem = fabs (space.force_) + (prev ? prev->demerits_ : 0); - Real penalty = 0; - if (page_start > 0) - penalty = lines_[page_start-1].page_penalty_ - + (page % 2 == 0) ? lines_[page_start-1].turn_penalty_ : 0; + Real dem = fabs (space.force_) + (prev ? prev->demerits_ : 0); + Real penalty = 0; + if (page_start > 0) + penalty = lines_[page_start-1].page_penalty_ + + (page % 2 == 0) ? lines_[page_start-1].turn_penalty_ : 0; - dem += penalty; - if (dem < cur.demerits_) - { - cur.demerits_ = dem; - cur.force_ = space.force_; - cur.penalty_ = penalty + (prev ? prev->penalty_ : 0); - cur.prev_ = page_start - 1; + dem += penalty; + if (dem < cur.demerits_) + { + cur.demerits_ = dem; + cur.force_ = space.force_; + cur.penalty_ = penalty + (prev ? prev->penalty_ : 0); + cur.prev_ = page_start - 1; + } } + + if (page_start > 0 + && lines_[page_start-1].page_permission_ == ly_symbol2scm ("force")) + break; } return !isinf (cur.demerits_); } static vsize -min_page_count (vector const &lines, Real page_height) +min_page_count (vector const &lines, Real page_height, bool ragged) { vsize ret = 1; Real cur_rod_height = 0; for (vsize i = 0; i < lines.size (); i++) { - Real next_height = cur_rod_height + lines[i].extent_.length () + Real ext_len = lines[i].extent_.length (); + Real next_height = cur_rod_height + + (ragged ? max (ext_len, lines[i].space_) : ext_len) + ((i > 0 && cur_rod_height > 0) ? lines[i-1].padding_: 0); if ((next_height > page_height && cur_rod_height > 0) || (i > 0 && lines[i-1].page_permission_ == ly_symbol2scm ("force"))) { ret++; - cur_rod_height = lines[i].extent_.length (); + cur_rod_height = ragged ? max (ext_len, lines[i].space_) : ext_len; } else cur_rod_height = next_height; @@ -373,31 +362,33 @@ min_page_count (vector const &lines, Real page_height) Spacing_result space_systems_on_min_pages (vector const &lines, Real page_height, - Real odd_pages_penalty) + Real odd_pages_penalty, + bool ragged, + bool ragged_last) { vector compressed_lines = compress_lines (lines); - vsize min_p_count = min_page_count (compressed_lines, page_height); + vsize min_p_count = min_page_count (compressed_lines, page_height, ragged); Spacing_result ret; if (min_p_count == 1) { - Spacing_result candidate1 = space_systems_on_1_page (compressed_lines, page_height); + Spacing_result candidate1 = space_systems_on_1_page (compressed_lines, page_height, ragged || ragged_last); candidate1.force_.back () += odd_pages_penalty; candidate1.demerits_ += odd_pages_penalty; if (compressed_lines.size () == 1) ret = candidate1; else { - Spacing_result candidate2 = space_systems_on_2_pages (compressed_lines, page_height); + Spacing_result candidate2 = space_systems_on_2_pages (compressed_lines, page_height, ragged, ragged_last); ret = (candidate1.demerits_ < candidate2.demerits_) ? candidate1 : candidate2; } } else if (min_p_count == 2) - ret = space_systems_on_2_pages (compressed_lines, page_height); + ret = space_systems_on_2_pages (compressed_lines, page_height, ragged, ragged_last); else { - Page_spacer ps (compressed_lines, page_height); + Page_spacer ps (compressed_lines, page_height, ragged, ragged_last); Spacing_result candidate1 = ps.solve (min_p_count); if (min_p_count % 2 == 0) ret = candidate1; @@ -423,13 +414,16 @@ space_systems_on_min_pages (vector const &lines, Spacing_result space_systems_on_best_pages (vector const &lines, Real page_height, - Real odd_pages_penalty) + Real odd_pages_penalty, + bool ragged, + bool ragged_last) { vector compressed_lines = compress_lines (lines); - vsize min_p_count = min_page_count (compressed_lines, page_height); + vsize min_p_count = min_page_count (compressed_lines, page_height, ragged); - Page_spacer ps (compressed_lines, page_height); + 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++) diff --git a/lily/page-turn-page-breaking.cc b/lily/page-turn-page-breaking.cc index 338fe4a54d..6171adca4d 100644 --- a/lily/page-turn-page-breaking.cc +++ b/lily/page-turn-page-breaking.cc @@ -18,8 +18,14 @@ #include "system.hh" #include "warn.hh" +static bool +is_break (Grob *g) +{ + return scm_is_symbol (g->get_property ("page-turn-permission")); +} + Page_turn_page_breaking::Page_turn_page_breaking (Paper_book *pb) - : Page_breaking (pb, true) + : Page_breaking (pb, is_break) { } @@ -52,14 +58,16 @@ Page_turn_page_breaking::Break_node Page_turn_page_breaking::put_systems_on_pages (vsize start, vsize end, vector const &lines, - vector const &div, + Line_division const &div, int 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); - Spacing_result result = space_systems_on_min_pages (lines, page_h, blank_force); + Spacing_result result = space_systems_on_min_pages (lines, page_h, blank_force, ragged_all, ragged_last); Break_node ret; ret.prev_ = start - 1; @@ -98,6 +106,10 @@ Page_turn_page_breaking::calc_subproblem (vsize ending_breakpoint) for (vsize start = end; start--;) { + if (start < end-1 + && breakpoint_property (start+1, "page-turn-permission") == ly_symbol2scm ("force")) + break; + if (start > 0 && best.demerits_ < state_[start-1].demerits_) continue; @@ -111,46 +123,26 @@ Page_turn_page_breaking::calc_subproblem (vsize ending_breakpoint) p_num = f_p_num + p_count + ((f_p_num > 1) ? p_count % 2 : 0); } - vector min_systems; - vector max_systems; - vsize min_system_count = 0; - vsize max_system_count = 0; - this_start_best.demerits_ = infinity_f; - calc_system_count_bounds (start, end, &min_systems, &max_systems); + Line_division min_division; + Line_division max_division; - /* heuristic: we've prepended some music, only the first system is allowed - to get more lines */ - if (this_start_best.page_count_ == 2 && this_start_best.div_.size ()) - { - vsize ds = max_systems.size () - this_start_best.div_.size (); - for (vsize i = ds + 1; i < max_systems.size (); i++) - { - assert (this_start_best.div_[i - ds] <= max_systems[i]); - max_systems[i] = this_start_best.div_[i - ds]; - } - } - - for (vsize i = 0; i < min_systems.size (); i++) - { - min_system_count += min_systems[i]; - max_system_count += max_systems[i]; - } + vsize min_sys_count = min_system_count (start, end); + vsize max_sys_count = max_system_count (start, end); + this_start_best.demerits_ = infinity_f; bool ok_page = true; /* heuristic: we've just added a breakpoint, we'll need at least as many systems as before */ - min_system_count = max (min_system_count, prev_best_system_count); - for (vsize sys_count = min_system_count; sys_count <= max_system_count && ok_page; sys_count++) + 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 > div; - vector blank; + vector div = line_divisions (start, end, sys_count, min_division, max_division); bool found = false; - divide_systems (sys_count, min_systems, max_systems, &div, &blank); for (vsize d = 0; d < div.size (); d++) { - vector line = get_line_details (start, end, div[d]); + vector line = line_details (start, end, div[d]); cur = put_systems_on_pages (start, end, line, div[d], p_num); @@ -169,6 +161,10 @@ Page_turn_page_breaking::calc_subproblem (vsize ending_breakpoint) found = true; this_start_best = cur; prev_best_system_count = sys_count; + + /* heuristic: if we increase the number of systems, we can bound the + division from below by our current best division */ + min_division = div[d]; } } if (!found && this_start_best.too_many_lines_) @@ -222,39 +218,10 @@ Page_turn_page_breaking::make_lines (vector *psoln) vsize start = n > 0 ? soln[n-1].break_pos_ : 0; vsize end = soln[n].break_pos_; - /* break each score into pieces */ - for (vsize i = next_system (start); i <= breaks_[end].sys_; i++) - { - vsize d = i - next_system (start); - if (all_[i].pscore_) - { - vector line_brk; - - line_brk = get_line_breaks (i, soln[n].div_[d], start, end); - all_[i].pscore_->root_system ()->break_into_pieces (line_brk); - assert (line_brk.size () == soln[n].div_[d]); - } - } + break_into_pieces (start, end, soln[n].div_); } - SCM ret = SCM_EOL; - SCM *tail = &ret; - for (vsize i = 0; i < all_.size (); i++) - { - if (all_[i].pscore_) - 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); - all_[i].prob_->unprotect (); - tail = SCM_CDRLOC (*tail); - } - } - return ret; + + return systems (); } SCM diff --git a/lily/paper-book.cc b/lily/paper-book.cc index b91129b5d2..81376da0a4 100644 --- a/lily/paper-book.cc +++ b/lily/paper-book.cc @@ -382,6 +382,20 @@ Paper_book::pages () pages_ = SCM_EOL; SCM proc = paper_->c_variable ("page-breaking"); pages_ = scm_apply_0 (proc, scm_list_1(self_scm ())); + + /* set systems_ from the pages */ + if (systems_ == SCM_BOOL_F) + { + systems_ = SCM_EOL; + for (SCM p = pages_; p != SCM_EOL; p = scm_cdr (p)) + { + Prob *page = unsmob_prob (scm_car (p)); + SCM systems = page->get_property ("lines"); + + systems_ = scm_append (scm_list_2 (systems_, systems)); + } + } + return pages_; }