]> git.donarmstrong.com Git - lilypond.git/blob - lily/spacing-loose-columns.cc
o * lily/spacing-engraver.cc (stop_translation_timestep): directly
[lilypond.git] / lily / spacing-loose-columns.cc
1 /*
2   spacing-loose-columns.cc -- implement loose column spacing.
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2005 Han-Wen Nienhuys <hanwen@xs4all.nl>
7 */
8
9
10 #include "system.hh"
11 #include "paper-column.hh"
12 #include "column-x-positions.hh"
13 #include "staff-spacing.hh"
14 #include "pointer-group-interface.hh"
15 #include "spacing-spanner.hh"
16 #include "note-spacing.hh"
17
18 #include "break-align-interface.hh"
19
20 /* Find the loose columns in POSNS, and drape them around the columns
21    specified in BETWEEN-COLS.  */
22 void
23 set_loose_columns (System *which, Column_x_positions const *posns)
24 {
25   int loose_col_count = posns->loose_cols_.size ();
26   for (int i = 0; i < loose_col_count; i++)
27     {
28       int divide_over = 1;
29       Item *loose = dynamic_cast<Item *> (posns->loose_cols_[i]);
30       Paper_column *col = dynamic_cast<Paper_column *> (loose);
31
32       if (col->get_system ())
33         continue;
34
35       Item *left = 0;
36       Item *right = 0;
37       while (1)
38         {
39           SCM between = loose->get_object ("between-cols");
40           if (!scm_is_pair (between))
41             break;
42
43           Item *le = dynamic_cast<Item *> (unsmob_grob (scm_car (between)));
44           Item *re = dynamic_cast<Item *> (unsmob_grob (scm_cdr (between)));
45
46           if (! (le && re))
47             break;
48
49           if (!left && le)
50             {
51               left = le->get_column ();
52               if (!left->get_system ())
53                 left = left->find_prebroken_piece (RIGHT);
54             }
55
56           divide_over++;
57           loose = right = re->get_column ();
58         }
59
60       if (!right->get_system ())
61         right = right->find_prebroken_piece (LEFT);
62
63       Grob *common = right->common_refpoint (left, X_AXIS);
64
65       int count = 0;
66       Real total_space = 0.0;
67       Real total_fixed = 0.0;
68
69       extract_grob_set (col, "spacing-wishes", wishes);
70       for (int i = 0; i < wishes.size (); i++)
71         {
72           Grob *spacing = wishes[i];
73           if (Staff_spacing::has_interface (spacing))
74             {
75               Real space = 0.0;
76               Real fixed = 0.0;
77               Staff_spacing::get_spacing_params (spacing, &space, &fixed);
78
79               total_fixed += fixed;
80               total_space += space;
81               count++;
82             }
83         }
84
85       Real right_point = 0.0;
86       Real distance_to_next = 0.0;
87       if (count)
88         {
89           total_space /= count;
90           total_fixed /= count;
91
92           distance_to_next = total_space;
93           right_point = right->relative_coordinate (common, X_AXIS);
94         }
95       else
96         {
97           Interval my_extent = col->extent (col, X_AXIS);
98           distance_to_next = my_extent[RIGHT] + 1.0;
99           right_point = right->extent (common, X_AXIS)[LEFT];
100         }
101
102       Real my_offset = right_point - distance_to_next;
103
104       col->system_ = which;
105       col->translate_axis (my_offset - col->relative_coordinate (common, X_AXIS), X_AXIS);
106     }
107 }
108
109
110
111 /*
112   Return whether COL is fixed to its neighbors by some kind of spacing
113   constraint.
114
115
116   If in doubt, then we're not loose; the spacing engine should space
117   for it, risking suboptimal spacing.
118
119   (Otherwise, we might risk core dumps, and other weird stuff.)
120 */
121 static bool
122 loose_column (Grob *l, Grob *c, Grob *r)
123 {
124   extract_grob_set (c, "right-neighbors", rns);
125   extract_grob_set (c, "left-neighbors", lns);
126   
127   /*
128     If this column doesn't have a proper neighbor, we should really
129     make it loose, but spacing it correctly is more than we can
130     currently can handle.
131
132     (this happens in the following situation:
133
134     |
135     |    clef G
136     *
137
138     |               |      ||
139     |               |      ||
140     O               O       ||
141
142
143     the column containing the clef is really loose, and should be
144     attached right to the first column, but that is a lot of work for
145     such a borderline case.)
146
147   */
148   if (lns.is_empty () || rns.is_empty ())
149     return false;
150
151   Item *l_neighbor = dynamic_cast<Item *> (lns[0]);
152   Item *r_neighbor = dynamic_cast<Item *> (rns[0]);
153
154   if (!l_neighbor || !r_neighbor)
155     return false;
156
157   l_neighbor = l_neighbor->get_column ();
158   r_neighbor = dynamic_cast<Item *> (Note_spacing::right_column (r_neighbor));
159
160   if (l == l_neighbor && r == r_neighbor)
161     return false;
162
163   if (!l_neighbor || !r_neighbor)
164     return false;
165
166   /*
167     Only declare loose if the bounds make a little sense.  This means
168     some cases (two isolated, consecutive clef changes) won't be
169     nicely folded, but hey, then don't do that.
170   */
171   if (! ((Paper_column::is_musical (l_neighbor) || Item::is_breakable (l_neighbor))
172          && (Paper_column::is_musical (r_neighbor) || Item::is_breakable (r_neighbor))))
173     {
174       return false;
175     }
176
177   /*
178     A rather hairy check, but we really only want to move around
179     clefs. (anything else?)
180
181     in any case, we don't want to move bar lines.
182   */
183   extract_grob_set (c, "elements", elts);
184   for (int i = elts.size (); i--; )
185     {
186       Grob *g = elts[i];
187       if (g && Break_align_interface::has_interface (g))
188         {
189           extract_grob_set (g, "elements", gelts);
190           for (int j = gelts.size (); j--; )
191             {
192               Grob *h = gelts[j];
193
194               /*
195                 ugh. -- fix staff-bar name?
196               */
197               if (h && h->get_property ("break-align-symbol") == ly_symbol2scm ("staff-bar"))
198                 return false;
199             }
200         }
201     }
202
203   return true;
204 }
205
206 /*
207   Remove columns that are not tightly fitting from COLS. In the
208   removed columns, set 'between-cols to the columns where it is in
209   between.
210 */
211 void
212 Spacing_spanner::prune_loose_columns (Grob *me, Link_array<Grob> *cols,
213                                       Spacing_options const *options)
214 {
215   Link_array<Grob> newcols;
216   Real increment = robust_scm2double (me->get_property ("spacing-increment"), 1.2);
217   for (int i = 0; i < cols->size (); i++)
218     {
219       if (Item::is_breakable (cols->elem (i))
220           || Paper_column::is_musical (cols->elem (i)))
221         {
222           newcols.push (cols->elem (i));
223           continue;
224         }
225
226       Grob *c = cols->elem (i);
227       if (loose_column (cols->elem (i - 1), c, cols->elem (i + 1)))
228         {
229           extract_grob_set (c, "right-neighbors", rns_arr);
230           extract_grob_set (c, "left-neighbors", lns_arr);
231           
232           SCM lns = lns_arr.size () ? lns_arr.top()->self_scm () : SCM_BOOL_F;
233           SCM rns = rns_arr.size () ? rns_arr.top()->self_scm () : SCM_BOOL_F;
234           
235           /*
236             Either object can be non existent, if the score ends
237             prematurely.
238           */
239
240           extract_grob_set (unsmob_grob (rns), "right-items", right_items);
241           c->set_object ("between-cols", scm_cons (lns,
242                                                    right_items[0]->self_scm ()));
243
244           /*
245             Set distance constraints for loose columns
246           */
247           Drul_array<Grob *> next_door;
248           next_door[LEFT] = cols->elem (i - 1);
249           next_door[RIGHT] = cols->elem (i + 1);
250           Direction d = LEFT;
251           Drul_array<Real> dists (0, 0);
252
253           do
254             {
255               dists[d] = 0.0;
256               Item *lc = dynamic_cast<Item *> ((d == LEFT) ? next_door[LEFT] : c);
257               Item *rc = dynamic_cast<Item *> (d == LEFT ? c : next_door[RIGHT]);
258
259
260               extract_grob_set (lc, "spacing-wishes", wishes);
261               for (int k = wishes.size(); k--;)
262                 {
263                   Grob *sp = wishes[k];
264                   if (Note_spacing::left_column (sp) != lc
265                       || Note_spacing::right_column (sp) != rc)
266                     continue;
267
268                   Real space, fixed;
269                   fixed = 0.0;
270                   bool dummy;
271
272                   if (d == LEFT)
273                     {
274                       /*
275                         The note spacing should be taken from the musical
276                         columns.
277
278                       */
279                       Real base = note_spacing (me, lc, rc, options, &dummy);
280                       Note_spacing::get_spacing (sp, rc, base, increment, &space, &fixed);
281
282                       space -= increment;
283
284                       dists[d] = max (dists[d], space);
285                     }
286                   else
287                     {
288                       Real space, fixed_space;
289                       Staff_spacing::get_spacing_params (sp,
290                                                          &space, &fixed_space);
291
292                       dists[d] = max (dists[d], fixed_space);
293                     }
294                 }
295             }
296           while (flip (&d) != LEFT);
297
298           Rod r;
299           r.distance_ = dists[LEFT] + dists[RIGHT];
300           r.item_drul_[LEFT] = dynamic_cast<Item *> (cols->elem (i - 1));
301           r.item_drul_[RIGHT] = dynamic_cast<Item *> (cols->elem (i + 1));
302
303           r.add_to_cols ();
304         }
305       else
306         {
307           newcols.push (c);
308         }
309     }
310
311   *cols = newcols;
312 }
313
314 /*
315   Set neighboring columns determined by the spacing-wishes grob property.
316 */
317 void
318 Spacing_spanner::set_explicit_neighbor_columns (Link_array<Grob> const &cols)
319 {
320   for (int i = 0; i < cols.size (); i++)
321     {
322       SCM right_neighbors = Grob_array::make_array ();
323       Grob_array *rn_arr = unsmob_grob_array (right_neighbors);
324       int min_rank = 100000;    // inf.
325
326       extract_grob_set (cols[i], "spacing-wishes", wishes);
327       for (int k = wishes.size(); k--;)
328         {
329           Item *wish = dynamic_cast<Item *> ( wishes[k]);
330
331           Item *lc = wish->get_column ();
332           Grob *right = Note_spacing::right_column (wish);
333
334           if (!right)
335             continue;
336
337           Item *rc = dynamic_cast<Item *> (right);
338
339           int right_rank = Paper_column::get_rank (rc);
340           int left_rank = Paper_column::get_rank (lc);
341
342           /*
343             update the left column.
344           */
345           if (right_rank <= min_rank)
346             {
347               if (right_rank < min_rank)
348                 rn_arr->clear ();
349
350               min_rank = right_rank;
351               rn_arr->add (wish);
352             }
353
354           /*
355             update the right column of the wish.
356           */
357           int maxrank = 0;
358
359           extract_grob_set (rc, "left-neighbors", lns_arr);
360           if (lns_arr.size ())
361             {
362               Item *it = dynamic_cast<Item *> (lns_arr.top());
363               maxrank = Paper_column::get_rank (it->get_column ());
364             }
365
366           if (left_rank >= maxrank)
367             {
368               
369               if (left_rank > maxrank)
370                 {
371                   Grob_array *ga = unsmob_grob_array (rc->get_object ("left-neighbors"));
372                   if (ga)
373                     ga->clear ();
374                 }
375
376               Pointer_group_interface::add_grob (rc, ly_symbol2scm ("left-neighbors"), wish);
377             }
378         }
379
380       if (rn_arr->size ())
381         {
382           cols[i]->set_object ("right-neighbors", right_neighbors);
383         }
384     }
385 }
386
387 /*
388   Set neighboring columns that have no left/right-neighbor set
389   yet. Only do breakable non-musical columns, and musical columns.
390 */
391 void
392 Spacing_spanner::set_implicit_neighbor_columns (Link_array<Grob> const &cols)
393 {
394   for (int i = 0; i < cols.size (); i++)
395     {
396       Item *it = dynamic_cast<Item *> (cols[i]);
397       if (!Item::is_breakable (it) && !Paper_column::is_musical (it))
398         continue;
399
400       // it->breakable || it->musical
401
402       /*
403         sloppy with typing left/right-neighbors should take list, but paper-column found instead.
404       */
405       extract_grob_set (cols[i], "left-neighbors", lns);
406       if (lns.is_empty () && i )
407         {
408           SCM ga_scm = Grob_array::make_array();
409           Grob_array *ga = unsmob_grob_array (ga_scm);
410           ga->add (cols[i-1]);
411           cols[i]->set_object ("left-neighbors", ga_scm);
412         }
413       extract_grob_set (cols[i], "right-neighbors", rns);
414       if (rns.is_empty () && i < cols.size () - 1)
415         {
416           SCM ga_scm = Grob_array::make_array();
417           Grob_array *ga = unsmob_grob_array (ga_scm);
418           ga->add (cols[i+1]);
419           cols[i]->set_object ("right-neighbors", ga_scm);
420         }
421     }
422 }