]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
Fix some bugs in the dynamic engraver and PostScript backend
[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       int k = broken_spanner_index (me);
226       Spanner *parent_sp = dynamic_cast<Spanner *> (me->original ());
227       Spanner *next_sp = parent_sp->broken_intos_ [k + 1];
228       Item *next_bound = next_sp->get_bound (RIGHT);
229
230       if (next_bound->break_status_dir ())
231         {
232           programming_error ("no note heads for the line spanner on next line?"
233                              " Confused.");
234           me->suicide ();
235           return SCM_EOL;
236         }
237
238       Grob *commonx = bound[LEFT]->common_refpoint (bound[RIGHT], X_AXIS);
239       commonx = me->common_refpoint (commonx, X_AXIS);
240
241       Grob *next_common_y = line_spanner_common_parent (next_bound);
242       Grob *this_common_y = line_spanner_common_parent (bound[LEFT]);
243
244       Grob *all_common_y = me->common_refpoint (this_common_y, Y_AXIS);
245
246       Interval next_ext = next_bound->extent (next_common_y, Y_AXIS);
247       Interval this_ext = bound[LEFT]->extent (this_common_y, Y_AXIS);
248
249       Real yoff = this_common_y->relative_coordinate (all_common_y, Y_AXIS);
250
251       Offset p1 (bound[LEFT]->extent (commonx, X_AXIS)[RIGHT],
252                  this_ext.center () + yoff);
253       Offset p2 (bound[RIGHT]->extent (commonx, X_AXIS)[LEFT],
254                  next_ext.center () + yoff);
255
256       Offset dz (p2 -p1);
257       Real len = dz.length ();
258
259       Offset dir = dz * (1 / len);
260       dz = (dz.length () - 2 * gap) * dir;
261
262       Stencil l (line_stencil (me, Offset (0, 0), dz));
263
264       l.translate (dir * gap + p1
265                    - Offset (me->relative_coordinate (commonx, X_AXIS),
266                              me->relative_coordinate (all_common_y, Y_AXIS)));
267
268       return l.smobbed_copy ();
269     }
270   else
271     {
272       Grob *common[] = { me, me };
273       for (int a = X_AXIS; a < NO_AXES; a++)
274         {
275           common[a] = me->common_refpoint (bound[RIGHT], Axis (a));
276           common[a] = common[a]->common_refpoint (bound[LEFT], Axis (a));
277         }
278
279       // distance from center to start of line      
280       Real off = gap + ((bound[LEFT]->extent (bound[LEFT], X_AXIS).length () * 3) / 4);
281
282       for (int a = X_AXIS; a < NO_AXES; a++)
283         {
284           Axis ax = (Axis)a;
285           dxy[ax]
286             = + robust_relative_extent (bound[RIGHT], common[X_AXIS], ax).center ()
287             - robust_relative_extent (bound[LEFT], common[X_AXIS], ax).center ();
288
289           my_off[ax] = me->relative_coordinate (common[a], ax);
290           his_off[ax] = bound[LEFT]->relative_coordinate (common[a], ax);
291         }
292
293       ofxy = dxy * (off / dxy.length ());
294       dxy -= 2*ofxy;
295
296       Stencil line = line_stencil (me, Offset (0, 0), dxy);
297
298       line.translate_axis (bound[LEFT]->extent (bound[LEFT], X_AXIS).length () / 2, X_AXIS);
299       line.translate (ofxy - my_off + his_off);
300       return line.smobbed_copy ();
301     }
302 }
303
304 ADD_INTERFACE (Line_spanner, "line-spanner-interface",
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                "gap zigzag-width zigzag-length thickness arrow");
311