]> git.donarmstrong.com Git - lilypond.git/blob - lily/ledger-line-spanner.cc
7a45bef2de731d78fba6ef3b064feaae62d29876
[lilypond.git] / lily / ledger-line-spanner.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2004--2014 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <map>
21 using namespace std;
22
23 #include "note-head.hh"
24 #include "staff-symbol-referencer.hh"
25 #include "staff-symbol.hh"
26 #include "lookup.hh"
27 #include "spanner.hh"
28 #include "pointer-group-interface.hh"
29 #include "paper-column.hh"
30
31 struct Ledger_line_spanner
32 {
33   DECLARE_SCHEME_CALLBACK (print, (SCM));
34   DECLARE_SCHEME_CALLBACK (set_spacing_rods, (SCM));
35   DECLARE_GROB_INTERFACE ();
36 };
37
38 static void
39 set_rods (Drul_array<Interval> const &current_extents,
40           Drul_array<Interval> const &previous_extents,
41           Item *current_column,
42           Item *previous_column,
43           Real min_length)
44 {
45   for (UP_and_DOWN (d))
46     {
47       if (!current_extents[d].is_empty ()
48           && !previous_extents[d].is_empty ())
49         {
50           Rod rod;
51           rod.distance_ = 2 * min_length
52                           /*
53                             we go from right to left.
54                           */
55                           - previous_extents[d][LEFT]
56                           + current_extents[d][RIGHT];
57
58           rod.item_drul_[LEFT] = current_column;
59           rod.item_drul_[RIGHT] = previous_column;
60           rod.add_to_cols ();
61         }
62     }
63 }
64
65 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, set_spacing_rods, 1);
66 SCM
67 Ledger_line_spanner::set_spacing_rods (SCM smob)
68 {
69   Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
70
71   // find size of note heads.
72   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
73   if (!staff)
74     {
75       me->suicide ();
76       return SCM_EOL;
77     }
78
79   Real min_length_fraction
80     = robust_scm2double (me->get_property ("minimum-length-fraction"), 0.15);
81
82   Drul_array<Interval> current_extents;
83   Drul_array<Interval> previous_extents;
84   Real current_head_width = 0.0;
85   Item *previous_column = 0;
86   Item *current_column = 0;
87
88   Real halfspace = Staff_symbol::staff_space (staff) / 2;
89
90   Interval staff_extent = staff->extent (staff, Y_AXIS);
91   staff_extent *= 1 / halfspace;
92
93   /*
94     Run through heads using a loop. Since Ledger_line_spanner can
95     contain a lot of noteheads, superlinear performance is too slow.
96   */
97   extract_item_set (me, "note-heads", heads);
98   for (vsize i = heads.size (); i--;)
99     {
100       Item *h = heads[i];
101
102       int pos = Staff_symbol_referencer::get_rounded_position (h);
103       if  (Staff_symbol::ledger_positions (staff, pos).empty ())
104         continue;
105
106       /* Ambitus heads can appear out-of-order in heads[],
107        * but as part of prefatory matter, they need no rods */
108       if (h->internal_has_interface (ly_symbol2scm ("ambitus-interface")))
109         continue;
110
111       Item *column = h->get_column ();
112       if (current_column != column)
113         {
114           set_rods (current_extents, previous_extents,
115                     current_column, previous_column,
116                     current_head_width * min_length_fraction);
117
118           previous_column = current_column;
119           current_column = column;
120           previous_extents = current_extents;
121
122           current_extents[DOWN].set_empty ();
123           current_extents[UP].set_empty ();
124           current_head_width = 0.0;
125         }
126
127       Interval head_extent = h->extent (column, X_AXIS);
128       Direction vdir = Direction (sign (pos));
129       if (!vdir)
130         continue;
131
132       current_extents[vdir].unite (head_extent);
133       current_head_width = max (current_head_width, head_extent.length ());
134     }
135
136   if (previous_column && current_column)
137     set_rods (current_extents, previous_extents,
138               current_column, previous_column,
139               current_head_width * min_length_fraction);
140
141   return SCM_UNSPECIFIED;
142 }
143
144 struct Ledger_request
145 {
146   Interval ledger_extent_;
147   Interval head_extent_;
148   int position_;
149   Ledger_request ()
150   {
151     ledger_extent_.set_empty ();
152     head_extent_.set_empty ();
153     position_ = 0;
154   }
155 };
156
157 typedef map < int, Drul_array<Ledger_request> > Ledger_requests;
158
159 /*
160   TODO: ledger share a lot of info. Lots of room to optimize away
161   common use of objects/variables.
162 */
163 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, print, 1);
164 SCM
165 Ledger_line_spanner::print (SCM smob)
166 {
167   Spanner *me = dynamic_cast<Spanner *> (unsmob_grob (smob));
168
169   extract_grob_set (me, "note-heads", heads);
170
171   if (heads.empty ())
172     return SCM_EOL;
173
174   // find size of note heads.
175   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
176   if (!staff)
177     return SCM_EOL;
178
179   Real halfspace = Staff_symbol::staff_space (staff) / 2;
180
181   Interval staff_extent = staff->extent (staff, Y_AXIS);
182   staff_extent *= 1 / halfspace;
183
184   Real length_fraction
185     = robust_scm2double (me->get_property ("length-fraction"), 0.25);
186
187   Stencil ledgers;
188
189   Grob *common[NO_AXES];
190
191   for (int i = X_AXIS; i < NO_AXES; i++)
192     {
193       Axis a = Axis (i);
194       common[a] = common_refpoint_of_array (heads, me, a);
195       for (vsize i = heads.size (); i--;)
196         if (Grob *g = unsmob_grob (me->get_object ("accidental-grob")))
197           common[a] = common[a]->common_refpoint (g, a);
198     }
199
200   Ledger_requests reqs;
201   for (vsize i = heads.size (); i--;)
202     {
203       Item *h = dynamic_cast<Item *> (heads[i]);
204
205       int pos = Staff_symbol_referencer::get_rounded_position (h);
206       if (pos && !staff_extent.contains (pos))
207         {
208           Interval head_extent = h->extent (common[X_AXIS], X_AXIS);
209           Interval ledger_extent = head_extent;
210           ledger_extent.widen (length_fraction * head_extent.length ());
211
212           Direction vdir = Direction (sign (pos));
213           int rank = h->get_column ()->get_rank ();
214
215           reqs[rank][vdir].ledger_extent_.unite (ledger_extent);
216           reqs[rank][vdir].head_extent_.unite (head_extent);
217           reqs[rank][vdir].position_
218             = vdir * max (vdir * reqs[rank][vdir].position_, vdir * pos);
219         }
220     }
221
222   // determine maximum size for non-colliding ledger.
223   Real gap = robust_scm2double (me->get_property ("gap"), 0.1);
224   Ledger_requests::iterator last (reqs.end ());
225   for (Ledger_requests::iterator i (reqs.begin ());
226        i != reqs.end (); last = i++)
227     {
228       if (last == reqs.end ())
229         continue;
230
231       for (DOWN_and_UP (d))
232         {
233           if (!staff_extent.contains (last->second[d].position_)
234               && !staff_extent.contains (i->second[d].position_))
235             {
236               Real center
237                 = (last->second[d].head_extent_[RIGHT]
238                    + i->second[d].head_extent_[LEFT]) / 2;
239
240               for (LEFT_and_RIGHT (which))
241                 {
242                   Ledger_request &lr = ((which == LEFT) ? * last : *i).second[d];
243
244                   // due tilt of quarter note-heads
245                   /* FIXME */
246                   bool both
247                     = (!staff_extent.contains (last->second[d].position_
248                                                - sign (last->second[d].position_))
249                        && !staff_extent.contains (i->second[d].position_
250                                                   - sign (i->second[d].position_)));
251                   Real limit = (center + (both ? which * gap / 2 : 0));
252                   lr.ledger_extent_.at (-which)
253                     = which * max (which * lr.ledger_extent_[-which], which * limit);
254                 }
255             }
256         }
257     }
258
259   // create ledgers for note heads
260   Real ledgerlinethickness
261     = Staff_symbol::get_ledger_line_thickness (staff);
262   for (vsize i = heads.size (); i--;)
263     {
264       Item *h = dynamic_cast<Item *> (heads[i]);
265
266       int pos = Staff_symbol_referencer::get_rounded_position (h);
267       vector<Real> ledger_positions = Staff_symbol::ledger_positions (staff, pos);
268       if (!ledger_positions.empty ())
269         {
270           int ledger_count = ledger_positions.size ();
271           Interval head_size = h->extent (common[X_AXIS], X_AXIS);
272           Interval ledger_size = head_size;
273           ledger_size.widen (ledger_size.length () * length_fraction);
274
275           if (pos && !staff_extent.contains (pos))
276             {
277               Interval max_size = reqs[h->get_column ()->get_rank ()]
278                                   [Direction (sign (pos))].ledger_extent_;
279
280               if (!max_size.is_empty ())
281                 ledger_size.intersect (max_size);
282             }
283
284           for (int i = 0; i < ledger_count; i++)
285             {
286               Real lpos = ledger_positions[i];
287               Interval x_extent = ledger_size;
288
289               if (i == 0)
290                 if (Grob *g = unsmob_grob (h->get_object ("accidental-grob")))
291                   {
292                     Interval accidental_size = g->extent (common[X_AXIS], X_AXIS);
293                     Real d
294                       = linear_combination (Drul_array<Real> (accidental_size[RIGHT],
295                                                               head_size[LEFT]),
296                                             0.0);
297
298                     Real left_shorten = max (-ledger_size[LEFT] + d, 0.0);
299
300                     x_extent[LEFT] += left_shorten;
301                     /*
302                       TODO: shorten 2 ledger lines for the case natural +
303                       downstem.
304                     */
305                   }
306
307               Real blotdiameter = ledgerlinethickness;
308               Interval y_extent
309                 = Interval (-0.5 * (ledgerlinethickness),
310                             +0.5 * (ledgerlinethickness));
311               Stencil ledger_line
312                 = Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
313
314               ledger_line.translate_axis ( lpos * halfspace, Y_AXIS);
315               ledgers.add_stencil (ledger_line);
316             }
317         }
318     }
319
320   ledgers.translate_axis (-me->relative_coordinate (common[X_AXIS], X_AXIS),
321                           X_AXIS);
322
323   return ledgers.smobbed_copy ();
324 }
325
326 ADD_INTERFACE (Ledger_line_spanner,
327                "This spanner draws the ledger lines of a staff.  This is a"
328                " separate grob because it has to process all potential"
329                " collisions between all note heads.  The thickness of ledger"
330                " lines is controlled by the @code{ledger-line-thickness}"
331                " property of the @ref{StaffSymbol} grob.",
332
333                /* properties */
334                "gap "
335                "length-fraction "
336                "minimum-length-fraction "
337                "note-heads "
338               );
339
340 struct Ledgered_interface
341 {
342   DECLARE_GROB_INTERFACE ();
343 };
344
345 ADD_INTERFACE (Ledgered_interface,
346                "Objects that need ledger lines, typically note heads.  See"
347                " also @ref{ledger-line-spanner-interface}.",
348
349                /* properties */
350                "no-ledgers "
351               );