]> git.donarmstrong.com Git - lilypond.git/blob - lily/line-spanner.cc
* input/regression/hairpin-dashed.ly: new file.
[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--2003 Jan Nieuwenhuizen <janneke@gnu.org>
7 */
8
9 #include "molecule.hh"
10 #include "item.hh"
11 #include "spanner.hh"
12 #include "line-spanner.hh"
13 #include "paper-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
21 #include <math.h>
22
23 /*
24   TODO: convert all Molecule functions to taking arguments of the form
25
26   Offset FROM, Offset TO.
27
28
29   TODO:
30
31   Introduce line-interface that allows dots/dashes/etc. to be set as
32   grob-properties. Make arbitrary paths.
33   
34  */
35 Molecule
36 dashed_line_molecule (Grob *me, Real thick, Offset from, Offset to)
37 {
38   SCM type = me->get_grob_property ("style");
39   if (type == ly_symbol2scm ("dotted-line")
40       || type == ly_symbol2scm ("dashed-line"))
41     {
42       Real fraction = (type == ly_symbol2scm ("dotted-line")) ? 0.0 : 0.4;
43
44       
45       SCM s = me->get_grob_property ("dash-fraction");
46       if (gh_number_p (s))
47         fraction = gh_scm2double (s);
48       fraction = (fraction >? 0) <? 1.0;
49
50       Real period = Staff_symbol_referencer::staff_space (me);
51       s = me->get_grob_property ("dash-period");
52       if (gh_number_p (s))
53         period *= gh_scm2double (s);
54
55       return Lookup::dashed_line (thick, from, to, period, fraction);
56     }
57   else
58     {
59       return Lookup::line (thick, from, to);
60     }
61 }
62
63 Molecule
64 zigzag_molecule (Grob *me, Real thick, 
65                  Offset from,
66                  Offset to)
67 {
68   Offset dz = to -from;
69   Real dx = dz[X_AXIS];
70   Real dy = dz[Y_AXIS];
71   
72   Real staff_space = Staff_symbol_referencer::staff_space (me);
73   SCM ws = me->get_grob_property ("zigzag-width");
74   SCM ls = me->get_grob_property ("zigzag-length");
75   double w = (gh_number_p(ws) ? gh_scm2double(ws) : 1)*staff_space;
76   double l = (gh_number_p(ls) ? gh_scm2double(ls) : 1)*w;
77   double h = l>w/2 ? sqrt(l*l-w*w/4) : 0;
78   
79   SCM list = scm_list_n (ly_symbol2scm ("zigzag-line"),
80                       gh_bool2scm (true),
81                       gh_double2scm (w),
82                       gh_double2scm (h),
83                       gh_double2scm (thick),
84                       gh_double2scm (dx),
85                       gh_double2scm (dy),
86                       SCM_UNDEFINED);
87   Box b;
88   b.add_point (Offset (0,0));
89   b.add_point (dz);
90   b[X_AXIS].widen (thick/2);
91   b[Y_AXIS].widen (thick/2);
92      
93   return Molecule (b, list);
94 }
95
96 MAKE_SCHEME_CALLBACK(Line_spanner, after_line_breaking, 1);
97 SCM
98 Line_spanner::after_line_breaking (SCM  g)
99 {
100   Grob *me  = unsmob_grob (g);
101   Spanner*sp = dynamic_cast<Spanner*> (me);
102
103   /*
104     We remove the line at the start of the line.  For piano voice
105     indicators, it makes no sense to have them at the start of the
106     line.
107
108     I'm not sure what the official rules for glissandi are, but
109     usually the 2nd note of the glissando is "exact", so when playing
110     from the start of the line, there is no need to glide.
111
112     From a typographical p.o.v. this makes sense, since the amount of
113     space left of a note at the start of a line is very small.
114
115     --hwn.
116     
117    */
118   if (sp->get_bound (LEFT)->break_status_dir()
119       && !sp->get_bound (RIGHT)->break_status_dir())
120     {
121       /*
122         Can't do suicide, since this mucks up finding the trend.
123        */
124       me->set_grob_property ("molecule-callback", SCM_EOL);
125       
126     }
127   return SCM_EOL;
128 }
129
130
131 Molecule
132 Line_spanner::line_molecule (Grob *me, Real thick,
133                              Offset from,
134                              Offset to)
135 {
136   Offset dz = to -from ; 
137   SCM type = me->get_grob_property ("style");
138   if (gh_symbol_p (type)
139       && (type == ly_symbol2scm ("line")
140           || type == ly_symbol2scm ("dashed-line")
141           || type == ly_symbol2scm ("dotted-line")
142           || type == ly_symbol2scm ("zigzag") 
143           || (type == ly_symbol2scm ("trill") && dz[Y_AXIS] != 0)))
144     {
145       return  (type == ly_symbol2scm ("zigzag"))
146         ? zigzag_molecule (me, thick, from, to)
147         : dashed_line_molecule (me, thick, from, to);
148     }
149   else if (gh_symbol_p (type)
150            && type == ly_symbol2scm ("trill"))
151     {
152       SCM alist_chain = Font_interface::font_alist_chain (me);
153       SCM style_alist = scm_list_n (gh_cons (ly_symbol2scm ("font-family"),
154                                              ly_symbol2scm ("music")),
155                                     SCM_UNDEFINED);
156       
157       Font_metric *fm = select_font (me->get_paper (),
158                                                   gh_cons (style_alist,
159                                                            alist_chain));
160       Molecule m = fm->find_by_name ("scripts-trill-element");
161       Molecule mol;
162
163       do
164         mol.add_at_edge (X_AXIS, RIGHT, m, 0,0);
165       while (m.extent (X_AXIS).length ()
166              && mol.extent (X_AXIS).length ()
167              + m.extent (X_AXIS).length () < dz[X_AXIS]);
168
169       /*
170         FIXME: should center element on x/y
171        */
172       mol.translate_axis (m.extent (X_AXIS).length () / 2, X_AXIS);
173       mol.translate_axis (-(mol.extent (Y_AXIS)[DOWN]
174                             + mol.extent (Y_AXIS).length ())/2, Y_AXIS);
175
176       mol.translate (from);
177       return mol;
178     }
179   return Molecule();
180 }
181
182 /*
183   Find a common Y parent, which --if found-- should be the
184   fixed-distance alignment.
185  */
186 Grob *
187 line_spanner_common_parent (Grob *me)
188 {
189   Grob * common = find_fixed_alignment_parent (me);
190   if (!common)
191     {
192       common = Staff_symbol_referencer::get_staff_symbol (me);
193       if (common)
194         common = common->get_parent (Y_AXIS);
195       else
196         common = me->get_parent (Y_AXIS);
197     }
198
199   return common;
200 }
201
202 /*
203   Warning: this thing is a cross-staff object, so it should have empty Y-dimensions.
204
205  (If not, you risk that this is called from the staff-alignment
206   routine, via molecule_extent. At this point, the staves aren't
207   separated yet, so it doesn't work cross-staff.
208
209 */
210
211
212 MAKE_SCHEME_CALLBACK (Line_spanner, brew_molecule, 1);
213 SCM
214 Line_spanner::brew_molecule (SCM smob) 
215 {
216   Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
217
218   Drul_array<Item*>  bound (me->get_bound (LEFT),
219                             me->get_bound (RIGHT));
220   
221   
222   Real gap = gh_scm2double (me->get_grob_property ("gap"));
223
224   Offset ofxy (gap, 0); /*offset from start point to start of line*/
225   Offset dxy ;
226   Offset my_off;
227   Offset his_off;
228   
229   Real thick = me->get_paper ()->get_realvar (ly_symbol2scm ("linethickness"));  
230
231   SCM s = me->get_grob_property ("thickness");
232   if (gh_number_p (s))
233     thick *= gh_scm2double (s);
234
235   if (bound[RIGHT]->break_status_dir())
236     {
237       if (bound[LEFT]->break_status_dir ())
238         {
239           programming_error ("line-spanner with two broken ends. Farewell sweet world.");
240
241           me->suicide();
242           return SCM_EOL;
243         }
244
245       /*
246         This is hairy. For the normal case, we simply find common
247         parents, and draw a line between the bounds. When two note
248         heads are on different lines, there is no common parent
249         anymore. We have to find the piano-staff object.
250       */
251       
252       int k = broken_spanner_index (me);
253       Spanner *parent_sp = dynamic_cast<Spanner*> (me->original_);
254       Spanner *next_sp  = parent_sp->broken_intos_ [k+1];
255       Item *next_bound = next_sp->get_bound (RIGHT);
256
257       if (next_bound->break_status_dir ())
258         {
259           programming_error ("no note heads for the line spanner on next line?"
260                              " Confused.");
261           me->suicide();
262           return SCM_EOL;
263         }
264             
265       Grob *commonx = bound[LEFT]->common_refpoint (bound[RIGHT], X_AXIS);
266       commonx = me->common_refpoint (commonx, X_AXIS);
267       
268       Grob *next_common_y = line_spanner_common_parent (next_bound);
269       Grob *this_common_y = line_spanner_common_parent (bound[LEFT]);
270
271       Grob *all_common_y = me->common_refpoint (this_common_y, Y_AXIS);
272       
273       Interval next_ext  = next_bound->extent (next_common_y, Y_AXIS);
274       Interval this_ext  = bound[LEFT]->extent (this_common_y, Y_AXIS);
275
276       Real yoff = this_common_y->relative_coordinate (all_common_y, Y_AXIS);
277       
278       Offset p1 (bound[LEFT]->extent (commonx, X_AXIS)[RIGHT],
279                  this_ext.center ()  + yoff); 
280       Offset p2 (bound[RIGHT]->extent (commonx, X_AXIS)[LEFT],
281                  next_ext.center () + yoff);
282       
283       Offset dz (p2 -p1);
284       Real len = dz.length ();
285
286       Offset dir  = dz *(1/ len);
287       dz = (dz.length () - 2*gap) *dir;
288       
289   
290       Molecule l (line_molecule (me, thick, Offset(0, 0), dz));
291
292       l.translate (dir * gap +  p1
293                    - Offset (me->relative_coordinate (commonx, X_AXIS),
294                              me->relative_coordinate (all_common_y, Y_AXIS)));
295
296       return l.smobbed_copy (); 
297     }
298   else
299     {
300       Grob *common[] = { me, me };
301       for (int a = X_AXIS;  a < NO_AXES; a++)
302         {
303           common[a] = me->common_refpoint (bound[RIGHT], Axis (a));
304           common[a] = common[a]->common_refpoint (bound[LEFT], Axis (a));
305         }
306
307       // distance from center to start of line      
308       Real off = gap + ((bound[LEFT]->extent (bound[LEFT], X_AXIS).length ()*3)/4);
309
310       for (int a = X_AXIS; a < NO_AXES; a++)
311         {
312           Axis ax = (Axis)a;
313           dxy[ax] =
314             + bound[RIGHT]->extent (common[X_AXIS], ax).center ()
315             - bound[LEFT]->extent (common[X_AXIS], ax).center ();
316
317           my_off[ax] =me->relative_coordinate (common[a], ax);
318           his_off[ax] = bound[LEFT]->relative_coordinate (common[a], ax);
319           
320         }
321
322       ofxy = dxy * (off/dxy.length ());
323       dxy -= 2*ofxy;
324   
325       Molecule line = line_molecule (me, thick, Offset (0,0),dxy);
326
327       line.translate_axis (bound[LEFT]->extent (bound[LEFT], X_AXIS).length ()/2, X_AXIS); 
328       line.translate (ofxy - my_off + his_off);
329       return line.smobbed_copy ();
330     }
331 }
332
333
334 ADD_INTERFACE (Line_spanner, "line-spanner-interface",
335   "Generic line drawn between two objects, eg. for use with glissandi.\n"
336 "gap is measured in staff-spaces.\n"
337 "The property 'type is one of: line, dashed-line, trill, dotted-line or zigzag.\n"
338 "\n",
339   "gap dash-period dash-fraction zigzag-width zigzag-length thickness style");
340
341