2 simple-spacer.cc -- implement Simple_spacer
4 source file of the GNU LilyPond music typesetter
6 (c) 1999--2006 Han-Wen Nienhuys <hanwen@xs4all.nl>
9 - add support for different stretch/shrink constants?
14 #include "column-x-positions.hh"
15 #include "dimensions.hh"
16 #include "international.hh"
17 #include "libc-extension.hh" // isinf
18 #include "paper-column.hh"
19 #include "simple-spacer.hh"
20 #include "spaceable-grob.hh"
25 A simple spacing constraint solver. The approach:
27 Stretch the line uniformly until none of the constraints (rods)
28 block. It then is very wide.
30 Compress until the next constraint blocks,
32 Mark the springs over the constrained part to be non-active.
34 Repeat with the smaller set of non-active constraints, until all
35 constraints blocked, or until the line is as short as desired.
37 This is much simpler, and much much faster than full scale
38 Constrained QP. On the other hand, a situation like this will not
39 be typeset as dense as possible, because
42 veryveryverylongsyllable2 veryveryverylongsyllable2
43 " "4 veryveryverylongsyllable2 syllable4
46 can be further compressed to
50 veryveryverylongsyllable2 veryveryverylongsyllable2
51 " "4 veryveryverylongsyllable2 syllable4
54 Perhaps this is not a bad thing, because the 1st looks better anyway. */
57 positive force = expanding, negative force = compressing.
60 Simple_spacer::Simple_spacer ()
67 Simple_spacer::force ()
73 Simple_spacer::fits ()
79 Simple_spacer::rod_force (int l, int r, Real dist)
81 Real c = range_stiffness (l, r);
82 Real d = range_ideal_len (l, r);
83 Real block_stretch = dist - d;
84 return c * block_stretch;
88 Simple_spacer::add_rod (int l, int r, Real dist)
90 if (isinf (dist) || isnan (dist))
92 programming_error ("ignoring weird minimum distance");
96 Real block_force = rod_force (l, r, dist);
98 if (isinf (block_force))
100 Real spring_dist = range_ideal_len (l, r);
101 if (spring_dist < dist)
102 for (int i = l; i < r; i++)
103 springs_[i].ideal_ *= dist / spring_dist;
107 force_ = max (force_, block_force);
108 for (int i = l; i < r; i++)
109 springs_[i].block_force_ = max (block_force, springs_[i].block_force_);
113 Simple_spacer::range_ideal_len (int l, int r) const
116 for (int i = l; i < r; i++)
117 d += springs_[i].ideal_;
122 Simple_spacer::range_stiffness (int l, int r) const
125 for (int i = l; i < r; i++)
126 den += springs_[i].inverse_hooke_;
132 Simple_spacer::configuration_length () const
135 for (vsize i = 0; i < springs_.size (); i++)
136 l += springs_[i].length (force_);
142 Simple_spacer::solve (Real line_len, bool ragged)
144 Real conf = configuration_length ();
147 line_len_ = line_len;
151 fits_ = configuration_length () <= line_len_;
152 /* we need to calculate a force here to prevent a bunch of short lines */
154 force_ = expand_line ();
156 else if (conf < line_len_)
157 force_ = expand_line ();
158 else if (conf > line_len_)
159 force_ = compress_line ();
163 Simple_spacer::expand_line ()
165 double inv_hooke = 0;
166 double cur_len = configuration_length ();
169 for (vsize i=0; i < springs_.size (); i++)
170 inv_hooke += springs_[i].inverse_hooke_;
172 assert (cur_len <= line_len_);
173 return (line_len_ - cur_len) / inv_hooke + force_;
177 Simple_spacer::compress_line ()
179 double inv_hooke = 0;
180 double cur_len = configuration_length ();
181 double cur_force = force_;
184 for (vsize i=0; i < springs_.size (); i++)
185 inv_hooke += springs_[i].inverse_hooke_;
187 assert (line_len_ <= cur_len);
189 vector<Spring_description> sorted_springs = springs_;
190 sort (sorted_springs.begin (), sorted_springs.end (), greater<Spring_description> ());
191 for (vsize i = 0; i < sorted_springs.size (); i++)
193 Spring_description sp = sorted_springs[i];
195 assert (sp.block_force_ <= cur_force);
196 if (isinf (sp.block_force_))
199 double block_dist = (cur_force - sp.block_force_) * inv_hooke;
200 if (cur_len - block_dist <= line_len_)
201 return cur_force + (line_len_ - cur_len) / inv_hooke;
202 cur_len -= block_dist;
203 inv_hooke -= sp.inverse_hooke_;
204 cur_force = sp.block_force_;
212 Simple_spacer::add_spring (Real ideal, Real inverse_hooke)
214 Spring_description desc;
217 desc.inverse_hooke_ = inverse_hooke;
218 if (!desc.is_sane ())
220 programming_error ("insane spring found, setting to unit");
222 desc.inverse_hooke_ = 1.0;
226 desc.block_force_ = -desc.ideal_ / desc.inverse_hooke_;
227 // block at distance 0
229 springs_.push_back (desc);
233 Simple_spacer::spring_positions () const
238 for (vsize i = 0; i < springs_.size (); i++)
239 ret.push_back (ret.back () + springs_[i].length (ragged_ ? 0.0 : force_));
243 /****************************************************************/
245 Spring_description::Spring_description ()
248 inverse_hooke_ = 0.0;
253 Spring_description::is_sane () const
255 return (inverse_hooke_ >= 0)
257 && !isinf (ideal_) && !isnan (ideal_);
261 Spring_description::length (Real f) const
263 return ideal_ + max (f, block_force_) * inverse_hooke_;
266 /****************************************************************/
269 TODO: should a add penalty for widely varying spring forces (caused
278 The ## forces the notes apart; we shouldn't allow the O's to touch
287 bool operator< (const Rod_desc r)
293 Rod_desc (vsize r, Real d)
302 vector<Rod_desc> rods_;
303 vector<Rod_desc> end_rods_; /* use these if they end at the last column of the line */
307 Real end_inverse_hooke_;
308 Interval keep_inside_line_;
311 static int compare_paper_column_rank (Grob *const &a, Grob *const &b);
316 return (scm_is_pair (g->get_object ("between-cols")));
320 maybe_find_prebroken_piece (Grob *g, Direction d)
322 Grob *ret = dynamic_cast<Item*> (g)->find_prebroken_piece (d);
329 next_spaceable_column (vector<Grob*> const &list, vsize starting)
331 for (vsize i = starting+1; i < list.size (); i++)
332 if (!is_loose (list[i]))
337 /* this only returns non-NULL if the line-ending column is the next
338 spaceable-or-breakable column */
340 next_line_ending_column (vector<Grob*> const &list, vsize starting)
342 vsize i = starting + 1;
343 for (; i < list.size ()
344 && is_loose (list[i])
345 && !Paper_column::is_breakable (list[i]);
348 return dynamic_cast<Item*> (list[i])->find_prebroken_piece (LEFT);
352 get_column_spring (Grob *this_col, Grob *next_col, Real *ideal, Real *inv_hooke)
354 Spring_smob *spring = 0;
356 for (SCM s = this_col->get_object ("ideal-distances");
357 !spring && scm_is_pair (s);
360 Spring_smob *sp = unsmob_spring (scm_car (s));
362 if (sp->other_ == next_col)
367 programming_error (_f ("No spring between column %d and next one",
368 Paper_column::get_rank (this_col)));
370 *ideal = (spring) ? spring->distance_ : 5.0;
371 *inv_hooke = (spring) ? spring->inverse_strength_ : 1.0;
375 get_column_desc (vector<Grob*> const &cols, vsize col_index, bool line_starter)
377 Grob *col = cols[col_index];
379 col = maybe_find_prebroken_piece (col, RIGHT);
382 Grob *next_col = next_spaceable_column (cols, col_index);
384 get_column_spring (col, next_col, &desc.ideal_, &desc.inverse_hooke_);
385 Grob *end_col = next_line_ending_column (cols, col_index);
387 get_column_spring (col, end_col, &desc.end_ideal_, &desc.end_inverse_hooke_);
389 for (SCM s = Spaceable_grob::get_minimum_distances (col);
390 scm_is_pair (s); s = scm_cdr (s))
392 Grob *other = unsmob_grob (scm_caar (s));
393 vsize j = binary_search (cols, other, &compare_paper_column_rank, col_index);
396 if (cols[j] == other)
397 desc.rods_.push_back (Rod_desc (j, scm_to_double (scm_cdar (s))));
398 else /* it must end at the LEFT prebroken_piece */
399 desc.end_rods_.push_back (Rod_desc (j, scm_to_double (scm_cdar (s))));
402 if (!line_starter && to_boolean (col->get_property ("keep-inside-line")))
403 desc.keep_inside_line_ = col->extent (col, X_AXIS);
408 get_line_forces (vector<Grob*> const &icols, vector<vsize> breaks,
409 Real line_len, Real indent, bool ragged)
412 force.resize (breaks.size () * breaks.size ());
414 vector<Column_desc> cols;
416 cols.push_back (Column_desc ());
417 for (vsize i = 1; i < icols.size () - 1; i++)
419 if (b < breaks.size () && breaks[b] == i)
421 breaks[b] = cols.size ();
424 if (!is_loose (icols[i]))
425 cols.push_back (get_column_desc (icols, i, false));
427 breaks.back () = cols.size () - 1;
429 for (vsize b = 0; b < breaks.size () - 1; b++)
431 cols[breaks[b]] = get_column_desc (icols, breaks[b], true);
432 vsize st = breaks[b];
434 for (vsize c = b+1; c < breaks.size (); c++)
436 vsize end = breaks[c];
437 Simple_spacer spacer;
439 for (vsize i = breaks[b]; i < end - 1; i++)
440 spacer.add_spring (cols[i].ideal_, cols[i].inverse_hooke_);
441 spacer.add_spring (cols[end-1].end_ideal_, cols[end-1].inverse_hooke_);
444 for (vsize i = breaks[b]; i < end; i++)
446 for (vsize r = 0; r < cols[i].rods_.size (); r++)
447 if (cols[i].rods_[r].r_ < end)
448 spacer.add_rod (i - st, cols[i].rods_[r].r_ - st, cols[i].rods_[r].dist_);
449 for (vsize r = 0; r < cols[i].end_rods_.size (); r++)
450 if (cols[i].end_rods_[r].r_ == end)
451 spacer.add_rod (i - st, end - st, cols[i].end_rods_[r].dist_);
452 if (!cols[i].keep_inside_line_.is_empty ())
454 spacer.add_rod (i - st, end - st, cols[i].keep_inside_line_[RIGHT]);
455 spacer.add_rod (0, i - st, cols[i].keep_inside_line_[LEFT]);
458 spacer.solve ((b == 0) ? line_len - indent : line_len, ragged);
459 force[b * breaks.size () + c] = spacer.force ();
462 force[b * breaks.size () + c] = infinity_f;
471 get_line_configuration (vector<Grob*>const &columns,
476 vector<Column_desc> cols;
477 Simple_spacer spacer;
478 Column_x_positions ret;
480 ret.cols_.push_back (dynamic_cast<Item*> (columns[0])->find_prebroken_piece (RIGHT));
481 for (vsize i = 1; i < columns.size () - 1; i++)
483 if (is_loose (columns[i]))
484 ret.loose_cols_.push_back (columns[i]);
486 ret.cols_.push_back (columns[i]);
488 ret.cols_.push_back (dynamic_cast<Item*> (columns.back ())->find_prebroken_piece (LEFT));
490 cols.resize (ret.cols_.size () - 1);
492 /* since we've already put our line-ending column in the column list, we can ignore
493 the end_XXX_ fields of our column_desc */
494 for (vsize i = 0; i < cols.size (); i++)
496 cols[i] = get_column_desc (ret.cols_, i, i == 0);
497 spacer.add_spring (cols[i].ideal_, cols[i].inverse_hooke_);
499 for (vsize i = 0; i < cols.size (); i++)
500 for (vsize r = 0; r < cols[i].rods_.size (); r++)
501 spacer.add_rod (i, cols[i].rods_[r].r_, cols[i].rods_[r].dist_);
503 spacer.solve (line_len, ragged);
504 ret.force_ = spacer.force ();
507 We used to have a penalty for compression, no matter what, but that
508 fucked up wtk1-fugue2 (taking 3 full pages.)
510 ret.config_ = spacer.spring_positions ();
511 for (vsize i = 0; i < ret.config_.size (); i++)
512 ret.config_[i] += indent;
514 ret.satisfies_constraints_ = spacer.fits ();
517 Check if breaking constraints are met.
519 for (vsize i = 1; i < ret.cols_.size () - 1; i++)
521 SCM p = ret.cols_[i]->get_property ("line-break-permission");
522 if (p == ly_symbol2scm ("force"))
523 ret.satisfies_constraints_ = false;
530 compare_paper_column_rank (Grob *const &a,
533 return Paper_column::get_rank (a) - Paper_column::get_rank (b);