]> git.donarmstrong.com Git - lilypond.git/blob - lily/ledger-line-spanner.cc
* lily/include/group-interface.hh (extract_grob_array): rename
[lilypond.git] / lily / ledger-line-spanner.cc
1 /* 
2   ledger-line-spanner.cc -- implement Ledger_line_spanner
3   
4   source file of the GNU LilyPond music typesetter
5   
6   (c) 2004--2005 Han-Wen Nienhuys <hanwen@xs4all.nl>
7   
8 */
9
10 #include <map>
11 #include <set>
12
13 #include "item.hh"
14 #include "note-head.hh" 
15 #include "staff-symbol-referencer.hh" 
16 #include "staff-symbol.hh" 
17 #include "lookup.hh" 
18 #include "spanner.hh" 
19 #include "group-interface.hh" 
20 #include "paper-column.hh"
21
22 struct Ledger_line_spanner
23 {
24   DECLARE_SCHEME_CALLBACK (print, (SCM ));
25   DECLARE_SCHEME_CALLBACK (set_spacing_rods, (SCM ));
26   static Stencil brew_ledger_lines (Grob *me,
27                                     int pos,
28                                     int interspaces,
29                                     Real, Real,
30                                     Interval x_extent,
31                                     Real left_shorten);
32
33   static bool has_interface (Grob*);
34 };
35
36
37 Stencil
38 Ledger_line_spanner::brew_ledger_lines (Grob *staff,
39                                         int pos,
40                                         int interspaces,
41                                         Real halfspace,
42                                         Real ledgerlinethickness,
43                                         Interval x_extent,
44                                         Real left_shorten)
45 {
46   int line_count = ((abs (pos) < interspaces)
47                     ? 0
48                     : (abs (pos) - interspaces) / 2);
49   Stencil stencil;
50   if (line_count)
51     {
52       Real blotdiameter = ledgerlinethickness;
53       Interval y_extent =
54         Interval (-0.5*(ledgerlinethickness),
55                   +0.5*(ledgerlinethickness));
56       Stencil proto_ledger_line =
57         Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
58
59       x_extent[LEFT] += left_shorten;
60       Stencil proto_first_line =
61         Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
62
63       Direction dir = (Direction)sign (pos);
64       Real offs = (Staff_symbol_referencer::on_staffline (staff, pos))
65         ? 0.0
66         : -dir * halfspace;
67
68
69       offs += pos * halfspace;
70       for (int i = 0; i < line_count; i++)
71         {
72           Stencil ledger_line ((i == 0) 
73                                 ? proto_first_line
74                                 : proto_ledger_line
75                                 );
76           ledger_line.translate_axis (-dir * halfspace * i * 2 + offs, Y_AXIS);
77           stencil.add_stencil (ledger_line);
78         }
79     }
80
81   return stencil;
82 }
83
84
85 typedef std::map<int, Drul_array<Interval> > Head_extents_map;
86 typedef std::map<int, Grob*> Column_map;
87
88 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, set_spacing_rods, 1);
89 SCM
90 Ledger_line_spanner::set_spacing_rods (SCM smob)
91 {
92   Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
93
94   // find size of note heads.
95   Grob * staff = Staff_symbol_referencer::get_staff_symbol (me);
96   if (!staff)
97     return SCM_EOL;
98
99   Link_array<Grob> heads (extract_grob_array (me, ly_symbol2scm ("note-heads")));
100   
101   if (heads.is_empty ())
102     return SCM_EOL;
103   
104   Real min_length_fraction
105     = robust_scm2double (me->get_property ("minimum-length-fraction"), 0.15);
106   
107   Head_extents_map head_extents;
108   Column_map columns;
109   
110   int interspaces = Staff_symbol::line_count (staff)-1;
111   for (int i = heads.size (); i--; )
112     {
113       Item *h = dynamic_cast<Item*> (heads[i]);
114       
115       int pos = Staff_symbol_referencer::get_rounded_position (h);
116       if (pos
117           && abs (pos) > interspaces)
118         {
119           Grob *column = h->get_column ();
120           int rank = Paper_column::get_rank (column);
121
122           Interval head_extent = h->extent (column, X_AXIS);
123           Direction vdir = Direction (sign (pos));
124           if (!vdir)
125             continue;
126           
127           Interval prev_extent; 
128
129           Head_extents_map::iterator j = head_extents.find (rank);
130           if (j != head_extents.end ())
131             prev_extent = (*j).second[vdir];
132           else
133             columns[rank] = column;
134           
135           prev_extent.unite (head_extent);
136           head_extents[rank][vdir] = prev_extent;
137         }
138     }
139
140   for (Column_map::const_iterator c (columns.begin()); c != columns.end(); c++)
141     {
142       Grob *column = (*c).second;
143       int rank = (*c).first;
144
145       int next_rank = rank + 2;
146       
147       if (head_extents.find (next_rank) != head_extents.end ())
148         {
149           Drul_array<Interval> extents_left = head_extents[rank]; 
150           Drul_array<Interval> extents_right = head_extents[next_rank]; 
151
152           Direction d = DOWN;
153           do
154             {
155               if (!extents_right[d].is_empty () && !extents_right[d].is_empty ())
156                 {
157                   Real l1 = extents_right[d].length() * min_length_fraction;
158                   Real l2 = extents_left[d].length() * min_length_fraction;
159
160                   Rod rod;
161                   rod.distance_ = l1 + l2 + (l1+ l2)/2.0
162                     + extents_left[d][RIGHT]
163                     - extents_right[d][LEFT];
164                   
165                   rod.item_drul_[LEFT] = dynamic_cast<Item*> (column);
166                   rod.item_drul_[RIGHT] = dynamic_cast<Item*> (columns[next_rank]);
167                   rod.add_to_cols ();
168                 }
169             }
170           while (flip (&d) != DOWN);
171         }
172     }
173
174   return SCM_UNSPECIFIED;
175 }
176
177
178 struct Ledger_request
179 {
180   Interval ledger_extent_;
181   Interval head_extent_;
182   int position_;
183   bool excentric_;
184   Ledger_request () {
185     ledger_extent_.set_empty ();
186     head_extent_.set_empty ();
187     position_ = 0;
188   }
189 };
190
191 typedef std::map<int, Drul_array<Ledger_request> > Ledger_requests;
192
193
194 /*
195   TODO: ledger share a lot of info. Lots of room to optimize away common
196   use of objects/variables.
197  */
198 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, print, 1);
199 SCM
200 Ledger_line_spanner::print (SCM smob)
201 {
202   Spanner *me = dynamic_cast<Spanner*> (unsmob_grob (smob));
203   Link_array<Grob> heads (extract_grob_array (me, ly_symbol2scm ("note-heads")));
204
205   if (heads.is_empty ())
206     return SCM_EOL;
207   
208   // find size of note heads.
209   Grob * staff = Staff_symbol_referencer::get_staff_symbol (me);
210   if (!staff)
211     return SCM_EOL;
212   
213     
214   Real length_fraction
215     = robust_scm2double (me->get_property ("length-fraction"), 0.25);
216
217   Stencil ledgers;
218   Stencil default_ledger;
219
220   Grob * common[NO_AXES];
221   
222   for (int i = X_AXIS;  i < NO_AXES; i++)
223     {
224       Axis a = Axis (i);
225       common[a] = common_refpoint_of_array (heads, me, a);
226       for (int i = heads.size (); i--; )
227         if (Grob * g = unsmob_grob (me->get_property ("accidental-grob")))
228           common[a] = common[a]->common_refpoint (g, a);
229     }
230
231   int interspaces = Staff_symbol::line_count (staff)-1;
232   Ledger_requests reqs;
233   for (int i = heads.size (); i--; )
234     {
235       Item *h = dynamic_cast<Item*> (heads[i]);
236       
237       int pos = Staff_symbol_referencer::get_rounded_position (h);
238       if (pos
239           && abs (pos) > interspaces)
240         {
241           Interval head_extent = h->extent (common[X_AXIS], X_AXIS);
242           Interval ledger_extent = head_extent;
243           ledger_extent.widen (length_fraction * head_extent.length ());
244
245           Direction vdir = Direction (sign (pos));
246           int rank = Paper_column::get_rank (h->get_column ());
247           
248           reqs[rank][vdir].ledger_extent_.unite (ledger_extent);
249           reqs[rank][vdir].head_extent_.unite (head_extent);
250           reqs[rank][vdir].position_ =
251             vdir * ((vdir* reqs[rank][vdir].position_) >? (vdir *pos));
252         }
253     }
254
255   // determine maximum size for non-colliding ledger.
256   Real gap = robust_scm2double (me->get_property ("gap"), 0.1);
257   Ledger_requests::iterator last (reqs.end ());
258   for (Ledger_requests::iterator i (reqs.begin ());
259        i != reqs.end (); last = i++)
260     {
261       if (last == reqs.end ())
262         {
263           continue;
264         }
265       
266       Direction d = DOWN;
267       do
268         {
269           if (abs (last->second[d].position_) > interspaces
270               && abs (i->second[d].position_) > interspaces)
271             {
272               Real center =  
273                 (last->second[d].head_extent_[RIGHT]
274                  + i->second[d].head_extent_[LEFT] )/2;
275
276               Direction which = LEFT;
277               do
278                 {
279                   Ledger_request &lr = ((which == LEFT) ? *last : *i).second[d];
280
281                   // due tilt of quarter note-heads
282                   bool both =
283                      (abs (last->second[d].position_) > interspaces + 1
284                       && abs (i->second[d].position_) > interspaces + 1);
285
286                   Real limit = (center + (both? which * gap/2 : 0));
287                   lr.ledger_extent_.elem_ref (-which)
288                     = which  * (which * lr.ledger_extent_[-which] >? which * limit);
289                 }
290               while (flip (&which) != LEFT); 
291             }
292         }
293       while (flip (&d) != DOWN); 
294     }
295
296   // create  ledgers for note heads
297   Real ledgerlinethickness =
298     Staff_symbol::get_ledger_line_thickness (staff);
299   Real halfspace = Staff_symbol::staff_space (staff)/2;
300   for (int i = heads.size (); i--; )
301     {
302       Item *h = dynamic_cast<Item*> (heads[i]);
303       
304       int pos = Staff_symbol_referencer::get_rounded_position (h);
305       if (abs (pos) > interspaces + 1)
306         {
307           Interval head_size = h->extent (common[X_AXIS], X_AXIS);
308           Interval ledger_size = head_size;
309           ledger_size.widen (ledger_size.length ()* length_fraction);
310
311           Interval max_size = reqs[Paper_column::get_rank (h->get_column ())][Direction (sign(pos))].ledger_extent_;
312
313           ledger_size.intersect (max_size);
314           Real left_shorten = 0.0;
315           if (Grob * g = unsmob_grob (h->get_property ("accidental-grob")))
316             {
317               Interval accidental_size = g->extent (common[X_AXIS], X_AXIS);
318               Real d =
319                 linear_combination (Drul_array<Real> (accidental_size[RIGHT],
320                                                       head_size[LEFT]),                                 
321                                     0.0);
322
323               left_shorten =  (-ledger_size[LEFT] + d) >?  0 ;
324
325               /*
326                 TODO: shorten 2 ledger lines for the case natural +
327                 downstem.
328               */
329               
330             }
331
332           ledgers.add_stencil (brew_ledger_lines (staff, pos, interspaces,
333                                                   halfspace,
334                                                   ledgerlinethickness,
335                                                   ledger_size,
336                                                   left_shorten));
337         }
338     }
339
340   ledgers.translate_axis (-me->relative_coordinate (common[X_AXIS], X_AXIS),
341                           X_AXIS);
342   
343   return ledgers.smobbed_copy ();
344 }
345
346 ADD_INTERFACE (Ledger_line_spanner,
347                "ledger-line-interface",
348                "This spanner draws the ledger lines of a staff, for note heads that stick out. ",
349                "note-heads thickness minimum-length-fraction length-fraction gap");
350
351
352 struct Ledgered_interface { 
353   static bool has_interface (Grob*);
354 };
355
356 ADD_INTERFACE (Ledgered_interface,
357                "ledgered-interface",
358                "Objects that need ledger lines.",
359                "no-ledgers");