]> git.donarmstrong.com Git - lilypond.git/blob - lily/note-spacing.cc
(get_spacing): remove kludge for EOL wide
[lilypond.git] / lily / note-spacing.cc
1 /*
2   note-spacing.cc -- implement Note_spacing
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2001--2005  Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 */
8
9 #include "note-spacing.hh"
10
11 #include "grob-array.hh"
12 #include "paper-column.hh"
13 #include "moment.hh"
14 #include "note-column.hh"
15 #include "warn.hh"
16 #include "stem.hh"
17 #include "separation-item.hh"
18 #include "staff-spacing.hh"
19 #include "accidental-placement.hh"
20 #include "output-def.hh"
21 #include "pointer-group-interface.hh"
22
23 /*
24   TODO: detect hshifts due to collisions, and account for them in
25   spacing?
26 */
27
28 void
29 Note_spacing::get_spacing (Grob *me, Item *right_col,
30                            Real base_space, Real increment, Real *space, Real *fixed)
31 {
32   Drul_array<SCM> props (me->get_object ("left-items"),
33                          me->get_object ("right-items"));
34   Direction d = LEFT;
35   Direction col_dir = right_col->break_status_dir ();
36   Drul_array<Interval> extents;
37
38   Interval left_head_wid;
39   do
40     {
41       Link_array<Grob> const &items (ly_scm2link_array (props [d]));
42       for (int i = items.size (); i--;)
43         {
44           Item *it = dynamic_cast<Item *> (items[i]);
45
46           if (d == RIGHT && it->break_status_dir () != col_dir)
47             it = it->find_prebroken_piece (col_dir);
48
49           /*
50             some kind of mismatch, eg. a note column, that is behind a
51             linebreak.
52           */
53           if (!it)
54             continue;
55
56           Item *it_col = it->get_column ();
57           if (d == RIGHT && right_col != it_col)
58             continue;
59
60           if (Separation_item::has_interface (it))
61             {
62               extents[d].unite (Separation_item::width (it));
63               continue;
64             }
65
66           if (d == LEFT)
67             {
68               SCM r = it->get_object ("rest");
69               Grob *g = unsmob_grob (r);
70               if (!g)
71                 g = Note_column::first_head (it);
72
73               /*
74                 Ugh. If Stem is switched off, we don't know what the
75                 first note head will be.
76               */
77               if (g)
78                 left_head_wid = g->extent (it_col, X_AXIS);
79             }
80
81           extents[d].unite (it->extent (it_col, X_AXIS));
82           if (d == RIGHT)
83             {
84               Grob *accs = Note_column::accidentals (it);
85               if (!accs)
86                 accs = Note_column::accidentals (it->get_parent (X_AXIS));
87
88               if (accs)
89                 {
90                   Interval v
91                     = Accidental_placement::get_relevant_accidental_extent (accs, it_col, me);
92
93                   extents[d].unite (v);
94                 }
95
96               if (Grob *arpeggio = Note_column::arpeggio (it))
97                 extents[d].unite (arpeggio->extent (it_col, X_AXIS));
98             }
99         }
100
101       if (extents[d].is_empty ())
102         extents[d] = Interval (0, 0);
103     }
104   while (flip (&d) != LEFT);
105
106   /*
107     We look at the width of the note head, since smaller heads get less space
108     eg. a quarter rest gets almost 0.5 ss less horizontal space than a note.
109
110     What is sticking out of the note head (eg. a flag), doesn't get
111     the full amount of space.
112
113     FIXED also includes the left part of the right object.
114   */
115   *fixed
116     = (left_head_wid.is_empty () ? increment
117        :        /*
118                   Size of the head:
119                 */
120        (left_head_wid[RIGHT]+
121
122         /*
123           What's sticking out of the head, eg. a flag:
124         */
125         (extents[LEFT][RIGHT] - left_head_wid[RIGHT]) / 2))
126
127     /*
128       What is sticking out on the left side of the right note:
129     */
130     + (extents[RIGHT].is_empty ()
131        ? 0.0
132        : ((- extents[RIGHT][LEFT] / 2)
133
134           /*
135             Add that which sticks out a lot.
136           */
137           + max (0.0, -extents[RIGHT][LEFT] - (base_space - increment))));
138
139   /*
140     We don't do complicated stuff: (base_space - increment) is the
141     normal amount of white, which also determines the amount of
142     stretch. Upon (extreme) stretching, notes with accidentals should
143     stretch as much as notes without accidentals.
144   */
145   *space = (base_space - increment) + *fixed;
146
147 #if 0
148   /*
149     The below situation is now handled by the "sticks out a lot" case
150     above. However we keep around the code for a few releases before
151     we drop it.
152    */
153   if (!extents[RIGHT].is_empty ()
154       && (Item::is_breakable (right_col)
155           || right_col->original_))
156     {
157       /*
158         This is for the situation
159
160         rest | 3/4 (eol)
161
162         Since we only take half of the right-object space above, the
163         barline will bump into the notes preceding it, if the right
164         thing is big. We add the rest of the extents here:
165       */
166
167       *space += -extents[RIGHT][LEFT] / 2;
168       *fixed += -extents[RIGHT][LEFT] / 2;
169     }
170 #endif
171   
172   stem_dir_correction (me, right_col, increment, space, fixed);
173 }
174
175 Item *
176 Note_spacing::left_column (Grob *me)
177 {
178   if (!me->is_live ())
179     return 0;
180
181   return dynamic_cast<Item *> (me)->get_column ();
182 }
183
184 /*
185   Compute the column of the right-items.  This is a big function,
186   since RIGHT-ITEMS may span more columns (eg. if a clef if inserted,
187   this will add a new columns to RIGHT-ITEMS. Here we look at the
188   columns, and return the left-most. If there are multiple columns, we
189   prune RIGHT-ITEMS.
190 */
191 Item *
192 Note_spacing::right_column (Grob *me)
193 {
194   if (!me->is_live ())
195     return 0;
196
197   Grob_array *a = unsmob_grob_array (me->get_object ("right-items"));
198   Item *mincol = 0;
199   int min_rank = INT_MAX;
200   bool prune = false;
201   for (int i = 0; a && i < a->size (); i++)
202     {
203       Item *ri = a->item (i);
204       Item *col = ri->get_column ();
205
206       int rank = Paper_column::get_rank (col);
207
208       if (rank < min_rank)
209         {
210           min_rank = rank;
211           if (mincol)
212             prune = true;
213
214           mincol = col;
215         }
216     }
217
218   if (prune && a)
219     {
220       Link_array<Grob> &right = a->array_reference ();
221       for (int i = right.size (); i--;)
222         {
223           if (dynamic_cast<Item *> (right[i])->get_column () != mincol)
224             right.del (i);
225         }
226     }
227
228   if (!mincol)
229     return 0;
230
231   return mincol;
232 }
233
234 /**
235    Correct for optical illusions. See [Wanske] p. 138. The combination
236    up-stem + down-stem should get extra space, the combination
237    down-stem + up-stem less.
238
239    TODO: have to check wether the stems are in the same staff.
240 */
241 void
242 Note_spacing::stem_dir_correction (Grob *me, Item *rcolumn,
243                                    Real increment,
244                                    Real *space, Real *fixed)
245 {
246   Drul_array<Direction> stem_dirs (CENTER, CENTER);
247   Drul_array<Interval> stem_posns;
248   Drul_array<Interval> head_posns;
249   Drul_array<SCM> props (me->get_object ("left-items"),
250                          me->get_object ("right-items"));
251
252   Drul_array<Spanner *> beams_drul (0, 0);
253   Drul_array<Grob *> stems_drul (0, 0);
254
255   stem_dirs[LEFT] = stem_dirs[RIGHT] = CENTER;
256   Interval intersect;
257   Interval bar_xextent;
258   Interval bar_yextent;
259
260   bool correct_stem_dirs = true;
261   Direction d = LEFT;
262   bool acc_right = false;
263
264   do
265     {
266       Link_array<Grob> const &items (ly_scm2link_array (props [d]));
267       for (int i = 0; i < items.size (); i++)
268         {
269           Item *it = dynamic_cast<Item *> (items[i]);
270
271           if (d == RIGHT)
272             acc_right = acc_right || Note_column::accidentals (it);
273
274           Grob *stem = Note_column::get_stem (it);
275
276           if (!stem || !stem->is_live ())
277             {
278               if (d == RIGHT && Separation_item::has_interface (it))
279                 {
280                   if (it->get_column () != rcolumn)
281                     it = it->find_prebroken_piece (rcolumn->break_status_dir ());
282
283                   Grob *last = Separation_item::extremal_break_aligned_grob (it, LEFT, &bar_xextent);
284
285                   if (last)
286                     bar_yextent = Staff_spacing::bar_y_positions (last);
287
288                   break;
289                 }
290
291               return;
292             }
293
294           if (Stem::is_invisible (stem))
295             {
296               correct_stem_dirs = false;
297               continue;
298             }
299
300           stems_drul[d] = stem;
301           beams_drul[d] = Stem::get_beam (stem);
302
303           Direction sd = Stem::get_direction (stem);
304           if (stem_dirs[d] && stem_dirs[d] != sd)
305             {
306               correct_stem_dirs = false;
307               continue;
308             }
309           stem_dirs[d] = sd;
310
311           /*
312             Correction doesn't seem appropriate  when there is a large flag
313             hanging from the note.
314           */
315           if (d == LEFT
316               && Stem::duration_log (stem) > 2 && !Stem::get_beam (stem))
317             correct_stem_dirs = false;
318
319           Interval hp = Stem::head_positions (stem);
320           if (!hp.is_empty ())
321             {
322               Real chord_start = hp[sd];
323               Real stem_end = Stem::stem_end_position (stem);
324
325               stem_posns[d] = Interval (min (chord_start, stem_end), max (chord_start, stem_end));
326               head_posns[d].unite (hp);
327             }
328         }
329     }
330   while (flip (&d) != LEFT);
331
332   /*
333     don't correct if accidentals are sticking out of the right side.
334   */
335   if (acc_right)
336     return;
337
338   Real correction = 0.0;
339
340   if (!bar_yextent.is_empty ())
341     {
342       stem_dirs[RIGHT] = -stem_dirs[LEFT];
343       stem_posns[RIGHT] = bar_yextent;
344     }
345
346   if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == -1)
347     {
348       if (beams_drul[LEFT] && beams_drul[LEFT] == beams_drul[RIGHT])
349         {
350
351           /*
352             this is a knee: maximal correction.
353           */
354           Real note_head_width = increment;
355           Grob *st = stems_drul[RIGHT];
356           Grob *head = st ? Stem::support_head (st) : 0;
357
358           Interval head_extent;
359           if (head)
360             {
361               head_extent = head->extent (rcolumn, X_AXIS);
362
363               if (!head_extent.is_empty ())
364                 note_head_width = head_extent[RIGHT];
365
366               if (st)
367                 {
368                   Real thick = Stem::thickness (st);
369
370                   note_head_width -= thick;
371                 }
372             }
373
374           correction = note_head_width * stem_dirs[LEFT];
375           correction *= robust_scm2double (me->get_property ("knee-spacing-correction"), 0);
376           *fixed += correction;
377         }
378       else
379         {
380           intersect = stem_posns[LEFT];
381           intersect.intersect (stem_posns[RIGHT]);
382           correct_stem_dirs = correct_stem_dirs && !intersect.is_empty ();
383
384           if (correct_stem_dirs)
385             {
386               correction = abs (intersect.length ());
387
388               /*
389                 Ugh. 7 is hardcoded.
390               */
391               correction = min (correction / 7, 1.0);
392               correction *= stem_dirs[LEFT];
393               correction
394                 *= robust_scm2double (me->get_property ("stem-spacing-correction"), 0);
395             }
396
397           if (!bar_yextent.is_empty ())
398             correction *= 0.5;
399         }
400     }
401   else if (correct_stem_dirs && stem_dirs[LEFT] * stem_dirs[RIGHT] == UP)
402     {
403       /*
404         Correct for the following situation:
405
406         X      X
407         |      |
408         |      |
409         |   X  |
410         |  |   |
411         ========
412
413         ^ move the center one to the left.
414
415
416         this effect seems to be much more subtle than the
417         stem-direction stuff (why?), and also does not scale with the
418         difference in stem length.
419
420       */
421
422       Interval hp = head_posns[LEFT];
423       hp.intersect (head_posns[RIGHT]);
424       if (!hp.is_empty ())
425         return;
426
427       Direction lowest
428         = (head_posns[LEFT][DOWN] > head_posns[RIGHT][UP]) ? RIGHT : LEFT;
429
430       Real delta = head_posns[-lowest][DOWN] - head_posns[lowest][UP];
431       Real corr = robust_scm2double (me->get_property ("same-direction-correction"), 0);
432
433       if (delta > 1)
434         correction = -lowest * corr;
435     }
436
437   *space += correction;
438
439   /* there used to be a correction for bar_xextent () here, but
440      it's unclear what that was good for ?
441   */
442 }
443
444 ADD_INTERFACE (Note_spacing, "note-spacing-interface",
445                "This object calculates spacing wishes for individual voices.",
446                "left-items right-items stem-spacing-correction same-direction-correction knee-spacing-correction");
447