]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
(class Grob): move pscore, dim_cache_,
[lilypond.git] / lily / tuplet-bracket.cc
1 /*
2   tuplet-bracket.cc -- implement Tuplet_bracket
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 1997--2005 Jan Nieuwenhuizen <janneke@gnu.org>
7   Han-Wen Nienhuys <hanwen@xs4all.nl>
8 */
9
10 /*
11   TODO:
12
13   - tuplet bracket should probably be subject to the same rules as
14   beam sloping/quanting.
15
16   - There is no support for kneed brackets, or nested brackets.
17
18   - number placement for parallel beams should be much more advanced:
19   for sloped beams some extra horizontal offset must be introduced.
20
21   - number placement is usually done over the center note, not the
22   graphical center.
23 */
24
25 /*
26   TODO: quantise, we don't want to collide with staff lines.
27   (or should we be above staff?)
28
29   todo: handle breaking elegantly.
30 */
31
32
33 #include "tuplet-bracket.hh"
34 #include "line-interface.hh"
35 #include "beam.hh"
36 #include "warn.hh"
37 #include "font-interface.hh"
38 #include "output-def.hh"
39 #include "text-interface.hh"
40 #include "stem.hh"
41 #include "note-column.hh"
42 #include "pointer-group-interface.hh"
43 #include "directional-element-interface.hh"
44 #include "spanner.hh"
45 #include "staff-symbol-referencer.hh"
46 #include "lookup.hh"
47
48 static Item *
49 get_x_bound_item (Grob *me_grob, Direction hdir, Direction my_dir)
50 {
51   Spanner *me = dynamic_cast<Spanner *> (me_grob);
52   Item *g = me->get_bound (hdir);
53   if (Note_column::has_interface (g)
54       && Note_column::get_stem (g)
55       && Note_column::dir (g) == my_dir)
56     g = Note_column::get_stem (g);
57
58   return g;
59 }
60
61 Grob *
62 Tuplet_bracket::parallel_beam (Grob *me_grob, Link_array<Grob> const &cols, bool *equally_long)
63 {
64   Spanner *me = dynamic_cast<Spanner *> (me_grob);
65
66   if (me->get_bound (LEFT)->break_status_dir ()
67       || me->get_bound (RIGHT)->break_status_dir ())
68     return 0;
69
70   Drul_array<Grob*> stems (Note_column::get_stem (cols[0]),
71                            Note_column::get_stem (cols.top ()));
72
73   if (dynamic_cast<Item*> (stems[RIGHT])->get_column ()
74       != me->get_bound (RIGHT)->get_column())
75     return 0;
76
77   Drul_array<Grob*> beams;
78   Direction d = LEFT;
79   do {
80     beams[d] = stems[d] ? Stem::get_beam (stems[d]) : 0;
81   } while (flip (&d) != LEFT);
82   
83   *equally_long = false;
84   if (! (beams[LEFT] && (beams[LEFT] == beams[RIGHT]) && !me->is_broken ()))
85     return 0;
86
87   extract_grob_set (beams[LEFT], "stems", beam_stems);
88   if (beam_stems.size () == 0)
89     {
90       programming_error ("beam under tuplet bracket has no stems");
91       *equally_long = 0;
92       return 0;
93     }
94
95   *equally_long = (beam_stems[0] == stems[LEFT] && beam_stems.top () == stems[RIGHT]);
96   return beams[LEFT];
97 }
98
99 /*
100   TODO:
101
102   in the case that there is no bracket, but there is a (single) beam,
103   follow beam precisely for determining tuplet number location.
104 */
105 MAKE_SCHEME_CALLBACK (Tuplet_bracket, print, 1);
106 SCM
107 Tuplet_bracket::print (SCM smob)
108 {
109   Spanner *me = unsmob_spanner (smob);
110   Stencil mol;
111   extract_grob_set (me, "note-columns", columns);
112
113   Drul_array<Real> positions = ly_scm2realdrul (me->get_property ("positions"));
114   Real dy = positions[RIGHT] - positions[LEFT];
115   bool equally_long = false;
116   Grob *par_beam = parallel_beam (me, columns, &equally_long);
117   Spanner *sp = dynamic_cast<Spanner *> (me);
118
119   bool bracket_visibility = !(par_beam && equally_long);
120   bool number_visibility = true;
121
122   /*
123     Fixme: the type of this prop is sucky.
124   */
125   SCM bracket = me->get_property ("bracket-visibility");
126   if (scm_is_bool (bracket))
127     bracket_visibility = ly_scm2bool (bracket);
128   else if (bracket == ly_symbol2scm ("if-no-beam"))
129     bracket_visibility = !par_beam;
130
131   SCM numb = me->get_property ("number-visibility");
132   if (scm_is_bool (numb))
133     number_visibility = ly_scm2bool (numb);
134   else if (numb == ly_symbol2scm ("if-no-beam"))
135     number_visibility = !par_beam;
136
137   Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
138   commonx = commonx->common_refpoint (sp->get_bound (LEFT), X_AXIS);
139   commonx = commonx->common_refpoint (sp->get_bound (RIGHT), X_AXIS);
140
141   Direction dir = get_grob_direction (me);
142
143   Drul_array<Item *> bounds;
144   bounds[LEFT] = get_x_bound_item (me, LEFT, dir);
145   bounds[RIGHT] = get_x_bound_item (me, RIGHT, dir);
146
147   Drul_array<bool> connect_to_other;
148   Interval x_span;
149   Direction d = LEFT;
150   do
151     {
152       x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[d];
153       Direction break_dir = bounds[d]->break_status_dir ();
154       Spanner *orig_spanner = dynamic_cast<Spanner *> (me->original ());
155
156       int neighbor_idx = me->get_break_index () - break_dir;
157
158       if (break_dir
159           && d == RIGHT
160           && neighbor_idx < orig_spanner->broken_intos_.size ())
161         {
162           Grob *neighbor = orig_spanner->broken_intos_[neighbor_idx];
163
164           /* trigger possible suicide*/
165           (void) neighbor->get_property ("positions");
166         }
167
168       connect_to_other[d]
169         = (break_dir
170            && (neighbor_idx < orig_spanner->broken_intos_.size ()
171                && neighbor_idx >= 0)
172            && orig_spanner->broken_intos_[neighbor_idx]->is_live ());
173            
174
175       if (connect_to_other[d])
176         {
177           Interval overshoot (robust_scm2drul (me->get_property ("break-overshoot"),
178                                                Interval (-0.5, 0.0)));
179
180           if (d == RIGHT)
181             x_span[d] += d * overshoot[d];
182           else
183             x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS)[RIGHT]
184               - overshoot[LEFT];
185         }
186       else if (d == RIGHT
187                && (columns.is_empty ()
188                    || (bounds[d]->get_column ()
189                        != dynamic_cast<Item *> (columns.top ())->get_column ())))
190         {
191           /*
192             TODO: make padding tunable?
193           */
194           Real padding = 1.0;
195
196           if (bounds[d]->break_status_dir ())
197             padding = 0.0;
198           
199           x_span[d] = robust_relative_extent (bounds[d], commonx, X_AXIS) [LEFT] - padding;
200         }
201     }
202   while (flip (&d) != LEFT);
203
204   Real w = x_span.length ();
205   SCM number = me->get_property ("text");
206
207   Output_def *pap = me->layout ();
208   Stencil num;
209   if (scm_is_string (number) && number_visibility)
210     {
211       SCM properties = Font_interface::text_font_alist_chain (me);
212       SCM snum = Text_interface::interpret_markup (pap->self_scm (),
213                                                    properties, number);
214       num = *unsmob_stencil (snum);
215       num.align_to (X_AXIS, CENTER);
216       num.translate_axis (w / 2, X_AXIS);
217       num.align_to (Y_AXIS, CENTER);
218
219       num.translate_axis (dy / 2, Y_AXIS);
220
221       mol.add_stencil (num);
222     }
223
224   /*
225     No bracket when it would be smaller than the number.
226
227     TODO: should use GAP in calculation too.
228   */
229   if (bracket_visibility && number_visibility
230       && mol.extent (X_AXIS).length () > w)
231     bracket_visibility = false;
232
233   if (bracket_visibility)
234     {
235       Real gap = 0.;
236
237       if (!num.extent (X_AXIS).is_empty ())
238         gap = num.extent (X_AXIS).length () + 1.0;
239
240       Drul_array<Real> zero (0, 0);
241       Real ss = Staff_symbol_referencer::staff_space (me);
242       Drul_array<Real> height
243         = robust_scm2drul (me->get_property ("edge-height"), zero);
244       Drul_array<Real> flare
245         = robust_scm2drul (me->get_property ("bracket-flare"), zero);
246       Drul_array<Real> shorten
247         = robust_scm2drul (me->get_property ("shorten-pair"), zero);
248       Drul_array<Stencil> edge_stencils;
249
250       scale_drul (&height, -ss * dir);
251       scale_drul (&flare, ss);
252       scale_drul (&shorten, ss);
253       do
254         {
255           if (connect_to_other[d])
256             {
257               height[d] = 0.0;
258               flare[d] = 0.0;
259               shorten[d] = 0.0;
260
261               SCM edge_text = me->get_property ("edge-text");
262
263               if (scm_is_pair (edge_text))
264                 {
265                   SCM properties = Font_interface::text_font_alist_chain (me);
266                   SCM text = index_get_cell (edge_text, d);
267                   if (Text_interface::is_markup (text))
268                     {
269                       SCM t = Text_interface::interpret_markup (pap->self_scm (),
270                                                                 properties, text);
271
272                       Stencil *edge_text = unsmob_stencil (t);
273                       edge_text->translate_axis (x_span[d] - x_span[LEFT], X_AXIS);
274                       edge_stencils[d] = *edge_text;
275                     }
276                 }
277             }
278         }
279       while (flip (&d) != LEFT);
280
281       Stencil brack = make_bracket (me, Y_AXIS,
282                                     Offset (w, dy),
283                                     height,
284                                     /*
285                                       0.1 = more space at right due to italics
286                                       TODO: use italic correction of font.
287                                     */
288                                     Interval (-0.5, 0.5) * gap + 0.1,
289                                     flare, shorten);
290
291       do
292         {
293           if (!edge_stencils[d].is_empty ())
294             brack.add_stencil (edge_stencils[d]);
295         }
296       while (flip (&d) != LEFT);
297
298       mol.add_stencil (brack);
299     }
300
301   mol.translate_axis (positions[LEFT], Y_AXIS);
302   mol.translate_axis (x_span[LEFT]
303                       - sp->get_bound (LEFT)->relative_coordinate (commonx, X_AXIS), X_AXIS);
304   return mol.smobbed_copy ();
305 }
306
307 /*
308   should move to lookup?
309
310   TODO: this will fail for very short (shorter than the flare)
311   brackets.
312 */
313 Stencil
314 Tuplet_bracket::make_bracket (Grob *me, // for line properties.
315                               Axis protusion_axis,
316                               Offset dz,
317                               Drul_array<Real> height,
318                               Interval gap,
319                               Drul_array<Real> flare,
320                               Drul_array<Real> shorten)
321 {
322   Drul_array<Offset> corners (Offset (0, 0), dz);
323
324   Real length = dz.length ();
325   Drul_array<Offset> gap_corners;
326
327   Axis bracket_axis = other_axis (protusion_axis);
328
329   Drul_array<Offset> straight_corners = corners;
330
331   Direction d = LEFT;
332   do
333     straight_corners[d] += -d * shorten[d] / length * dz;
334   while (flip (&d) != LEFT)
335     ;
336
337   if (gap.is_empty ())
338     gap = Interval (0, 0);
339   do
340     gap_corners[d] = (dz * 0.5) + gap[d] / length * dz;
341   while (flip (&d) != LEFT)
342     ;
343
344   Drul_array<Offset> flare_corners = straight_corners;
345   do
346     {
347       flare_corners[d][bracket_axis] = straight_corners[d][bracket_axis];
348       flare_corners[d][protusion_axis] += height[d];
349       straight_corners[d][bracket_axis] += -d * flare[d];
350     }
351   while (flip (&d) != LEFT);
352
353   Stencil m;
354   do
355     {
356       m.add_stencil (Line_interface::line (me, straight_corners[d],
357                                            gap_corners[d]));
358
359       m.add_stencil (Line_interface::line (me, straight_corners[d],
360                                            flare_corners[d]));
361     }
362   while (flip (&d) != LEFT);
363
364   return m;
365 }
366
367 void
368 Tuplet_bracket::get_bounds (Grob *me, Grob **left, Grob **right)
369 {
370   extract_grob_set (me, "note-columns", columns);
371   int l = 0;
372   while (l < columns.size () && Note_column::has_rests (columns[l]))
373     l++;
374
375   int r = columns.size ()- 1;
376   while (r >= l && Note_column::has_rests (columns[r]))
377     r--;
378
379   *left = *right = 0;
380
381   if (l <= r)
382     {
383       *left = columns[l];
384       *right = columns[r];
385     }
386 }
387
388 /*
389   use first -> last note for slope, and then correct for disturbing
390   notes in between.  */
391 void
392 Tuplet_bracket::calc_position_and_height (Grob *me_grob, Real *offset, Real *dy)
393 {
394   Spanner *me = dynamic_cast<Spanner *> (me_grob);
395
396   extract_grob_set (me, "note-columns", columns);
397   extract_grob_set (me, "tuplets", tuplets);
398
399   Grob *commony = common_refpoint_of_array (columns, me, Y_AXIS);
400   commony = common_refpoint_of_array (tuplets, commony, Y_AXIS);
401   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
402     commony = st->common_refpoint (commony, Y_AXIS);
403
404   Grob *commonx = common_refpoint_of_array (columns, me, X_AXIS);
405   commonx = common_refpoint_of_array (tuplets, commonx, Y_AXIS);
406   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
407   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
408
409   Interval staff;
410   if (Grob *st = Staff_symbol_referencer::get_staff_symbol (me))
411     {
412       staff = st->extent (commony, Y_AXIS);
413       Real pad = robust_scm2double (me->get_property ("staff-padding"), 0.5);
414       staff.widen (pad);
415     }
416   
417   Direction dir = get_grob_direction (me);
418
419   /*
420     Use outer non-rest columns to determine slope
421   */
422   Grob *left_col = 0;
423   Grob *right_col = 0;
424   get_bounds (me, &left_col, &right_col);
425   if (left_col && right_col)
426     {
427       Interval rv = right_col->extent (commony, Y_AXIS);
428       Interval lv = left_col->extent (commony, Y_AXIS);
429       rv.unite (staff);
430       lv.unite (staff);
431       Real graphical_dy = rv[dir] - lv[dir];
432
433       Slice ls = Note_column::head_positions_interval (left_col);
434       Slice rs = Note_column::head_positions_interval (right_col);
435
436       Interval musical_dy;
437       musical_dy[UP] = rs[UP] - ls[UP];
438       musical_dy[DOWN] = rs[DOWN] - ls[DOWN];
439       if (sign (musical_dy[UP]) != sign (musical_dy[DOWN]))
440         *dy = 0.0;
441       else if (sign (graphical_dy) != sign (musical_dy[DOWN]))
442         *dy = 0.0;
443       else
444         *dy = graphical_dy;
445     }
446   else
447     *dy = 0;
448
449   *offset = -dir * infinity_f;
450
451   Item *lgr = get_x_bound_item (me, LEFT, dir);
452   Item *rgr = get_x_bound_item (me, RIGHT, dir);
453   Real x0 = robust_relative_extent (lgr, commonx, X_AXIS)[LEFT];
454   Real x1 = robust_relative_extent (rgr, commonx, X_AXIS)[RIGHT];
455
456   Array<Offset> points;
457   points.push (Offset (x0 - x0, staff[dir]));
458   points.push (Offset (x1 - x0, staff[dir]));
459
460   for (int i = 0; i < columns.size (); i++)
461     {
462       Interval note_ext = columns[i]->extent (commony, Y_AXIS);
463       Real notey = note_ext[dir] - me->relative_coordinate (commony, Y_AXIS);
464
465       Real x = columns[i]->relative_coordinate (commonx, X_AXIS) - x0;
466       points.push (Offset (x, notey));
467     }
468
469   /*
470     This is a slight hack. We compute two encompass points from the
471     bbox of the smaller tuplets.
472
473     We assume that the smaller bracket is 1.0 space high.
474   */
475   Real ss = Staff_symbol_referencer::staff_space (me);
476   for (int i = 0; i < tuplets.size (); i++)
477     {
478       Interval tuplet_x (tuplets[i]->extent (commonx, X_AXIS));
479       Interval tuplet_y (tuplets[i]->extent (commony, Y_AXIS));
480
481       Direction d = LEFT;
482       Drul_array<Real> positions = ly_scm2realdrul (tuplets[i]->get_property ("positions"));
483
484       
485       Real other_dy = positions[RIGHT] - positions[LEFT];
486
487       do
488         {
489           Real y
490             = tuplet_y.linear_combination (d * sign (other_dy));
491
492 #if 0
493           /*
494             Let's not take padding into account for nested tuplets.
495             the edges can come very close to the stems, likewise for
496             nested tuplets?
497           */
498           Drul_array<Real> my_height
499             = robust_scm2drul (me->get_property ("edge-height"),
500                                Interval (0, 0));
501           if (dynamic_cast<Spanner *> (tuplets[i])->get_bound (d)
502               == me->get_bound (d))
503             y += dir * my_height[d];
504 #endif
505
506           points.push (Offset (tuplet_x[d] - x0, y));
507         }
508       while (flip (&d) != LEFT);
509     }
510
511   Real factor = (columns.size () > 1) ? 1 / (x1 - x0) : 1.0;
512   for (int i = 0; i < points.size (); i++)
513     {
514       Real x = points[i][X_AXIS];
515       Real tuplety = (*dy) * x * factor;
516
517       if (points[i][Y_AXIS] * dir > (*offset + tuplety) * dir)
518         *offset = points[i][Y_AXIS] - tuplety;
519     }
520
521   *offset += scm_to_double (me->get_property ("padding")) * dir;
522
523   /*
524     horizontal brackets should not collide with staff lines.
525
526     Kind of pointless since we put them outside the staff anyway, but
527     let's leave code for the future when possibly allow them to move
528     into the staff once again.
529   */
530   if (*dy == 0
531       && fabs (*offset) < ss * Staff_symbol_referencer::staff_radius (me))
532     {
533       // quantize, then do collision check.
534       *offset *= 2 / ss;
535
536       *offset = rint (*offset);
537       if (Staff_symbol_referencer::on_staffline (me, (int) rint (*offset)))
538         *offset += dir;
539
540       *offset *= 0.5 * ss;
541     }
542 }
543
544
545 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_direction, 1);
546 SCM
547 Tuplet_bracket::calc_direction (SCM smob)
548 {
549   Grob *me = unsmob_grob (smob);
550   Direction dir = Tuplet_bracket::get_default_dir (me);
551   return scm_from_int (dir);
552 }
553
554 MAKE_SCHEME_CALLBACK (Tuplet_bracket, calc_positions, 1);
555 SCM
556 Tuplet_bracket::calc_positions (SCM smob)
557 {
558   Grob *me = unsmob_grob (smob);
559   extract_grob_set (me, "note-columns", columns);
560
561   if (columns.is_empty())
562     {
563       me->suicide ();
564       return scm_cons (scm_from_double (0),
565                        scm_from_double (0));
566     }
567   
568   Direction dir = get_grob_direction (me);
569   bool equally_long = false;
570   Grob *par_beam = parallel_beam (me, columns, &equally_long);
571
572   /*
573     We follow the beam only if there is one, and we are next to it.
574   */
575   Real dy = 0.0;
576   Real offset = 0.0;
577   if (!par_beam
578       || get_grob_direction (par_beam) != dir)
579     calc_position_and_height (me, &offset, &dy);
580   else
581     {
582       SCM ps = par_beam->get_property ("positions");
583
584       Real lp = scm_to_double (scm_car (ps));
585       Real rp = scm_to_double (scm_cdr (ps));
586
587       /*
588         duh. magic.
589       */
590       offset = lp + dir * (0.5 + scm_to_double (me->get_property ("padding")));
591       dy = rp - lp;
592     }
593
594   
595   SCM x = scm_cons (scm_from_double (offset),
596                     scm_from_double (offset + dy));
597   
598   return x;
599 }
600
601 /*
602   similar to beam ?
603 */
604 Direction
605 Tuplet_bracket::get_default_dir (Grob *me)
606 {
607   Drul_array<int> dirs (0, 0);
608   extract_grob_set (me, "note-columns", columns);
609   for (int i = 0; i < columns.size (); i++)
610     {
611       Grob *nc = columns[i];
612       Direction d = Note_column::dir (nc);
613       if (d)
614         dirs[d]++;
615     }
616
617   return dirs[UP] >= dirs[DOWN] ? UP : DOWN;
618 }
619
620 void
621 Tuplet_bracket::add_column (Grob *me, Item *n)
622 {
623   Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-columns"), n);
624   add_bound_item (dynamic_cast<Spanner *> (me), n);
625 }
626
627 void
628 Tuplet_bracket::add_tuplet_bracket (Grob *me, Grob *bracket)
629 {
630   Pointer_group_interface::add_grob (me, ly_symbol2scm ("tuplets"), bracket);
631 }
632
633 ADD_INTERFACE (Tuplet_bracket,
634                "tuplet-bracket-interface",
635                "A bracket with a number in the middle, used for tuplets. "
636                "When the bracket spans  a line break, the value of "
637                "@code{break-overshoot} determines how far it extends "
638                "beyond the staff. "
639                "At a line break, the markups in the @code{edge-text} are printed "
640                "at the edges. ",
641
642
643                /* properties */
644                "bracket-flare "
645                "bracket-visibility "
646                "break-overshoot "
647                "direction "
648                "edge-height "
649                "edge-text "
650                "positions "
651                "note-columns "
652                "number-visibility "
653                "padding "
654                "shorten-pair "
655                "staff-padding "
656                "thickness "
657                "tuplets ");
658