2 This file is part of LilyPond, the GNU music typesetter.
4 Copyright (C) 1997--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/>.
20 #include "paper-column.hh"
22 #include "axis-group-interface.hh"
23 #include "bar-line.hh"
24 #include "break-align-interface.hh"
25 #include "font-interface.hh"
26 #include "grob-array.hh"
29 #include "output-def.hh"
30 #include "paper-score.hh"
31 #include "pointer-group-interface.hh"
32 #include "rhythmic-head.hh"
33 #include "separation-item.hh"
34 #include "skyline-pair.hh"
35 #include "spaceable-grob.hh"
37 #include "string-convert.hh"
39 #include "text-interface.hh"
46 Paper_column::clone () const
48 return new Paper_column (*this);
52 Paper_column::do_break_processing ()
54 Item::do_break_processing ();
58 Paper_column::get_rank (Grob const *me)
60 return dynamic_cast<Paper_column const *> (me)->rank_;
64 Paper_column::set_rank (int rank)
70 Paper_column::get_system () const
76 Paper_column::set_system (System *s)
82 Paper_column::get_column () const
84 return (Paper_column *) (this);
87 Paper_column::Paper_column (SCM l)
94 Paper_column::Paper_column (Paper_column const &src)
102 Paper_column::compare (Grob *const &a,
105 return sign (dynamic_cast<Paper_column *> (a)->rank_
106 - dynamic_cast<Paper_column *> (b)->rank_);
110 Paper_column::less_than (Grob *const &a,
113 Paper_column *pa = dynamic_cast<Paper_column *> (a);
114 Paper_column *pb = dynamic_cast<Paper_column *> (b);
116 return pa->rank_ < pb->rank_;
120 Paper_column::when_mom (Grob *me)
122 SCM m = me->get_property ("when");
123 if (Moment *when = unsmob<Moment> (m))
129 Paper_column::is_musical (Grob *me)
131 SCM m = me->get_property ("shortest-starter-duration");
133 if (unsmob<Moment> (m))
134 s = *unsmob<Moment> (m);
135 return s != Moment (0);
139 Paper_column::is_used (Grob *me)
141 extract_grob_set (me, "elements", elts);
145 extract_grob_set (me, "bounded-by-me", bbm);
149 if (Paper_column::is_breakable (me))
152 if (to_boolean (me->get_property ("used")))
155 if (scm_is_pair (me->get_property ("labels")))
162 Paper_column::is_breakable (Grob *me)
164 return scm_is_symbol (me->get_property ("line-break-permission"));
168 Paper_column::minimum_distance (Grob *left, Grob *right)
170 Drul_array<Grob *> cols (left, right);
171 Drul_array<Skyline> skys = Drul_array<Skyline> (Skyline (RIGHT), Skyline (LEFT));
173 for (LEFT_and_RIGHT (d))
175 Skyline_pair *sp = unsmob<Skyline_pair> (cols[d]->get_property ("horizontal-skylines"));
180 skys[RIGHT].merge (Separation_item::conditional_skyline (right, left));
182 return max (0.0, skys[LEFT].distance (skys[RIGHT]));
186 Paper_column::break_align_width (Grob *me, SCM align_syms)
188 Grob *p = me->get_parent (X_AXIS);
190 if (scm_is_symbol (align_syms))
191 align_syms = scm_list_1 (align_syms);
195 me->programming_error ("tried to get break-align-width of a musical column");
196 return Interval (0, 0) + me->relative_coordinate (p, X_AXIS);
200 for (;!align && scm_is_pair (align_syms); align_syms = scm_cdr (align_syms))
202 SCM align_sym = scm_car (align_syms);
203 if (scm_is_eq (align_sym, ly_symbol2scm ("staff-bar"))
204 || scm_is_eq (align_sym, ly_symbol2scm ("break-alignment")))
205 align = Pointer_group_interface::find_grob
206 (me, ly_symbol2scm ("elements"),
207 (scm_is_eq (align_sym, ly_symbol2scm ("staff-bar"))
208 ? Bar_line::non_empty_barline
209 : has_interface<Break_alignment_interface>));
212 extract_grob_set (me, "elements", elts);
213 for (vsize i = 0; i < elts.size (); i++)
215 if (elts[i]->get_property ("break-align-symbol") == align_sym
216 && !elts[i]->extent (elts[i], X_AXIS).is_empty ())
226 return Interval (0, 0) + me->relative_coordinate (p, X_AXIS);
228 return align->extent (p, X_AXIS);
232 Loop through elements of a PaperColumn, find all grobs implementing specified
233 interface and return their combined extent.
236 Paper_column::get_interface_extent (Grob *column, SCM iface, Axis a)
238 Interval extent = Interval (0, 0);
239 extract_grob_set (column, "elements", elts);
241 for (vsize i = 0; i < elts.size (); i++)
242 if (elts[i]->internal_has_interface (iface))
243 extent.unite (robust_relative_extent (elts[i], elts[i], a));
253 - blue arrow representing ideal distance,
254 - red arrow representing minimum distance
255 to aid debugging. To turn this on, simply add
256 \override Score.PaperColumn #'stencil = #ly:paper-column::print
257 \override Score.NonMusicalPaperColumn #'stencil = #ly:paper-column::print
259 Also, as of 2013-10-16 there's a switch in Frescobaldi that turns this on.
261 MAKE_DOCUMENTED_SCHEME_CALLBACK (Paper_column, print, 1,
262 "Optional stencil for @code{PaperColumn} or"
263 "@code{NonMusicalPaperColumn}.\n"
264 "Draws the @code{rank number} of each column,"
265 " its moment in time, a blue arrow showing the"
266 " ideal distance, and a red arrow showing the"
267 " minimum distance between columns.");
269 Paper_column::print (SCM p)
271 Paper_column *me = unsmob<Paper_column> (p);
273 string r = ::to_string (Paper_column::get_rank (me));
275 Moment *mom = unsmob<Moment> (me->get_property ("when"));
276 string when = mom ? mom->to_string () : "?/?";
278 Font_metric *musfont = Font_interface::get_default_font (me);
279 SCM properties = Font_interface::text_font_alist_chain (me);
280 SCM scm_mol = Text_interface::interpret_markup (me->layout ()->self_scm (),
283 SCM when_mol = Text_interface::interpret_markup (me->layout ()->self_scm (),
285 ly_string2scm (when));
286 Stencil t = *unsmob<Stencil> (scm_mol);
288 t.add_at_edge (Y_AXIS, DOWN, *unsmob<Stencil> (when_mol), 0.1);
289 t.align_to (X_AXIS, LEFT);
290 // compensate for font serifs and half letter-distance
291 t.translate (Offset (-0.1, 0));
292 t.align_to (Y_AXIS, DOWN);
294 Stencil l = Lookup::filled_box (Box (Interval (0, 0.02),
297 Real small_pad = 0.15;
300 // number of printed arrows from *both* loops
303 for (SCM s = me->get_object ("ideal-distances");
304 scm_is_pair (s); s = scm_cdr (s))
306 Spring *sp = unsmob<Spring> (scm_caar (s));
307 if (!unsmob<Grob> (scm_cdar (s))
308 || !unsmob<Grob> (scm_cdar (s))->get_system ())
313 Stencil arrowhead (musfont->find_by_name ("arrowheads.open.01"));
314 // initial scaling; it will also scale with font-size.
315 arrowhead.scale (1, 1.66);
316 Real head_len = arrowhead.extent (X_AXIS).length ();
318 SCM stil = Text_interface::interpret_markup (me->layout ()->self_scm (),
320 ly_string2scm (String_convert::form_string ("%5.2lf", sp->distance ())));
321 Stencil *number_stc = unsmob<Stencil> (stil);
322 number_stc->scale (1, 1.1);
323 Real num_height = number_stc->extent (Y_AXIS).length ();
324 Real num_len = number_stc->extent (X_AXIS).length ();
325 number_stc->align_to (Y_AXIS, DOWN);
327 // arrow's y-coord relative to the top of l stencil:
329 y -= j * (num_height + small_pad + big_pad);
330 // horizontally center number on the arrow, excluding arrowhead.
331 Offset num_off = Offset ((sp->distance () - num_len - head_len) / 2,
335 pts.push_back (Offset (0, y));
337 Offset p2 (sp->distance (), y);
340 Stencil id_stencil = Lookup::points_to_line_stencil (0.1, pts);
341 id_stencil.add_stencil (arrowhead.translated (p2));
342 id_stencil.add_stencil (number_stc->translated (num_off));
343 // use a lighter shade of blue so it will remain legible on black background.
344 id_stencil = id_stencil.in_color (0.2, 0.4, 1);
345 l.add_stencil (id_stencil);
348 for (SCM s = me->get_object ("minimum-distances");
349 scm_is_pair (s); s = scm_cdr (s))
351 Real dist = scm_to_double (scm_cdar (s));
352 Grob *other = unsmob<Grob> (scm_caar (s));
353 if (!other || other->get_system () != me->get_system ())
358 Stencil arrowhead (musfont->find_by_name ("arrowheads.open.01"));
359 // initial scaling; it will also scale with font-size.
360 arrowhead.scale (1, 1.66);
361 Real head_len = arrowhead.extent (X_AXIS).length ();
363 SCM stil = Text_interface::interpret_markup (me->layout ()->self_scm (),
365 ly_string2scm (String_convert::form_string ("%5.2lf", dist)));
366 Stencil *number_stc = unsmob<Stencil> (stil);
367 number_stc->scale (1, 1.1);
368 Real num_height = number_stc->extent (Y_AXIS).length ();
369 Real num_len = number_stc->extent (X_AXIS).length ();
370 number_stc->align_to (Y_AXIS, UP);
372 // arrow's y-coord relative to the top of l stencil:
374 y -= j * (num_height + small_pad + big_pad);
375 // horizontally center number on the arrow, excluding arrowhead.
376 Offset num_off = Offset ((dist - num_len - head_len) / 2,
380 pts.push_back (Offset (0, y));
385 Stencil id_stencil = Lookup::points_to_line_stencil (0.1, pts);
386 id_stencil.add_stencil (arrowhead.translated (p2));
387 id_stencil.add_stencil (number_stc->translated (num_off));
388 // use a lighter shade of red so it will remain legible on black background.
389 id_stencil = id_stencil.in_color (1, 0.25, 0.25);
390 l.add_stencil (id_stencil);
393 return t.smobbed_copy ();
397 This is all too hairy. We use bounded-by-me to make sure that some
398 columns are kept "alive". Unfortunately, when spanners are suicided,
399 this falls apart again, because suicided spanners are still in
402 THIS IS BROKEN KLUDGE. WE SHOULD INVENT SOMETHING BETTER.
404 MAKE_SCHEME_CALLBACK (Paper_column, before_line_breaking, 1);
406 Paper_column::before_line_breaking (SCM grob)
408 Grob *me = unsmob<Grob> (grob);
410 SCM bbm = me->get_object ("bounded-by-me");
411 Grob_array *ga = unsmob<Grob_array> (bbm);
413 return SCM_UNSPECIFIED;
415 vector<Grob *> &array (ga->array_reference ());
417 for (vsize i = array.size (); i--;)
421 if (!g || !g->is_live ())
422 /* UGH . potentially quadratic. */
423 array.erase (array.begin () + i);
426 return SCM_UNSPECIFIED;
429 /* FIXME: This is a hack that we use to identify columns that used to
430 contain note-heads but whose note-heads were moved by one of the ligature
431 engravers. Once the ligature engravers are fixed to behave nicely, this
432 function can be removed.
435 Paper_column::is_extraneous_column_from_ligature (Grob *me)
437 if (!is_musical (me))
440 // If all the note-heads that I think are my children actually belong
441 // to another column, then I am extraneous.
442 extract_grob_set (me, "elements", elts);
443 bool has_notehead = false;
444 for (vsize i = 0; i < elts.size (); i++)
446 if (has_interface<Rhythmic_head> (elts[i]))
449 if (dynamic_cast<Item *> (elts[i])->get_column () == me)
456 ADD_INTERFACE (Paper_column,
457 "@code{Paper_column} objects form the top-most X@tie{}parents"
458 " for items. There are two types of columns: musical and"
459 " non-musical, to which musical and non-musical objects are"
460 " attached respectively. The spacing engine determines the"
461 " X@tie{}positions of these objects.\n"
463 "They are numbered, the first (leftmost) is column@tie{}0."
464 " Numbering happens before line breaking, and columns are not"
465 " renumbered after line breaking. Since many columns go"
466 " unused, you should only use the rank field to get ordering"
467 " information. Two adjacent columns may have non-adjacent"
473 "full-measure-extra-space "
476 "line-break-system-details "
477 "line-break-penalty "
478 "line-break-permission "
480 "page-break-penalty "
481 "page-break-permission "
483 "page-turn-permission "
485 "shortest-playing-duration "
486 "shortest-starter-duration "