]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
Fix (sort of) the ending position of a line-spanner over a line break.
[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       Grob *common_y = me->common_refpoint (me->get_bound (dir), Y_AXIS);
97       if (me->get_bound (dir)->break_status_dir ())
98         {
99           Spanner *next_sp = me->broken_neighbor (dir);
100           Item *next_bound = next_sp->get_bound (dir);
101
102           if (next_bound->break_status_dir ())
103             {
104               programming_error ("no note heads for the line spanner on neighbor line?"
105                                  " Confused.");
106               me->suicide ();
107               return SCM_EOL;
108             }
109
110           Grob *next_common_y = next_sp->common_refpoint (next_bound, X_AXIS);
111           Interval next_ext = next_bound->extent (next_common_y, Y_AXIS);
112
113           /* FIXME: this is not a real solution. We want to know would be the
114              y-position of the right bound (relative to the line-spanner)
115              if it belonged to the same system
116              as the left bound. What we do instead is to calculate the
117              y-position of the right bound relative to the VerticalAlignment
118              (of the next System!) and subtract the y-position of the
119              line-spanner relative to the VerticalAlignment (of this System!).
120              This works as long as the distance between staves is approximately
121              the same before and after breaking. */
122           y = next_ext.center () - me->relative_coordinate (common_y, Y_AXIS);
123         }
124       else
125         {
126           y = me->get_bound (dir)->extent (common_y, Y_AXIS).center ();
127           details = scm_acons (ly_symbol2scm ("common-Y"), common_y->self_scm (), details);
128         }
129
130       y += dir * extra_dy / 2; 
131       details = scm_acons (ly_symbol2scm ("Y"), scm_from_double (y), details);
132     }
133
134   return details;
135 }
136
137 MAKE_SCHEME_CALLBACK (Line_spanner, calc_right_bound_info, 1);
138 SCM
139 Line_spanner::calc_right_bound_info (SCM smob)
140 {
141   return Line_spanner::calc_bound_info (smob, RIGHT);
142 }
143
144 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info, 1);
145 SCM
146 Line_spanner::calc_left_bound_info (SCM smob)
147 {
148   return Line_spanner::calc_bound_info (smob, LEFT);
149 }
150
151 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info_and_text, 1);
152 SCM
153 Line_spanner::calc_left_bound_info_and_text (SCM smob)
154 {
155   SCM alist = Line_spanner::calc_bound_info (smob, LEFT);
156   Spanner *me = unsmob_spanner (smob);
157
158   SCM text = me->get_property ("text");
159   if (Text_interface::is_markup (text)
160       && me->get_bound (LEFT)->break_status_dir () == CENTER
161       && ly_assoc_get (ly_symbol2scm ("stencil"), alist, SCM_BOOL_F) == SCM_BOOL_F)
162     {
163       Output_def *layout = me->layout ();
164       SCM properties = Font_interface::text_font_alist_chain (me);
165       alist = scm_acons (ly_symbol2scm ("stencil"),
166                          Text_interface::interpret_markup (layout->self_scm (),
167                                                            properties, text),
168                          alist);
169     }
170   
171   return alist;
172 }
173
174 MAKE_SCHEME_CALLBACK (Line_spanner, print, 1);
175 SCM
176 Line_spanner::print (SCM smob)
177 {
178   Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
179
180   Interval_t<Moment> moments = me->spanned_time ();
181   /*
182     We remove the line at the start of the line.  For piano voice
183     indicators, it makes no sense to have them at the start of the
184     line.
185
186     I'm not sure what the official rules for glissandi are, but
187     usually the 2nd note of the glissando is "exact", so when playing
188     from the start of the line, there is no need to glide.
189
190     From a typographical p.o.v. this makes sense, since the amount of
191     space left of a note at the start of a line is very small.
192
193     --hwn.
194
195   */
196   if (moments.length () == Moment (0,0))
197     return SCM_EOL;
198   
199   Drul_array<SCM> bounds (me->get_property ("left-bound-info"),
200                           me->get_property ("right-bound-info"));
201
202   
203   Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
204   commonx = me->common_refpoint (commonx, X_AXIS);
205
206   Drul_array<Offset> span_points;
207
208   Direction d =  LEFT;
209   do
210     {
211       Offset z (robust_scm2double (ly_assoc_get (ly_symbol2scm ("X"),
212                                                  bounds[d], SCM_BOOL_F), 0.0)
213                 + commonx->relative_coordinate (commonx, X_AXIS),
214                 robust_scm2double (ly_assoc_get (ly_symbol2scm ("Y"),
215                                                  bounds[d], SCM_BOOL_F), 0.0));
216       
217       span_points[d] = z;
218     }
219   while (flip (&d) != LEFT);
220
221   Drul_array<Real> gaps (0, 0);
222   Drul_array<bool> arrows (0, 0);
223   Drul_array<Stencil*> stencils (0,0);
224   Drul_array<Grob*> common_y (0, 0);
225   do
226     {
227       gaps[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("padding"),
228                                                  bounds[d], SCM_BOOL_F), 0.0);
229       arrows[d] = to_boolean (ly_assoc_get (ly_symbol2scm ("arrow"),
230                                             bounds[d], SCM_BOOL_F));
231       stencils[d] = unsmob_stencil (ly_assoc_get (ly_symbol2scm ("stencil"),
232                                                   bounds[d], SCM_BOOL_F));
233       common_y[d] = unsmob_grob (ly_assoc_get (ly_symbol2scm ("common-Y"),
234                                                bounds[d], SCM_BOOL_F));
235       if (!common_y[d])
236         common_y[d] = me; 
237     }
238   while (flip (&d) != LEFT);
239
240   Grob *my_common_y = common_y[LEFT]->common_refpoint (common_y[RIGHT], Y_AXIS);
241   do
242     span_points[d][Y_AXIS] += common_y[d]->relative_coordinate (my_common_y, Y_AXIS);
243   while (flip (&d) != LEFT);
244
245   Offset dz = (span_points[RIGHT] - span_points[LEFT]);
246   Offset dz_dir = dz.direction ();
247   if (gaps[LEFT] + gaps[RIGHT] > dz.length ())
248     {
249       return SCM_EOL;
250     }
251
252   Stencil line;
253   do
254     {
255       if (stencils[d])
256         {
257          Stencil s = stencils[d]->translated (span_points[d]);
258          SCM align = ly_assoc_get (ly_symbol2scm ("stencil-align-dir-y"),
259                                    bounds[d], SCM_BOOL_F);
260          SCM off = ly_assoc_get (ly_symbol2scm ("stencil-offset"),
261                                    bounds[d], SCM_BOOL_F);
262
263          if (scm_is_number (align)) 
264            s.align_to (Y_AXIS, scm_to_double (align));
265
266          /*
267            todo: should use font-size.
268           */
269          if (is_number_pair (off))
270            s.translate (ly_scm2offset (off));
271          
272          line.add_stencil (s);
273         }
274     }
275   while (flip (&d) != LEFT);
276
277   do
278     {
279       if (stencils[d])
280         span_points[d] += dz_dir *
281           (stencils[d]->extent (X_AXIS)[-d] / dz_dir[X_AXIS]);
282       
283       span_points[d] += -d * gaps[d] *  dz.direction ();
284     }
285   while (flip (&d) != LEFT);
286
287   line.add_stencil (Line_interface::line (me, 
288                                           span_points[LEFT],
289                                           span_points[RIGHT]));
290
291   line.add_stencil (Line_interface::arrows (me,
292                                             span_points[LEFT],
293                                             span_points[RIGHT],
294                                             arrows[LEFT],
295                                             arrows[RIGHT]));
296
297   line.translate (Offset (-me->relative_coordinate (commonx, X_AXIS),
298                           -me->relative_coordinate (my_common_y, Y_AXIS)));
299                           
300     
301   return line.smobbed_copy ();
302 }
303
304 ADD_INTERFACE (Line_spanner,
305                "Generic line drawn between two objects, e.g. for use with glissandi.\n"
306                "The property @code{style} can be @code{line}, "
307                "@code{dashed-line}, @code{trill}, \n"
308                "@code{dotted-line} or @code{zigzag}.\n"
309                "\n",
310
311                "extra-dy "
312                "gap "
313                "thickness "
314                "bound-details " 
315                "left-bound-info " 
316                "right-bound-info " 
317                );
318