]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
Run `make grand-replace'.
[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--2008 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 (robust_relative_extent (bound_grob, 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<Real> anchor_align (0, 0);
258   Drul_array<Stencil*> stencils (0,0);
259   Drul_array<Grob*> common_y (0, 0);
260   do
261     {
262       gaps[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("padding"),
263                                                  bounds[d], SCM_BOOL_F), 0.0);
264       arrows[d] = to_boolean (ly_assoc_get (ly_symbol2scm ("arrow"),
265                                             bounds[d], SCM_BOOL_F));
266       anchor_align[d] = robust_scm2double (ly_assoc_get (ly_symbol2scm ("anchor-alignment"),
267                                                                  bounds[d], SCM_BOOL_F), LEFT);
268       stencils[d] = unsmob_stencil (ly_assoc_get (ly_symbol2scm ("stencil"),
269                                                   bounds[d], SCM_BOOL_F));
270       common_y[d] = unsmob_grob (ly_assoc_get (ly_symbol2scm ("common-Y"),
271                                                bounds[d], SCM_BOOL_F));
272       if (!common_y[d])
273         common_y[d] = me; 
274     }
275   while (flip (&d) != LEFT);
276
277   Grob *my_common_y = common_y[LEFT]->common_refpoint (common_y[RIGHT], Y_AXIS);
278   do
279     span_points[d][Y_AXIS] += common_y[d]->relative_coordinate (my_common_y, Y_AXIS);
280   while (flip (&d) != LEFT);
281
282   Offset dz = (span_points[RIGHT] - span_points[LEFT]);
283   Offset dz_dir = dz.direction ();
284   if (gaps[LEFT] + gaps[RIGHT] > dz.length ())
285     {
286       return SCM_EOL;
287     }
288
289   Stencil line;
290   do
291     {
292       span_points[d] += -d * gaps[d] *  dz.direction ();
293
294       if (stencils[d])
295         {
296           Interval ext = stencils[d]->extent (X_AXIS);
297           Real anchor = ext.linear_combination (anchor_align[d]) - ext[LEFT];
298           span_points[d][X_AXIS] -= anchor;
299
300           Stencil s = stencils[d]->translated (span_points[d]);
301           SCM align = ly_assoc_get (ly_symbol2scm ("stencil-align-dir-y"),
302                                     bounds[d], SCM_BOOL_F);
303           SCM off = ly_assoc_get (ly_symbol2scm ("stencil-offset"),
304                                   bounds[d], SCM_BOOL_F);
305
306           if (scm_is_number (align)) 
307             s.align_to (Y_AXIS, scm_to_double (align));
308
309           /*
310             todo: should use font-size.
311           */
312           if (is_number_pair (off))
313             s.translate (ly_scm2offset (off));
314          
315           line.add_stencil (s);
316         }
317     }
318   while (flip (&d) != LEFT);
319
320   do
321     {
322       if (stencils[d])
323         span_points[d] += dz_dir *
324           (stencils[d]->extent (X_AXIS)[-d] / dz_dir[X_AXIS]);
325     }
326   while (flip (&d) != LEFT);
327
328   Offset adjust = dz.direction() * Staff_symbol_referencer::staff_space (me);
329
330   Offset line_left = span_points[LEFT] + (arrows[LEFT] ? adjust*1.4 : Offset (0, 0));
331   Offset line_right = span_points[RIGHT] - (arrows[RIGHT] ? adjust*0.55 : Offset (0, 0));
332   if (line_right[X_AXIS] > line_left[X_AXIS])
333     {
334       line.add_stencil (Line_interface::line (me, line_left, line_right));
335  
336       line.add_stencil (Line_interface::arrows (me,
337                                                 span_points[LEFT],
338                                                 span_points[RIGHT],
339                                                 arrows[LEFT],
340                                                 arrows[RIGHT]));
341     }
342
343   line.translate (Offset (-me->relative_coordinate (commonx, X_AXIS),
344                           -me->relative_coordinate (my_common_y, Y_AXIS)));
345                           
346
347   return line.smobbed_copy ();
348 }
349
350 ADD_INTERFACE (Line_spanner,
351                "Generic line drawn between two objects, e.g., for use with"
352                " glissandi.\n"
353                "\n"
354                "The property @code{style} can be @code{line},"
355                " @code{dashed-line}, @code{trill}, @code{dotted-line} or"
356                " @code{zigzag}.",
357
358                /* properties */
359                "bound-details "
360                "extra-dy "
361                "gap "
362                "left-bound-info "
363                "note-columns "
364                "right-bound-info "
365                "thickness "
366                "to-barline "
367                );
368