2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2004--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
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.
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.
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/>.
23 #include "note-head.hh"
24 #include "staff-symbol-referencer.hh"
25 #include "staff-symbol.hh"
28 #include "pointer-group-interface.hh"
29 #include "paper-column.hh"
31 struct Ledger_line_spanner
33 DECLARE_SCHEME_CALLBACK (print, (SCM));
34 DECLARE_SCHEME_CALLBACK (set_spacing_rods, (SCM));
38 set_rods (Drul_array<Interval> const ¤t_extents,
39 Drul_array<Interval> const &previous_extents,
41 Item *previous_column,
46 if (!current_extents[d].is_empty ()
47 && !previous_extents[d].is_empty ())
50 rod.distance_ = 2 * min_length
52 we go from right to left.
54 - previous_extents[d][LEFT]
55 + current_extents[d][RIGHT];
57 rod.item_drul_[LEFT] = current_column;
58 rod.item_drul_[RIGHT] = previous_column;
64 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, set_spacing_rods, 1);
66 Ledger_line_spanner::set_spacing_rods (SCM smob)
68 Spanner *me = unsmob<Spanner> (smob);
70 // find size of note heads.
71 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
78 Real min_length_fraction
79 = robust_scm2double (me->get_property ("minimum-length-fraction"), 0.15);
81 Drul_array<Interval> current_extents;
82 Drul_array<Interval> previous_extents;
83 Real current_head_width = 0.0;
84 Item *previous_column = 0;
85 Item *current_column = 0;
87 Real halfspace = Staff_symbol::staff_space (staff) / 2;
89 Interval staff_extent = staff->extent (staff, Y_AXIS);
90 staff_extent *= 1 / halfspace;
93 Run through heads using a loop. Since Ledger_line_spanner can
94 contain a lot of noteheads, superlinear performance is too slow.
96 extract_item_set (me, "note-heads", heads);
97 for (vsize i = heads.size (); i--;)
101 int pos = Staff_symbol_referencer::get_rounded_position (h);
102 if (Staff_symbol::ledger_positions (staff, pos).empty ())
105 /* Ambitus heads can appear out-of-order in heads[],
106 * but as part of prefatory matter, they need no rods */
107 if (h->internal_has_interface (ly_symbol2scm ("ambitus-interface")))
110 Item *column = h->get_column ();
111 if (current_column != column)
113 set_rods (current_extents, previous_extents,
114 current_column, previous_column,
115 current_head_width * min_length_fraction);
117 previous_column = current_column;
118 current_column = column;
119 previous_extents = current_extents;
121 current_extents[DOWN].set_empty ();
122 current_extents[UP].set_empty ();
123 current_head_width = 0.0;
126 Interval head_extent = h->extent (column, X_AXIS);
127 Direction vdir = Direction (sign (pos));
131 current_extents[vdir].unite (head_extent);
132 current_head_width = max (current_head_width, head_extent.length ());
135 if (previous_column && current_column)
136 set_rods (current_extents, previous_extents,
137 current_column, previous_column,
138 current_head_width * min_length_fraction);
140 return SCM_UNSPECIFIED;
146 vector<Real> ledger_positions_;
147 Interval head_extent_;
148 Interval ledger_extent_;
149 Interval accidental_extent_;
152 head_extent_.set_empty ();
153 ledger_extent_.set_empty ();
154 accidental_extent_.set_empty ();
158 struct Ledger_request
160 Interval max_ledger_extent_;
161 Interval max_head_extent_;
163 vector <Head_data> heads_;
164 // The map's keys are vertical ledger line positions. The values are
165 // vectors of the x-extents of ledger lines.
166 map <Real, vector <Interval> > ledger_extents_;
169 max_ledger_extent_.set_empty ();
170 max_head_extent_.set_empty ();
175 typedef map < int, Drul_array<Ledger_request> > Ledger_requests;
178 TODO: ledger share a lot of info. Lots of room to optimize away
179 common use of objects/variables.
181 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, print, 1);
183 Ledger_line_spanner::print (SCM smob)
185 Spanner *me = unsmob<Spanner> (smob);
187 // Generate ledger requests from note head properties, etc.
188 extract_grob_set (me, "note-heads", heads);
193 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
197 Real halfspace = Staff_symbol::staff_space (staff) / 2;
199 Interval staff_extent = staff->extent (staff, Y_AXIS);
200 staff_extent *= 1 / halfspace;
203 = robust_scm2double (me->get_property ("length-fraction"), 0.25);
205 Grob *common_x = common_refpoint_of_array (heads, me, X_AXIS);
206 for (vsize i = heads.size (); i--;)
208 if (Grob *g = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
209 common_x = common_x->common_refpoint (g, X_AXIS);
212 Ledger_requests reqs;
213 for (vsize i = heads.size (); i--;)
215 Item *h = dynamic_cast<Item *> (heads[i]);
216 int pos = Staff_symbol_referencer::get_rounded_position (h);
217 vector<Real> ledger_positions =
218 Staff_symbol::ledger_positions (staff, pos, h);
220 // We work with all notes that produce ledgers and any notes that
221 // fall outside the staff that do not produce ledgers, such as
222 // notes in the first space just beyond the staff.
223 if (ledger_positions.size () != 0 || !staff_extent.contains (pos))
225 Interval head_extent = h->extent (common_x, X_AXIS);
226 Interval ledger_extent = head_extent;
227 ledger_extent.widen (length_fraction * head_extent.length ());
229 Direction vdir = Direction (sign (pos != 0 ? pos : 1));
230 int rank = h->get_column ()->get_rank ();
232 reqs[rank][vdir].max_ledger_extent_.unite (ledger_extent);
233 reqs[rank][vdir].max_head_extent_.unite (head_extent);
234 reqs[rank][vdir].max_position_
235 = vdir * max (vdir * reqs[rank][vdir].max_position_,
239 hd.ledger_positions_ = ledger_positions;
240 hd.ledger_extent_ = ledger_extent;
241 hd.head_extent_ = head_extent;
242 if (Grob *g = unsmob<Grob> (h->get_object ("accidental-grob")))
243 hd.accidental_extent_ = g->extent (common_x, X_AXIS);
244 reqs[rank][vdir].heads_.push_back(hd);
248 if (reqs.size () == 0)
251 // Iterate through ledger requests and when ledger lines will be
252 // too close together horizontally, shorten max_ledger_extent to
253 // produce more space between them.
254 Real gap = robust_scm2double (me->get_property ("gap"), 0.1);
255 Ledger_requests::iterator last (reqs.end ());
256 for (Ledger_requests::iterator i (reqs.begin ());
257 i != reqs.end (); last = i++)
259 if (last == reqs.end ())
262 for (DOWN_and_UP (d))
264 // Some rank--> vdir--> reqs will be 'empty' because notes
265 // will not be above AND below the staff for a given rank.
266 if (!staff_extent.contains (last->second[d].max_position_)
267 && !staff_extent.contains (i->second[d].max_position_))
269 // Midpoint between the furthest bounds of the two heads.
271 = (last->second[d].max_head_extent_[RIGHT]
272 + i->second[d].max_head_extent_[LEFT]) / 2;
274 // Do both reqs have notes further than the first space
276 // (due tilt of quarter note-heads)
279 = (!staff_extent.contains (last->second[d].max_position_
280 - sign (last->second[d].max_position_))
281 && !staff_extent.contains (i->second[d].max_position_
282 - sign (i->second[d].max_position_)));
284 for (LEFT_and_RIGHT (which))
286 Ledger_request &lr = ((which == LEFT) ? * last : *i).second[d];
288 Real limit = (center + (both ? which * gap / 2 : 0));
289 lr.max_ledger_extent_.at (-which)
290 = which * max (which * lr.max_ledger_extent_[-which],
297 // Iterate through ledger requests and the data they have about each
298 // note head to generate the final extents for all ledger lines.
299 // Note heads of different widths produce different ledger extents.
300 for (Ledger_requests::iterator i (reqs.begin ());
301 i != reqs.end (); i++)
303 for (DOWN_and_UP (d))
305 Ledger_request &lr = i->second[d];
306 for (vsize h = 0; h < lr.heads_.size (); h++)
308 vector<Real> &ledger_posns = lr.heads_[h].ledger_positions_;
309 Interval &ledger_size = lr.heads_[h].ledger_extent_;
310 Interval &head_size = lr.heads_[h].head_extent_;
311 Interval &acc_extent = lr.heads_[h].accidental_extent_;
313 // Limit ledger extents to a maximum to preserve space
314 // between ledgers when note heads get close.
315 if (!lr.max_ledger_extent_.is_empty ())
316 ledger_size.intersect (lr.max_ledger_extent_);
318 // Iterate through the ledgers for a given note head.
319 for (vsize l = 0; l < ledger_posns.size (); l++)
321 Real lpos = ledger_posns[l];
322 Interval x_extent = ledger_size;
324 // Notes with accidental signs get shorter ledgers.
325 // (Only happens for the furthest note in the column.)
326 if (l == 0 && !acc_extent.is_empty ())
329 = linear_combination (Drul_array<Real> (acc_extent[RIGHT],
333 Real left_shorten = max (-ledger_size[LEFT] + dist, 0.0);
334 x_extent[LEFT] += left_shorten;
336 TODO: shorten 2 ledger lines for the case
340 // When the extents of two ledgers at the same
341 // vertical position overlap horizontally, we merge
342 // them together to produce a single stencil. In rare
343 // cases they do not overlap and we do not merge them.
345 if (lr.ledger_extents_.find (lpos) == lr.ledger_extents_.end ())
346 // Found nothing for this lpos.
347 lr.ledger_extents_[lpos].push_back(x_extent);
350 vector<Interval> &extents = lr.ledger_extents_.find (lpos)->second;
351 for (vsize e = 0; e < extents.size (); e++)
353 if (intersection (extents[e], x_extent).is_empty ())
354 extents.push_back (x_extent);
356 extents[e].unite (x_extent);
364 // Create the stencil for the ledger line spanner by iterating
365 // through the ledger requests and their data on ledger extents.
367 Real thickness = Staff_symbol::get_ledger_line_thickness (staff);
368 Real half_thickness = thickness * 0.5;
369 Interval y_extent = Interval (-half_thickness, half_thickness);
371 for (Ledger_requests::iterator i (reqs.begin ()); i != reqs.end (); i++)
373 for (DOWN_and_UP (d))
375 map<Real, vector<Interval> > &lex = i->second[d].ledger_extents_;
376 for (map<Real, vector<Interval> >::iterator k = lex.begin ();
377 k != lex.end (); k++)
379 Real lpos = k->first;
380 vector<Interval> &x_extents = k->second;
382 for (vsize n = 0; n < x_extents.size (); n++)
384 // thickness (ledger line thickness) is the blot diameter
385 Stencil line = Lookup::round_filled_box (Box (x_extents[n], y_extent),
388 line.translate_axis (lpos * halfspace, Y_AXIS);
389 ledgers.add_stencil (line);
394 ledgers.translate_axis (-me->relative_coordinate (common_x, X_AXIS), X_AXIS);
395 return ledgers.smobbed_copy ();
398 ADD_INTERFACE (Ledger_line_spanner,
399 "This spanner draws the ledger lines of a staff. This is a"
400 " separate grob because it has to process all potential"
401 " collisions between all note heads. The thickness of ledger"
402 " lines is controlled by the @code{ledger-line-thickness}"
403 " property of the @ref{StaffSymbol} grob.",
408 "minimum-length-fraction "
412 struct Ledgered_interface
416 ADD_INTERFACE (Ledgered_interface,
417 "Objects that need ledger lines, typically note heads. See"
418 " also @ref{ledger-line-spanner-interface}.",