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 map <Real, Interval> ledger_extents_;
167 max_ledger_extent_.set_empty ();
168 max_head_extent_.set_empty ();
173 typedef map < int, Drul_array<Ledger_request> > Ledger_requests;
176 TODO: ledger share a lot of info. Lots of room to optimize away
177 common use of objects/variables.
179 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, print, 1);
181 Ledger_line_spanner::print (SCM smob)
183 Spanner *me = unsmob<Spanner> (smob);
185 // Generate ledger requests from note head properties, etc.
186 extract_grob_set (me, "note-heads", heads);
191 Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
195 Real halfspace = Staff_symbol::staff_space (staff) / 2;
197 Interval staff_extent = staff->extent (staff, Y_AXIS);
198 staff_extent *= 1 / halfspace;
201 = robust_scm2double (me->get_property ("length-fraction"), 0.25);
203 Grob *common_x = common_refpoint_of_array (heads, me, X_AXIS);
204 for (vsize i = heads.size (); i--;)
206 if (Grob *g = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
207 common_x = common_x->common_refpoint (g, X_AXIS);
210 Ledger_requests reqs;
211 for (vsize i = heads.size (); i--;)
213 Item *h = dynamic_cast<Item *> (heads[i]);
214 int pos = Staff_symbol_referencer::get_rounded_position (h);
215 vector<Real> ledger_positions =
216 Staff_symbol::ledger_positions (staff, pos, h);
218 // We work with all notes that produce ledgers and any notes that
219 // fall outside the staff that do not produce ledgers, such as
220 // notes in the first space just beyond the staff.
221 if (ledger_positions.size () != 0 || !staff_extent.contains (pos))
223 Interval head_extent = h->extent (common_x, X_AXIS);
224 Interval ledger_extent = head_extent;
225 ledger_extent.widen (length_fraction * head_extent.length ());
227 Direction vdir = Direction (sign (pos != 0 ? pos : 1));
228 int rank = h->get_column ()->get_rank ();
230 reqs[rank][vdir].max_ledger_extent_.unite (ledger_extent);
231 reqs[rank][vdir].max_head_extent_.unite (head_extent);
232 reqs[rank][vdir].max_position_
233 = vdir * max (vdir * reqs[rank][vdir].max_position_,
237 hd.ledger_positions_ = ledger_positions;
238 hd.ledger_extent_ = ledger_extent;
239 hd.head_extent_ = head_extent;
240 if (Grob *g = unsmob<Grob> (h->get_object ("accidental-grob")))
241 hd.accidental_extent_ = g->extent (common_x, X_AXIS);
242 reqs[rank][vdir].heads_.push_back(hd);
246 if (reqs.size () == 0)
249 // Iterate through ledger requests and when ledger lines will be
250 // too close together horizontally, shorten max_ledger_extent to
251 // produce more space between them.
252 Real gap = robust_scm2double (me->get_property ("gap"), 0.1);
253 Ledger_requests::iterator last (reqs.end ());
254 for (Ledger_requests::iterator i (reqs.begin ());
255 i != reqs.end (); last = i++)
257 if (last == reqs.end ())
260 for (DOWN_and_UP (d))
262 // Some rank--> vdir--> reqs will be 'empty' because notes
263 // will not be above AND below the staff for a given rank.
264 if (!staff_extent.contains (last->second[d].max_position_)
265 && !staff_extent.contains (i->second[d].max_position_))
267 // Midpoint between the furthest bounds of the two heads.
269 = (last->second[d].max_head_extent_[RIGHT]
270 + i->second[d].max_head_extent_[LEFT]) / 2;
272 // Do both reqs have notes further than the first space
274 // (due tilt of quarter note-heads)
277 = (!staff_extent.contains (last->second[d].max_position_
278 - sign (last->second[d].max_position_))
279 && !staff_extent.contains (i->second[d].max_position_
280 - sign (i->second[d].max_position_)));
282 for (LEFT_and_RIGHT (which))
284 Ledger_request &lr = ((which == LEFT) ? * last : *i).second[d];
286 Real limit = (center + (both ? which * gap / 2 : 0));
287 lr.max_ledger_extent_.at (-which)
288 = which * max (which * lr.max_ledger_extent_[-which],
295 // Iterate through ledger requests and the data they have about each
296 // note head to generate the final extents for all ledger lines.
297 // Note heads that are different widths produce different ledger
298 // extents and these are merged so the widest extent prevails
299 // (the union of the intervals) for each ledger line.
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 if (lr.ledger_extents_.find (lpos) == lr.ledger_extents_.end ())
341 lr.ledger_extents_[lpos] = x_extent;
343 lr.ledger_extents_[lpos].unite (x_extent);
349 // Create the stencil for the ledger line spanner by iterating
350 // through the ledger requests and their data on ledger extents.
352 Real ledgerlinethickness
353 = Staff_symbol::get_ledger_line_thickness (staff);
355 for (Ledger_requests::iterator i (reqs.begin ());
356 i != reqs.end (); i++)
358 for (DOWN_and_UP (d))
360 map<Real, Interval> &lex = i->second[d].ledger_extents_;
361 for (map<Real, Interval>::iterator k = lex.begin ();
362 k != lex.end (); k++)
364 Real blotdiameter = ledgerlinethickness;
365 Real lpos = k->first;
366 Interval x_extent = k->second;
368 = Interval (-0.5 * (ledgerlinethickness),
369 +0.5 * (ledgerlinethickness));
371 = Lookup::round_filled_box (Box (x_extent, y_extent), blotdiameter);
373 ledger_line.translate_axis ( lpos * halfspace, Y_AXIS);
374 ledgers.add_stencil (ledger_line);
379 ledgers.translate_axis (-me->relative_coordinate (common_x, X_AXIS),
382 return ledgers.smobbed_copy ();
385 ADD_INTERFACE (Ledger_line_spanner,
386 "This spanner draws the ledger lines of a staff. This is a"
387 " separate grob because it has to process all potential"
388 " collisions between all note heads. The thickness of ledger"
389 " lines is controlled by the @code{ledger-line-thickness}"
390 " property of the @ref{StaffSymbol} grob.",
395 "minimum-length-fraction "
399 struct Ledgered_interface
403 ADD_INTERFACE (Ledgered_interface,
404 "Objects that need ledger lines, typically note heads. See"
405 " also @ref{ledger-line-spanner-interface}.",