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 thickness = robust_scm2double (get_property("whiteout"), 3.0)
153 * layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
154 retval = *unsmob<Stencil>
155 (Lily::stencil_whiteout (retval.smobbed_copy (),
156 scm_from_double (thickness)));
159 /* Calls the scheme procedure stencil-whiteout-box in scm/stencils.scm */
160 if (!transparent && (scm_is_number (get_property("whiteout-box"))
161 || to_boolean (get_property ("whiteout-box"))))
163 Real thickness = robust_scm2double (get_property("whiteout-box"), 0.0)
164 * layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
165 retval = *unsmob<Stencil>
166 (Lily::stencil_whiteout_box (retval.smobbed_copy (),
167 scm_from_double (thickness)));
171 retval = Stencil (m->extent_box (), SCM_EOL);
174 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
178 retval = Stencil (retval.extent_box (), expr);
181 SCM rot = get_property ("rotation");
182 if (scm_is_pair (rot))
184 Real angle = scm_to_double (scm_car (rot));
185 Real x = scm_to_double (scm_cadr (rot));
186 Real y = scm_to_double (scm_caddr (rot));
188 retval.rotate_degrees (angle, Offset (x, y));
191 /* color support... see interpret_stencil_expression () for more... */
192 SCM color = get_property ("color");
193 if (scm_is_pair (color))
195 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
199 retval = Stencil (retval.extent_box (), expr);
202 SCM id = get_property ("id");
203 if (scm_is_string (id))
205 SCM expr = scm_list_3 (ly_symbol2scm ("id"),
209 retval = Stencil (retval.extent_box (), expr);
217 /****************************************************************
219 ****************************************************************/
221 Grob::do_break_processing ()
226 Grob::discretionary_processing ()
231 Grob::get_system () const
236 /* This version of get_system is more reliable than this->get_system ()
237 before line-breaking has been done, at which point there is only
238 one system in the whole score and we can find it just by following
241 Grob::get_system (Grob *me)
243 Grob *p = me->get_parent (X_AXIS);
244 return p ? get_system (p) : dynamic_cast<System *>(me);
248 Grob::handle_broken_dependencies ()
250 Spanner *sp = dynamic_cast<Spanner *> (this);
251 if (original () && sp)
255 /* THIS, SP is the original spanner. We use a special function
256 because some Spanners have enormously long lists in their
257 properties, and a special function fixes FOO */
259 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
260 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
262 System *system = get_system ();
266 && common_refpoint (system, X_AXIS)
267 && common_refpoint (system, Y_AXIS))
268 substitute_object_links (system->self_scm (), object_alist_);
269 else if (dynamic_cast<System *> (this))
270 substitute_object_links (SCM_UNDEFINED, object_alist_);
272 /* THIS element is `invalid'; it has been removed from all
273 dependencies, so let's junk the element itself.
275 Do not do this for System, since that would remove references
276 to the originals of score-grobs, which get then GC'd (a bad
281 /* Note that we still want references to this element to be
282 rearranged, and not silently thrown away, so we keep pointers like
283 {broken_into_{drul, array}, original}
291 for (int a = X_AXIS; a < NO_AXES; a++)
292 dim_cache_[a].clear ();
294 mutable_property_alist_ = SCM_EOL;
295 object_alist_ = SCM_EOL;
296 immutable_property_alist_ = SCM_EOL;
297 interfaces_ = SCM_EOL;
301 Grob::handle_prebroken_dependencies ()
303 /* Don't do this in the derived method, since we want to keep access to
304 object_alist_ centralized. */
307 Item *it = dynamic_cast<Item *> (this);
308 substitute_object_links (scm_from_int (it->break_status_dir ()),
309 original ()->object_alist_);
314 Grob::find_broken_piece (System *) const
319 /****************************************************************
321 ****************************************************************/
324 Grob::translate_axis (Real y, Axis a)
326 if (isinf (y) || isnan (y))
328 programming_error ("Infinity or NaN encountered");
332 if (!dim_cache_[a].offset_)
333 dim_cache_[a].offset_ = new Real (y);
335 *dim_cache_[a].offset_ += y;
338 /* Find the offset relative to D. If D equals THIS, then it is 0.
339 Otherwise, it recursively defd as
341 OFFSET_ + PARENT_L_->relative_coordinate (D) */
343 Grob::relative_coordinate (Grob const *refp, Axis a) const
345 /* eaa - hmmm, should we do a programming_error() here? */
346 if ((this == NULL) || (refp == this))
349 /* We catch PARENT_L_ == nil case with this, but we crash if we did
350 not ask for the absolute coordinate (ie. REFP == nil.) */
351 Real off = get_offset (a);
352 if (refp == dim_cache_[a].parent_)
355 off += dim_cache_[a].parent_->relative_coordinate (refp, a);
361 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
368 if (dim_cache_[Y_AXIS].offset_)
370 if (to_boolean (get_property ("pure-Y-offset-in-progress")))
371 programming_error ("cyclic chain in pure-Y-offset callbacks");
373 off = *dim_cache_[Y_AXIS].offset_;
377 SCM proc = get_property_data ("Y-offset");
379 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
380 set_property ("pure-Y-offset-in-progress", SCM_BOOL_T);
381 off = robust_scm2double (call_pure_function (proc,
382 scm_list_1 (self_scm ()),
385 del_property ("pure-Y-offset-in-progress");
386 delete dim_cache_[Y_AXIS].offset_;
387 dim_cache_[Y_AXIS].offset_ = 0;
390 /* we simulate positioning-done if we are the child of a VerticalAlignment,
391 but only if we don't have a cached offset. If we do have a cached offset,
392 it probably means that the Alignment was fixed and it has already been
395 if (Grob *p = get_parent (Y_AXIS))
398 if (has_interface<Align_interface> (p) && !dim_cache_[Y_AXIS].offset_)
399 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
401 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
406 /* Invoke callbacks to get offset relative to parent. */
408 Grob::get_offset (Axis a) const
410 if (dim_cache_[a].offset_)
411 return *dim_cache_[a].offset_;
413 Grob *me = (Grob *) this;
415 SCM sym = axis_offset_symbol (a);
416 me->dim_cache_[a].offset_ = new Real (0.0);
419 UGH: can't fold next 2 statements together. Apparently GCC thinks
420 dim_cache_[a].offset_ is unaliased.
422 Real off = robust_scm2double (get_property (sym), 0.0);
423 if (me->dim_cache_[a].offset_)
425 *me->dim_cache_[a].offset_ += off;
426 me->del_property (sym);
427 return *me->dim_cache_[a].offset_;
434 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
436 if (pure && a != Y_AXIS)
437 programming_error ("tried to get pure X-offset");
438 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
439 : relative_coordinate (refp, a);
442 /****************************************************************
444 ****************************************************************/
447 Grob::flush_extent_cache (Axis axis)
449 if (dim_cache_[axis].extent_)
452 Ugh, this is not accurate; will flush property, causing
453 callback to be called if.
455 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
456 delete dim_cache_[axis].extent_;
457 dim_cache_[axis].extent_ = 0;
458 if (get_parent (axis))
459 get_parent (axis)->flush_extent_cache (axis);
464 Grob::extent (Grob *refp, Axis a) const
466 Real offset = relative_coordinate (refp, a);
468 if (dim_cache_[a].extent_)
470 real_ext = *dim_cache_[a].extent_;
475 Order is significant: ?-extent may trigger suicide.
477 SCM ext = (a == X_AXIS)
478 ? get_property ("X-extent")
479 : get_property ("Y-extent");
480 if (is_number_pair (ext))
481 real_ext.unite (ly_scm2interval (ext));
483 SCM min_ext = (a == X_AXIS)
484 ? get_property ("minimum-X-extent")
485 : get_property ("minimum-Y-extent");
486 if (is_number_pair (min_ext))
487 real_ext.unite (ly_scm2interval (min_ext));
489 ((Grob *)this)->dim_cache_[a].extent_ = new Interval (real_ext);
492 // We never want nan, so we avoid shifting infinite values.
494 real_ext.translate(offset);
496 warning(_f ("ignored infinite %s-offset",
497 a == X_AXIS ? "X" : "Y"));
503 Grob::pure_y_extent (Grob *refp, int start, int end)
505 SCM iv_scm = get_pure_property ("Y-extent", start, end);
506 Interval iv = robust_scm2interval (iv_scm, Interval ());
507 Real offset = pure_relative_y_coordinate (refp, start, end);
509 SCM min_ext = get_property ("minimum-Y-extent");
511 /* we don't add minimum-Y-extent if the extent is empty. This solves
512 a problem with Hara-kiri spanners. They would request_suicide and
513 return empty extents, but we would force them here to be large. */
514 if (!iv.is_empty () && is_number_pair (min_ext))
515 iv.unite (ly_scm2interval (min_ext));
518 iv.translate (offset);
523 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
525 return (pure && a == Y_AXIS) ? pure_y_extent (refp, start, end) : extent (refp, a);
529 Grob::spanned_rank_interval () const
531 return Interval_t<int> (-1, 0);
535 Grob::pure_is_visible (int /* start */, int /* end */) const
540 /* Sort grobs according to their starting column. */
542 Grob::less (Grob *g1, Grob *g2)
544 return g1->spanned_rank_interval ()[LEFT] < g2->spanned_rank_interval ()[LEFT];
547 /****************************************************************
549 ****************************************************************/
551 /* Find the group-element which has both #this# and #s# */
553 Grob::common_refpoint (Grob const *s, Axis a) const
556 /* Catching the trivial cases is likely costlier than just running
557 through: one can't avoid going to the respective chain ends
558 anyway. We might save the second run through when the chain ends
559 differ, but keeping track of the ends makes the loop more costly.
566 for (c = this; c; ++balance)
567 c = c->dim_cache_[a].parent_;
569 for (d = s; d; --balance)
570 d = d->dim_cache_[a].parent_;
572 /* Cut down ancestry to same size */
574 for (c = this; balance > 0; --balance)
575 c = c->dim_cache_[a].parent_;
577 for (d = s; balance < 0; ++balance)
578 d = d->dim_cache_[a].parent_;
580 /* Now find point where our lineages converge */
583 c = c->dim_cache_[a].parent_;
584 d = d->dim_cache_[a].parent_;
591 Grob::set_parent (Grob *g, Axis a)
593 dim_cache_[a].parent_ = g;
597 Grob::get_parent (Axis a) const
599 return dim_cache_[a].parent_;
603 Grob::fixup_refpoint ()
605 for (int a = X_AXIS; a < NO_AXES; a++)
608 Grob *parent = get_parent (ax);
613 if (parent->get_system () != get_system () && get_system ())
615 Grob *newparent = parent->find_broken_piece (get_system ());
616 set_parent (newparent, ax);
619 if (Item *i = dynamic_cast<Item *> (this))
621 Item *parenti = dynamic_cast<Item *> (parent);
625 Direction my_dir = i->break_status_dir ();
626 if (my_dir != parenti->break_status_dir ())
628 Item *newparent = parenti->find_prebroken_piece (my_dir);
629 set_parent (newparent, ax);
636 /****************************************************************
638 ****************************************************************/
641 get_maybe_root_vertical_alignment (Grob *g, Grob *maybe)
645 if (has_interface<Align_interface> (g))
646 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), g);
647 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), maybe);
652 Grob::get_root_vertical_alignment (Grob *g)
654 return get_maybe_root_vertical_alignment (g, 0);
658 Grob::get_vertical_axis_group (Grob *g)
662 if (!g->get_parent (Y_AXIS))
664 if (has_interface<Axis_group_interface> (g)
665 && has_interface<Align_interface> (g->get_parent (Y_AXIS)))
667 return get_vertical_axis_group (g->get_parent (Y_AXIS));
672 Grob::get_vertical_axis_group_index (Grob *g)
674 Grob *val = get_root_vertical_alignment (g);
677 Grob *vax = get_vertical_axis_group (g);
678 extract_grob_set (val, "elements", elts);
679 for (vsize i = 0; i < elts.size (); i++)
682 g->programming_error ("could not find this grob's vertical axis group in the vertical alignment");
687 Grob::vertical_less (Grob *g1, Grob *g2)
689 return internal_vertical_less (g1, g2, false);
693 Grob::pure_vertical_less (Grob *g1, Grob *g2)
695 return internal_vertical_less (g1, g2, true);
699 Grob::internal_vertical_less (Grob *g1, Grob *g2, bool pure)
701 Grob *vag = get_root_vertical_alignment (g1);
704 g1->programming_error ("grob does not belong to a VerticalAlignment?");
708 Grob *ag1 = get_vertical_axis_group (g1);
709 Grob *ag2 = get_vertical_axis_group (g2);
711 extract_grob_set (vag, "elements", elts);
713 if (ag1 == ag2 && !pure)
715 Grob *common = g1->common_refpoint (g2, Y_AXIS);
716 return g1->relative_coordinate (common, Y_AXIS) > g2->relative_coordinate (common, Y_AXIS);
719 for (vsize i = 0; i < elts.size (); i++)
727 g1->programming_error ("could not place this grob in its axis group");
731 /****************************************************************
733 ****************************************************************/
735 Grob::programming_error (const string &s) const
737 SCM cause = self_scm ();
738 while (Grob *g = unsmob<Grob> (cause))
739 cause = g->get_property ("cause");
741 /* ES TODO: cause can't be Music*/
742 if (Music *m = unsmob<Music> (cause))
743 m->origin ()->programming_error (s);
744 else if (Stream_event *ev = unsmob<Stream_event> (cause))
745 ev->origin ()->programming_error (s);
747 ::programming_error (s);
751 Grob::warning (const string &s) const
753 SCM cause = self_scm ();
754 while (Grob *g = unsmob<Grob> (cause))
755 cause = g->get_property ("cause");
757 /* ES TODO: cause can't be Music*/
758 if (Music *m = unsmob<Music> (cause))
759 m->origin ()->warning (s);
760 else if (Stream_event *ev = unsmob<Stream_event> (cause))
761 ev->origin ()->warning (s);
769 SCM meta = get_property ("meta");
770 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
771 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
772 return scm_is_symbol (nm) ? ly_symbol2string (nm) : class_name ();
776 "A grob represents a piece of music notation.\n"
778 "All grobs have an X and Y@tie{}position on the page. These"
779 " X and Y@tie{}positions are stored in a relative format, thus"
780 " they can easily be combined by stacking them, hanging one"
781 " grob to the side of another, or coupling them into grouping"
784 "Each grob has a reference point (a.k.a.@: parent): The"
785 " position of a grob is stored relative to that reference"
786 " point. For example, the X@tie{}reference point of a staccato"
787 " dot usually is the note head that it applies to. When the"
788 " note head is moved, the staccato dot moves along"
791 "A grob is often associated with a symbol, but some grobs do"
792 " not print any symbols. They take care of grouping objects."
793 " For example, there is a separate grob that stacks staves"
794 " vertically. The @ref{NoteCollision} object is also an"
795 " abstract grob: It only moves around chords, but doesn't print"
798 "Grobs have properties (Scheme variables) that can be read and"
799 " set. Two types of them exist: immutable and mutable."
800 " Immutable variables define the default style and behavior."
801 " They are shared between many objects. They can be changed"
802 " using @code{\\override} and @code{\\revert}. Mutable"
803 " properties are variables that are specific to one grob."
804 " Typically, lists of other objects, or results from"
805 " computations are stored in mutable properties. In"
806 " particular, every call to @code{ly:grob-set-property!}"
807 " (or its C++ equivalent) sets a mutable property.\n"
809 "The properties @code{after-line-breaking} and"
810 " @code{before-line-breaking} are dummies that are not"
811 " user-serviceable.",
818 "after-line-breaking "
820 "axis-group-parent-X "
821 "axis-group-parent-Y "
822 "before-line-breaking "
830 "horizontal-skylines "
836 "parenthesis-friends "
837 "pure-Y-offset-in-progress "
839 "skyline-horizontal-padding "
849 /****************************************************************
851 ****************************************************************/
854 grob_stencil_extent (Grob *me, Axis a)
856 Stencil *m = me->get_stencil ();
860 return ly_interval2scm (e);
863 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
865 Grob::stencil_height (SCM smob)
867 Grob *me = unsmob<Grob> (smob);
868 return grob_stencil_extent (me, Y_AXIS);
871 MAKE_SCHEME_CALLBACK (Grob, pure_stencil_height, 3);
873 Grob::pure_stencil_height (SCM smob, SCM /* beg */, SCM /* end */)
875 Grob *me = unsmob<Grob> (smob);
876 if (unsmob<Stencil> (me->get_property_data ("stencil")))
877 return grob_stencil_extent (me, Y_AXIS);
879 return ly_interval2scm (Interval ());
883 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
885 Grob::y_parent_positioning (SCM smob)
887 Grob *me = unsmob<Grob> (smob);
888 Grob *par = me->get_parent (Y_AXIS);
890 (void) par->get_property ("positioning-done");
892 return scm_from_double (0.0);
895 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
897 Grob::x_parent_positioning (SCM smob)
899 Grob *me = unsmob<Grob> (smob);
901 Grob *par = me->get_parent (X_AXIS);
903 (void) par->get_property ("positioning-done");
905 return scm_from_double (0.0);
908 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
910 Grob::stencil_width (SCM smob)
912 Grob *me = unsmob<Grob> (smob);
913 return grob_stencil_extent (me, X_AXIS);
917 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
919 for (; scm_is_pair (elist); elist = scm_cdr (elist))
920 if (Grob *s = unsmob<Grob> (scm_car (elist)))
923 common = common->common_refpoint (s, a);
932 common_refpoint_of_array (vector<Grob *> const &arr, Grob *common, Axis a)
934 for (vsize i = 0; i < arr.size (); i++)
936 common = common->common_refpoint (arr[i], a);
944 common_refpoint_of_array (set<Grob *> const &arr, Grob *common, Axis a)
946 set<Grob *>::iterator it;
948 for (it = arr.begin (); it != arr.end (); it++)
950 common = common->common_refpoint (*it, a);
958 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
960 Interval ext = me->extent (refpoint, a);
962 ext.add_point (me->relative_coordinate (refpoint, a));
967 // Checks whether there is a vertical alignment in the chain of
968 // parents between this and commony.
970 Grob::check_cross_staff (Grob *commony)
972 if (has_interface<Align_interface> (commony))
975 for (Grob *g = this; g && g != commony; g = g->get_parent (Y_AXIS))
976 if (has_interface<Align_interface> (g))
984 indirect_less (Grob **a, Grob **b)
986 // Use original order as tie breaker. That gives us a stable sort
987 // at the lower price tag of an unstable one, and we want a stable
988 // sort in order to reliably retain the first instance of a grob
990 return *a < *b || (*a == *b && a < b);
995 indirect_eq (Grob **a, Grob **b)
1002 direct_less (Grob **a, Grob **b)
1007 // uniquify uniquifies on the memory addresses of the Grobs, but then
1008 // uses the original order. This makes results independent from the
1009 // memory allocation of Grobs.
1012 uniquify (vector <Grob *> & grobs)
1014 vector <Grob **> vec (grobs.size ());
1015 for (vsize i = 0; i < grobs.size (); i++)
1017 vector_sort (vec, indirect_less);
1018 vec.erase (unique (vec.begin (), vec.end (), indirect_eq), vec.end ());
1019 vector_sort (vec, direct_less);
1021 // Since the output is a sorted copy of the input with some elements
1022 // removed, we can fill in the vector in-place if we do it starting
1024 for (vsize i = 0; i < vec.size (); i++)
1026 grobs.erase (grobs.begin () + vec.size (), grobs.end ());