]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
fix overlap of trill-like line spanners and arrowheads
[lilypond.git] / lily / line-spanner.cc
1 /*
2   line-spanner.cc -- implement Line_spanner
3
4   source file of the GNU LilyPond music typesetter
5
6   (c) 2000--2007 Jan Nieuwenhuizen <janneke@gnu.org>
7 */
8
9 #include "spanner.hh"
10 #include "output-def.hh"
11 #include "item.hh"
12 #include "staff-symbol-referencer.hh"
13 #include "font-interface.hh"
14 #include "warn.hh"
15 #include "align-interface.hh"
16 #include "line-interface.hh"
17 #include "moment.hh"
18
19 #include "lily-proto.hh"
20 #include "grob-interface.hh"
21 #include "text-interface.hh"
22
23 class Line_spanner
24 {
25 public:
26   DECLARE_SCHEME_CALLBACK (print, (SCM));
27   DECLARE_SCHEME_CALLBACK (after_line_breaking, (SCM));
28   DECLARE_SCHEME_CALLBACK (calc_left_bound_info, (SCM));
29   DECLARE_SCHEME_CALLBACK (calc_left_bound_info_and_text, (SCM));
30   DECLARE_SCHEME_CALLBACK (calc_right_bound_info, (SCM));
31   DECLARE_SCHEME_CALLBACK (calc_bound_info, (SCM, Direction));
32   DECLARE_GROB_INTERFACE ();
33 };
34
35 SCM
36 Line_spanner::calc_bound_info (SCM smob, Direction dir)
37 {
38   Spanner *me = unsmob_spanner (smob);
39
40   Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
41   commonx = me->common_refpoint (commonx, X_AXIS);
42
43   SCM bound_details = me->get_property ("bound-details");
44
45   SCM details = SCM_BOOL_F;
46   if (details == SCM_BOOL_F)
47     details = ly_assoc_get ((dir == LEFT)
48                             ? ly_symbol2scm ("left")
49                             : ly_symbol2scm ("right"), bound_details, SCM_BOOL_F);
50
51   if (me->get_bound (dir)->break_status_dir ())
52     {
53       SCM extra = ly_assoc_get ((dir == LEFT)
54                                 ? ly_symbol2scm ("left-broken")
55                                 : ly_symbol2scm ("right-broken"), bound_details, SCM_EOL);
56
57       for (SCM s = extra; scm_is_pair (s); s = scm_cdr (s))
58         details = scm_cons (scm_car (s), details);
59     }
60   
61   if (details == SCM_BOOL_F)
62     details = ly_assoc_get (ly_symbol2scm ("default"), bound_details, SCM_EOL);
63
64   SCM text = ly_assoc_get (ly_symbol2scm ("text"), details, SCM_BOOL_F);
65   if (Text_interface::is_markup (text))
66     {
67       Output_def *layout = me->layout ();
68       SCM properties = Font_interface::text_font_alist_chain (me);
69       details = scm_acons (ly_symbol2scm ("stencil"),
70                            Text_interface::interpret_markup (layout->self_scm (),
71                                                              properties, text),
72                            details);
73     }
74   
75   if (!scm_is_number (ly_assoc_get (ly_symbol2scm ("X"), details, SCM_BOOL_F)))
76     {
77       Direction attach = (Direction)
78         robust_scm2int (ly_assoc_get (ly_symbol2scm ("attach-dir"),
79                                                      details, SCM_BOOL_F),
80                         CENTER);
81
82       details = scm_acons (ly_symbol2scm ("X"),
83                            scm_from_double (me->get_bound (dir)->extent (commonx, X_AXIS)
84                                             .linear_combination (attach)),
85                            details);
86     }
87   
88
89   if (!scm_is_number (ly_assoc_get (ly_symbol2scm ("Y"), details, SCM_BOOL_F)))
90     {
91       Real y = 0.0;
92
93       Real extra_dy = robust_scm2double (me->get_property ("extra-dy"),
94                                          0.0);
95          
96       if (me->get_bound (dir)->break_status_dir ())
97         {
98           Spanner *next_sp = me->broken_neighbor (dir);
99           Item *next_bound = next_sp->get_bound (dir);
100
101           if (next_bound->break_status_dir ())
102             {
103               programming_error ("no note heads for the line spanner on neighbor line?"
104                                  " Confused.");
105               me->suicide ();
106               return SCM_EOL;
107             }
108
109           Grob *next_common_y = next_sp->common_refpoint (next_bound, X_AXIS);
110           Interval next_ext = next_bound->extent (next_common_y, Y_AXIS);
111
112           y = next_ext.center ();
113         }
114       else
115         {
116           Grob *commony = me->common_refpoint (me->get_bound (dir), Y_AXIS);
117           y = me->get_bound (dir)->extent (commony, Y_AXIS).center ();
118           details = scm_acons (ly_symbol2scm ("common-Y"), commony->self_scm (), details);
119         }
120
121       y += dir * extra_dy / 2; 
122       details = scm_acons (ly_symbol2scm ("Y"), scm_from_double (y), details);
123     }
124
125   return details;
126 }
127
128 MAKE_SCHEME_CALLBACK (Line_spanner, calc_right_bound_info, 1);
129 SCM
130 Line_spanner::calc_right_bound_info (SCM smob)
131 {
132   return Line_spanner::calc_bound_info (smob, RIGHT);
133 }
134
135 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info, 1);
136 SCM
137 Line_spanner::calc_left_bound_info (SCM smob)
138 {
139   return Line_spanner::calc_bound_info (smob, LEFT);
140 }
141
142 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info_and_text, 1);
143 SCM
144 Line_spanner::calc_left_bound_info_and_text (SCM smob)
145 {
146   SCM alist = Line_spanner::calc_bound_info (smob, LEFT);
147   Spanner *me = unsmob_spanner (smob);
148
149   SCM text = me->get_property ("text");
150   if (Text_interface::is_markup (text)
151       && me->get_bound (LEFT)->break_status_dir () == CENTER
152       && ly_assoc_get (ly_symbol2scm ("stencil"), alist, SCM_BOOL_F) == SCM_BOOL_F)
153     {
154       Output_def *layout = me->layout ();
155       SCM properties = Font_interface::text_font_alist_chain (me);
156       alist = scm_acons (ly_symbol2scm ("stencil"),
157                          Text_interface::interpret_markup (layout->self_scm (),
158                                                            properties, text),
159                          alist);
160     }
161   
162   return alist;
163 }
164
165 MAKE_SCHEME_CALLBACK (Line_spanner, print, 1);
166 SCM
167 Line_spanner::print (SCM smob)
168 {
169   Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
170
171   Interval_t<Moment> moments = me->spanned_time ();
172   /*
173     We remove the line at the start of the line.  For piano voice
174     indicators, it makes no sense to have them at the start of the
175     line.
176
177     I'm not sure what the official rules for glissandi are, but
178     usually the 2nd note of the glissando is "exact", so when playing
179     from the start of the line, there is no need to glide.
180
181     From a typographical p.o.v. this makes sense, since the amount of
182     space left of a note at the start of a line is very small.
183
184     --hwn.
185
186   */
187   if (moments.length () == Moment (0,0))
188     return SCM_EOL;
189   
190   Drul_array<SCM> bounds (me->get_property ("left-bound-info"),
191                           me->get_property ("right-bound-info"));
192
193   
194   Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
195   commonx = me->common_refpoint (commonx, X_AXIS);
196
197   Drul_array<Offset> span_points;
198
199   Direction d =  LEFT;
200   do
201     {
202       Offset z (robust_scm2double (ly_assoc_get (ly_symbol2scm ("X"),
203                                                  bounds[d], SCM_BOOL_F), 0.0)
204                 + commonx->relative_coordinate (commonx, X_AXIS),
205                 robust_scm2double (ly_assoc_get (ly_symbol2scm ("Y"),
206                                                  bounds[d], SCM_BOOL_F), 0.0));
207       
208       span_points[d] = z;
209     }
210   while (flip (&d) != LEFT);
211
212   Drul_array<Real> gaps (0, 0);
213   Drul_array<bool> arrows (0, 0);
214   Drul_array<Stencil*> stencils (0,0);
215   Drul_array<Grob*> common_y (0, 0);
216   do
217     {
218       gaps[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("padding"),
219                                                  bounds[d], SCM_BOOL_F), 0.0);
220       arrows[d] = to_boolean (ly_assoc_get (ly_symbol2scm ("arrow"),
221                                             bounds[d], SCM_BOOL_F));
222       stencils[d] = unsmob_stencil (ly_assoc_get (ly_symbol2scm ("stencil"),
223                                                   bounds[d], SCM_BOOL_F));
224       common_y[d] = unsmob_grob (ly_assoc_get (ly_symbol2scm ("common-Y"),
225                                                bounds[d], SCM_BOOL_F));
226       if (!common_y[d])
227         common_y[d] = me; 
228     }
229   while (flip (&d) != LEFT);
230
231   Grob *my_common_y = common_y[LEFT]->common_refpoint (common_y[RIGHT], Y_AXIS);
232   do
233     span_points[d][Y_AXIS] += common_y[d]->relative_coordinate (my_common_y, Y_AXIS);
234   while (flip (&d) != LEFT);
235
236   Offset dz = (span_points[RIGHT] - span_points[LEFT]);
237   Offset dz_dir = dz.direction ();
238   if (gaps[LEFT] + gaps[RIGHT] > dz.length ())
239     {
240       return SCM_EOL;
241     }
242
243   Stencil line;
244   do
245     {
246       if (stencils[d])
247         {
248          Stencil s = stencils[d]->translated (span_points[d]);
249          SCM align = ly_assoc_get (ly_symbol2scm ("stencil-align-dir-y"),
250                                    bounds[d], SCM_BOOL_F);
251          SCM off = ly_assoc_get (ly_symbol2scm ("stencil-offset"),
252                                    bounds[d], SCM_BOOL_F);
253
254          if (scm_is_number (align)) 
255            s.align_to (Y_AXIS, scm_to_double (align));
256
257          /*
258            todo: should use font-size.
259           */
260          if (is_number_pair (off))
261            s.translate (ly_scm2offset (off));
262          
263          line.add_stencil (s);
264         }
265     }
266   while (flip (&d) != LEFT);
267
268   do
269     {
270       if (stencils[d])
271         span_points[d] += dz_dir *
272           (stencils[d]->extent (X_AXIS)[-d] / dz_dir[X_AXIS]);
273       
274       span_points[d] += -d * gaps[d] *  dz.direction ();
275     }
276   while (flip (&d) != LEFT);
277
278   Offset adjust = dz.direction() * Staff_symbol_referencer::staff_space (me);
279   line.add_stencil (Line_interface::line (me, 
280                                           span_points[LEFT]  + (arrows[LEFT]  ? adjust*1.4  : Offset(0,0)),
281                                           span_points[RIGHT] - (arrows[RIGHT] ? adjust*0.55 : Offset(0,0))));
282
283   line.add_stencil (Line_interface::arrows (me,
284                                             span_points[LEFT],
285                                             span_points[RIGHT],
286                                             arrows[LEFT],
287                                             arrows[RIGHT]));
288
289   line.translate (Offset (-me->relative_coordinate (commonx, X_AXIS),
290                           -me->relative_coordinate (my_common_y, Y_AXIS)));
291                           
292     
293   return line.smobbed_copy ();
294 }
295
296 ADD_INTERFACE (Line_spanner,
297                "Generic line drawn between two objects, e.g. for use with glissandi.\n"
298                "The property @code{style} can be @code{line}, "
299                "@code{dashed-line}, @code{trill}, \n"
300                "@code{dotted-line} or @code{zigzag}.\n"
301                "\n",
302
303                "extra-dy "
304                "gap "
305                "thickness "
306                "bound-details " 
307                "left-bound-info " 
308                "right-bound-info " 
309                );
310