1 /*
2 This file is part of LilyPond, the GNU music typesetter.
3
4 Copyright (C) 1997--2021 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 "grob.hh"
21
22 #include "align-interface.hh"
23 #include "axis-group-interface.hh"
24 #include "input.hh"
25 #include "international.hh"
26 #include "item.hh"
27 #include "misc.hh"
28 #include "music.hh"
29 #include "output-def.hh"
30 #include "pointer-group-interface.hh"
31 #include "program-option.hh"
32 #include "stencil.hh"
33 #include "stream-event.hh"
34 #include "system.hh"
35 #include "unpure-pure-container.hh"
36 #include "warn.hh"
37 #include "lily-imports.hh"
38
39 #include <cstring>
40 #include <set>
41 #include <unordered_set>
42
43 using std::set;
44 using std::string;
45 using std::vector;
46
Grob(SCM basicprops)47 Grob::Grob (SCM basicprops)
48 {
49
50 /* FIXME: default should be no callback. */
51 layout_ = 0;
52 original_ = 0;
53 interfaces_ = SCM_EOL;
54 immutable_property_alist_ = basicprops;
55 mutable_property_alist_ = SCM_EOL;
56 object_alist_ = SCM_EOL;
57
58 /* We do smobify_self () as the first step. Since the object lives
59 on the heap, none of its SCM variables are protected from
60 GC. After smobify_self (), they are. */
61 smobify_self ();
62
63 SCM meta = get_property (this, "meta");
64 if (scm_is_pair (meta))
65 {
66 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
67
68 SCM object_cbs = scm_assq (ly_symbol2scm ("object-callbacks"), meta);
69 if (scm_is_pair (object_cbs))
70 {
71 for (SCM s = scm_cdr (object_cbs); scm_is_pair (s); s = scm_cdr (s))
72 set_object (this, scm_caar (s), scm_cdar (s));
73 }
74 }
75
76 if (scm_is_null (get_property_data (this, "X-extent")))
77 set_property (this, "X-extent", Grob::stencil_width_proc);
78 if (scm_is_null (get_property_data (this, "Y-extent")))
79 set_property (this, "Y-extent",
80 Unpure_pure_container::make_smob (Grob::stencil_height_proc,
81 Grob::pure_stencil_height_proc));
82 if (scm_is_null (get_property_data (this, "vertical-skylines")))
83 set_property (this, "vertical-skylines",
84 Unpure_pure_container::make_smob (Grob::simple_vertical_skylines_from_extents_proc,
85 Grob::pure_simple_vertical_skylines_from_extents_proc));
86 if (scm_is_null (get_property_data (this, "horizontal-skylines")))
87 set_property (this, "horizontal-skylines",
88 Unpure_pure_container::make_smob (Grob::simple_horizontal_skylines_from_extents_proc,
89 Grob::pure_simple_horizontal_skylines_from_extents_proc));
90 }
91
Grob(Grob const & s)92 Grob::Grob (Grob const &s)
93 : Smob<Grob> ()
94 {
95 original_ = const_cast<Grob *> (&s);
96
97 immutable_property_alist_ = s.immutable_property_alist_;
98 mutable_property_alist_ = SCM_EOL;
99
100 for (const auto a : {X_AXIS, Y_AXIS})
101 dim_cache_ [a] = s.dim_cache_ [a];
102
103 interfaces_ = s.interfaces_;
104 object_alist_ = SCM_EOL;
105
106 layout_ = 0;
107
108 smobify_self ();
109
110 mutable_property_alist_ = ly_alist_copy (s.mutable_property_alist_);
111 }
112
~Grob()113 Grob::~Grob ()
114 {
115 }
116 /****************************************************************
117 STENCILS
118 ****************************************************************/
119
120 const Stencil *
get_stencil() const121 Grob::get_stencil () const
122 {
123 if (!is_live ())
124 return 0;
125
126 SCM stil = get_property (this, "stencil");
127 return unsmob<const Stencil> (stil);
128 }
129
130 Stencil
get_print_stencil() const131 Grob::get_print_stencil () const
132 {
133 SCM stil = get_property (this, "stencil");
134
135 Stencil retval;
136 if (auto *m = unsmob<const Stencil> (stil))
137 {
138 retval = *m;
139 bool transparent = from_scm<bool> (get_property (this, "transparent"));
140
141 /* Process whiteout before color and grob-cause to prevent colored */
142 /* whiteout background and larger file sizes with \pointAndClickOn. */
143 /* A grob has to be visible, otherwise the whiteout property has no effect. */
144 /* Calls the scheme procedure stencil-whiteout in scm/stencils.scm */
145 if (!transparent && (scm_is_number (get_property (this, "whiteout"))
146 || from_scm<bool> (get_property (this, "whiteout"))))
147 {
148 Real line_thickness = layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
149 retval = *unsmob<const Stencil>
150 (Lily::stencil_whiteout (retval.smobbed_copy (),
151 get_property (this, "whiteout-style"),
152 get_property (this, "whiteout"),
153 to_scm (line_thickness)));
154 }
155
156 if (transparent)
157 retval = Stencil (m->extent_box (), SCM_EOL);
158 else
159 {
160 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
161 self_scm (),
162 retval.expr ());
163
164 retval = Stencil (retval.extent_box (), expr);
165 }
166
167 SCM rot = get_property (this, "rotation");
168 if (scm_is_pair (rot))
169 {
170 Real angle = scm_to_double (scm_car (rot));
171 Real x = scm_to_double (scm_cadr (rot));
172 Real y = scm_to_double (scm_caddr (rot));
173
174 retval.rotate_degrees (angle, Offset (x, y));
175 }
176
177 /* color support... see interpret_stencil_expression () for more... */
178 SCM color = get_property (this, "color");
179 if (scm_is_string (color))
180 {
181 retval = retval.in_color (ly_scm2string (color));
182 }
183 else if (scm_is_pair (color))
184 {
185 retval = retval.in_color (from_scm<Real> (scm_car (color)),
186 from_scm<Real> (scm_cadr (color)),
187 from_scm<Real> (scm_caddr (color)),
188 scm_is_pair (scm_cdddr (color))
189 ? from_scm<Real> (scm_cadddr (color))
190 : 1.0);
191 }
192
193 SCM attributes = get_property (this, "output-attributes");
194 if (scm_is_pair (attributes))
195 {
196 SCM expr = scm_list_3 (ly_symbol2scm ("output-attributes"),
197 attributes,
198 retval.expr ());
199
200 retval = Stencil (retval.extent_box (), expr);
201 }
202
203 }
204
205 return retval;
206 }
207
208 /****************************************************************
209 VIRTUAL STUBS
210 ****************************************************************/
211 void
do_break_processing()212 Grob::do_break_processing ()
213 {
214 }
215
216 void
break_breakable_item(System *)217 Grob::break_breakable_item (System *)
218 {
219 }
220
221 System *
get_system() const222 Grob::get_system () const
223 {
224 return 0;
225 }
226
227 /* This version of get_system is more reliable than this->get_system ()
228 before line-breaking has been done, at which point there is only
229 one system in the whole score and we can find it just by following
230 parent pointers. */
231 System *
get_system(Grob * me)232 Grob::get_system (Grob *me)
233 {
234 Grob *p = me->get_x_parent ();
235 return p ? get_system (p) : dynamic_cast<System *> (me);
236 }
237
238 void
handle_broken_dependencies()239 Grob::handle_broken_dependencies ()
240 {
241 Spanner *sp = dynamic_cast<Spanner *> (this);
242 if (original () && sp)
243 return;
244
245 if (sp)
246 /* THIS, SP is the original spanner. We use a special function
247 because some Spanners have enormously long lists in their
248 properties, and a special function fixes FOO */
249 {
250 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
251 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
252 }
253 System *system = get_system ();
254
255 if (is_live ()
256 && system
257 && common_refpoint (system, X_AXIS)
258 && common_refpoint (system, Y_AXIS))
259 substitute_object_links (system, object_alist_);
260 else if (dynamic_cast<System *> (this))
261 substitute_object_links (nullptr, object_alist_);
262 else
263 /* THIS element is `invalid'; it has been removed from all
264 dependencies, so let's junk the element itself.
265
266 Do not do this for System, since that would remove references
267 to the originals of score-grobs, which get then GC'd (a bad
268 thing). */
269 suicide ();
270 }
271
272 /* Note that we still want references to this element to be
273 rearranged, and not silently thrown away, so we keep pointers like
274 {broken_into_{drul, array}, original}
275 */
276 void
suicide()277 Grob::suicide ()
278 {
279 if (!is_live ())
280 return;
281
282 for (const auto a : {X_AXIS, Y_AXIS})
283 dim_cache_[a].clear ();
284
285 mutable_property_alist_ = SCM_EOL;
286 object_alist_ = SCM_EOL;
287 immutable_property_alist_ = SCM_EOL;
288 interfaces_ = SCM_EOL;
289 }
290
291 void
handle_prebroken_dependencies()292 Grob::handle_prebroken_dependencies ()
293 {
294 /* Don't do this in the derived method, since we want to keep access to
295 object_alist_ centralized. */
296 if (original ())
297 {
298 Item *it = dynamic_cast<Item *> (this);
299 substitute_object_links (it->break_status_dir (),
300 original ()->object_alist_);
301 }
302 }
303
304 Grob *
find_broken_piece(System *) const305 Grob::find_broken_piece (System *) const
306 {
307 return 0;
308 }
309
310 /****************************************************************
311 OFFSETS
312 ****************************************************************/
313
314 void
translate_axis(Real y,Axis a)315 Grob::translate_axis (Real y, Axis a)
316 {
317 if (!std::isfinite (y))
318 {
319 programming_error ("Infinity or NaN encountered");
320 return;
321 }
322
323 if (!dim_cache_[a].offset_)
324 dim_cache_[a].offset_ = y;
325 else
326 *dim_cache_[a].offset_ += y;
327 }
328
329 /* Find the offset on axis a relative to refp. */
330 Real
relative_coordinate(Grob const * refp,Axis a) const331 Grob::relative_coordinate (Grob const *refp, Axis a) const
332 {
333 // refp should really always be non-null, but this
334 // does not hold currently.
335 Real result = 0.0;
336 for (const Grob *ancestor = this;
337 ancestor != refp;
338 ancestor = ancestor->get_parent (a))
339 {
340 // !ancestor here means that we asked for a coordinate
341 // relative to something that is not a reference point.
342 // This shouldn't occur (issue #6149), but does at the moment.
343 if (!ancestor)
344 break;
345 result += ancestor->get_offset (a);
346 }
347 return result;
348 }
349
350 Real
parent_relative(Grob const * refp,Axis a) const351 Grob::parent_relative (Grob const *refp, Axis a) const
352 {
353 if (Grob *p = get_parent (a))
354 return p->relative_coordinate (refp, a);
355 return 0.0;
356 }
357
358 Real
pure_relative_y_coordinate(Grob const * refp,vsize start,vsize end)359 Grob::pure_relative_y_coordinate (Grob const *refp, vsize start, vsize end)
360 {
361 if (refp == this)
362 return 0.0;
363
364 Real off = 0;
365
366 if (dim_cache_[Y_AXIS].offset_)
367 {
368 if (from_scm<bool> (get_property (this, "pure-Y-offset-in-progress")))
369 programming_error ("cyclic chain in pure-Y-offset callbacks");
370
371 off = *dim_cache_[Y_AXIS].offset_;
372 }
373 else
374 {
375 SCM proc = get_property_data (this, "Y-offset");
376
377 dim_cache_[Y_AXIS].offset_ = 0;
378 set_property (this, "pure-Y-offset-in-progress", SCM_BOOL_T);
379 off = from_scm<double> (call_pure_function (proc,
380 scm_list_1 (self_scm ()),
381 start, end),
382 0.0);
383 del_property (this, "pure-Y-offset-in-progress");
384 dim_cache_[Y_AXIS].offset_.reset ();
385 }
386
387 /* we simulate positioning-done if we are the child of a VerticalAlignment,
388 but only if we don't have a cached offset. If we do have a cached offset,
389 it probably means that the Alignment was fixed and it has already been
390 calculated.
391 */
392 if (Grob *p = get_y_parent ())
393 {
394 Real trans = 0;
395 if (has_interface<Align_interface> (p) && !dim_cache_[Y_AXIS].offset_)
396 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
397
398 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
399 }
400 return off;
401 }
402
403 /* Invoke callbacks to get offset relative to parent. */
404 Real
get_offset(Axis a) const405 Grob::get_offset (Axis a) const
406 {
407 if (dim_cache_[a].offset_)
408 return *dim_cache_[a].offset_;
409
410 SCM sym = axis_offset_symbol (a);
411 dim_cache_[a].offset_ = 0;
412
413 /*
414 UGH: can't fold next 2 statements together. Apparently GCC thinks
415 dim_cache_[a].offset_ is unaliased.
416 */
417 Real off = from_scm<double> (get_property (this, sym), 0.0);
418 if (dim_cache_[a].offset_)
419 {
420 *dim_cache_[a].offset_ += off;
421 del_property (const_cast<Grob *> (this), sym);
422 return *dim_cache_[a].offset_;
423 }
424 else
425 return 0.0;
426 }
427
428 Real
maybe_pure_coordinate(Grob const * refp,Axis a,bool pure,vsize start,vsize end)429 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure,
430 vsize start, vsize end)
431 {
432 if (pure && a != Y_AXIS)
433 programming_error ("tried to get pure X-offset");
434 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
435 : relative_coordinate (refp, a);
436 }
437
438 /****************************************************************
439 extents
440 ****************************************************************/
441
442 void
flush_extent_cache(Axis axis)443 Grob::flush_extent_cache (Axis axis)
444 {
445 if (dim_cache_[axis].extent_)
446 {
447 /*
448 Ugh, this is not accurate; will flush property, causing
449 callback to be called if.
450 */
451 if (axis == X_AXIS)
452 del_property (this, "X-extent");
453 else
454 del_property (this, "Y-extent");
455 dim_cache_[axis].extent_.reset ();
456 if (get_parent (axis))
457 get_parent (axis)->flush_extent_cache (axis);
458 }
459 }
460
461 Interval
extent(Grob const * refp,Axis a) const462 Grob::extent (Grob const *refp, Axis a) const
463 {
464 Real offset = relative_coordinate (refp, a);
465 Interval real_ext;
466 if (dim_cache_[a].extent_)
467 {
468 real_ext = *dim_cache_[a].extent_;
469 }
470 else
471 {
472 /*
473 Order is significant: ?-extent may trigger suicide.
474 */
475 SCM ext = (a == X_AXIS)
476 ? get_property (this, "X-extent")
477 : get_property (this, "Y-extent");
478 if (is_number_pair (ext))
479 real_ext.unite (from_scm<Interval> (ext));
480
481 SCM min_ext = (a == X_AXIS)
482 ? get_property (this, "minimum-X-extent")
483 : get_property (this, "minimum-Y-extent");
484 if (is_number_pair (min_ext))
485 real_ext.unite (from_scm<Interval> (min_ext));
486
487 dim_cache_[a].extent_ = real_ext;
488 }
489
490 // We never want nan, so we avoid shifting infinite values.
491 if (!std::isinf (offset))
492 real_ext.translate (offset);
493 else
494 warning (_f ("ignored infinite %s-offset",
495 a == X_AXIS ? "X" : "Y"));
496
497 return real_ext;
498 }
499
500 Interval
pure_y_extent(Grob * refp,vsize start,vsize end)501 Grob::pure_y_extent (Grob *refp, vsize start, vsize end)
502 {
503 SCM iv_scm = get_pure_property (this, "Y-extent", start, end);
504 Interval iv = from_scm (iv_scm, Interval ());
505 Real offset = pure_relative_y_coordinate (refp, start, end);
506
507 SCM min_ext = get_property (this, "minimum-Y-extent");
508
509 /* we don't add minimum-Y-extent if the extent is empty. This solves
510 a problem with Hara-kiri spanners. They would request_suicide and
511 return empty extents, but we would force them here to be large. */
512 if (!iv.is_empty () && is_number_pair (min_ext))
513 iv.unite (from_scm<Interval> (min_ext));
514
515 if (!iv.is_empty ())
516 iv.translate (offset);
517 return iv;
518 }
519
520 Interval
maybe_pure_extent(Grob * refp,Axis a,bool pure,vsize start,vsize end)521 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, vsize start, vsize end)
522 {
523 return (pure && a == Y_AXIS) ? pure_y_extent (refp, start, end) : extent (refp, a);
524 }
525
526 /* Sort grobs according to their starting column. */
527 bool
less(Grob * g1,Grob * g2)528 Grob::less (Grob *g1, Grob *g2)
529 {
530 return g1->spanned_rank_interval ()[LEFT] < g2->spanned_rank_interval ()[LEFT];
531 }
532
533 /****************************************************************
534 REFPOINTS
535 ****************************************************************/
536
537 /* Find the group-element which has both #this# and #s# */
538 Grob *
common_refpoint(Grob const * s,Axis a) const539 Grob::common_refpoint (Grob const *s, Axis a) const
540 {
541
542 /* Catching the trivial cases is likely costlier than just running
543 through: one can't avoid going to the respective chain ends
544 anyway. We might save the second run through when the chain ends
545 differ, but keeping track of the ends makes the loop more costly.
546 */
547
548 int balance = 0;
549 Grob const *c;
550 Grob const *d;
551
552 for (c = this; c; ++balance)
553 c = c->dim_cache_[a].parent_;
554
555 for (d = s; d; --balance)
556 d = d->dim_cache_[a].parent_;
557
558 /* Cut down ancestry to same size */
559
560 for (c = this; balance > 0; --balance)
561 c = c->dim_cache_[a].parent_;
562
563 for (d = s; balance < 0; ++balance)
564 d = d->dim_cache_[a].parent_;
565
566 /* Now find point where our lineages converge */
567 while (c != d)
568 {
569 c = c->dim_cache_[a].parent_;
570 d = d->dim_cache_[a].parent_;
571 }
572
573 return const_cast<Grob *> (c);
574 }
575
576 void
fixup_refpoint()577 Grob::fixup_refpoint ()
578 {
579 for (const auto ax : {X_AXIS, Y_AXIS})
580 {
581 Grob *parent = get_parent (ax);
582
583 if (!parent)
584 continue;
585
586 if (parent->get_system () != get_system () && get_system ())
587 {
588 Grob *newparent = parent->find_broken_piece (get_system ());
589 set_parent (newparent, ax);
590 }
591
592 if (Item *i = dynamic_cast<Item *> (this))
593 {
594 Item *parenti = dynamic_cast<Item *> (parent);
595
596 if (parenti && i)
597 {
598 Direction my_dir = i->break_status_dir ();
599 if (my_dir != parenti->break_status_dir ())
600 {
601 Item *newparent = parenti->find_prebroken_piece (my_dir);
602 set_parent (newparent, ax);
603 }
604 }
605 }
606 }
607 }
608
609 // Is possible_ancestor a parent, or parent of parent, etc. of this
610 // on axis a?
611 bool
has_in_ancestry(const Grob * possible_ancestor,Axis a) const612 Grob::has_in_ancestry (const Grob *possible_ancestor, Axis a) const
613 {
614 for (const Grob *parent = this; parent; parent = parent->get_parent (a))
615 {
616 if (parent == possible_ancestor)
617 return true;
618 }
619 return false;
620 }
621
622 /****************************************************************
623 VERTICAL ORDERING
624 ****************************************************************/
625
626 Grob *
get_maybe_root_vertical_alignment(Grob * g,Grob * maybe)627 get_maybe_root_vertical_alignment (Grob *g, Grob *maybe)
628 {
629 if (!g)
630 return maybe;
631 if (has_interface<Align_interface> (g))
632 return get_maybe_root_vertical_alignment (g->get_y_parent (), g);
633 return get_maybe_root_vertical_alignment (g->get_y_parent (), maybe);
634
635 }
636
637 Grob *
get_root_vertical_alignment(Grob * g)638 Grob::get_root_vertical_alignment (Grob *g)
639 {
640 return get_maybe_root_vertical_alignment (g, 0);
641 }
642
643 Grob *
get_vertical_axis_group(Grob * g)644 Grob::get_vertical_axis_group (Grob *g)
645 {
646 if (!g)
647 return 0;
648 if (!g->get_y_parent ())
649 return 0;
650 if (has_interface<Axis_group_interface> (g)
651 && has_interface<Align_interface> (g->get_y_parent ()))
652 return g;
653 return get_vertical_axis_group (g->get_y_parent ());
654
655 }
656
657 int
get_vertical_axis_group_index(Grob * g)658 Grob::get_vertical_axis_group_index (Grob *g)
659 {
660 Grob *val = get_root_vertical_alignment (g);
661 if (!val)
662 return -1;
663 Grob *vax = get_vertical_axis_group (g);
664 extract_grob_set (val, "elements", elts);
665 for (vsize i = 0; i < elts.size (); i++)
666 if (elts[i] == vax)
667 return static_cast<int> (i);
668 g->programming_error ("could not find this grob's vertical axis group in the vertical alignment");
669 return -1;
670 }
671
672 bool
vertical_less(Grob * g1,Grob * g2)673 Grob::vertical_less (Grob *g1, Grob *g2)
674 {
675 return internal_vertical_less (g1, g2, false);
676 }
677
678 bool
pure_vertical_less(Grob * g1,Grob * g2)679 Grob::pure_vertical_less (Grob *g1, Grob *g2)
680 {
681 return internal_vertical_less (g1, g2, true);
682 }
683
684 bool
internal_vertical_less(Grob * g1,Grob * g2,bool pure)685 Grob::internal_vertical_less (Grob *g1, Grob *g2, bool pure)
686 {
687 Grob *vag = get_root_vertical_alignment (g1);
688 if (!vag)
689 {
690 g1->programming_error ("grob does not belong to a VerticalAlignment?");
691 return false;
692 }
693
694 Grob *ag1 = get_vertical_axis_group (g1);
695 Grob *ag2 = get_vertical_axis_group (g2);
696
697 extract_grob_set (vag, "elements", elts);
698
699 if (ag1 == ag2 && !pure)
700 {
701 Grob *common = g1->common_refpoint (g2, Y_AXIS);
702 return g1->relative_coordinate (common, Y_AXIS) > g2->relative_coordinate (common, Y_AXIS);
703 }
704
705 for (vsize i = 0; i < elts.size (); i++)
706 {
707 if (elts[i] == ag1)
708 return true;
709 if (elts[i] == ag2)
710 return false;
711 }
712
713 g1->programming_error ("could not place this grob in its axis group");
714 return false;
715 }
716
717 /****************************************************************
718 CAUSES
719 ****************************************************************/
720 Stream_event *
event_cause() const721 Grob::event_cause () const
722 {
723 SCM cause = get_property (this, "cause");
724 return unsmob<Stream_event> (cause);
725 }
726
727 Stream_event *
ultimate_event_cause() const728 Grob::ultimate_event_cause () const
729 {
730 SCM cause = get_property (this, "cause");
731 while (Grob *g = unsmob<Grob> (cause))
732 {
733 cause = get_property (g, "cause");
734 }
735 return unsmob<Stream_event> (cause);
736 }
737
738 /****************************************************************
739 MESSAGES
740 ****************************************************************/
741 Input *
origin() const742 Grob::origin () const
743 {
744 if (Stream_event *ev = ultimate_event_cause ())
745 return ev->origin ();
746 return nullptr;
747 }
748
749 string
name() const750 Grob::name () const
751 {
752 SCM meta = get_property (this, "meta");
753 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
754 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
755 return scm_is_symbol (nm) ? ly_symbol2string (nm) : class_name ();
756 }
757
758 ADD_INTERFACE (Grob,
759 "A grob represents a piece of music notation.\n"
760 "\n"
761 "All grobs have an X and Y@tie{}position on the page. These"
762 " X and Y@tie{}positions are stored in a relative format, thus"
763 " they can easily be combined by stacking them, hanging one"
764 " grob to the side of another, or coupling them into grouping"
765 " objects.\n"
766 "\n"
767 "Each grob has a reference point (a.k.a.@: parent): The"
768 " position of a grob is stored relative to that reference"
769 " point. For example, the X@tie{}reference point of a staccato"
770 " dot usually is the note head that it applies to. When the"
771 " note head is moved, the staccato dot moves along"
772 " automatically.\n"
773 "\n"
774 "A grob is often associated with a symbol, but some grobs do"
775 " not print any symbols. They take care of grouping objects."
776 " For example, there is a separate grob that stacks staves"
777 " vertically. The @ref{NoteCollision} object is also an"
778 " abstract grob: It only moves around chords, but doesn't print"
779 " anything.\n"
780 "\n"
781 "Grobs have properties (Scheme variables) that can be read and"
782 " set. Two types of them exist: immutable and mutable."
783 " Immutable variables define the default style and behavior."
784 " They are shared between many objects. They can be changed"
785 " using @code{\\override} and @code{\\revert}. Mutable"
786 " properties are variables that are specific to one grob."
787 " Typically, lists of other objects, or results from"
788 " computations are stored in mutable properties. In"
789 " particular, every call to @code{ly:grob-set-property!}"
790 " (or its C++ equivalent) sets a mutable property.\n"
791 "\n"
792 "The properties @code{after-line-breaking} and"
793 " @code{before-line-breaking} are dummies that are not"
794 " user-serviceable.",
795
796 /* properties */
797 "X-extent "
798 "X-offset "
799 "Y-extent "
800 "Y-offset "
801 "after-line-breaking "
802 "avoid-slur "
803 "axis-group-parent-X "
804 "axis-group-parent-Y "
805 "before-line-breaking "
806 "cause "
807 "color "
808 "cross-staff "
809 "extra-offset "
810 "footnote-music "
811 "forced-spacing "
812 "horizontal-skylines "
813 "id "
814 "interfaces "
815 "layer "
816 "meta "
817 "minimum-X-extent "
818 "minimum-Y-extent "
819 "output-attributes "
820 "parenthesis-id "
821 "parenthesis-friends "
822 "parenthesized "
823 "pure-Y-offset-in-progress "
824 "rotation "
825 "skyline-horizontal-padding "
826 "springs-and-rods "
827 "staff-symbol "
828 "stencil "
829 "transparent "
830 "vertical-skylines "
831 "whiteout "
832 "whiteout-style "
833 );
834
835 /****************************************************************
836 CALLBACKS
837 ****************************************************************/
838
839 static SCM
grob_stencil_extent(Grob * me,Axis a)840 grob_stencil_extent (Grob *me, Axis a)
841 {
842 Interval e;
843 if (auto *m = me->get_stencil ())
844 e = m->extent (a);
845 return to_scm (e);
846 }
847
848 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
849 SCM
stencil_height(SCM smob)850 Grob::stencil_height (SCM smob)
851 {
852 Grob *me = unsmob<Grob> (smob);
853 return grob_stencil_extent (me, Y_AXIS);
854 }
855
856 MAKE_SCHEME_CALLBACK (Grob, pure_stencil_height, 3);
857 SCM
pure_stencil_height(SCM smob,SCM,SCM)858 Grob::pure_stencil_height (SCM smob, SCM /* beg */, SCM /* end */)
859 {
860 Grob *me = unsmob<Grob> (smob);
861 if (unsmob<const Stencil> (get_property_data (me, "stencil")))
862 return grob_stencil_extent (me, Y_AXIS);
863
864 return to_scm (Interval ());
865
866 }
867
868 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
869 SCM
y_parent_positioning(SCM smob)870 Grob::y_parent_positioning (SCM smob)
871 {
872 Grob *me = unsmob<Grob> (smob);
873 Grob *par = me->get_y_parent ();
874 if (par)
875 (void) get_property (par, "positioning-done");
876
877 return to_scm (0.0);
878 }
879
880 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
881 SCM
x_parent_positioning(SCM smob)882 Grob::x_parent_positioning (SCM smob)
883 {
884 Grob *me = unsmob<Grob> (smob);
885
886 Grob *par = me->get_x_parent ();
887 if (par)
888 (void) get_property (par, "positioning-done");
889
890 return to_scm (0.0);
891 }
892
893 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
894 SCM
stencil_width(SCM smob)895 Grob::stencil_width (SCM smob)
896 {
897 Grob *me = unsmob<Grob> (smob);
898 return grob_stencil_extent (me, X_AXIS);
899 }
900
901 Grob *
common_refpoint_of_list(SCM elist,Grob * common,Axis a)902 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
903 {
904 for (; scm_is_pair (elist); elist = scm_cdr (elist))
905 if (Grob *s = unsmob<Grob> (scm_car (elist)))
906 {
907 if (common)
908 common = common->common_refpoint (s, a);
909 else
910 common = s;
911 }
912
913 return common;
914 }
915
916 Grob *
common_refpoint_of_array(vector<Grob * > const & arr,Grob * common,Axis a)917 common_refpoint_of_array (vector<Grob *> const &arr, Grob *common, Axis a)
918 {
919 for (vsize i = 0; i < arr.size (); i++)
920 if (common)
921 common = common->common_refpoint (arr[i], a);
922 else
923 common = arr[i];
924
925 return common;
926 }
927
928 Grob *
common_refpoint_of_array(set<Grob * > const & arr,Grob * common,Axis a)929 common_refpoint_of_array (set<Grob *> const &arr, Grob *common, Axis a)
930 {
931 set<Grob *>::iterator it;
932
933 for (it = arr.begin (); it != arr.end (); it++)
934 if (common)
935 common = common->common_refpoint (*it, a);
936 else
937 common = *it;
938
939 return common;
940 }
941
942 Interval
robust_relative_extent(Grob * me,Grob * refpoint,Axis a)943 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
944 {
945 Interval ext = me->extent (refpoint, a);
946 if (ext.is_empty ())
947 ext.add_point (me->relative_coordinate (refpoint, a));
948
949 return ext;
950 }
951
952 Interval
robust_relative_pure_y_extent(Grob * me,Grob * refpoint,vsize start,vsize end)953 robust_relative_pure_y_extent (Grob *me, Grob *refpoint, vsize start, vsize end)
954 {
955 Interval ext = me->pure_y_extent (refpoint, start, end);
956 if (ext.is_empty ())
957 ext.add_point (me->pure_relative_y_coordinate (refpoint, start, end));
958
959 return ext;
960 }
961
962 // Checks whether there is a vertical alignment in the chain of
963 // parents between this and commony.
964 bool
check_cross_staff(Grob * commony)965 Grob::check_cross_staff (Grob *commony)
966 {
967 if (has_interface<Align_interface> (commony))
968 return true;
969
970 for (Grob *g = this; g && g != commony; g = g->get_y_parent ())
971 if (has_interface<Align_interface> (g))
972 return true;
973
974 return false;
975 }
976
977 void
uniquify(vector<Grob * > & grobs)978 uniquify (vector <Grob *> &grobs)
979 {
980 std::unordered_set<Grob *> seen (grobs.size ());
981 vsize j = 0;
982 for (vsize i = 0; i < grobs.size (); i++)
983 {
984 if (seen.insert (grobs[i]).second)
985 {
986 grobs[j++] = grobs[i];
987 }
988 }
989
990 grobs.resize (j);
991 }
992