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