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