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