]> git.donarmstrong.com Git - lilypond.git/blob - lily/tuplet-bracket.cc
* lily/parser.yy (simple_string): allow \new STRING_IDENTIFIER.
[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   commonx = commonx->common_refpoint (me->get_bound (LEFT), X_AXIS);
390   commonx = commonx->common_refpoint (me->get_bound (RIGHT), X_AXIS);
391
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 "
669                "beyond the staff. "
670                "At a line break, the markups in the @code{edge-text} are printed "
671                "at the edges. ",
672
673                "note-columns bracket-flare edge-height shorten-pair "
674                "tuplets edge-text break-overshoot "
675                "padding left-position right-position bracket-visibility "
676                "number-visibility thickness direction");
677