]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
Merge with master
[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 "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);
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   Real extra_dy = robust_scm2double (me->get_property ("extra-dy"),
209                                      0.0);
210   
211   if (bound[RIGHT]->break_status_dir ())
212     {
213       if (bound[LEFT]->break_status_dir ())
214         {
215           programming_error ("line-spanner with two broken ends. Farewell sweet world.");
216
217           me->suicide ();
218           return SCM_EOL;
219         }
220
221       /*
222         This is hairy. For the normal case, we simply find common
223         parents, and draw a line between the bounds. When two note
224         heads are on different systems, there is no common parent
225         anymore. We have to find the piano-staff object.
226       */
227
228       Spanner *next_sp = me->broken_neighbor (RIGHT);
229       Item *next_bound = next_sp->get_bound (RIGHT);
230
231       if (next_bound->break_status_dir ())
232         {
233           programming_error ("no note heads for the line spanner on next line?"
234                              " Confused.");
235           me->suicide ();
236           return SCM_EOL;
237         }
238
239       Grob *commonx = bound[LEFT]->common_refpoint (bound[RIGHT], X_AXIS);
240       commonx = me->common_refpoint (commonx, X_AXIS);
241
242       Grob *next_common_y = line_spanner_common_parent (next_bound);
243       Grob *this_common_y = line_spanner_common_parent (bound[LEFT]);
244
245       Grob *all_common_y = me->common_refpoint (this_common_y, Y_AXIS);
246
247       Interval next_ext = next_bound->extent (next_common_y, Y_AXIS);
248       Interval this_ext = bound[LEFT]->extent (this_common_y, Y_AXIS);
249
250       Real yoff = this_common_y->relative_coordinate (all_common_y, Y_AXIS);
251
252       Offset p1 (bound[LEFT]->extent (commonx, X_AXIS)[RIGHT],
253                  this_ext.center () + yoff - extra_dy / 2);
254       Offset p2 (bound[RIGHT]->extent (commonx, X_AXIS)[LEFT],
255                  next_ext.center () + yoff + extra_dy / 2);
256
257       Offset dz (p2 - p1);
258       Real len = dz.length ();
259
260       Offset dir = dz * (1 / len);
261       dz = (dz.length () - 2 * gap) * dir;
262
263       Stencil l (line_stencil (me, Offset (0, 0), dz));
264
265       l.translate (dir * gap + p1
266                    - Offset (me->relative_coordinate (commonx, X_AXIS),
267                              me->relative_coordinate (all_common_y, Y_AXIS)));
268
269       return l.smobbed_copy ();
270     }
271   else
272     {
273       Grob *common[] = { me, me };
274       for (int a = X_AXIS; a < NO_AXES; a++)
275         {
276           common[a] = me->common_refpoint (bound[RIGHT], Axis (a));
277           common[a] = common[a]->common_refpoint (bound[LEFT], Axis (a));
278         }
279
280       // distance from center to start of line      
281       Real off = gap + ((bound[LEFT]->extent (bound[LEFT], X_AXIS).length () * 3) / 4);
282
283       for (int a = X_AXIS; a < NO_AXES; a++)
284         {
285           Axis ax = (Axis)a;
286           dxy[ax]
287             = + robust_relative_extent (bound[RIGHT], common[X_AXIS], ax).center ()
288             - robust_relative_extent (bound[LEFT], common[X_AXIS], ax).center ();
289
290           my_off[ax] = me->relative_coordinate (common[a], ax);
291           his_off[ax] = bound[LEFT]->relative_coordinate (common[a], ax);
292         }
293
294       ofxy = dxy * (off / dxy.length ()) ;
295       dxy -= 2*ofxy;
296
297       dxy[Y_AXIS] += extra_dy;
298       
299       Stencil line = line_stencil (me, Offset (0, 0), dxy);
300
301       line.translate_axis (bound[LEFT]->extent (bound[LEFT], X_AXIS).length () / 2, X_AXIS);
302       line.translate (ofxy - my_off + his_off + Offset (0, -extra_dy/2));
303       return line.smobbed_copy ();
304     }
305 }
306
307 ADD_INTERFACE (Line_spanner,
308                "Generic line drawn between two objects, e.g. for use with glissandi.\n"
309                "The property @code{style} can be @code{line}, "
310                "@code{dashed-line}, @code{trill}, \n"
311                "@code{dotted-line} or @code{zigzag}.\n"
312                "\n",
313
314                "extra-dy "
315                "arrow "
316                "gap "
317                "thickness "
318                "zigzag-length "
319                "zigzag-width "
320                );
321