2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 2004--2010 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
));
35 static Stencil
brew_ledger_lines (Grob
*me
,
42 DECLARE_GROB_INTERFACE ();
46 Ledger_line_spanner::brew_ledger_lines (Grob
*staff
,
48 Interval staff_extent
,
50 Real ledgerlinethickness
,
54 int line_count
= (staff_extent
.contains (pos
)
56 : sign (pos
) * int (rint (pos
- staff_extent
[Direction (sign (pos
))])) / 2);
60 Real blotdiameter
= ledgerlinethickness
;
62 = Interval (-0.5 * (ledgerlinethickness
),
63 +0.5 * (ledgerlinethickness
));
64 Stencil proto_ledger_line
65 = Lookup::round_filled_box (Box (x_extent
, y_extent
), blotdiameter
);
67 x_extent
[LEFT
] += left_shorten
;
68 Stencil proto_first_line
69 = Lookup::round_filled_box (Box (x_extent
, y_extent
), blotdiameter
);
71 Direction dir
= (Direction
)sign (pos
);
72 Real offs
= (Staff_symbol_referencer::on_line (staff
, pos
))
76 offs
+= pos
* halfspace
;
77 for (int i
= 0; i
< line_count
; i
++)
79 Stencil
ledger_line ((i
== 0)
82 ledger_line
.translate_axis (-dir
* halfspace
* i
* 2 + offs
, Y_AXIS
);
83 stencil
.add_stencil (ledger_line
);
91 set_rods (Drul_array
<Interval
> const ¤t_extents
,
92 Drul_array
<Interval
> const &previous_extents
,
94 Item
*previous_column
,
95 Real min_length_fraction
)
100 if (!current_extents
[d
].is_empty ()
101 && !previous_extents
[d
].is_empty ())
103 Real total_head_length
= previous_extents
[d
].length ()
104 + current_extents
[d
].length ();
107 rod
.distance_
= total_head_length
108 * (3 / 2 * min_length_fraction
)
110 we go from right to left.
112 - previous_extents
[d
][LEFT
]
113 + current_extents
[d
][RIGHT
];
115 rod
.item_drul_
[LEFT
] = current_column
;
116 rod
.item_drul_
[RIGHT
] = previous_column
;
120 while (flip (&d
) != DOWN
);
123 MAKE_SCHEME_CALLBACK (Ledger_line_spanner
, set_spacing_rods
, 1);
125 Ledger_line_spanner::set_spacing_rods (SCM smob
)
127 Spanner
*me
= dynamic_cast<Spanner
*> (unsmob_grob (smob
));
129 // find size of note heads.
130 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
137 Real min_length_fraction
138 = robust_scm2double (me
->get_property ("minimum-length-fraction"), 0.15);
140 Drul_array
<Interval
> current_extents
;
141 Drul_array
<Interval
> previous_extents
;
142 Item
*previous_column
= 0;
143 Item
*current_column
= 0;
145 Real halfspace
= Staff_symbol::staff_space (staff
) / 2;
147 Interval staff_extent
= staff
->extent (staff
, Y_AXIS
);
148 staff_extent
*= 1 / halfspace
;
151 Run through heads using a loop. Since Ledger_line_spanner can
152 contain a lot of noteheads, superlinear performance is too slow.
154 extract_item_set (me
, "note-heads", heads
);
155 for (vsize i
= heads
.size (); i
--;)
159 int pos
= Staff_symbol_referencer::get_rounded_position (h
);
160 if (staff_extent
.contains (pos
))
163 Item
*column
= h
->get_column ();
164 if (current_column
!= column
)
166 set_rods (current_extents
, previous_extents
,
167 current_column
, previous_column
,
168 min_length_fraction
);
170 previous_column
= current_column
;
171 current_column
= column
;
172 previous_extents
= current_extents
;
174 current_extents
[DOWN
].set_empty ();
175 current_extents
[UP
].set_empty ();
178 Interval head_extent
= h
->extent (column
, X_AXIS
);
179 Direction vdir
= Direction (sign (pos
));
183 current_extents
[vdir
].unite (head_extent
);
186 if (previous_column
&& current_column
)
187 set_rods (current_extents
, previous_extents
,
188 current_column
, previous_column
,
189 min_length_fraction
);
191 return SCM_UNSPECIFIED
;
194 struct Ledger_request
196 Interval ledger_extent_
;
197 Interval head_extent_
;
202 ledger_extent_
.set_empty ();
203 head_extent_
.set_empty ();
208 typedef map
< int, Drul_array
<Ledger_request
> > Ledger_requests
;
211 TODO: ledger share a lot of info. Lots of room to optimize away
212 common use of objects/variables.
214 MAKE_SCHEME_CALLBACK (Ledger_line_spanner
, print
, 1);
216 Ledger_line_spanner::print (SCM smob
)
218 Spanner
*me
= dynamic_cast<Spanner
*> (unsmob_grob (smob
));
220 extract_grob_set (me
, "note-heads", heads
);
225 // find size of note heads.
226 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
230 Real halfspace
= Staff_symbol::staff_space (staff
) / 2;
232 Interval staff_extent
= staff
->extent (staff
, Y_AXIS
);
233 staff_extent
*= 1 / halfspace
;
236 = robust_scm2double (me
->get_property ("length-fraction"), 0.25);
239 Stencil default_ledger
;
241 Grob
*common
[NO_AXES
];
243 for (int i
= X_AXIS
; i
< NO_AXES
; i
++)
246 common
[a
] = common_refpoint_of_array (heads
, me
, a
);
247 for (vsize i
= heads
.size (); i
--;)
248 if (Grob
*g
= unsmob_grob (me
->get_object ("accidental-grob")))
249 common
[a
] = common
[a
]->common_refpoint (g
, a
);
252 Ledger_requests reqs
;
253 for (vsize i
= heads
.size (); i
--;)
255 Item
*h
= dynamic_cast<Item
*> (heads
[i
]);
257 int pos
= Staff_symbol_referencer::get_rounded_position (h
);
258 if (pos
&& !staff_extent
.contains (pos
))
260 Interval head_extent
= h
->extent (common
[X_AXIS
], X_AXIS
);
261 Interval ledger_extent
= head_extent
;
262 ledger_extent
.widen (length_fraction
* head_extent
.length ());
264 Direction vdir
= Direction (sign (pos
));
265 int rank
= h
->get_column ()->get_rank ();
267 reqs
[rank
][vdir
].ledger_extent_
.unite (ledger_extent
);
268 reqs
[rank
][vdir
].head_extent_
.unite (head_extent
);
269 reqs
[rank
][vdir
].position_
270 = vdir
* max (vdir
* reqs
[rank
][vdir
].position_
, vdir
* pos
);
274 // determine maximum size for non-colliding ledger.
275 Real gap
= robust_scm2double (me
->get_property ("gap"), 0.1);
276 Ledger_requests::iterator
last (reqs
.end ());
277 for (Ledger_requests::iterator
i (reqs
.begin ());
278 i
!= reqs
.end (); last
= i
++)
280 if (last
== reqs
.end ())
286 if (!staff_extent
.contains (last
->second
[d
].position_
)
287 && !staff_extent
.contains (i
->second
[d
].position_
))
290 = (last
->second
[d
].head_extent_
[RIGHT
]
291 + i
->second
[d
].head_extent_
[LEFT
]) / 2;
293 Direction which
= LEFT
;
296 Ledger_request
&lr
= ((which
== LEFT
) ? * last
: *i
).second
[d
];
298 // due tilt of quarter note-heads
301 = (!staff_extent
.contains (last
->second
[d
].position_
302 - sign (last
->second
[d
].position_
))
303 && !staff_extent
.contains (i
->second
[d
].position_
304 - sign (i
->second
[d
].position_
)));
305 Real limit
= (center
+ (both
? which
* gap
/ 2 : 0));
306 lr
.ledger_extent_
.at (-which
)
307 = which
* max (which
* lr
.ledger_extent_
[-which
], which
* limit
);
309 while (flip (&which
) != LEFT
);
312 while (flip (&d
) != DOWN
);
315 // create ledgers for note heads
316 Real ledgerlinethickness
317 = Staff_symbol::get_ledger_line_thickness (staff
);
318 for (vsize i
= heads
.size (); i
--;)
320 Item
*h
= dynamic_cast<Item
*> (heads
[i
]);
322 int pos
= Staff_symbol_referencer::get_rounded_position (h
);
323 if (!staff_extent
.contains (pos
- sign (pos
)))
325 Interval head_size
= h
->extent (common
[X_AXIS
], X_AXIS
);
326 Interval ledger_size
= head_size
;
327 ledger_size
.widen (ledger_size
.length () * length_fraction
);
329 Interval max_size
= reqs
[h
->get_column ()->get_rank ()]
330 [Direction (sign (pos
))].ledger_extent_
;
332 ledger_size
.intersect (max_size
);
333 Real left_shorten
= 0.0;
334 if (Grob
*g
= unsmob_grob (h
->get_object ("accidental-grob")))
336 Interval accidental_size
= g
->extent (common
[X_AXIS
], X_AXIS
);
338 = linear_combination (Drul_array
<Real
> (accidental_size
[RIGHT
],
342 left_shorten
= max (-ledger_size
[LEFT
] + d
, 0.0);
345 TODO: shorten 2 ledger lines for the case natural +
350 ledgers
.add_stencil (brew_ledger_lines (staff
, pos
, staff_extent
,
358 ledgers
.translate_axis (-me
->relative_coordinate (common
[X_AXIS
], X_AXIS
),
361 return ledgers
.smobbed_copy ();
364 ADD_INTERFACE (Ledger_line_spanner
,
365 "This spanner draws the ledger lines of a staff. This is a"
366 " separate grob because it has to process all potential"
367 " collisions between all note heads.",
372 "minimum-length-fraction "
377 struct Ledgered_interface
379 DECLARE_GROB_INTERFACE ();
382 ADD_INTERFACE (Ledgered_interface
,
383 "Objects that need ledger lines, typically note heads. See"
384 " also @ref{ledger-line-spanner-interface}.",