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"
50 return new Grob (*this);
53 Grob::Grob (SCM basicprops)
56 /* FIXME: default should be no callback. */
59 interfaces_ = SCM_EOL;
60 immutable_property_alist_ = basicprops;
61 mutable_property_alist_ = SCM_EOL;
62 object_alist_ = SCM_EOL;
64 /* We do smobify_self () as the first step. Since the object lives
65 on the heap, none of its SCM variables are protected from
66 GC. After smobify_self (), they are. */
69 SCM meta = get_property ("meta");
70 if (scm_is_pair (meta))
72 interfaces_ = scm_cdr (scm_assq (ly_symbol2scm ("interfaces"), meta));
74 SCM object_cbs = scm_assq (ly_symbol2scm ("object-callbacks"), meta);
75 if (scm_is_pair (object_cbs))
77 for (SCM s = scm_cdr (object_cbs); scm_is_pair (s); s = scm_cdr (s))
78 set_object (scm_caar (s), scm_cdar (s));
82 if (scm_is_null (get_property_data ("X-extent")))
83 set_property ("X-extent", Grob::stencil_width_proc);
84 if (scm_is_null (get_property_data ("Y-extent")))
85 set_property ("Y-extent",
86 Unpure_pure_container::make_smob (Grob::stencil_height_proc,
87 Grob::pure_stencil_height_proc));
88 if (scm_is_null (get_property_data ("vertical-skylines")))
89 set_property ("vertical-skylines",
90 Unpure_pure_container::make_smob (Grob::simple_vertical_skylines_from_extents_proc,
91 Grob::pure_simple_vertical_skylines_from_extents_proc));
92 if (scm_is_null (get_property_data ("horizontal-skylines")))
93 set_property ("horizontal-skylines",
94 Unpure_pure_container::make_smob (Grob::simple_horizontal_skylines_from_extents_proc,
95 Grob::pure_simple_horizontal_skylines_from_extents_proc));
98 Grob::Grob (Grob const &s)
101 original_ = (Grob *) & s;
103 immutable_property_alist_ = s.immutable_property_alist_;
104 mutable_property_alist_ = SCM_EOL;
106 for (Axis a = X_AXIS; a < NO_AXES; incr (a))
107 dim_cache_ [a] = s.dim_cache_ [a];
109 interfaces_ = s.interfaces_;
110 object_alist_ = SCM_EOL;
116 mutable_property_alist_ = ly_deep_copy (s.mutable_property_alist_);
123 /****************************************************************
125 ****************************************************************/
128 Grob::get_stencil () const
133 SCM stil = get_property ("stencil");
134 return unsmob<Stencil> (stil);
138 Grob::get_print_stencil () const
140 SCM stil = get_property ("stencil");
143 if (Stencil *m = unsmob<Stencil> (stil))
146 bool transparent = to_boolean (get_property ("transparent"));
148 /* Process whiteout before color and grob-cause to prevent colored */
149 /* whiteout background and larger file sizes with \pointAndClickOn. */
150 /* A grob has to be visible, otherwise the whiteout property has no effect. */
151 /* Calls the scheme procedure stencil-whiteout in scm/stencils.scm */
152 if (!transparent && (scm_is_number (get_property("whiteout"))
153 || to_boolean (get_property ("whiteout"))))
155 Real thickness = robust_scm2double (get_property("whiteout"), 3.0)
156 * layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
157 retval = *unsmob<Stencil>
158 (Lily::stencil_whiteout (retval.smobbed_copy (),
159 scm_from_double (thickness)));
162 /* Calls the scheme procedure stencil-whiteout-box in scm/stencils.scm */
163 if (!transparent && (scm_is_number (get_property("whiteout-box"))
164 || to_boolean (get_property ("whiteout-box"))))
166 Real thickness = robust_scm2double (get_property("whiteout-box"), 0.0)
167 * layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
168 retval = *unsmob<Stencil>
169 (Lily::stencil_whiteout_box (retval.smobbed_copy (),
170 scm_from_double (thickness)));
174 retval = Stencil (m->extent_box (), SCM_EOL);
177 SCM expr = scm_list_3 (ly_symbol2scm ("grob-cause"),
181 retval = Stencil (retval.extent_box (), expr);
184 SCM rot = get_property ("rotation");
185 if (scm_is_pair (rot))
187 Real angle = scm_to_double (scm_car (rot));
188 Real x = scm_to_double (scm_cadr (rot));
189 Real y = scm_to_double (scm_caddr (rot));
191 retval.rotate_degrees (angle, Offset (x, y));
194 /* color support... see interpret_stencil_expression () for more... */
195 SCM color = get_property ("color");
196 if (scm_is_pair (color))
198 SCM expr = scm_list_3 (ly_symbol2scm ("color"),
202 retval = Stencil (retval.extent_box (), expr);
205 SCM id = get_property ("id");
206 if (scm_is_string (id))
208 SCM expr = scm_list_3 (ly_symbol2scm ("id"),
212 retval = Stencil (retval.extent_box (), expr);
220 /****************************************************************
222 ****************************************************************/
224 Grob::do_break_processing ()
229 Grob::discretionary_processing ()
234 Grob::get_system () const
239 /* This version of get_system is more reliable than this->get_system ()
240 before line-breaking has been done, at which point there is only
241 one system in the whole score and we can find it just by following
244 Grob::get_system (Grob *me)
246 Grob *p = me->get_parent (X_AXIS);
247 return p ? get_system (p) : dynamic_cast<System *>(me);
251 Grob::handle_broken_dependencies ()
253 Spanner *sp = dynamic_cast<Spanner *> (this);
254 if (original () && sp)
258 /* THIS, SP is the original spanner. We use a special function
259 because some Spanners have enormously long lists in their
260 properties, and a special function fixes FOO */
262 for (SCM s = object_alist_; scm_is_pair (s); s = scm_cdr (s))
263 sp->substitute_one_mutable_property (scm_caar (s), scm_cdar (s));
265 System *system = get_system ();
269 && common_refpoint (system, X_AXIS)
270 && common_refpoint (system, Y_AXIS))
271 substitute_object_links (system->self_scm (), object_alist_);
272 else if (dynamic_cast<System *> (this))
273 substitute_object_links (SCM_UNDEFINED, object_alist_);
275 /* THIS element is `invalid'; it has been removed from all
276 dependencies, so let's junk the element itself.
278 Do not do this for System, since that would remove references
279 to the originals of score-grobs, which get then GC'd (a bad
284 /* Note that we still want references to this element to be
285 rearranged, and not silently thrown away, so we keep pointers like
286 {broken_into_{drul, array}, original}
294 for (int a = X_AXIS; a < NO_AXES; a++)
295 dim_cache_[a].clear ();
297 mutable_property_alist_ = SCM_EOL;
298 object_alist_ = SCM_EOL;
299 immutable_property_alist_ = SCM_EOL;
300 interfaces_ = SCM_EOL;
304 Grob::handle_prebroken_dependencies ()
306 /* Don't do this in the derived method, since we want to keep access to
307 object_alist_ centralized. */
310 Item *it = dynamic_cast<Item *> (this);
311 substitute_object_links (scm_from_int (it->break_status_dir ()),
312 original ()->object_alist_);
317 Grob::find_broken_piece (System *) const
322 /****************************************************************
324 ****************************************************************/
327 Grob::translate_axis (Real y, Axis a)
329 if (isinf (y) || isnan (y))
331 programming_error ("Infinity or NaN encountered");
335 if (!dim_cache_[a].offset_)
336 dim_cache_[a].offset_ = new Real (y);
338 *dim_cache_[a].offset_ += y;
341 /* Find the offset relative to D. If D equals THIS, then it is 0.
342 Otherwise, it recursively defd as
344 OFFSET_ + PARENT_L_->relative_coordinate (D) */
346 Grob::relative_coordinate (Grob const *refp, Axis a) const
348 /* eaa - hmmm, should we do a programming_error() here? */
349 if ((this == NULL) || (refp == this))
352 /* We catch PARENT_L_ == nil case with this, but we crash if we did
353 not ask for the absolute coordinate (ie. REFP == nil.) */
354 Real off = get_offset (a);
355 if (refp == dim_cache_[a].parent_)
358 off += dim_cache_[a].parent_->relative_coordinate (refp, a);
364 Grob::pure_relative_y_coordinate (Grob const *refp, int start, int end)
371 if (dim_cache_[Y_AXIS].offset_)
373 if (to_boolean (get_property ("pure-Y-offset-in-progress")))
374 programming_error ("cyclic chain in pure-Y-offset callbacks");
376 off = *dim_cache_[Y_AXIS].offset_;
380 SCM proc = get_property_data ("Y-offset");
382 dim_cache_[Y_AXIS].offset_ = new Real (0.0);
383 set_property ("pure-Y-offset-in-progress", SCM_BOOL_T);
384 off = robust_scm2double (call_pure_function (proc,
385 scm_list_1 (self_scm ()),
388 del_property ("pure-Y-offset-in-progress");
389 delete dim_cache_[Y_AXIS].offset_;
390 dim_cache_[Y_AXIS].offset_ = 0;
393 /* we simulate positioning-done if we are the child of a VerticalAlignment,
394 but only if we don't have a cached offset. If we do have a cached offset,
395 it probably means that the Alignment was fixed and it has already been
398 if (Grob *p = get_parent (Y_AXIS))
401 if (has_interface<Align_interface> (p) && !dim_cache_[Y_AXIS].offset_)
402 trans = Align_interface::get_pure_child_y_translation (p, this, start, end);
404 return off + trans + p->pure_relative_y_coordinate (refp, start, end);
409 /* Invoke callbacks to get offset relative to parent. */
411 Grob::get_offset (Axis a) const
413 if (dim_cache_[a].offset_)
414 return *dim_cache_[a].offset_;
416 Grob *me = (Grob *) this;
418 SCM sym = axis_offset_symbol (a);
419 me->dim_cache_[a].offset_ = new Real (0.0);
422 UGH: can't fold next 2 statements together. Apparently GCC thinks
423 dim_cache_[a].offset_ is unaliased.
425 Real off = robust_scm2double (get_property (sym), 0.0);
426 if (me->dim_cache_[a].offset_)
428 *me->dim_cache_[a].offset_ += off;
429 me->del_property (sym);
430 return *me->dim_cache_[a].offset_;
437 Grob::maybe_pure_coordinate (Grob const *refp, Axis a, bool pure, int start, int end)
439 if (pure && a != Y_AXIS)
440 programming_error ("tried to get pure X-offset");
441 return (pure && a == Y_AXIS) ? pure_relative_y_coordinate (refp, start, end)
442 : relative_coordinate (refp, a);
445 /****************************************************************
447 ****************************************************************/
450 Grob::flush_extent_cache (Axis axis)
452 if (dim_cache_[axis].extent_)
455 Ugh, this is not accurate; will flush property, causing
456 callback to be called if.
458 del_property ((axis == X_AXIS) ? ly_symbol2scm ("X-extent") : ly_symbol2scm ("Y-extent"));
459 delete dim_cache_[axis].extent_;
460 dim_cache_[axis].extent_ = 0;
461 if (get_parent (axis))
462 get_parent (axis)->flush_extent_cache (axis);
467 Grob::extent (Grob *refp, Axis a) const
469 Real offset = relative_coordinate (refp, a);
471 if (dim_cache_[a].extent_)
473 real_ext = *dim_cache_[a].extent_;
478 Order is significant: ?-extent may trigger suicide.
480 SCM ext = (a == X_AXIS)
481 ? get_property ("X-extent")
482 : get_property ("Y-extent");
483 if (is_number_pair (ext))
484 real_ext.unite (ly_scm2interval (ext));
486 SCM min_ext = (a == X_AXIS)
487 ? get_property ("minimum-X-extent")
488 : get_property ("minimum-Y-extent");
489 if (is_number_pair (min_ext))
490 real_ext.unite (ly_scm2interval (min_ext));
492 ((Grob *)this)->dim_cache_[a].extent_ = new Interval (real_ext);
495 // We never want nan, so we avoid shifting infinite values.
497 real_ext.translate(offset);
499 warning(_f ("ignored infinite %s-offset",
500 a == X_AXIS ? "X" : "Y"));
506 Grob::pure_y_extent (Grob *refp, int start, int end)
508 SCM iv_scm = get_pure_property ("Y-extent", start, end);
509 Interval iv = robust_scm2interval (iv_scm, Interval ());
510 Real offset = pure_relative_y_coordinate (refp, start, end);
512 SCM min_ext = get_property ("minimum-Y-extent");
514 /* we don't add minimum-Y-extent if the extent is empty. This solves
515 a problem with Hara-kiri spanners. They would request_suicide and
516 return empty extents, but we would force them here to be large. */
517 if (!iv.is_empty () && is_number_pair (min_ext))
518 iv.unite (ly_scm2interval (min_ext));
521 iv.translate (offset);
526 Grob::maybe_pure_extent (Grob *refp, Axis a, bool pure, int start, int end)
528 return (pure && a == Y_AXIS) ? pure_y_extent (refp, start, end) : extent (refp, a);
532 Grob::spanned_rank_interval () const
534 return Interval_t<int> (-1, 0);
538 Grob::pure_is_visible (int /* start */, int /* end */) const
543 /* Sort grobs according to their starting column. */
545 Grob::less (Grob *g1, Grob *g2)
547 return g1->spanned_rank_interval ()[LEFT] < g2->spanned_rank_interval ()[LEFT];
550 /****************************************************************
552 ****************************************************************/
554 /* Find the group-element which has both #this# and #s# */
556 Grob::common_refpoint (Grob const *s, Axis a) const
559 /* Catching the trivial cases is likely costlier than just running
560 through: one can't avoid going to the respective chain ends
561 anyway. We might save the second run through when the chain ends
562 differ, but keeping track of the ends makes the loop more costly.
569 for (c = this; c; ++balance)
570 c = c->dim_cache_[a].parent_;
572 for (d = s; d; --balance)
573 d = d->dim_cache_[a].parent_;
575 /* Cut down ancestry to same size */
577 for (c = this; balance > 0; --balance)
578 c = c->dim_cache_[a].parent_;
580 for (d = s; balance < 0; ++balance)
581 d = d->dim_cache_[a].parent_;
583 /* Now find point where our lineages converge */
586 c = c->dim_cache_[a].parent_;
587 d = d->dim_cache_[a].parent_;
594 Grob::set_parent (Grob *g, Axis a)
596 dim_cache_[a].parent_ = g;
600 Grob::get_parent (Axis a) const
602 return dim_cache_[a].parent_;
606 Grob::fixup_refpoint ()
608 for (int a = X_AXIS; a < NO_AXES; a++)
611 Grob *parent = get_parent (ax);
616 if (parent->get_system () != get_system () && get_system ())
618 Grob *newparent = parent->find_broken_piece (get_system ());
619 set_parent (newparent, ax);
622 if (Item *i = dynamic_cast<Item *> (this))
624 Item *parenti = dynamic_cast<Item *> (parent);
628 Direction my_dir = i->break_status_dir ();
629 if (my_dir != parenti->break_status_dir ())
631 Item *newparent = parenti->find_prebroken_piece (my_dir);
632 set_parent (newparent, ax);
639 /****************************************************************
641 ****************************************************************/
644 get_maybe_root_vertical_alignment (Grob *g, Grob *maybe)
648 if (has_interface<Align_interface> (g))
649 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), g);
650 return get_maybe_root_vertical_alignment (g->get_parent (Y_AXIS), maybe);
655 Grob::get_root_vertical_alignment (Grob *g)
657 return get_maybe_root_vertical_alignment (g, 0);
661 Grob::get_vertical_axis_group (Grob *g)
665 if (!g->get_parent (Y_AXIS))
667 if (has_interface<Axis_group_interface> (g)
668 && has_interface<Align_interface> (g->get_parent (Y_AXIS)))
670 return get_vertical_axis_group (g->get_parent (Y_AXIS));
675 Grob::get_vertical_axis_group_index (Grob *g)
677 Grob *val = get_root_vertical_alignment (g);
680 Grob *vax = get_vertical_axis_group (g);
681 extract_grob_set (val, "elements", elts);
682 for (vsize i = 0; i < elts.size (); i++)
685 g->programming_error ("could not find this grob's vertical axis group in the vertical alignment");
690 Grob::vertical_less (Grob *g1, Grob *g2)
692 return internal_vertical_less (g1, g2, false);
696 Grob::pure_vertical_less (Grob *g1, Grob *g2)
698 return internal_vertical_less (g1, g2, true);
702 Grob::internal_vertical_less (Grob *g1, Grob *g2, bool pure)
704 Grob *vag = get_root_vertical_alignment (g1);
707 g1->programming_error ("grob does not belong to a VerticalAlignment?");
711 Grob *ag1 = get_vertical_axis_group (g1);
712 Grob *ag2 = get_vertical_axis_group (g2);
714 extract_grob_set (vag, "elements", elts);
716 if (ag1 == ag2 && !pure)
718 Grob *common = g1->common_refpoint (g2, Y_AXIS);
719 return g1->relative_coordinate (common, Y_AXIS) > g2->relative_coordinate (common, Y_AXIS);
722 for (vsize i = 0; i < elts.size (); i++)
730 g1->programming_error ("could not place this grob in its axis group");
734 /****************************************************************
736 ****************************************************************/
738 Grob::programming_error (const string &s) const
740 SCM cause = self_scm ();
741 while (Grob *g = unsmob<Grob> (cause))
742 cause = g->get_property ("cause");
744 /* ES TODO: cause can't be Music*/
745 if (Music *m = unsmob<Music> (cause))
746 m->origin ()->programming_error (s);
747 else if (Stream_event *ev = unsmob<Stream_event> (cause))
748 ev->origin ()->programming_error (s);
750 ::programming_error (s);
754 Grob::warning (const string &s) const
756 SCM cause = self_scm ();
757 while (Grob *g = unsmob<Grob> (cause))
758 cause = g->get_property ("cause");
760 /* ES TODO: cause can't be Music*/
761 if (Music *m = unsmob<Music> (cause))
762 m->origin ()->warning (s);
763 else if (Stream_event *ev = unsmob<Stream_event> (cause))
764 ev->origin ()->warning (s);
772 SCM meta = get_property ("meta");
773 SCM nm = scm_assq (ly_symbol2scm ("name"), meta);
774 nm = (scm_is_pair (nm)) ? scm_cdr (nm) : SCM_EOL;
775 return scm_is_symbol (nm) ? ly_symbol2string (nm) : class_name ();
779 "A grob represents a piece of music notation.\n"
781 "All grobs have an X and Y@tie{}position on the page. These"
782 " X and Y@tie{}positions are stored in a relative format, thus"
783 " they can easily be combined by stacking them, hanging one"
784 " grob to the side of another, or coupling them into grouping"
787 "Each grob has a reference point (a.k.a.@: parent): The"
788 " position of a grob is stored relative to that reference"
789 " point. For example, the X@tie{}reference point of a staccato"
790 " dot usually is the note head that it applies to. When the"
791 " note head is moved, the staccato dot moves along"
794 "A grob is often associated with a symbol, but some grobs do"
795 " not print any symbols. They take care of grouping objects."
796 " For example, there is a separate grob that stacks staves"
797 " vertically. The @ref{NoteCollision} object is also an"
798 " abstract grob: It only moves around chords, but doesn't print"
801 "Grobs have properties (Scheme variables) that can be read and"
802 " set. Two types of them exist: immutable and mutable."
803 " Immutable variables define the default style and behavior."
804 " They are shared between many objects. They can be changed"
805 " using @code{\\override} and @code{\\revert}. Mutable"
806 " properties are variables that are specific to one grob."
807 " Typically, lists of other objects, or results from"
808 " computations are stored in mutable properties. In"
809 " particular, every call to @code{ly:grob-set-property!}"
810 " (or its C++ equivalent) sets a mutable property.\n"
812 "The properties @code{after-line-breaking} and"
813 " @code{before-line-breaking} are dummies that are not"
814 " user-serviceable.",
821 "after-line-breaking "
823 "axis-group-parent-X "
824 "axis-group-parent-Y "
825 "before-line-breaking "
833 "horizontal-skylines "
839 "parenthesis-friends "
840 "pure-Y-offset-in-progress "
842 "skyline-horizontal-padding "
852 /****************************************************************
854 ****************************************************************/
857 grob_stencil_extent (Grob *me, Axis a)
859 Stencil *m = me->get_stencil ();
863 return ly_interval2scm (e);
866 MAKE_SCHEME_CALLBACK (Grob, stencil_height, 1);
868 Grob::stencil_height (SCM smob)
870 Grob *me = unsmob<Grob> (smob);
871 return grob_stencil_extent (me, Y_AXIS);
874 MAKE_SCHEME_CALLBACK (Grob, pure_stencil_height, 3);
876 Grob::pure_stencil_height (SCM smob, SCM /* beg */, SCM /* end */)
878 Grob *me = unsmob<Grob> (smob);
879 if (unsmob<Stencil> (me->get_property_data ("stencil")))
880 return grob_stencil_extent (me, Y_AXIS);
882 return ly_interval2scm (Interval ());
886 MAKE_SCHEME_CALLBACK (Grob, y_parent_positioning, 1);
888 Grob::y_parent_positioning (SCM smob)
890 Grob *me = unsmob<Grob> (smob);
891 Grob *par = me->get_parent (Y_AXIS);
893 (void) par->get_property ("positioning-done");
895 return scm_from_double (0.0);
898 MAKE_SCHEME_CALLBACK (Grob, x_parent_positioning, 1);
900 Grob::x_parent_positioning (SCM smob)
902 Grob *me = unsmob<Grob> (smob);
904 Grob *par = me->get_parent (X_AXIS);
906 (void) par->get_property ("positioning-done");
908 return scm_from_double (0.0);
911 MAKE_SCHEME_CALLBACK (Grob, stencil_width, 1);
913 Grob::stencil_width (SCM smob)
915 Grob *me = unsmob<Grob> (smob);
916 return grob_stencil_extent (me, X_AXIS);
920 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
922 for (; scm_is_pair (elist); elist = scm_cdr (elist))
923 if (Grob *s = unsmob<Grob> (scm_car (elist)))
926 common = common->common_refpoint (s, a);
935 common_refpoint_of_array (vector<Grob *> const &arr, Grob *common, Axis a)
937 for (vsize i = 0; i < arr.size (); i++)
939 common = common->common_refpoint (arr[i], a);
947 common_refpoint_of_array (set<Grob *> const &arr, Grob *common, Axis a)
949 set<Grob *>::iterator it;
951 for (it = arr.begin (); it != arr.end (); it++)
953 common = common->common_refpoint (*it, a);
961 robust_relative_extent (Grob *me, Grob *refpoint, Axis a)
963 Interval ext = me->extent (refpoint, a);
965 ext.add_point (me->relative_coordinate (refpoint, a));
970 // Checks whether there is a vertical alignment in the chain of
971 // parents between this and commony.
973 Grob::check_cross_staff (Grob *commony)
975 if (has_interface<Align_interface> (commony))
978 for (Grob *g = this; g && g != commony; g = g->get_parent (Y_AXIS))
979 if (has_interface<Align_interface> (g))
987 indirect_less (Grob **a, Grob **b)
989 // Use original order as tie breaker. That gives us a stable sort
990 // at the lower price tag of an unstable one, and we want a stable
991 // sort in order to reliably retain the first instance of a grob
993 return *a < *b || (*a == *b && a < b);
998 indirect_eq (Grob **a, Grob **b)
1005 direct_less (Grob **a, Grob **b)
1010 // uniquify uniquifies on the memory addresses of the Grobs, but then
1011 // uses the original order. This makes results independent from the
1012 // memory allocation of Grobs.
1015 uniquify (vector <Grob *> & grobs)
1017 vector <Grob **> vec (grobs.size ());
1018 for (vsize i = 0; i < grobs.size (); i++)
1020 vector_sort (vec, indirect_less);
1021 vec.erase (unique (vec.begin (), vec.end (), indirect_eq), vec.end ());
1022 vector_sort (vec, direct_less);
1024 // Since the output is a sorted copy of the input with some elements
1025 // removed, we can fill in the vector in-place if we do it starting
1027 for (vsize i = 0; i < vec.size (); i++)
1029 grobs.erase (grobs.begin () + vec.size (), grobs.end ());