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