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