]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
Merge with git+ssh://jneem@git.sv.gnu.org/srv/git/lilypond.git
[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--2006 Jan Nieuwenhuizen <janneke@gnu.org>
7 */
8
9 #include "line-spanner.hh"
10
11
12 #include "spanner.hh"
13 #include "output-def.hh"
14 #include "paper-column.hh"
15 #include "staff-symbol-referencer.hh"
16 #include "font-interface.hh"
17 #include "warn.hh"
18 #include "align-interface.hh"
19 #include "lookup.hh"
20 #include "line-interface.hh"
21
22 Stencil
23 zigzag_stencil (Grob *me,
24                 Offset from,
25                 Offset to)
26 {
27   Offset dz = to -from;
28
29   Real thick = Staff_symbol_referencer::line_thickness (me);
30   thick *= robust_scm2double (me->get_property ("thickness"), 1.0); // todo: staff sym referencer? 
31
32   Real staff_space = Staff_symbol_referencer::staff_space (me);
33
34   Real w = robust_scm2double (me->get_property ("zigzag-width"), 1) * staff_space;
35   int count = (int) ceil (dz.length () / w);
36   w = dz.length () / count;
37
38   Real l = robust_scm2double (me->get_property ("zigzag-length"), 1) * w;
39   Real h = l > w / 2 ? sqrt (l * l - w * w / 4) : 0;
40
41   Offset rotation_factor = complex_exp (Offset (0, dz.arg ()));
42
43   Offset points[3];
44   points[0] = Offset (0, -h / 2);
45   points[1] = Offset (w / 2, h / 2);
46   points[2] = Offset (w, -h / 2);
47   for (int i = 0; i < 3; i++)
48     points[i] = complex_multiply (points[i], rotation_factor);
49
50   Stencil squiggle (Line_interface::make_line (thick, points[0], points[1]));
51   squiggle.add_stencil (Line_interface::make_line (thick, points[1], points[2]));
52
53   Stencil total;
54   for (int i = 0; i < count; i++)
55     {
56       Stencil moved_squiggle (squiggle);
57       moved_squiggle.translate (from + Offset (i * w, 0) * rotation_factor);
58       total.add_stencil (moved_squiggle);
59     }
60
61   Box b;
62   b.add_point (Offset (0, 0));
63   b.add_point (dz);
64   b[X_AXIS].widen (thick / 2);
65   b[Y_AXIS].widen (thick / 2);
66
67   return Stencil (b, total.expr ());
68 }
69
70 MAKE_SCHEME_CALLBACK (Line_spanner, after_line_breaking, 1);
71 SCM
72 Line_spanner::after_line_breaking (SCM g)
73 {
74   Grob *me = unsmob_grob (g);
75   Spanner *sp = dynamic_cast<Spanner *> (me);
76
77   /*
78     We remove the line at the start of the line.  For piano voice
79     indicators, it makes no sense to have them at the start of the
80     line.
81
82     I'm not sure what the official rules for glissandi are, but
83     usually the 2nd note of the glissando is "exact", so when playing
84     from the start of the line, there is no need to glide.
85
86     From a typographical p.o.v. this makes sense, since the amount of
87     space left of a note at the start of a line is very small.
88
89     --hwn.
90
91   */
92   if (sp->get_bound (LEFT)->break_status_dir ()
93       && !sp->get_bound (RIGHT)->break_status_dir ())
94     {
95       /*
96         Can't do suicide, since this mucks up finding the trend.
97       */
98       me->set_property ("transparent", SCM_BOOL_T);
99     }
100   return SCM_EOL;
101 }
102
103 Stencil
104 Line_spanner::line_stencil (Grob *me,
105                             Offset from,
106                             Offset to)
107 {
108   Offset dz = to -from;
109   SCM type = me->get_property ("style");
110
111   Stencil line;
112
113   if (scm_is_symbol (type)
114       && (type == ly_symbol2scm ("line")
115           || type == ly_symbol2scm ("dashed-line")
116           || type == ly_symbol2scm ("dotted-line")
117           || type == ly_symbol2scm ("zigzag")
118           || (type == ly_symbol2scm ("trill") && dz[Y_AXIS] != 0)))
119     {
120       line = (type == ly_symbol2scm ("zigzag"))
121         ? zigzag_stencil (me, from, to)
122         : Line_interface::line (me, from, to);
123     }
124   else if (scm_is_symbol (type)
125            && type == ly_symbol2scm ("trill"))
126     {
127       SCM alist_chain = Font_interface::text_font_alist_chain (me);
128       SCM style_alist = scm_list_n (scm_cons (ly_symbol2scm ("font-encoding"),
129                                               ly_symbol2scm ("fetaMusic")),
130                                     SCM_UNDEFINED);
131
132       Font_metric *fm = select_font (me->layout (),
133                                      scm_cons (style_alist,
134                                                alist_chain));
135       Stencil m = fm->find_by_name ("scripts.trill_element");
136       Stencil mol;
137
138       do
139         mol.add_at_edge (X_AXIS, RIGHT, m, 0, 0);
140       while (m.extent (X_AXIS).length ()
141              && mol.extent (X_AXIS).length ()
142              + m.extent (X_AXIS).length () < dz[X_AXIS])
143         ;
144
145       /*
146         FIXME: should center element on x/y
147       */
148       mol.translate_axis (m.extent (X_AXIS).length () / 2, X_AXIS);
149       mol.translate_axis (- (mol.extent (Y_AXIS)[DOWN]
150                              + mol.extent (Y_AXIS).length ()) / 2, Y_AXIS);
151
152       mol.translate (from);
153       line = mol;
154     }
155
156   if (to_boolean (me->get_property ("arrow")))
157     line.add_stencil (Line_interface::arrows (me, from, to, false, true));
158
159   return line;
160 }
161
162 /*
163   Find a common Y parent, which --if found-- should be the
164   fixed-distance alignment.
165 */
166 Grob *
167 line_spanner_common_parent (Grob *me)
168 {
169   Grob *common = find_fixed_alignment_parent (me);
170   if (!common)
171     {
172       common = Staff_symbol_referencer::get_staff_symbol (me);
173       if (common)
174         common = common->get_parent (Y_AXIS);
175       else
176         common = me->get_parent (Y_AXIS);
177     }
178
179   return common;
180 }
181
182 /*
183   Warning: this thing is a cross-staff object, so it should have empty Y-dimensions.
184
185   (If not, you risk that this is called from the staff-alignment
186   routine, via stencil_extent. At this point, the staves aren't
187   separated yet, so it doesn't work cross-staff.
188
189   (huh? crossable staves have fixed distance? --hwn)
190 */
191
192 MAKE_SCHEME_CALLBACK (Line_spanner, print, 1);
193 SCM
194 Line_spanner::print (SCM smob)
195 {
196   Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
197
198   Drul_array<Item *> bound (me->get_bound (LEFT),
199                             me->get_bound (RIGHT));
200
201   Real gap = robust_scm2double (me->get_property ("gap"), 0.0);
202
203   Offset ofxy (gap, 0); /* offset from start point to start of line */
204   Offset dxy;
205   Offset my_off;
206   Offset his_off;
207
208   if (bound[RIGHT]->break_status_dir ())
209     {
210       if (bound[LEFT]->break_status_dir ())
211         {
212           programming_error ("line-spanner with two broken ends. Farewell sweet world.");
213
214           me->suicide ();
215           return SCM_EOL;
216         }
217
218       /*
219         This is hairy. For the normal case, we simply find common
220         parents, and draw a line between the bounds. When two note
221         heads are on different systems, there is no common parent
222         anymore. We have to find the piano-staff object.
223       */
224
225       Spanner *next_sp = me->broken_neighbor (RIGHT);
226       Item *next_bound = next_sp->get_bound (RIGHT);
227
228       if (next_bound->break_status_dir ())
229         {
230           programming_error ("no note heads for the line spanner on next line?"
231                              " Confused.");
232           me->suicide ();
233           return SCM_EOL;
234         }
235
236       Grob *commonx = bound[LEFT]->common_refpoint (bound[RIGHT], X_AXIS);
237       commonx = me->common_refpoint (commonx, X_AXIS);
238
239       Grob *next_common_y = line_spanner_common_parent (next_bound);
240       Grob *this_common_y = line_spanner_common_parent (bound[LEFT]);
241
242       Grob *all_common_y = me->common_refpoint (this_common_y, Y_AXIS);
243
244       Interval next_ext = next_bound->extent (next_common_y, Y_AXIS);
245       Interval this_ext = bound[LEFT]->extent (this_common_y, Y_AXIS);
246
247       Real yoff = this_common_y->relative_coordinate (all_common_y, Y_AXIS);
248
249       Offset p1 (bound[LEFT]->extent (commonx, X_AXIS)[RIGHT],
250                  this_ext.center () + yoff);
251       Offset p2 (bound[RIGHT]->extent (commonx, X_AXIS)[LEFT],
252                  next_ext.center () + yoff);
253
254       Offset dz (p2 -p1);
255       Real len = dz.length ();
256
257       Offset dir = dz * (1 / len);
258       dz = (dz.length () - 2 * gap) * dir;
259
260       Stencil l (line_stencil (me, Offset (0, 0), dz));
261
262       l.translate (dir * gap + p1
263                    - Offset (me->relative_coordinate (commonx, X_AXIS),
264                              me->relative_coordinate (all_common_y, Y_AXIS)));
265
266       return l.smobbed_copy ();
267     }
268   else
269     {
270       Grob *common[] = { me, me };
271       for (int a = X_AXIS; a < NO_AXES; a++)
272         {
273           common[a] = me->common_refpoint (bound[RIGHT], Axis (a));
274           common[a] = common[a]->common_refpoint (bound[LEFT], Axis (a));
275         }
276
277       // distance from center to start of line      
278       Real off = gap + ((bound[LEFT]->extent (bound[LEFT], X_AXIS).length () * 3) / 4);
279
280       for (int a = X_AXIS; a < NO_AXES; a++)
281         {
282           Axis ax = (Axis)a;
283           dxy[ax]
284             = + robust_relative_extent (bound[RIGHT], common[X_AXIS], ax).center ()
285             - robust_relative_extent (bound[LEFT], common[X_AXIS], ax).center ();
286
287           my_off[ax] = me->relative_coordinate (common[a], ax);
288           his_off[ax] = bound[LEFT]->relative_coordinate (common[a], ax);
289         }
290
291       ofxy = dxy * (off / dxy.length ());
292       dxy -= 2*ofxy;
293
294       Stencil line = line_stencil (me, Offset (0, 0), dxy);
295
296       line.translate_axis (bound[LEFT]->extent (bound[LEFT], X_AXIS).length () / 2, X_AXIS);
297       line.translate (ofxy - my_off + his_off);
298       return line.smobbed_copy ();
299     }
300 }
301
302 ADD_INTERFACE (Line_spanner, "line-spanner-interface",
303                "Generic line drawn between two objects, e.g. for use with glissandi.\n"
304                "The property @code{style} can be @code{line}, "
305                "@code{dashed-line}, @code{trill}, \n"
306                "@code{dotted-line} or @code{zigzag}.\n"
307                "\n",
308                
309                "arrow "
310                "gap "
311                "thickness "
312                "zigzag-length "
313                "zigzag-width "
314                );
315