2 line-spanner.cc -- implement Line_spanner
4 source file of the GNU LilyPond music typesetter
6 (c) 2000--2007 Jan Nieuwenhuizen <janneke@gnu.org>
9 #include "align-interface.hh"
10 #include "axis-group-interface.hh"
11 #include "font-interface.hh"
12 #include "grob-interface.hh"
14 #include "lily-proto.hh"
15 #include "line-interface.hh"
17 #include "output-def.hh"
18 #include "pointer-group-interface.hh"
20 #include "staff-symbol-referencer.hh"
22 #include "text-interface.hh"
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 ();
37 Spanner *parent_spanner (Grob *g)
39 if (Spanner::has_interface (g))
40 return dynamic_cast<Spanner*> (g);
41 return parent_spanner (g->get_parent (Y_AXIS));
45 Line_spanner::calc_bound_info (SCM smob, Direction dir)
47 Spanner *me = unsmob_spanner (smob);
49 Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
50 commonx = me->common_refpoint (commonx, X_AXIS);
52 SCM bound_details = me->get_property ("bound-details");
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);
60 if (me->get_bound (dir)->break_status_dir ())
62 SCM extra = ly_assoc_get ((dir == LEFT)
63 ? ly_symbol2scm ("left-broken")
64 : ly_symbol2scm ("right-broken"), bound_details, SCM_EOL);
66 for (SCM s = extra; scm_is_pair (s); s = scm_cdr (s))
67 details = scm_cons (scm_car (s), details);
70 if (details == SCM_BOOL_F)
71 details = ly_assoc_get (ly_symbol2scm ("default"), bound_details, SCM_EOL);
73 SCM text = ly_assoc_get (ly_symbol2scm ("text"), details, SCM_BOOL_F);
74 if (Text_interface::is_markup (text))
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 (),
84 if (!scm_is_number (ly_assoc_get (ly_symbol2scm ("X"), details, SCM_BOOL_F)))
86 Direction attach = (Direction)
87 robust_scm2int (ly_assoc_get (ly_symbol2scm ("attach-dir"),
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 ())
96 extract_grob_set (me, "note-columns", columns);
98 bound_grob = (dir == LEFT)
99 ? columns[0] : columns.back();
102 details = scm_acons (ly_symbol2scm ("X"),
103 scm_from_double (bound_grob->extent (commonx, X_AXIS)
104 .linear_combination (attach)),
109 if (!scm_is_number (ly_assoc_get (ly_symbol2scm ("Y"), details, SCM_BOOL_F)))
113 Real extra_dy = robust_scm2double (me->get_property ("extra-dy"),
116 Grob *common_y = me->common_refpoint (me->get_bound (dir), Y_AXIS);
117 if (me->get_bound (dir)->break_status_dir ())
119 Spanner *next_sp = me->broken_neighbor (dir);
120 Item *next_bound = next_sp->get_bound (dir);
122 if (next_bound->break_status_dir ())
124 programming_error ("no note heads for the line spanner on neighbor line?"
130 Spanner *next_bound_parent = parent_spanner (next_bound);
131 Interval next_ext = next_bound->extent (next_bound_parent, Y_AXIS);
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.
139 Spanner *next_bound_parent_on_this_line =
140 next_bound_parent->broken_neighbor (other_dir (dir));
142 if (next_bound_parent_on_this_line)
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);
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);
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);
165 y += dir * extra_dy / 2;
166 details = scm_acons (ly_symbol2scm ("Y"), scm_from_double (y), details);
172 MAKE_SCHEME_CALLBACK (Line_spanner, calc_right_bound_info, 1);
174 Line_spanner::calc_right_bound_info (SCM smob)
176 return Line_spanner::calc_bound_info (smob, RIGHT);
179 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info, 1);
181 Line_spanner::calc_left_bound_info (SCM smob)
183 return Line_spanner::calc_bound_info (smob, LEFT);
186 MAKE_SCHEME_CALLBACK (Line_spanner, calc_left_bound_info_and_text, 1);
188 Line_spanner::calc_left_bound_info_and_text (SCM smob)
190 SCM alist = Line_spanner::calc_bound_info (smob, LEFT);
191 Spanner *me = unsmob_spanner (smob);
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)
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 (),
209 MAKE_SCHEME_CALLBACK (Line_spanner, print, 1);
211 Line_spanner::print (SCM smob)
213 Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
215 Interval_t<Moment> moments = me->spanned_time ();
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
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.
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.
231 if (moments.length () == Moment (0,0))
234 Drul_array<SCM> bounds (me->get_property ("left-bound-info"),
235 me->get_property ("right-bound-info"));
238 Grob *commonx = me->get_bound (LEFT)->common_refpoint (me->get_bound (RIGHT), X_AXIS);
239 commonx = me->common_refpoint (commonx, X_AXIS);
241 Drul_array<Offset> span_points;
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));
253 while (flip (&d) != LEFT);
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);
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));
272 while (flip (&d) != LEFT);
274 Grob *my_common_y = common_y[LEFT]->common_refpoint (common_y[RIGHT], Y_AXIS);
276 span_points[d][Y_AXIS] += common_y[d]->relative_coordinate (my_common_y, Y_AXIS);
277 while (flip (&d) != LEFT);
279 Offset dz = (span_points[RIGHT] - span_points[LEFT]);
280 Offset dz_dir = dz.direction ();
281 if (gaps[LEFT] + gaps[RIGHT] > dz.length ())
289 span_points[d] += -d * gaps[d] * dz.direction ();
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);
299 if (scm_is_number (align))
300 s.align_to (Y_AXIS, scm_to_double (align));
303 todo: should use font-size.
305 if (is_number_pair (off))
306 s.translate (ly_scm2offset (off));
308 line.add_stencil (s);
311 while (flip (&d) != LEFT);
316 span_points[d] += dz_dir *
317 (stencils[d]->extent (X_AXIS)[-d] / dz_dir[X_AXIS]);
319 while (flip (&d) != LEFT);
321 Offset adjust = dz.direction() * Staff_symbol_referencer::staff_space (me);
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])
327 line.add_stencil (Line_interface::line (me, line_left, line_right));
329 line.add_stencil (Line_interface::arrows (me,
336 line.translate (Offset (-me->relative_coordinate (commonx, X_AXIS),
337 -me->relative_coordinate (my_common_y, Y_AXIS)));
340 return line.smobbed_copy ();
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"