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