2 ledger-line-spanner.cc -- implement Ledger_line_spanner
4 source file of the GNU LilyPond music typesetter
6 (c) 2004--2009 Han-Wen Nienhuys <hanwen@xs4all.nl>
12 #include "note-head.hh"
13 #include "staff-symbol-referencer.hh"
14 #include "staff-symbol.hh"
17 #include "pointer-group-interface.hh"
18 #include "paper-column.hh"
20 struct Ledger_line_spanner
22 DECLARE_SCHEME_CALLBACK (print
, (SCM
));
23 DECLARE_SCHEME_CALLBACK (set_spacing_rods
, (SCM
));
24 static Stencil
brew_ledger_lines (Grob
*me
,
31 DECLARE_GROB_INTERFACE ();
35 Ledger_line_spanner::brew_ledger_lines (Grob
*staff
,
37 Interval staff_extent
,
39 Real ledgerlinethickness
,
43 int line_count
= (staff_extent
.contains (pos
)
45 : sign (pos
) * int (rint (pos
- staff_extent
[Direction (sign (pos
))])) / 2);
49 Real blotdiameter
= ledgerlinethickness
;
51 = Interval (-0.5 * (ledgerlinethickness
),
52 +0.5 * (ledgerlinethickness
));
53 Stencil proto_ledger_line
54 = Lookup::round_filled_box (Box (x_extent
, y_extent
), blotdiameter
);
56 x_extent
[LEFT
] += left_shorten
;
57 Stencil proto_first_line
58 = Lookup::round_filled_box (Box (x_extent
, y_extent
), blotdiameter
);
60 Direction dir
= (Direction
)sign (pos
);
61 Real offs
= (Staff_symbol_referencer::on_line (staff
, pos
))
65 offs
+= pos
* halfspace
;
66 for (int i
= 0; i
< line_count
; i
++)
68 Stencil
ledger_line ((i
== 0)
71 ledger_line
.translate_axis (-dir
* halfspace
* i
* 2 + offs
, Y_AXIS
);
72 stencil
.add_stencil (ledger_line
);
80 set_rods (Drul_array
<Interval
> const ¤t_extents
,
81 Drul_array
<Interval
> const &previous_extents
,
83 Item
*previous_column
,
84 Real min_length_fraction
)
89 if (!current_extents
[d
].is_empty ()
90 && !previous_extents
[d
].is_empty ())
92 Real total_head_length
= previous_extents
[d
].length ()
93 + current_extents
[d
].length ();
96 rod
.distance_
= total_head_length
97 * (3 / 2 * min_length_fraction
)
99 we go from right to left.
101 - previous_extents
[d
][LEFT
]
102 + current_extents
[d
][RIGHT
];
104 rod
.item_drul_
[LEFT
] = current_column
;
105 rod
.item_drul_
[RIGHT
] = previous_column
;
109 while (flip (&d
) != DOWN
);
112 MAKE_SCHEME_CALLBACK (Ledger_line_spanner
, set_spacing_rods
, 1);
114 Ledger_line_spanner::set_spacing_rods (SCM smob
)
116 Spanner
*me
= dynamic_cast<Spanner
*> (unsmob_grob (smob
));
118 // find size of note heads.
119 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
126 Real min_length_fraction
127 = robust_scm2double (me
->get_property ("minimum-length-fraction"), 0.15);
129 Drul_array
<Interval
> current_extents
;
130 Drul_array
<Interval
> previous_extents
;
131 Item
*previous_column
= 0;
132 Item
*current_column
= 0;
134 Real halfspace
= Staff_symbol::staff_space (staff
) / 2;
136 Interval staff_extent
= staff
->extent (staff
, Y_AXIS
);
137 staff_extent
*= 1 / halfspace
;
140 Run through heads using a loop. Since Ledger_line_spanner can
141 contain a lot of noteheads, superlinear performance is too slow.
143 extract_item_set (me
, "note-heads", heads
);
144 for (vsize i
= heads
.size (); i
--;)
148 int pos
= Staff_symbol_referencer::get_rounded_position (h
);
149 if (staff_extent
.contains (pos
))
152 Item
*column
= h
->get_column ();
153 if (current_column
!= column
)
155 set_rods (current_extents
, previous_extents
,
156 current_column
, previous_column
,
157 min_length_fraction
);
159 previous_column
= current_column
;
160 current_column
= column
;
161 previous_extents
= current_extents
;
163 current_extents
[DOWN
].set_empty ();
164 current_extents
[UP
].set_empty ();
167 Interval head_extent
= h
->extent (column
, X_AXIS
);
168 Direction vdir
= Direction (sign (pos
));
172 current_extents
[vdir
].unite (head_extent
);
175 if (previous_column
&& current_column
)
176 set_rods (current_extents
, previous_extents
,
177 current_column
, previous_column
,
178 min_length_fraction
);
180 return SCM_UNSPECIFIED
;
183 struct Ledger_request
185 Interval ledger_extent_
;
186 Interval head_extent_
;
191 ledger_extent_
.set_empty ();
192 head_extent_
.set_empty ();
197 typedef map
< int, Drul_array
<Ledger_request
> > Ledger_requests
;
200 TODO: ledger share a lot of info. Lots of room to optimize away
201 common use of objects/variables.
203 MAKE_SCHEME_CALLBACK (Ledger_line_spanner
, print
, 1);
205 Ledger_line_spanner::print (SCM smob
)
207 Spanner
*me
= dynamic_cast<Spanner
*> (unsmob_grob (smob
));
209 extract_grob_set (me
, "note-heads", heads
);
214 // find size of note heads.
215 Grob
*staff
= Staff_symbol_referencer::get_staff_symbol (me
);
219 Real halfspace
= Staff_symbol::staff_space (staff
) / 2;
221 Interval staff_extent
= staff
->extent (staff
, Y_AXIS
);
222 staff_extent
*= 1 / halfspace
;
225 = robust_scm2double (me
->get_property ("length-fraction"), 0.25);
228 Stencil default_ledger
;
230 Grob
*common
[NO_AXES
];
232 for (int i
= X_AXIS
; i
< NO_AXES
; i
++)
235 common
[a
] = common_refpoint_of_array (heads
, me
, a
);
236 for (vsize i
= heads
.size (); i
--;)
237 if (Grob
*g
= unsmob_grob (me
->get_object ("accidental-grob")))
238 common
[a
] = common
[a
]->common_refpoint (g
, a
);
241 Ledger_requests reqs
;
242 for (vsize i
= heads
.size (); i
--;)
244 Item
*h
= dynamic_cast<Item
*> (heads
[i
]);
246 int pos
= Staff_symbol_referencer::get_rounded_position (h
);
247 if (pos
&& !staff_extent
.contains (pos
))
249 Interval head_extent
= h
->extent (common
[X_AXIS
], X_AXIS
);
250 Interval ledger_extent
= head_extent
;
251 ledger_extent
.widen (length_fraction
* head_extent
.length ());
253 Direction vdir
= Direction (sign (pos
));
254 int rank
= h
->get_column ()->get_rank ();
256 reqs
[rank
][vdir
].ledger_extent_
.unite (ledger_extent
);
257 reqs
[rank
][vdir
].head_extent_
.unite (head_extent
);
258 reqs
[rank
][vdir
].position_
259 = vdir
* max (vdir
* reqs
[rank
][vdir
].position_
, vdir
* pos
);
263 // determine maximum size for non-colliding ledger.
264 Real gap
= robust_scm2double (me
->get_property ("gap"), 0.1);
265 Ledger_requests::iterator
last (reqs
.end ());
266 for (Ledger_requests::iterator
i (reqs
.begin ());
267 i
!= reqs
.end (); last
= i
++)
269 if (last
== reqs
.end ())
275 if (!staff_extent
.contains (last
->second
[d
].position_
)
276 && !staff_extent
.contains (i
->second
[d
].position_
))
279 = (last
->second
[d
].head_extent_
[RIGHT
]
280 + i
->second
[d
].head_extent_
[LEFT
]) / 2;
282 Direction which
= LEFT
;
285 Ledger_request
&lr
= ((which
== LEFT
) ? * last
: *i
).second
[d
];
287 // due tilt of quarter note-heads
290 = (!staff_extent
.contains (last
->second
[d
].position_
291 - sign (last
->second
[d
].position_
))
292 && !staff_extent
.contains (i
->second
[d
].position_
293 - sign (i
->second
[d
].position_
)));
294 Real limit
= (center
+ (both
? which
* gap
/ 2 : 0));
295 lr
.ledger_extent_
.at (-which
)
296 = which
* max (which
* lr
.ledger_extent_
[-which
], which
* limit
);
298 while (flip (&which
) != LEFT
);
301 while (flip (&d
) != DOWN
);
304 // create ledgers for note heads
305 Real ledgerlinethickness
306 = Staff_symbol::get_ledger_line_thickness (staff
);
307 for (vsize i
= heads
.size (); i
--;)
309 Item
*h
= dynamic_cast<Item
*> (heads
[i
]);
311 int pos
= Staff_symbol_referencer::get_rounded_position (h
);
312 if (!staff_extent
.contains (pos
- sign (pos
)))
314 Interval head_size
= h
->extent (common
[X_AXIS
], X_AXIS
);
315 Interval ledger_size
= head_size
;
316 ledger_size
.widen (ledger_size
.length () * length_fraction
);
318 Interval max_size
= reqs
[h
->get_column ()->get_rank ()]
319 [Direction (sign (pos
))].ledger_extent_
;
321 ledger_size
.intersect (max_size
);
322 Real left_shorten
= 0.0;
323 if (Grob
*g
= unsmob_grob (h
->get_object ("accidental-grob")))
325 Interval accidental_size
= g
->extent (common
[X_AXIS
], X_AXIS
);
327 = linear_combination (Drul_array
<Real
> (accidental_size
[RIGHT
],
331 left_shorten
= max (-ledger_size
[LEFT
] + d
, 0.0);
334 TODO: shorten 2 ledger lines for the case natural +
339 ledgers
.add_stencil (brew_ledger_lines (staff
, pos
, staff_extent
,
347 ledgers
.translate_axis (-me
->relative_coordinate (common
[X_AXIS
], X_AXIS
),
350 return ledgers
.smobbed_copy ();
353 ADD_INTERFACE (Ledger_line_spanner
,
354 "This spanner draws the ledger lines of a staff. This is a"
355 " separate grob because it has to process all potential"
356 " collisions between all note heads.",
361 "minimum-length-fraction "
366 struct Ledgered_interface
368 DECLARE_GROB_INTERFACE ();
371 ADD_INTERFACE (Ledgered_interface
,
372 "Objects that need ledger lines, typically note heads. See"
373 " also @ref{ledger-line-spanner-interface}.",