/*
page-spacing.cc - implement routines for spacing
systems vertically on pages
source file of the GNU LilyPond music typesetter
(c) 2006 Han-Wen Nienhuys
*/
#include "page-spacing.hh"
/*
A much simplified rods-and-springs problem.
*/
struct Page_spacing
{
Real force_;
Real page_height_;
Real rod_height_;
Real spring_len_;
Real inverse_spring_k_;
Line_details last_line_;
Page_spacing (Real page_height)
{
page_height_ = page_height;
clear ();
}
void calc_force ();
void append_system (const Line_details &line);
void prepend_system (const Line_details &line);
void clear ();
};
void
Page_spacing::calc_force ()
{
if (rod_height_ - last_line_.bottom_padding_ >= page_height_ || !inverse_spring_k_)
force_ = infinity_f;
else
force_ = (page_height_ - rod_height_ - last_line_.bottom_padding_ - spring_len_) / inverse_spring_k_;
}
void
Page_spacing::append_system (const Line_details &line)
{
rod_height_ += last_line_.padding_;
rod_height_ += line.extent_;
spring_len_ += line.space_;
inverse_spring_k_ += line.inverse_hooke_;
last_line_ = line;
calc_force ();
}
void
Page_spacing::prepend_system (const Line_details &line)
{
if (rod_height_)
rod_height_ += line.padding_;
else
last_line_ = line;
rod_height_ += line.extent_;
spring_len_ += line.space_;
inverse_spring_k_ += line.inverse_hooke_;
calc_force ();
}
void
Page_spacing::clear ()
{
force_ = rod_height_ = spring_len_ = 0;
inverse_spring_k_ = 0;
last_line_.padding_ = 0;
}
/* for each forbidden page break, merge the systems around it into one system. */
static vector
compress_lines (const vector &orig)
{
vector ret;
for (vsize i = 0; i < orig.size (); i++)
{
if (i < orig.size () - 1 && orig[i].page_permission_ == SCM_EOL)
{
Line_details compressed = orig[i+1];
compressed.extent_ += orig[i].extent_ + orig[i].padding_;
compressed.space_ += orig[i].space_;
compressed.inverse_hooke_ += orig[i].inverse_hooke_;
/* we don't need the force_ field for the vertical spacing,
so we use force_ = -1 to signal that the line was compressed.
This makes uncompression much easier. */
compressed.force_ = -1;
ret.push_back (compressed);
i++;
}
else
{
ret.push_back (orig[i]);
ret.back ().force_ = 1;
}
}
return ret;
}
/* translate the number of systems-per-page into something meaningful for
the uncompressed lines.
*/
static vector
uncompress_solution (vector const &sol,
vector const &compressed)
{
vector ret;
vsize start_sys = 0;
for (vsize i = 0; i < sol.size (); i++)
{
int compressed_count = 0;
for (vsize j = start_sys; j < start_sys + sol[i]; j++)
if (compressed[j].force_ < 0)
compressed_count++;
ret.push_back (sol[i] + compressed_count);
start_sys += sol[i];
}
return ret;
}
/* the cases for page_count = 1 or 2 can be done in O(n) time. Since they
are by far the most common cases, we have special functions for them */
static Spacing_result
space_systems_on_1_page (vector const &lines, Real page_height)
{
Page_spacing space (page_height);
Spacing_result ret;
for (vsize i = 0; i < lines.size (); i++)
space.append_system (lines[i]);
ret.systems_per_page_.push_back (lines.size ());
ret.force_.push_back (space.force_);
ret.penalty_ = lines.back ().page_penalty_ + lines.back ().turn_penalty_;
ret.demerits_ = fabs (ret.force_.back ()) + ret.penalty_;
return ret;
}
static Spacing_result
space_systems_on_2_pages (vector const &lines, Real page_height)
{
/* if there is a forced break, this reduces to 2 1-page problems */
SCM sym = ly_symbol2scm ("force");
for (vsize i = 0; i < lines.size () - 1; i++)
if (lines[i].page_permission_ == sym)
{
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);
p1.systems_per_page_.push_back (p2.systems_per_page_[0]);
p1.force_.push_back (p2.force_[0]);
p1.penalty_ += p2.penalty_ - lines[i].turn_penalty_;
return p1;
}
vector page1_force;
vector page2_force;
Page_spacing page1 (page_height);
Page_spacing page2 (page_height);
page1_force.resize (lines.size () - 1);
page2_force.resize (lines.size () - 1);
for (vsize i = 0; i < page1_force.size (); i++)
{
page1.append_system (lines[i]);
page2.prepend_system (lines[lines.size () - 1 - i]);
page1_force[i] = page1.force_;
page2_force[page2_force.size () - 1 - i] = page2.force_;
}
vsize best_sys_count = 1;
Real best_demerits = infinity_f;
for (vsize i = 0; i < page1_force.size (); i++)
{
Real dem = fabs (page1_force[i]) + fabs (page2_force[i]) + lines[i+1].page_penalty_;
if (dem < best_demerits)
{
best_demerits = dem;
best_sys_count = i+1;
}
}
Spacing_result ret;
ret.systems_per_page_.push_back (best_sys_count);
ret.systems_per_page_.push_back (lines.size () - best_sys_count);
ret.force_.push_back (page1_force[best_sys_count-1]);
ret.force_.push_back (page2_force[best_sys_count-1]);
ret.penalty_ = lines[best_sys_count-1].page_penalty_
+ lines.back ().page_penalty_
+ lines.back ().turn_penalty_;
ret.demerits_ = best_demerits;
return ret;
}
/* 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.
*/
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_;
};
class Page_spacer
{
public:
Page_spacer (vector const &lines, Real page_height);
Spacing_result solve (vsize page_count);
private:
Real page_height_;
vector lines_;
vector 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)
: lines_ (lines)
{
page_height_ = page_height;
max_page_count_ = 0;
}
Spacing_result
Page_spacer::solve (vsize page_count)
{
if (page_count > max_page_count_)
resize (page_count);
Spacing_result ret;
ret.force_.resize (page_count);
ret.systems_per_page_.resize (page_count);
vsize rank = lines_.size ();
vsize system = lines_.size () - 1;
ret.penalty_ = state_[(page_count-1)*rank + system].penalty_
+ lines_.back ().page_penalty_ + lines_.back ().turn_penalty_;
for (vsize p = page_count - 1; p != VPOS; p--)
{
assert (system != VPOS);
Page_spacing_node const &ps = state_[p*rank + system];
ret.force_[p] = ps.force_;
ret.demerits_ += fabs (ps.force_);
if (p == 0)
ret.systems_per_page_[p] = system + 1;
else
ret.systems_per_page_[p] = system - ps.prev_;
system = ps.prev_;
}
ret.demerits_ += ret.penalty_;
return ret;
}
void
Page_spacer::resize (vsize page_count)
{
assert (page_count > 0);
if (max_page_count_ >= page_count)
return;
state_.resize (page_count * lines_.size ());
for (vsize page = max_page_count_; page < page_count; page++)
for (vsize line = page; line < lines_.size (); line++)
if (!calc_subproblem (page, line))
break;
max_page_count_ = page_count;
}
bool
Page_spacer::calc_subproblem (vsize page, vsize line)
{
Page_spacing space (page_height_);
vsize rank = lines_.size ();
Page_spacing_node &cur = state_[page*rank + line];
for (vsize page_start = line; page_start >= page && page_start != VPOS; page_start--)
{
Page_spacing_node const *prev = page > 0 ? &state_[(page-1)*rank + page_start - 1] : 0;
space.prepend_system (lines_[page_start]);
if (isinf (space.force_))
break;
if (page == 0 && page_start > 0)
continue;
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;
}
}
return !isinf (cur.demerits_);
}
static vsize
min_pages (vector const &lines, Real page_height)
{
vsize ret = 1;
Real cur_rod_height = 0;
SCM sym = ly_symbol2scm ("force");
for (vsize i = 0; i < lines.size (); i++)
{
Real next_height = cur_rod_height + lines[i].extent_
+ ((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_ == sym))
{
ret++;
cur_rod_height = lines[i].extent_;
}
else
cur_rod_height = next_height;
}
return ret;
}
Spacing_result
space_systems_on_min_pages (vector const &lines,
Real page_height,
Real odd_pages_penalty)
{
vector compressed_lines = compress_lines (lines);
vsize min_p = min_pages (compressed_lines, page_height);
Spacing_result ret;
if (min_p == 1)
{
Spacing_result candidate1 = space_systems_on_1_page (compressed_lines, page_height);
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);
ret = (candidate1.demerits_ < candidate2.demerits_) ?
candidate1 : candidate2;
}
}
else if (min_p == 2)
ret = space_systems_on_2_pages (compressed_lines, page_height);
else
{
Page_spacer ps (lines, page_height);
Spacing_result candidate1 = ps.solve (min_p);
if (min_p % 2 == 0 || min_p == lines.size ())
ret = candidate1;
else
{
Spacing_result candidate2 = ps.solve (min_p + 1);
candidate1.force_.back () += odd_pages_penalty;
candidate1.demerits_ += odd_pages_penalty;
ret = (candidate1.demerits_ < candidate2.demerits_) ?
candidate1 : candidate2;
}
}
ret.systems_per_page_ = uncompress_solution (ret.systems_per_page_, compressed_lines);
return ret;
}