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