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