]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
Merge branch 'master' of ssh+git://gpercival@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                 robust_scm2double (ly_assoc_get (ly_symbol2scm ("Y"),
238                                                  bounds[d], SCM_BOOL_F), 0.0));
239       
240       span_points[d] = z;
241     }
242   while (flip (&d) != LEFT);
243
244   Drul_array<Real> gaps (0, 0);
245   Drul_array<bool> arrows (0, 0);
246   Drul_array<Stencil*> stencils (0,0);
247   Drul_array<Grob*> common_y (0, 0);
248   do
249     {
250       gaps[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("padding"),
251                                                  bounds[d], SCM_BOOL_F), 0.0);
252       arrows[d] = to_boolean (ly_assoc_get (ly_symbol2scm ("arrow"),
253                                             bounds[d], SCM_BOOL_F));
254       stencils[d] = unsmob_stencil (ly_assoc_get (ly_symbol2scm ("stencil"),
255                                                   bounds[d], SCM_BOOL_F));
256       common_y[d] = unsmob_grob (ly_assoc_get (ly_symbol2scm ("common-Y"),
257                                                bounds[d], SCM_BOOL_F));
258       if (!common_y[d])
259         common_y[d] = me; 
260     }
261   while (flip (&d) != LEFT);
262
263   Grob *my_common_y = common_y[LEFT]->common_refpoint (common_y[RIGHT], Y_AXIS);
264   do
265     span_points[d][Y_AXIS] += common_y[d]->relative_coordinate (my_common_y, Y_AXIS);
266   while (flip (&d) != LEFT);
267
268   Offset dz = (span_points[RIGHT] - span_points[LEFT]);
269   Offset dz_dir = dz.direction ();
270   if (gaps[LEFT] + gaps[RIGHT] > dz.length ())
271     {
272       return SCM_EOL;
273     }
274
275   Stencil line;
276   do
277     {
278       span_points[d] += -d * gaps[d] *  dz.direction ();
279
280       if (stencils[d])
281         {
282          Stencil s = stencils[d]->translated (span_points[d]);
283          SCM align = ly_assoc_get (ly_symbol2scm ("stencil-align-dir-y"),
284                                    bounds[d], SCM_BOOL_F);
285          SCM off = ly_assoc_get (ly_symbol2scm ("stencil-offset"),
286                                    bounds[d], SCM_BOOL_F);
287
288          if (scm_is_number (align)) 
289            s.align_to (Y_AXIS, scm_to_double (align));
290
291          /*
292            todo: should use font-size.
293           */
294          if (is_number_pair (off))
295            s.translate (ly_scm2offset (off));
296          
297          line.add_stencil (s);
298         }
299     }
300   while (flip (&d) != LEFT);
301
302   do
303     {
304       if (stencils[d])
305         span_points[d] += dz_dir *
306           (stencils[d]->extent (X_AXIS)[-d] / dz_dir[X_AXIS]);
307     }
308   while (flip (&d) != LEFT);
309
310   Offset adjust = dz.direction() * Staff_symbol_referencer::staff_space (me);
311
312   Offset line_left = span_points[LEFT] + (arrows[LEFT] ? adjust*1.4 : Offset (0, 0));
313   Offset line_right = span_points[RIGHT] - (arrows[RIGHT] ? adjust*0.55 : Offset (0, 0));
314   if (line_right[X_AXIS] > line_left[X_AXIS])
315     {
316       line.add_stencil (Line_interface::line (me, line_left, line_right));
317  
318       line.add_stencil (Line_interface::arrows (me,
319                                                 span_points[LEFT],
320                                                 span_points[RIGHT],
321                                                 arrows[LEFT],
322                                                 arrows[RIGHT]));
323     }
324
325   line.translate (Offset (-me->relative_coordinate (commonx, X_AXIS),
326                           -me->relative_coordinate (my_common_y, Y_AXIS)));
327                           
328     
329   return line.smobbed_copy ();
330 }
331
332 ADD_INTERFACE (Line_spanner,
333                "Generic line drawn between two objects, e.g. for use with glissandi.\n"
334                "The property @code{style} can be @code{line}, "
335                "@code{dashed-line}, @code{trill}, \n"
336                "@code{dotted-line} or @code{zigzag}.\n"
337                "\n",
338
339                "extra-dy "
340                "gap "
341                "thickness "
342                "bound-details " 
343                "left-bound-info " 
344                "right-bound-info " 
345                );
346