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/>.
25 #include "align-interface.hh"
26 #include "axis-group-interface.hh"
28 #include "international.hh"
33 #include "output-def.hh"
34 #include "pointer-group-interface.hh"
35 #include "program-option.hh"
37 #include "stream-event.hh"
39 #include "unpure-pure-container.hh"
41 #include "lily-imports.hh"
47 return new Grob (*this);
50 Grob::Grob (SCM basicprops)
53 /* FIXME: default should be no callback. */
56 interfaces_ = SCM_EOL;
57 immutable_property_alist_ = basicprops;
58 mutable_property_alist_ = SCM_EOL;
59 object_alist_ = SCM_EOL;
61 /* We do smobify_self () as the first step. Since the object lives
62 on the heap, none of its SCM variables are protected from
63 GC. After smobify_self (), they are. */
66 SCM meta = get_property ("meta");
67 if (scm_is_pair (meta))
69 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
71 SCM object_cbs = scm_assq (ly_symbol2scm ("object-callbacks"), meta);
72 if (scm_is_pair (object_cbs))
74 for (SCM s = scm_cdr (object_cbs); scm_is_pair (s); s = scm_cdr (s))
75 set_object (scm_caar (s), scm_cdar (s));
79 if (scm_is_null (get_property_data ("X-extent")))
80 set_property ("X-extent", Grob::stencil_width_proc);
81 if (scm_is_null (get_property_data ("Y-extent")))
82 set_property ("Y-extent",
83 Unpure_pure_container::make_smob (Grob::stencil_height_proc,
84 Grob::pure_stencil_height_proc));
85 if (scm_is_null (get_property_data ("vertical-skylines")))
86 set_property ("vertical-skylines",
87 Unpure_pure_container::make_smob (Grob::simple_vertical_skylines_from_extents_proc,
88 Grob::pure_simple_vertical_skylines_from_extents_proc));
89 if (scm_is_null (get_property_data ("horizontal-skylines")))
90 set_property ("horizontal-skylines",
91 Unpure_pure_container::make_smob (Grob::simple_horizontal_skylines_from_extents_proc,
92 Grob::pure_simple_horizontal_skylines_from_extents_proc));
95 Grob::Grob (Grob const &s)
98 original_ = (Grob *) & s;
100 immutable_property_alist_ = s.immutable_property_alist_;
101 mutable_property_alist_ = SCM_EOL;
103 for (Axis a = X_AXIS; a < NO_AXES; incr (a))
104 dim_cache_ [a] = s.dim_cache_ [a];
106 interfaces_ = s.interfaces_;
107 object_alist_ = SCM_EOL;
113 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
120 /****************************************************************
122 ****************************************************************/
125 Grob::get_stencil () const
130 SCM stil = get_property ("stencil");
131 return unsmob<Stencil> (stil);
135 Grob::get_print_stencil () const
137 SCM stil = get_property ("stencil");
140 if (Stencil *m = unsmob<Stencil> (stil))
143 bool transparent = to_boolean (get_property ("transparent"));
145 /* Process whiteout before color and grob-cause to prevent colored */
146 /* whiteout background and larger file sizes with \pointAndClickOn. */
147 /* A grob has to be visible, otherwise the whiteout property has no effect. */
148 /* Calls the scheme procedure stencil-whiteout in scm/stencils.scm */
149 if (!transparent && (scm_is_number (get_property ("whiteout"))
150 || to_boolean (get_property ("whiteout"))))
152 Real line_thickness = layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
153 retval = *unsmob<Stencil>
154 (Lily::stencil_whiteout (retval.smobbed_copy (),
155 get_property ("whiteout-style"),
156 get_property ("whiteout"),
157 scm_from_double (line_thickness)));
161 retval = Stencil (m->extent_box (), SCM_EOL);
164 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
168 retval = Stencil (retval.extent_box (), expr);
171 SCM rot = get_property ("rotation");
172 if (scm_is_pair (rot))
174 Real angle = scm_to_double (scm_car (rot));
175 Real x = scm_to_double (scm_cadr (rot));
176 Real y = scm_to_double (scm_caddr (rot));
178 retval.rotate_degrees (angle, Offset (x, y));
181 /* color support... see interpret_stencil_expression () for more... */
182 SCM color = get_property ("color");
183 if (scm_is_pair (color))
185 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
189 retval = Stencil (retval.extent_box (), expr);
192 SCM attributes = get_property ("output-attributes");
193 if (scm_is_pair (attributes))
195 SCM expr = scm_list_3 (ly_symbol2scm ("output-attributes"),
199 retval = Stencil (retval.extent_box (), expr);
207 /****************************************************************
209 ****************************************************************/
211 Grob::do_break_processing ()
216 Grob::discretionary_processing ()
221 Grob::get_system () const
226 /* This version of get_system is more reliable than this->get_system ()
227 before line-breaking has been done, at which point there is only
228 one system in the whole score and we can find it just by following
231 Grob::get_system (Grob *me)
233 Grob *p = me->get_parent (X_AXIS);
234 return p ? get_system (p) : dynamic_cast<System *>(me);
238 Grob::handle_broken_dependencies ()
240 Spanner *sp = dynamic_cast<Spanner *> (this);
241 if (original () && sp)
245 /* THIS, SP is the original spanner. We use a special function
246 because some Spanners have enormously long lists in their
247 properties, and a special function fixes FOO */
249 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
250 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
252 System *system = get_system ();
256 && common_refpoint (system, X_AXIS)
257 && common_refpoint (system, Y_AXIS))
258 substitute_object_links (system->self_scm (), object_alist_);
259 else if (dynamic_cast<System *> (this))
260 substitute_object_links (SCM_UNDEFINED, object_alist_);
262 /* THIS element is `invalid'; it has been removed from all
263 dependencies, so let's junk the element itself.
265 Do not do this for System, since that would remove references
266 to the originals of score-grobs, which get then GC'd (a bad
271 /* Note that we still want references to this element to be
272 rearranged, and not silently thrown away, so we keep pointers like
273 {broken_into_{drul, array}, original}
281 for (int a = X_AXIS; a < NO_AXES; a++)
282 dim_cache_[a].clear ();
284 mutable_property_alist_ = SCM_EOL;
285 object_alist_ = SCM_EOL;
286 immutable_property_alist_ = SCM_EOL;
287 interfaces_ = SCM_EOL;
291 Grob::handle_prebroken_dependencies ()
293 /* Don't do this in the derived method, since we want to keep access to
294 object_alist_ centralized. */
297 Item *it = dynamic_cast<Item *> (this);
298 substitute_object_links (scm_from_int (it->break_status_dir ()),
299 original ()->object_alist_);
304 Grob::find_broken_piece (System *) const
309 /****************************************************************
311 ****************************************************************/
314 Grob::translate_axis (Real y, Axis a)
316 if (isinf (y) || isnan (y))
318 programming_error ("Infinity or NaN encountered");
322 if (!dim_cache_[a].offset_)
323 dim_cache_[a].offset_ = new Real (y);
325 *dim_cache_[a].offset_ += y;
328 /* Find the offset relative to D. If D equals THIS, then it is 0.
329 Otherwise, it recursively defd as
331 OFFSET_ + PARENT_L_->relative_coordinate (D) */
333 Grob::relative_coordinate (Grob const *refp, Axis a) const
335 /* eaa - hmmm, should we do a programming_error() here? */
339 /* We catch PARENT_L_ == nil case with this, but we crash if we did
340 not ask for the absolute coordinate (ie. REFP == nil.) */
342 return get_offset (a) + parent_relative (refp, a);
346 Grob::parent_relative (Grob const *refp, Axis a) const
348 if (Grob *p = get_parent (a))
349 return p->relative_coordinate (refp, a);
354 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
361 if (dim_cache_[Y_AXIS].offset_)
363 if (to_boolean (get_property ("pure-Y-offset-in-progress")))
364 programming_error ("cyclic chain in pure-Y-offset callbacks");
366 off = *dim_cache_[Y_AXIS].offset_;
370 SCM proc = get_property_data ("Y-offset");
372 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
373 set_property ("pure-Y-offset-in-progress", SCM_BOOL_T);
374 off = robust_scm2double (call_pure_function (proc,
375 scm_list_1 (self_scm ()),
378 del_property ("pure-Y-offset-in-progress");
379 delete dim_cache_[Y_AXIS].offset_;
380 dim_cache_[Y_AXIS].offset_ = 0;
383 /* we simulate positioning-done if we are the child of a VerticalAlignment,
384 but only if we don't have a cached offset. If we do have a cached offset,
385 it probably means that the Alignment was fixed and it has already been
388 if (Grob *p = get_parent (Y_AXIS))
391 if (has_interface<Align_interface> (p) && !dim_cache_[Y_AXIS].offset_)
392 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
394 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
399 /* Invoke callbacks to get offset relative to parent. */
401 Grob::get_offset (Axis a) const
403 if (dim_cache_[a].offset_)
404 return *dim_cache_[a].offset_;
406 Grob *me = (Grob *) this;
408 SCM sym = axis_offset_symbol (a);
409 me->dim_cache_[a].offset_ = new Real (0.0);
412 UGH: can't fold next 2 statements together. Apparently GCC thinks
413 dim_cache_[a].offset_ is unaliased.
415 Real off = robust_scm2double (get_property (sym), 0.0);
416 if (me->dim_cache_[a].offset_)
418 *me->dim_cache_[a].offset_ += off;
419 me->del_property (sym);
420 return *me->dim_cache_[a].offset_;
427 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
429 if (pure && a != Y_AXIS)
430 programming_error ("tried to get pure X-offset");
431 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
432 : relative_coordinate (refp, a);
435 /****************************************************************
437 ****************************************************************/
440 Grob::flush_extent_cache (Axis axis)
442 if (dim_cache_[axis].extent_)
445 Ugh, this is not accurate; will flush property, causing
446 callback to be called if.
448 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
449 delete dim_cache_[axis].extent_;
450 dim_cache_[axis].extent_ = 0;
451 if (get_parent (axis))
452 get_parent (axis)->flush_extent_cache (axis);
457 Grob::extent (Grob *refp, Axis a) const
459 Real offset = relative_coordinate (refp, a);
461 if (dim_cache_[a].extent_)
463 real_ext = *dim_cache_[a].extent_;
468 Order is significant: ?-extent may trigger suicide.
470 SCM ext = (a == X_AXIS)
471 ? get_property ("X-extent")
472 : get_property ("Y-extent");
473 if (is_number_pair (ext))
474 real_ext.unite (ly_scm2interval (ext));
476 SCM min_ext = (a == X_AXIS)
477 ? get_property ("minimum-X-extent")
478 : get_property ("minimum-Y-extent");
479 if (is_number_pair (min_ext))
480 real_ext.unite (ly_scm2interval (min_ext));
482 ((Grob *)this)->dim_cache_[a].extent_ = new Interval (real_ext);
485 // We never want nan, so we avoid shifting infinite values.
487 real_ext.translate(offset);
489 warning(_f ("ignored infinite %s-offset",
490 a == X_AXIS ? "X" : "Y"));
496 Grob::pure_y_extent (Grob *refp, int start, int end)
498 SCM iv_scm = get_pure_property ("Y-extent", start, end);
499 Interval iv = robust_scm2interval (iv_scm, Interval ());
500 Real offset = pure_relative_y_coordinate (refp, start, end);
502 SCM min_ext = get_property ("minimum-Y-extent");
504 /* we don't add minimum-Y-extent if the extent is empty. This solves
505 a problem with Hara-kiri spanners. They would request_suicide and
506 return empty extents, but we would force them here to be large. */
507 if (!iv.is_empty () && is_number_pair (min_ext))
508 iv.unite (ly_scm2interval (min_ext));
511 iv.translate (offset);
516 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
518 return (pure && a == Y_AXIS) ? pure_y_extent (refp, start, end) : extent (refp, a);
522 Grob::spanned_rank_interval () const
524 return Interval_t<int> (-1, 0);
528 Grob::pure_is_visible (int /* start */, int /* end */) const
533 /* Sort grobs according to their starting column. */
535 Grob::less (Grob *g1, Grob *g2)
537 return g1->spanned_rank_interval ()[LEFT] < g2->spanned_rank_interval ()[LEFT];
540 /****************************************************************
542 ****************************************************************/
544 /* Find the group-element which has both #this# and #s# */
546 Grob::common_refpoint (Grob const *s, Axis a) const
549 /* Catching the trivial cases is likely costlier than just running
550 through: one can't avoid going to the respective chain ends
551 anyway. We might save the second run through when the chain ends
552 differ, but keeping track of the ends makes the loop more costly.
559 for (c = this; c; ++balance)
560 c = c->dim_cache_[a].parent_;
562 for (d = s; d; --balance)
563 d = d->dim_cache_[a].parent_;
565 /* Cut down ancestry to same size */
567 for (c = this; balance > 0; --balance)
568 c = c->dim_cache_[a].parent_;
570 for (d = s; balance < 0; ++balance)
571 d = d->dim_cache_[a].parent_;
573 /* Now find point where our lineages converge */
576 c = c->dim_cache_[a].parent_;
577 d = d->dim_cache_[a].parent_;
584 Grob::set_parent (Grob *g, Axis a)
586 dim_cache_[a].parent_ = g;
590 Grob::get_parent (Axis a) const
592 return dim_cache_[a].parent_;
596 Grob::fixup_refpoint ()
598 for (int a = X_AXIS; a < NO_AXES; a++)
601 Grob *parent = get_parent (ax);
606 if (parent->get_system () != get_system () && get_system ())
608 Grob *newparent = parent->find_broken_piece (get_system ());
609 set_parent (newparent, ax);
612 if (Item *i = dynamic_cast<Item *> (this))
614 Item *parenti = dynamic_cast<Item *> (parent);
618 Direction my_dir = i->break_status_dir ();
619 if (my_dir != parenti->break_status_dir ())
621 Item *newparent = parenti->find_prebroken_piece (my_dir);
622 set_parent (newparent, ax);
629 /****************************************************************
631 ****************************************************************/
634 get_maybe_root_vertical_alignment (Grob *g, Grob *maybe)
638 if (has_interface<Align_interface> (g))
639 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), g);
640 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), maybe);
645 Grob::get_root_vertical_alignment (Grob *g)
647 return get_maybe_root_vertical_alignment (g, 0);
651 Grob::get_vertical_axis_group (Grob *g)
655 if (!g->get_parent (Y_AXIS))
657 if (has_interface<Axis_group_interface> (g)
658 && has_interface<Align_interface> (g->get_parent (Y_AXIS)))
660 return get_vertical_axis_group (g->get_parent (Y_AXIS));
665 Grob::get_vertical_axis_group_index (Grob *g)
667 Grob *val = get_root_vertical_alignment (g);
670 Grob *vax = get_vertical_axis_group (g);
671 extract_grob_set (val, "elements", elts);
672 for (vsize i = 0; i < elts.size (); i++)
675 g->programming_error ("could not find this grob's vertical axis group in the vertical alignment");
680 Grob::vertical_less (Grob *g1, Grob *g2)
682 return internal_vertical_less (g1, g2, false);
686 Grob::pure_vertical_less (Grob *g1, Grob *g2)
688 return internal_vertical_less (g1, g2, true);
692 Grob::internal_vertical_less (Grob *g1, Grob *g2, bool pure)
694 Grob *vag = get_root_vertical_alignment (g1);
697 g1->programming_error ("grob does not belong to a VerticalAlignment?");
701 Grob *ag1 = get_vertical_axis_group (g1);
702 Grob *ag2 = get_vertical_axis_group (g2);
704 extract_grob_set (vag, "elements", elts);
706 if (ag1 == ag2 && !pure)
708 Grob *common = g1->common_refpoint (g2, Y_AXIS);
709 return g1->relative_coordinate (common, Y_AXIS) > g2->relative_coordinate (common, Y_AXIS);
712 for (vsize i = 0; i < elts.size (); i++)
720 g1->programming_error ("could not place this grob in its axis group");
724 /****************************************************************
726 ****************************************************************/
728 Grob::programming_error (const string &s) const
730 SCM cause = self_scm ();
731 while (Grob *g = unsmob<Grob> (cause))
732 cause = g->get_property ("cause");
734 /* ES TODO: cause can't be Music*/
735 if (Music *m = unsmob<Music> (cause))
736 m->origin ()->programming_error (s);
737 else if (Stream_event *ev = unsmob<Stream_event> (cause))
738 ev->origin ()->programming_error (s);
740 ::programming_error (s);
744 Grob::warning (const string &s) const
746 SCM cause = self_scm ();
747 while (Grob *g = unsmob<Grob> (cause))
748 cause = g->get_property ("cause");
750 /* ES TODO: cause can't be Music*/
751 if (Music *m = unsmob<Music> (cause))
752 m->origin ()->warning (s);
753 else if (Stream_event *ev = unsmob<Stream_event> (cause))
754 ev->origin ()->warning (s);
762 SCM meta = get_property ("meta");
763 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
764 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
765 return scm_is_symbol (nm) ? ly_symbol2string (nm) : class_name ();
769 "A grob represents a piece of music notation.\n"
771 "All grobs have an X and Y@tie{}position on the page. These"
772 " X and Y@tie{}positions are stored in a relative format, thus"
773 " they can easily be combined by stacking them, hanging one"
774 " grob to the side of another, or coupling them into grouping"
777 "Each grob has a reference point (a.k.a.@: parent): The"
778 " position of a grob is stored relative to that reference"
779 " point. For example, the X@tie{}reference point of a staccato"
780 " dot usually is the note head that it applies to. When the"
781 " note head is moved, the staccato dot moves along"
784 "A grob is often associated with a symbol, but some grobs do"
785 " not print any symbols. They take care of grouping objects."
786 " For example, there is a separate grob that stacks staves"
787 " vertically. The @ref{NoteCollision} object is also an"
788 " abstract grob: It only moves around chords, but doesn't print"
791 "Grobs have properties (Scheme variables) that can be read and"
792 " set. Two types of them exist: immutable and mutable."
793 " Immutable variables define the default style and behavior."
794 " They are shared between many objects. They can be changed"
795 " using @code{\\override} and @code{\\revert}. Mutable"
796 " properties are variables that are specific to one grob."
797 " Typically, lists of other objects, or results from"
798 " computations are stored in mutable properties. In"
799 " particular, every call to @code{ly:grob-set-property!}"
800 " (or its C++ equivalent) sets a mutable property.\n"
802 "The properties @code{after-line-breaking} and"
803 " @code{before-line-breaking} are dummies that are not"
804 " user-serviceable.",
811 "after-line-breaking "
813 "axis-group-parent-X "
814 "axis-group-parent-Y "
815 "before-line-breaking "
822 "horizontal-skylines "
830 "parenthesis-friends "
831 "pure-Y-offset-in-progress "
833 "skyline-horizontal-padding "
843 /****************************************************************
845 ****************************************************************/
848 grob_stencil_extent (Grob *me, Axis a)
850 Stencil *m = me->get_stencil ();
854 return ly_interval2scm (e);
857 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
859 Grob::stencil_height (SCM smob)
861 Grob *me = unsmob<Grob> (smob);
862 return grob_stencil_extent (me, Y_AXIS);
865 MAKE_SCHEME_CALLBACK (Grob, pure_stencil_height, 3);
867 Grob::pure_stencil_height (SCM smob, SCM /* beg */, SCM /* end */)
869 Grob *me = unsmob<Grob> (smob);
870 if (unsmob<Stencil> (me->get_property_data ("stencil")))
871 return grob_stencil_extent (me, Y_AXIS);
873 return ly_interval2scm (Interval ());
877 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
879 Grob::y_parent_positioning (SCM smob)
881 Grob *me = unsmob<Grob> (smob);
882 Grob *par = me->get_parent (Y_AXIS);
884 (void) par->get_property ("positioning-done");
886 return scm_from_double (0.0);
889 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
891 Grob::x_parent_positioning (SCM smob)
893 Grob *me = unsmob<Grob> (smob);
895 Grob *par = me->get_parent (X_AXIS);
897 (void) par->get_property ("positioning-done");
899 return scm_from_double (0.0);
902 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
904 Grob::stencil_width (SCM smob)
906 Grob *me = unsmob<Grob> (smob);
907 return grob_stencil_extent (me, X_AXIS);
911 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
913 for (; scm_is_pair (elist); elist = scm_cdr (elist))
914 if (Grob *s = unsmob<Grob> (scm_car (elist)))
917 common = common->common_refpoint (s, a);
926 common_refpoint_of_array (vector<Grob *> const &arr, Grob *common, Axis a)
928 for (vsize i = 0; i < arr.size (); i++)
930 common = common->common_refpoint (arr[i], a);
938 common_refpoint_of_array (set<Grob *> const &arr, Grob *common, Axis a)
940 set<Grob *>::iterator it;
942 for (it = arr.begin (); it != arr.end (); it++)
944 common = common->common_refpoint (*it, a);
952 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
954 Interval ext = me->extent (refpoint, a);
956 ext.add_point (me->relative_coordinate (refpoint, a));
961 // Checks whether there is a vertical alignment in the chain of
962 // parents between this and commony.
964 Grob::check_cross_staff (Grob *commony)
966 if (has_interface<Align_interface> (commony))
969 for (Grob *g = this; g && g != commony; g = g->get_parent (Y_AXIS))
970 if (has_interface<Align_interface> (g))
978 indirect_less (Grob **a, Grob **b)
980 // Use original order as tie breaker. That gives us a stable sort
981 // at the lower price tag of an unstable one, and we want a stable
982 // sort in order to reliably retain the first instance of a grob
984 return *a < *b || (*a == *b && a < b);
989 indirect_eq (Grob **a, Grob **b)
996 direct_less (Grob **a, Grob **b)
1001 // uniquify uniquifies on the memory addresses of the Grobs, but then
1002 // uses the original order. This makes results independent from the
1003 // memory allocation of Grobs.
1006 uniquify (vector <Grob *> & grobs)
1008 vector <Grob **> vec (grobs.size ());
1009 for (vsize i = 0; i < grobs.size (); i++)
1011 vector_sort (vec, indirect_less);
1012 vec.erase (unique (vec.begin (), vec.end (), indirect_eq), vec.end ());
1013 vector_sort (vec, direct_less);
1015 // Since the output is a sorted copy of the input with some elements
1016 // removed, we can fill in the vector in-place if we do it starting
1018 for (vsize i = 0; i < vec.size (); i++)
1020 grobs.erase (grobs.begin () + vec.size (), grobs.end ());