]> git.donarmstrong.com Git - lilypond.git/blob - lily/ledger-line-spanner.cc
Web-ja: update introduction
[lilypond.git] / lily / ledger-line-spanner.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 2004--2015 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 };
36
37 static void
38 set_rods (Drul_array<Interval> const &current_extents,
39           Drul_array<Interval> const &previous_extents,
40           Item *current_column,
41           Item *previous_column,
42           Real min_length)
43 {
44   for (UP_and_DOWN (d))
45     {
46       if (!current_extents[d].is_empty ()
47           && !previous_extents[d].is_empty ())
48         {
49           Rod rod;
50           rod.distance_ = 2 * min_length
51                           /*
52                             we go from right to left.
53                           */
54                           - previous_extents[d][LEFT]
55                           + current_extents[d][RIGHT];
56
57           rod.item_drul_[LEFT] = current_column;
58           rod.item_drul_[RIGHT] = previous_column;
59           rod.add_to_cols ();
60         }
61     }
62 }
63
64 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, set_spacing_rods, 1);
65 SCM
66 Ledger_line_spanner::set_spacing_rods (SCM smob)
67 {
68   Spanner *me = unsmob<Spanner> (smob);
69
70   // find size of note heads.
71   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
72   if (!staff)
73     {
74       me->suicide ();
75       return SCM_EOL;
76     }
77
78   Real min_length_fraction
79     = robust_scm2double (me->get_property ("minimum-length-fraction"), 0.15);
80
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;
86
87   Real halfspace = Staff_symbol::staff_space (staff) / 2;
88
89   Interval staff_extent = staff->extent (staff, Y_AXIS);
90   staff_extent *= 1 / halfspace;
91
92   /*
93     Run through heads using a loop. Since Ledger_line_spanner can
94     contain a lot of noteheads, superlinear performance is too slow.
95   */
96   extract_item_set (me, "note-heads", heads);
97   for (vsize i = heads.size (); i--;)
98     {
99       Item *h = heads[i];
100
101       int pos = Staff_symbol_referencer::get_rounded_position (h);
102       if  (Staff_symbol::ledger_positions (staff, pos).empty ())
103         continue;
104
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")))
108         continue;
109
110       Item *column = h->get_column ();
111       if (current_column != column)
112         {
113           set_rods (current_extents, previous_extents,
114                     current_column, previous_column,
115                     current_head_width * min_length_fraction);
116
117           previous_column = current_column;
118           current_column = column;
119           previous_extents = current_extents;
120
121           current_extents[DOWN].set_empty ();
122           current_extents[UP].set_empty ();
123           current_head_width = 0.0;
124         }
125
126       Interval head_extent = h->extent (column, X_AXIS);
127       Direction vdir = Direction (sign (pos));
128       if (!vdir)
129         continue;
130
131       current_extents[vdir].unite (head_extent);
132       current_head_width = max (current_head_width, head_extent.length ());
133     }
134
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);
139
140   return SCM_UNSPECIFIED;
141 }
142
143 struct Head_data
144 {
145   int position_;
146   vector<Real> ledger_positions_;
147   Interval head_extent_;
148   Interval ledger_extent_;
149   Interval accidental_extent_;
150   Head_data ()
151   {
152     head_extent_.set_empty ();
153     ledger_extent_.set_empty ();
154     accidental_extent_.set_empty ();
155   }
156 };
157
158 struct Ledger_request
159 {
160   Interval max_ledger_extent_;
161   Interval max_head_extent_;
162   int max_position_;
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_;
167   Ledger_request ()
168   {
169     max_ledger_extent_.set_empty ();
170     max_head_extent_.set_empty ();
171     max_position_ = 0;
172   }
173 };
174
175 typedef map < int, Drul_array<Ledger_request> > Ledger_requests;
176
177 /*
178   TODO: ledger share a lot of info. Lots of room to optimize away
179   common use of objects/variables.
180 */
181 MAKE_SCHEME_CALLBACK (Ledger_line_spanner, print, 1);
182 SCM
183 Ledger_line_spanner::print (SCM smob)
184 {
185   Spanner *me = unsmob<Spanner> (smob);
186
187   // Generate ledger requests from note head properties, etc.
188   extract_grob_set (me, "note-heads", heads);
189
190   if (heads.empty ())
191     return SCM_EOL;
192
193   Grob *staff = Staff_symbol_referencer::get_staff_symbol (me);
194   if (!staff)
195     return SCM_EOL;
196
197   Real halfspace = Staff_symbol::staff_space (staff) / 2;
198
199   Interval staff_extent = staff->extent (staff, Y_AXIS);
200   staff_extent *= 1 / halfspace;
201
202   Real length_fraction
203     = robust_scm2double (me->get_property ("length-fraction"), 0.25);
204
205   Grob *common_x = common_refpoint_of_array (heads, me, X_AXIS);
206   for (vsize i = heads.size (); i--;)
207     {
208       if (Grob *g = unsmob<Grob> (heads[i]->get_object ("accidental-grob")))
209         common_x = common_x->common_refpoint (g, X_AXIS);
210     }
211
212   Ledger_requests reqs;
213   for (vsize i = heads.size (); i--;)
214     {
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);
219
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))
224         {
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 ());
228
229           Direction vdir = Direction (sign (pos != 0 ? pos : 1));
230           int rank = h->get_column ()->get_rank ();
231
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_,
236                           vdir * pos);
237           Head_data hd;
238           hd.position_ = pos;
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);
245         }
246     }
247
248   if (reqs.size () == 0)
249     return SCM_EOL;
250
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++)
258     {
259       if (last == reqs.end ())
260         continue;
261
262       for (DOWN_and_UP (d))
263         {
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_))
268             {
269               // Midpoint between the furthest bounds of the two heads.
270               Real center
271                 = (last->second[d].max_head_extent_[RIGHT]
272                    + i->second[d].max_head_extent_[LEFT]) / 2;
273
274               // Do both reqs have notes further than the first space
275               // beyond the staff?
276               // (due tilt of quarter note-heads)
277               /* FIXME */
278               bool both
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_)));
283
284               for (LEFT_and_RIGHT (which))
285                 {
286                   Ledger_request &lr = ((which == LEFT) ? * last : *i).second[d];
287
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],
291                                    which * limit);
292                 }
293             }
294         }
295     }
296
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++)
302     {
303       for (DOWN_and_UP (d))
304         {
305           Ledger_request &lr = i->second[d];
306           for (vsize h = 0; h < lr.heads_.size (); h++)
307             {
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_;
312
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_);
317
318               // Iterate through the ledgers for a given note head.
319               for (vsize l = 0; l < ledger_posns.size (); l++)
320                 {
321                   Real lpos = ledger_posns[l];
322                   Interval x_extent = ledger_size;
323
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 ())
327                     {
328                       Real dist
329                         = linear_combination (Drul_array<Real> (acc_extent[RIGHT],
330                                                                 head_size[LEFT]),
331                                               0.0);
332
333                       Real left_shorten = max (-ledger_size[LEFT] + dist, 0.0);
334                       x_extent[LEFT] += left_shorten;
335                       /*
336                         TODO: shorten 2 ledger lines for the case
337                         natural + downstem.
338                       */
339                     }
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.
344
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);
348                   else
349                     {
350                       vector<Interval> &extents = lr.ledger_extents_.find (lpos)->second;
351                       for (vsize e = 0; e < extents.size (); e++)
352                         {
353                           if (intersection (extents[e], x_extent).is_empty ())
354                             extents.push_back (x_extent);
355                           else
356                             extents[e].unite (x_extent);
357                         }
358                     }
359                 }
360             }
361         }
362     }
363
364   // Create the stencil for the ledger line spanner by iterating
365   // through the ledger requests and their data on ledger extents.
366   Stencil ledgers;
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);
370
371   for (Ledger_requests::iterator i (reqs.begin ()); i != reqs.end (); i++)
372     {
373       for (DOWN_and_UP (d))
374         {
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++)
378             {
379               Real lpos = k->first;
380               vector<Interval> &x_extents = k->second;
381
382               for (vsize n = 0; n < x_extents.size (); n++)
383                 {
384                   // thickness (ledger line thickness) is the blot diameter
385                   Stencil line = Lookup::round_filled_box (Box (x_extents[n], y_extent),
386                                                            thickness);
387
388                   line.translate_axis (lpos * halfspace, Y_AXIS);
389                   ledgers.add_stencil (line);
390                 }
391             }
392         }
393     }
394   ledgers.translate_axis (-me->relative_coordinate (common_x, X_AXIS), X_AXIS);
395   return ledgers.smobbed_copy ();
396 }
397
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.",
404
405                /* properties */
406                "gap "
407                "length-fraction "
408                "minimum-length-fraction "
409                "note-heads "
410               );
411
412 struct Ledgered_interface
413 {
414 };
415
416 ADD_INTERFACE (Ledgered_interface,
417                "Objects that need ledger lines, typically note heads.  See"
418                " also @ref{ledger-line-spanner-interface}.",
419
420                /* properties */
421                "no-ledgers "
422               );