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