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 id = get_property ("id");
193 if (scm_is_string (id))
195 SCM expr = scm_list_3 (ly_symbol2scm ("id"),
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? */
336 if ((this == NULL) || (refp == this))
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.) */
341 Real off = get_offset (a);
342 if (refp == dim_cache_[a].parent_)
345 off += dim_cache_[a].parent_->relative_coordinate (refp, a);
351 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
358 if (dim_cache_[Y_AXIS].offset_)
360 if (to_boolean (get_property ("pure-Y-offset-in-progress")))
361 programming_error ("cyclic chain in pure-Y-offset callbacks");
363 off = *dim_cache_[Y_AXIS].offset_;
367 SCM proc = get_property_data ("Y-offset");
369 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
370 set_property ("pure-Y-offset-in-progress", SCM_BOOL_T);
371 off = robust_scm2double (call_pure_function (proc,
372 scm_list_1 (self_scm ()),
375 del_property ("pure-Y-offset-in-progress");
376 delete dim_cache_[Y_AXIS].offset_;
377 dim_cache_[Y_AXIS].offset_ = 0;
380 /* we simulate positioning-done if we are the child of a VerticalAlignment,
381 but only if we don't have a cached offset. If we do have a cached offset,
382 it probably means that the Alignment was fixed and it has already been
385 if (Grob *p = get_parent (Y_AXIS))
388 if (has_interface<Align_interface> (p) && !dim_cache_[Y_AXIS].offset_)
389 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
391 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
396 /* Invoke callbacks to get offset relative to parent. */
398 Grob::get_offset (Axis a) const
400 if (dim_cache_[a].offset_)
401 return *dim_cache_[a].offset_;
403 Grob *me = (Grob *) this;
405 SCM sym = axis_offset_symbol (a);
406 me->dim_cache_[a].offset_ = new Real (0.0);
409 UGH: can't fold next 2 statements together. Apparently GCC thinks
410 dim_cache_[a].offset_ is unaliased.
412 Real off = robust_scm2double (get_property (sym), 0.0);
413 if (me->dim_cache_[a].offset_)
415 *me->dim_cache_[a].offset_ += off;
416 me->del_property (sym);
417 return *me->dim_cache_[a].offset_;
424 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
426 if (pure && a != Y_AXIS)
427 programming_error ("tried to get pure X-offset");
428 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
429 : relative_coordinate (refp, a);
432 /****************************************************************
434 ****************************************************************/
437 Grob::flush_extent_cache (Axis axis)
439 if (dim_cache_[axis].extent_)
442 Ugh, this is not accurate; will flush property, causing
443 callback to be called if.
445 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
446 delete dim_cache_[axis].extent_;
447 dim_cache_[axis].extent_ = 0;
448 if (get_parent (axis))
449 get_parent (axis)->flush_extent_cache (axis);
454 Grob::extent (Grob *refp, Axis a) const
456 Real offset = relative_coordinate (refp, a);
458 if (dim_cache_[a].extent_)
460 real_ext = *dim_cache_[a].extent_;
465 Order is significant: ?-extent may trigger suicide.
467 SCM ext = (a == X_AXIS)
468 ? get_property ("X-extent")
469 : get_property ("Y-extent");
470 if (is_number_pair (ext))
471 real_ext.unite (ly_scm2interval (ext));
473 SCM min_ext = (a == X_AXIS)
474 ? get_property ("minimum-X-extent")
475 : get_property ("minimum-Y-extent");
476 if (is_number_pair (min_ext))
477 real_ext.unite (ly_scm2interval (min_ext));
479 ((Grob *)this)->dim_cache_[a].extent_ = new Interval (real_ext);
482 // We never want nan, so we avoid shifting infinite values.
484 real_ext.translate(offset);
486 warning(_f ("ignored infinite %s-offset",
487 a == X_AXIS ? "X" : "Y"));
493 Grob::pure_y_extent (Grob *refp, int start, int end)
495 SCM iv_scm = get_pure_property ("Y-extent", start, end);
496 Interval iv = robust_scm2interval (iv_scm, Interval ());
497 Real offset = pure_relative_y_coordinate (refp, start, end);
499 SCM min_ext = get_property ("minimum-Y-extent");
501 /* we don't add minimum-Y-extent if the extent is empty. This solves
502 a problem with Hara-kiri spanners. They would request_suicide and
503 return empty extents, but we would force them here to be large. */
504 if (!iv.is_empty () && is_number_pair (min_ext))
505 iv.unite (ly_scm2interval (min_ext));
508 iv.translate (offset);
513 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
515 return (pure && a == Y_AXIS) ? pure_y_extent (refp, start, end) : extent (refp, a);
519 Grob::spanned_rank_interval () const
521 return Interval_t<int> (-1, 0);
525 Grob::pure_is_visible (int /* start */, int /* end */) const
530 /* Sort grobs according to their starting column. */
532 Grob::less (Grob *g1, Grob *g2)
534 return g1->spanned_rank_interval ()[LEFT] < g2->spanned_rank_interval ()[LEFT];
537 /****************************************************************
539 ****************************************************************/
541 /* Find the group-element which has both #this# and #s# */
543 Grob::common_refpoint (Grob const *s, Axis a) const
546 /* Catching the trivial cases is likely costlier than just running
547 through: one can't avoid going to the respective chain ends
548 anyway. We might save the second run through when the chain ends
549 differ, but keeping track of the ends makes the loop more costly.
556 for (c = this; c; ++balance)
557 c = c->dim_cache_[a].parent_;
559 for (d = s; d; --balance)
560 d = d->dim_cache_[a].parent_;
562 /* Cut down ancestry to same size */
564 for (c = this; balance > 0; --balance)
565 c = c->dim_cache_[a].parent_;
567 for (d = s; balance < 0; ++balance)
568 d = d->dim_cache_[a].parent_;
570 /* Now find point where our lineages converge */
573 c = c->dim_cache_[a].parent_;
574 d = d->dim_cache_[a].parent_;
581 Grob::set_parent (Grob *g, Axis a)
583 dim_cache_[a].parent_ = g;
587 Grob::get_parent (Axis a) const
589 return dim_cache_[a].parent_;
593 Grob::fixup_refpoint ()
595 for (int a = X_AXIS; a < NO_AXES; a++)
598 Grob *parent = get_parent (ax);
603 if (parent->get_system () != get_system () && get_system ())
605 Grob *newparent = parent->find_broken_piece (get_system ());
606 set_parent (newparent, ax);
609 if (Item *i = dynamic_cast<Item *> (this))
611 Item *parenti = dynamic_cast<Item *> (parent);
615 Direction my_dir = i->break_status_dir ();
616 if (my_dir != parenti->break_status_dir ())
618 Item *newparent = parenti->find_prebroken_piece (my_dir);
619 set_parent (newparent, ax);
626 /****************************************************************
628 ****************************************************************/
631 get_maybe_root_vertical_alignment (Grob *g, Grob *maybe)
635 if (has_interface<Align_interface> (g))
636 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), g);
637 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), maybe);
642 Grob::get_root_vertical_alignment (Grob *g)
644 return get_maybe_root_vertical_alignment (g, 0);
648 Grob::get_vertical_axis_group (Grob *g)
652 if (!g->get_parent (Y_AXIS))
654 if (has_interface<Axis_group_interface> (g)
655 && has_interface<Align_interface> (g->get_parent (Y_AXIS)))
657 return get_vertical_axis_group (g->get_parent (Y_AXIS));
662 Grob::get_vertical_axis_group_index (Grob *g)
664 Grob *val = get_root_vertical_alignment (g);
667 Grob *vax = get_vertical_axis_group (g);
668 extract_grob_set (val, "elements", elts);
669 for (vsize i = 0; i < elts.size (); i++)
672 g->programming_error ("could not find this grob's vertical axis group in the vertical alignment");
677 Grob::vertical_less (Grob *g1, Grob *g2)
679 return internal_vertical_less (g1, g2, false);
683 Grob::pure_vertical_less (Grob *g1, Grob *g2)
685 return internal_vertical_less (g1, g2, true);
689 Grob::internal_vertical_less (Grob *g1, Grob *g2, bool pure)
691 Grob *vag = get_root_vertical_alignment (g1);
694 g1->programming_error ("grob does not belong to a VerticalAlignment?");
698 Grob *ag1 = get_vertical_axis_group (g1);
699 Grob *ag2 = get_vertical_axis_group (g2);
701 extract_grob_set (vag, "elements", elts);
703 if (ag1 == ag2 && !pure)
705 Grob *common = g1->common_refpoint (g2, Y_AXIS);
706 return g1->relative_coordinate (common, Y_AXIS) > g2->relative_coordinate (common, Y_AXIS);
709 for (vsize i = 0; i < elts.size (); i++)
717 g1->programming_error ("could not place this grob in its axis group");
721 /****************************************************************
723 ****************************************************************/
725 Grob::programming_error (const string &s) const
727 SCM cause = self_scm ();
728 while (Grob *g = unsmob<Grob> (cause))
729 cause = g->get_property ("cause");
731 /* ES TODO: cause can't be Music*/
732 if (Music *m = unsmob<Music> (cause))
733 m->origin ()->programming_error (s);
734 else if (Stream_event *ev = unsmob<Stream_event> (cause))
735 ev->origin ()->programming_error (s);
737 ::programming_error (s);
741 Grob::warning (const string &s) const
743 SCM cause = self_scm ();
744 while (Grob *g = unsmob<Grob> (cause))
745 cause = g->get_property ("cause");
747 /* ES TODO: cause can't be Music*/
748 if (Music *m = unsmob<Music> (cause))
749 m->origin ()->warning (s);
750 else if (Stream_event *ev = unsmob<Stream_event> (cause))
751 ev->origin ()->warning (s);
759 SCM meta = get_property ("meta");
760 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
761 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
762 return scm_is_symbol (nm) ? ly_symbol2string (nm) : class_name ();
766 "A grob represents a piece of music notation.\n"
768 "All grobs have an X and Y@tie{}position on the page. These"
769 " X and Y@tie{}positions are stored in a relative format, thus"
770 " they can easily be combined by stacking them, hanging one"
771 " grob to the side of another, or coupling them into grouping"
774 "Each grob has a reference point (a.k.a.@: parent): The"
775 " position of a grob is stored relative to that reference"
776 " point. For example, the X@tie{}reference point of a staccato"
777 " dot usually is the note head that it applies to. When the"
778 " note head is moved, the staccato dot moves along"
781 "A grob is often associated with a symbol, but some grobs do"
782 " not print any symbols. They take care of grouping objects."
783 " For example, there is a separate grob that stacks staves"
784 " vertically. The @ref{NoteCollision} object is also an"
785 " abstract grob: It only moves around chords, but doesn't print"
788 "Grobs have properties (Scheme variables) that can be read and"
789 " set. Two types of them exist: immutable and mutable."
790 " Immutable variables define the default style and behavior."
791 " They are shared between many objects. They can be changed"
792 " using @code{\\override} and @code{\\revert}. Mutable"
793 " properties are variables that are specific to one grob."
794 " Typically, lists of other objects, or results from"
795 " computations are stored in mutable properties. In"
796 " particular, every call to @code{ly:grob-set-property!}"
797 " (or its C++ equivalent) sets a mutable property.\n"
799 "The properties @code{after-line-breaking} and"
800 " @code{before-line-breaking} are dummies that are not"
801 " user-serviceable.",
808 "after-line-breaking "
810 "axis-group-parent-X "
811 "axis-group-parent-Y "
812 "before-line-breaking "
820 "horizontal-skylines "
826 "parenthesis-friends "
827 "pure-Y-offset-in-progress "
829 "skyline-horizontal-padding "
839 /****************************************************************
841 ****************************************************************/
844 grob_stencil_extent (Grob *me, Axis a)
846 Stencil *m = me->get_stencil ();
850 return ly_interval2scm (e);
853 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
855 Grob::stencil_height (SCM smob)
857 Grob *me = unsmob<Grob> (smob);
858 return grob_stencil_extent (me, Y_AXIS);
861 MAKE_SCHEME_CALLBACK (Grob, pure_stencil_height, 3);
863 Grob::pure_stencil_height (SCM smob, SCM /* beg */, SCM /* end */)
865 Grob *me = unsmob<Grob> (smob);
866 if (unsmob<Stencil> (me->get_property_data ("stencil")))
867 return grob_stencil_extent (me, Y_AXIS);
869 return ly_interval2scm (Interval ());
873 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
875 Grob::y_parent_positioning (SCM smob)
877 Grob *me = unsmob<Grob> (smob);
878 Grob *par = me->get_parent (Y_AXIS);
880 (void) par->get_property ("positioning-done");
882 return scm_from_double (0.0);
885 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
887 Grob::x_parent_positioning (SCM smob)
889 Grob *me = unsmob<Grob> (smob);
891 Grob *par = me->get_parent (X_AXIS);
893 (void) par->get_property ("positioning-done");
895 return scm_from_double (0.0);
898 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
900 Grob::stencil_width (SCM smob)
902 Grob *me = unsmob<Grob> (smob);
903 return grob_stencil_extent (me, X_AXIS);
907 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
909 for (; scm_is_pair (elist); elist = scm_cdr (elist))
910 if (Grob *s = unsmob<Grob> (scm_car (elist)))
913 common = common->common_refpoint (s, a);
922 common_refpoint_of_array (vector<Grob *> const &arr, Grob *common, Axis a)
924 for (vsize i = 0; i < arr.size (); i++)
926 common = common->common_refpoint (arr[i], a);
934 common_refpoint_of_array (set<Grob *> const &arr, Grob *common, Axis a)
936 set<Grob *>::iterator it;
938 for (it = arr.begin (); it != arr.end (); it++)
940 common = common->common_refpoint (*it, a);
948 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
950 Interval ext = me->extent (refpoint, a);
952 ext.add_point (me->relative_coordinate (refpoint, a));
957 // Checks whether there is a vertical alignment in the chain of
958 // parents between this and commony.
960 Grob::check_cross_staff (Grob *commony)
962 if (has_interface<Align_interface> (commony))
965 for (Grob *g = this; g && g != commony; g = g->get_parent (Y_AXIS))
966 if (has_interface<Align_interface> (g))
974 indirect_less (Grob **a, Grob **b)
976 // Use original order as tie breaker. That gives us a stable sort
977 // at the lower price tag of an unstable one, and we want a stable
978 // sort in order to reliably retain the first instance of a grob
980 return *a < *b || (*a == *b && a < b);
985 indirect_eq (Grob **a, Grob **b)
992 direct_less (Grob **a, Grob **b)
997 // uniquify uniquifies on the memory addresses of the Grobs, but then
998 // uses the original order. This makes results independent from the
999 // memory allocation of Grobs.
1002 uniquify (vector <Grob *> & grobs)
1004 vector <Grob **> vec (grobs.size ());
1005 for (vsize i = 0; i < grobs.size (); i++)
1007 vector_sort (vec, indirect_less);
1008 vec.erase (unique (vec.begin (), vec.end (), indirect_eq), vec.end ());
1009 vector_sort (vec, direct_less);
1011 // Since the output is a sorted copy of the input with some elements
1012 // removed, we can fill in the vector in-place if we do it starting
1014 for (vsize i = 0; i < vec.size (); i++)
1016 grobs.erase (grobs.begin () + vec.size (), grobs.end ());