2 page-breaking.cc -- implement a superclass and utility
3 functions shared by various page-breaking algorithms
5 source file of the GNU LilyPond music typesetter
7 (c) 2006--2007 Joe Neeman <joeneeman@gmail.com>
10 #include "page-breaking.hh"
12 #include "international.hh"
14 #include "output-def.hh"
15 #include "page-spacing.hh"
16 #include "paper-book.hh"
17 #include "paper-score.hh"
18 #include "paper-system.hh"
22 /* for Page_breaking, the start index (when we are dealing with the stuff
23 between a pair of breakpoints) refers to the break_ index of the end of
24 the previous page. So the system index of the start of the current page
25 could either be the same as the end of the previous page or one more than
28 /* Turn a break index into the sys index that starts the next page */
30 Page_breaking::next_system (Break_position const &break_pos) const
32 vsize sys = break_pos.sys_;
34 if (sys == VPOS) /* beginning of the book */
36 if (all_[sys].pscore_ && !break_pos.score_ender_)
37 return sys; /* the score overflows the previous page */
38 return sys + 1; /* this page starts with a new sys */
41 Page_breaking::Page_breaking (Paper_book *pb, Break_predicate is_break)
44 create_system_list ();
45 find_chunks_and_breaks (is_break);
48 Page_breaking::~Page_breaking ()
52 /* translate indices into breaks_ into start-end parameters for the line breaker */
54 Page_breaking::line_breaker_args (vsize sys,
55 Break_position const &start,
56 Break_position const &end,
57 vsize *line_breaker_start,
58 vsize *line_breaker_end)
60 assert (all_[sys].pscore_);
61 assert (next_system (start) <= sys && sys <= end.sys_);
63 if (start.sys_ == sys)
64 *line_breaker_start = start.score_break_;
66 *line_breaker_start = 0;
69 *line_breaker_end = end.score_break_;
71 *line_breaker_end = VPOS;
75 Page_breaking::break_into_pieces (vsize start_break, vsize end_break, Line_division const &div)
77 vector<Break_position> chunks = chunk_list (start_break, end_break);
78 assert (chunks.size () == div.size () + 1);
80 for (vsize i = 0; i + 1 < chunks.size (); i++)
82 vsize sys = next_system (chunks[i]);
83 if (all_[sys].pscore_)
87 line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end);
89 vector<Column_x_positions> pos = line_breaking_[sys].solve (start, end, div[i]);
90 all_[sys].pscore_->root_system ()->break_into_pieces (pos);
96 Page_breaking::systems ()
99 for (vsize sys = 0; sys < all_.size (); sys++)
101 if (all_[sys].pscore_)
103 all_[sys].pscore_->root_system ()->do_break_substitution_and_fixup_refpoints ();
104 SCM lines = all_[sys].pscore_->root_system ()->get_broken_system_grobs ();
105 ret = scm_cons (lines, ret);
109 Prob *pb = all_[sys].prob_;
110 ret = scm_cons (scm_list_1 (pb->self_scm ()), ret);
114 return scm_append (scm_reverse (ret));
118 Page_breaking::line_details (vsize start_break, vsize end_break, Line_division const &div)
120 vector<Break_position> chunks = chunk_list (start_break, end_break);
121 vector<Line_details> ret;
122 assert (chunks.size () == div.size () + 1);
124 for (vsize i = 0; i + 1 < chunks.size (); i++)
126 vsize sys = next_system (chunks[i]);
127 if (all_[sys].pscore_)
131 line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end);
133 vector<Line_details> details = line_breaking_[sys].line_details (start, end, div[i]);
134 ret.insert (ret.end (), details.begin (), details.end ());
138 assert (div[i] == 1);
139 ret.push_back (Line_details (all_[sys].prob_));
146 Page_breaking::page_height (int page_num, bool last)
148 SCM mod = scm_c_resolve_module ("scm page");
149 SCM calc_height = scm_c_module_lookup (mod, "calc-printable-height");
150 SCM make_page = scm_c_module_lookup (mod, "make-page");
152 calc_height = scm_variable_ref (calc_height);
153 make_page = scm_variable_ref (make_page);
155 SCM page = scm_apply_0 (make_page, scm_list_n (
157 ly_symbol2scm ("page-number"), scm_from_int (page_num),
158 ly_symbol2scm ("is-last"), scm_from_bool (last),
160 SCM height = scm_apply_1 (calc_height, page, SCM_EOL);
161 return scm_to_double (height) - scm_to_double (book_->paper_->c_variable ("page-top-space"));
165 Page_breaking::breakpoint_property (vsize breakpoint, char const *str)
167 Break_position const &pos = breaks_[breakpoint];
169 if (pos.sys_ == VPOS)
171 if (all_[pos.sys_].pscore_)
172 return pos.col_->get_property (str);
173 return all_[pos.sys_].prob_->get_property (str);
177 Page_breaking::make_pages (vector<vsize> lines_per_page, SCM systems)
179 SCM layout_module = scm_c_resolve_module ("scm layout-page-layout");
180 SCM page_module = scm_c_resolve_module ("scm page");
182 SCM make_page = scm_c_module_lookup (layout_module, "stretch-and-draw-page");
183 SCM page_stencil = scm_c_module_lookup (page_module, "page-stencil");
184 make_page = scm_variable_ref (make_page);
185 page_stencil = scm_variable_ref (page_stencil);
187 SCM book = book_->self_scm ();
188 bool ragged_all = to_boolean (book_->paper_->c_variable ("ragged-bottom"));
189 bool ragged_last = to_boolean (book_->paper_->c_variable ("ragged-last-bottom"));
190 int first_page_number = robust_scm2int (book_->paper_->c_variable ("first-page-number"), 1);
193 for (vsize i = 0; i < lines_per_page.size (); i++)
195 SCM page_num = scm_from_int (i + first_page_number);
196 SCM last = scm_from_bool (i == lines_per_page.size () - 1);
197 SCM ragged = scm_from_bool (ragged_all || (to_boolean (last) && ragged_last));
198 SCM line_count = scm_from_int (lines_per_page[i]);
199 SCM lines = scm_list_head (systems, line_count);
200 SCM page = scm_apply_0 (make_page,
201 scm_list_n (book, lines, page_num, ragged, last, SCM_UNDEFINED));
203 scm_apply_1 (page_stencil, page, SCM_EOL);
204 ret = scm_cons (page, ret);
205 systems = scm_list_tail (systems, line_count);
207 ret = scm_reverse (ret);
211 /* The page-turn-page-breaker needs to have a line-breaker between any two
212 columns with non-NULL page-turn-permission.
214 The optimal-breaker needs to have a line-breaker between any two columns
215 with page-break-permission = 'force.
217 By using a grob predicate, we can accommodate both of these uses.
220 Page_breaking::create_system_list ()
222 SCM specs = book_->get_system_specs ();
223 for (SCM s = specs; scm_is_pair (s); s = scm_cdr (s))
225 if (Paper_score *ps = dynamic_cast<Paper_score*> (unsmob_music_output (scm_car (s))))
227 SCM system_count = ps->layout ()->c_variable ("system-count");
229 if (scm_is_number (system_count))
230 s = scm_append (scm_list_3 (scm_list_1 (scm_car (s)),
231 scm_vector_to_list (ps->get_paper_systems ()),
234 all_.push_back (System_spec (ps));
238 Prob *pb = unsmob_prob (scm_car (s));
242 all_.push_back (System_spec (pb));
248 Page_breaking::find_chunks_and_breaks (Break_predicate is_break)
250 SCM force_sym = ly_symbol2scm ("force");
252 chunks_.push_back (Break_position ());
253 breaks_.push_back (Break_position ());
255 for (vsize i = 0; i < all_.size (); i++)
259 vector<Grob*> cols = all_[i].pscore_->root_system ()->used_columns ();
260 vector<vsize> line_breaker_columns;
261 line_breaker_columns.push_back (0);
263 for (vsize j = 1; j < cols.size (); j++)
265 bool last = j == cols.size () - 1;
266 bool break_point = is_break (cols[j]);
267 bool chunk_end = cols[j]->get_property ("page-break-permission") == force_sym;
268 Break_position cur_pos = Break_position (i,
269 line_breaker_columns.size (),
273 if (break_point || (i == all_.size () - 1 && last))
274 breaks_.push_back (cur_pos);
275 if (chunk_end || last)
276 chunks_.push_back (cur_pos);
278 if ((break_point || chunk_end) && !last)
279 line_breaker_columns.push_back (j);
281 line_breaking_.push_back (Constrained_breaking (all_[i].pscore_, line_breaker_columns));
285 /* TODO: we want some way of applying Break_p to a prob? */
286 if (i == all_.size () - 1)
287 breaks_.push_back (Break_position (i));
289 chunks_.push_back (Break_position (i));
290 line_breaking_.push_back (Constrained_breaking (NULL));
295 vector<Break_position>
296 Page_breaking::chunk_list (vsize start_index, vsize end_index)
298 Break_position start = breaks_[start_index];
299 Break_position end = breaks_[end_index];
302 for (i = 0; i < chunks_.size () && chunks_[i] <= start; i++)
305 vector<Break_position> ret;
306 ret.push_back (start);
307 for (; i < chunks_.size () && chunks_[i] < end; i++)
308 ret.push_back (chunks_[i]);
314 Page_breaking::min_system_count (vsize start, vsize end)
316 vector<Break_position> chunks = chunk_list (start, end);
317 Line_division div = system_count_bounds (chunks, true);
320 for (vsize i = 0; i < div.size (); i++)
326 Page_breaking::max_system_count (vsize start, vsize end)
328 vector<Break_position> chunks = chunk_list (start, end);
329 Line_division div = system_count_bounds (chunks, false);
332 for (vsize i = 0; i < div.size (); i++)
337 Page_breaking::Line_division
338 Page_breaking::system_count_bounds (vector<Break_position> const &chunks, bool min)
340 assert (chunks.size () >= 2);
343 ret.resize (chunks.size () - 1, 1);
345 for (vsize i = 0; i + 1 < chunks.size (); i++)
347 vsize sys = next_system (chunks[i]);
348 if (all_[sys].pscore_)
352 line_breaker_args (sys, chunks[i], chunks[i+1], &start, &end);
354 ? line_breaking_[sys].min_system_count (start, end)
355 : line_breaking_[sys].max_system_count (start, end);
362 vector<Page_breaking::Line_division>
363 Page_breaking::line_divisions (vsize start,
366 Line_division lower_bound,
367 Line_division upper_bound)
369 vector<Break_position> chunks = chunk_list (start, end);
371 if (!lower_bound.size ())
372 lower_bound = system_count_bounds (chunks, true);
373 if (!upper_bound.size ())
374 upper_bound = system_count_bounds (chunks, false);
376 assert (lower_bound.size () == chunks.size () - 1);
377 assert (upper_bound.size () == chunks.size () - 1);
379 vector<Line_division> ret;
380 Line_division work_in_progress;
382 line_divisions_rec (system_count,
391 Page_breaking::line_divisions_rec (vsize system_count,
392 Line_division const &min_sys,
393 Line_division const &max_sys,
394 vector<Line_division > *result,
395 Line_division *cur_division)
397 vsize my_index = cur_division->size ();
398 vsize others_min = 0;
399 vsize others_max = 0;
401 for (vsize i = my_index + 1; i < min_sys.size (); i++)
403 others_min += min_sys[i];
404 others_max += max_sys[i];
406 others_max = min (others_max, system_count);
407 vsize real_min = max (min_sys[my_index], system_count - others_max);
408 vsize real_max = min (max_sys[my_index], system_count - others_min);
410 if (real_min > real_max || real_min <= 0)
412 /* this should never happen within a recursive call. If it happens
413 at all, it means that we were called with an unsolvable problem
414 and we should return an empty result */
415 assert (my_index == 0);
419 for (vsize i = real_min; i <= real_max; i++)
421 cur_division->push_back (i);
422 if (my_index == min_sys.size () - 1)
423 result->push_back (*cur_division);
425 line_divisions_rec (system_count - i, min_sys, max_sys, result, cur_division);
426 cur_division->pop_back ();