2 grob.cc -- implement Grob
4 source file of the GNU LilyPond music typesetter
6 (c) 1997--2002 Han-Wen Nienhuys <hanwen@cs.uu.nl>
14 #include "input-smob.hh"
16 #include "group-interface.hh"
18 #include "paper-score.hh"
19 #include "paper-def.hh"
20 #include "molecule.hh"
26 #include "paper-column.hh"
27 #include "molecule.hh"
29 #include "paper-outputter.hh"
33 #include "ly-smobs.icc"
38 remove dynamic_cast<Spanner,Item> and put this code into respective
43 #define INFINITY_MSG "Infinity or NaN encountered"
45 Grob::Grob (SCM basicprops)
48 fixme: default should be no callback.
54 immutable_property_alist_ = basicprops;
55 mutable_property_alist_ = SCM_EOL;
58 We do smobify_self() as the first step. Since the object lives on
59 the heap, none of its SCM variables are protected from GC. After
60 smobify_self(), they are.
65 SCM meta = get_grob_property ("meta");
68 SCM ifs = scm_assoc (ly_symbol2scm ("interfaces"), meta);
71 do it directly to bypass interface checks.
73 mutable_property_alist_ = gh_cons (gh_cons (ly_symbol2scm ("interfaces"),
75 mutable_property_alist_);
81 destill this into a function, so we can re-init the immutable
82 properties with a new BASICPROPS value after creation. Convenient
83 eg. when using \override with StaffSymbol. */
85 char const*onames[] = {"X-offset-callbacks", "Y-offset-callbacks"};
86 char const*enames[] = {"X-extent-callback", "Y-extent-callback"};
88 for (int a = X_AXIS; a <= Y_AXIS; a++)
90 SCM l = get_grob_property (onames[a]);
92 if (scm_ilength (l) >=0)
94 dim_cache_[a].offset_callbacks_ = l;
95 dim_cache_[a].offsets_left_ = scm_ilength (l);
99 programming_error ("[XY]-offset-callbacks must be a list");
102 SCM cb = get_grob_property (enames[a]);
105 Should change default to be empty?
108 && !gh_procedure_p (cb) && !gh_pair_p (cb)
109 && gh_procedure_p (get_grob_property ("molecule-callback"))
111 cb = molecule_extent_proc;
113 dim_cache_[a].dimension_ = cb;
118 Grob::Grob (Grob const&s)
119 : dim_cache_ (s.dim_cache_)
121 original_l_ = (Grob*) &s;
122 immutable_property_alist_ = s.immutable_property_alist_;
123 mutable_property_alist_ = SCM_EOL;
126 No properties are copied. That is the job of handle_broken_dependencies.
129 status_c_ = s.status_c_;
130 pscore_l_ = s.pscore_l_;
140 do nothing scm-ish and no unprotecting here.
146 extern void check_interfaces_for_property (Grob const *me, SCM sym);
149 Grob::internal_set_grob_property (SCM s, SCM v)
152 if (internal_type_checking_global_b)
154 assert (type_check_assignment (s, v, ly_symbol2scm ("backend-type?")));
155 check_interfaces_for_property(this, s);
160 mutable_property_alist_ = scm_assq_set_x (mutable_property_alist_, s, v);
165 Grob::internal_get_grob_property (SCM sym) const
167 SCM s = scm_sloppy_assq (sym, mutable_property_alist_);
171 s = scm_sloppy_assq (sym, immutable_property_alist_);
174 if (internal_type_checking_global_b && gh_pair_p (s))
176 assert (type_check_assignment (sym, gh_cdr (s), ly_symbol2scm ("backend-type?")));
177 check_interfaces_for_property(this, sym);
181 return (s == SCM_BOOL_F) ? SCM_EOL : ly_cdr (s);
185 Remove the value associated with KEY, and return it. The result is
186 that a next call will yield SCM_EOL (and not the underlying
190 Grob::remove_grob_property (const char* key)
192 SCM val = get_grob_property (key);
194 set_grob_property (key, SCM_EOL);
200 MAKE_SCHEME_CALLBACK (Grob,molecule_extent,2);
202 Grob::molecule_extent (SCM element_smob, SCM scm_axis)
204 Grob *s = unsmob_grob (element_smob);
205 Axis a = (Axis) gh_scm2int (scm_axis);
207 Molecule *m = s->get_molecule ();
211 return ly_interval2scm (e);
214 MAKE_SCHEME_CALLBACK (Grob,preset_extent,2);
217 Grob::preset_extent (SCM element_smob, SCM scm_axis)
219 Grob *s = unsmob_grob (element_smob);
220 Axis a = (Axis) gh_scm2int (scm_axis);
222 SCM ext = s->get_grob_property ((a == X_AXIS)
228 Real l = gh_scm2double (ly_car (ext));
229 Real r = gh_scm2double (ly_cdr (ext));
230 return ly_interval2scm (Interval (l, r));
233 return ly_interval2scm (Interval ());
239 Grob::paper_l () const
241 return pscore_l_ ? pscore_l_->paper_l_ : 0;
245 Grob::calculate_dependencies (int final, int busy, SCM funcname)
247 if (status_c_ >= final)
250 if (status_c_== busy)
252 programming_error ("Element is busy, come back later");
258 for (SCM d = get_grob_property ("dependencies"); gh_pair_p (d);
261 unsmob_grob (ly_car (d))
262 ->calculate_dependencies (final, busy, funcname);
266 SCM proc = internal_get_grob_property (funcname);
267 if (gh_procedure_p (proc))
268 gh_call1 (proc, this->self_scm ());
274 Grob::get_molecule () const
276 if (immutable_property_alist_ == SCM_EOL)
282 SCM mol = get_grob_property ("molecule");
283 if (unsmob_molecule (mol))
284 return unsmob_molecule (mol);
286 mol = get_uncached_molecule ();
288 Grob *me = (Grob*)this;
289 me->set_grob_property ("molecule", mol);
291 return unsmob_molecule (mol);
294 Grob::get_uncached_molecule ()const
296 SCM proc = get_grob_property ("molecule-callback");
299 if (gh_procedure_p (proc))
300 mol = gh_apply (proc, scm_list_n (this->self_scm (), SCM_UNDEFINED));
303 Molecule *m = unsmob_molecule (mol);
305 if (unsmob_molecule (mol))
307 SCM origin = ly_symbol2scm ("no-origin");
309 if (store_locations_global_b){
310 SCM cause = get_grob_property ("cause");
311 if (Music*m = unsmob_music (cause))
313 SCM music_origin = m->get_mus_property ("origin");
314 if (unsmob_input (music_origin))
315 origin = music_origin;
321 mol = Molecule (m->extent_box (),
322 scm_list_n (origin, m->get_expr (), SCM_UNDEFINED)
325 m = unsmob_molecule (mol);
329 transparent retains dimensions of element.
331 if (m && to_boolean (get_grob_property ("transparent")))
332 mol = Molecule (m->extent_box (), SCM_EOL).smobbed_copy ();
343 Grob::do_break_processing ()
353 Grob::line_l () const
359 Grob::add_dependency (Grob*e)
363 Pointer_group_interface::add_grob (this, ly_symbol2scm ("dependencies"),e);
366 programming_error ("Null dependency added");
373 Do break substitution in S, using CRITERION. Return new value.
374 CRITERION is either a SMOB pointer to the desired line, or a number
375 representing the break direction. Do not modify SRC.
377 It is rather tightly coded, since it takes a lot of time; it is
378 one of the top functions in the profile.
380 We don't pass break_criterion as a parameter, since it is
381 `constant', but takes up stack space.
386 static SCM break_criterion;
388 set_break_subsititution (SCM criterion)
390 break_criterion = criterion;
394 do_break_substitution (SCM src)
397 Grob *sc = unsmob_grob (src);
400 if (SCM_INUMP (break_criterion))
402 Item * i = dynamic_cast<Item*> (sc);
403 Direction d = to_dir (break_criterion);
404 if (i && i->break_status_dir () != d)
406 Item *br = i->find_prebroken_piece (d);
407 return (br) ? br->self_scm () : SCM_UNDEFINED;
413 = dynamic_cast<System*> (unsmob_grob (break_criterion));
414 if (sc->line_l () != line)
416 sc = sc->find_broken_piece (line);
420 /* now: !sc || (sc && sc->line_l () == line) */
422 return SCM_UNDEFINED;
424 /* now: sc && sc->line_l () == line */
426 return sc->self_scm();
428 This was introduced in 1.3.49 as a measure to prevent
429 programming errors. It looks expensive (?).
433 benchmark , document when (what kind of programming
434 errors) this happens.
436 if (sc->common_refpoint (line, X_AXIS)
437 && sc->common_refpoint (line, Y_AXIS))
439 return sc->self_scm ();
441 return SCM_UNDEFINED;
444 else if (ly_pair_p (src)) // SCM_CONSP (src)) // huh?
446 SCM oldcar =ly_car (src);
448 UGH! breaks on circular lists.
450 SCM newcar = do_break_substitution (oldcar);
451 SCM oldcdr = ly_cdr (src);
453 if (newcar == SCM_UNDEFINED
454 && (gh_pair_p (oldcdr) || oldcdr == SCM_EOL))
457 This is tail-recursion, ie.
459 return do_break_substution (cdr, break_criterion);
461 We don't want to rely on the compiler to do this. Without
462 tail-recursion, this easily crashes with a stack overflow. */
467 SCM newcdr = do_break_substitution (oldcdr);
468 return scm_cons (newcar, newcdr);
477 Grob::handle_broken_dependencies ()
479 Spanner * s= dynamic_cast<Spanner*> (this);
480 if (original_l_ && s)
485 for (int i = 0; i< s->broken_into_l_arr_ .size (); i++)
487 Grob * sc = s->broken_into_l_arr_[i];
488 System * l = sc->line_l ();
490 set_break_subsititution (l ? l->self_scm () : SCM_UNDEFINED);
491 sc->mutable_property_alist_ =
492 do_break_substitution (mutable_property_alist_);
498 System *line = line_l ();
500 if (line && common_refpoint (line, X_AXIS) && common_refpoint (line, Y_AXIS))
502 set_break_subsititution (line ? line->self_scm () : SCM_UNDEFINED);
503 mutable_property_alist_ = do_break_substitution (mutable_property_alist_);
505 else if (dynamic_cast <System*> (this))
507 set_break_subsititution (SCM_UNDEFINED);
508 mutable_property_alist_ = do_break_substitution (mutable_property_alist_);
513 This element is `invalid'; it has been removed from all
514 dependencies, so let's junk the element itself.
516 do not do this for System, since that would remove
517 references to the originals of score-grobs, which get then GC'd
525 Note that we still want references to this element to be
526 rearranged, and not silently thrown away, so we keep pointers
527 like {broken_into_{drul,array}, original}
532 mutable_property_alist_ = SCM_EOL;
533 immutable_property_alist_ = SCM_EOL;
535 set_extent (SCM_EOL, Y_AXIS);
536 set_extent (SCM_EOL, X_AXIS);
538 for (int a= X_AXIS; a <= Y_AXIS; a++)
540 dim_cache_[a].offset_callbacks_ = SCM_EOL;
541 dim_cache_[a].offsets_left_ = 0;
546 Grob::handle_prebroken_dependencies ()
551 Grob::find_broken_piece (System*) const
557 translate in one direction
560 Grob::translate_axis (Real y, Axis a)
562 if (isinf (y) || isnan (y))
563 programming_error (_ (INFINITY_MSG));
566 dim_cache_[a].offset_ += y;
572 Find the offset relative to D. If D equals THIS, then it is 0.
573 Otherwise, it recursively defd as
575 OFFSET_ + PARENT_L_->relative_coordinate (D)
578 Grob::relative_coordinate (Grob const*refp, Axis a) const
584 We catch PARENT_L_ == nil case with this, but we crash if we did
585 not ask for the absolute coordinate (ie. REFP == nil.)
588 if (refp == dim_cache_[a].parent_l_)
589 return get_offset (a);
591 return get_offset (a) + dim_cache_[a].parent_l_->relative_coordinate (refp, a);
597 Invoke callbacks to get offset relative to parent.
600 Grob::get_offset (Axis a) const
602 Grob *me = (Grob*) this;
603 while (dim_cache_[a].offsets_left_)
605 int l = --me->dim_cache_[a].offsets_left_;
606 SCM cb = scm_list_ref (dim_cache_[a].offset_callbacks_, gh_int2scm (l));
607 SCM retval = gh_call2 (cb, self_scm (), gh_int2scm (a));
609 Real r = gh_scm2double (retval);
610 if (isinf (r) || isnan (r))
612 programming_error (INFINITY_MSG);
615 me->dim_cache_[a].offset_ +=r;
617 return dim_cache_[a].offset_;
621 MAKE_SCHEME_CALLBACK (Grob,point_dimension_callback,2);
623 Grob::point_dimension_callback (SCM , SCM)
625 return ly_interval2scm (Interval (0,0));
629 Grob::empty_b (Axis a)const
631 return ! (gh_pair_p (dim_cache_[a].dimension_) ||
632 gh_procedure_p (dim_cache_[a].dimension_));
636 Grob::extent (Grob * refp, Axis a) const
638 Real x = relative_coordinate (refp, a);
641 Dimension_cache * d = (Dimension_cache *)&dim_cache_[a];
643 if (gh_pair_p (d->dimension_))
645 else if (gh_procedure_p (d->dimension_))
648 FIXME: add doco on types, and should typecheck maybe?
650 d->dimension_= gh_call2 (d->dimension_, self_scm (), gh_int2scm (a));
655 if (!gh_pair_p (d->dimension_))
658 ext = ly_scm2interval (d->dimension_);
660 SCM extra = get_grob_property (a == X_AXIS
667 if (gh_pair_p (extra))
669 ext[BIGGER] += gh_scm2double (ly_cdr (extra));
670 ext[SMALLER] += gh_scm2double (ly_car (extra));
673 extra = get_grob_property (a == X_AXIS
675 : "minimum-extent-Y");
676 if (gh_pair_p (extra))
678 ext.unite (Interval (gh_scm2double (ly_car (extra)),
679 gh_scm2double (ly_cdr (extra))));
688 Find the group-element which has both #this# and #s#
691 Grob::common_refpoint (Grob const* s, Axis a) const
694 I don't like the quadratic aspect of this code, but I see no other
695 way. The largest chain of parents might be 10 high or so, so
696 it shouldn't be a real issue. */
697 for (Grob const *c = this; c; c = c->dim_cache_[a].parent_l_)
698 for (Grob const * d = s; d; d = d->dim_cache_[a].parent_l_)
707 common_refpoint_of_list (SCM elist, Grob *common, Axis a)
709 for (; gh_pair_p (elist); elist = ly_cdr (elist))
711 Grob * s = unsmob_grob (ly_car (elist));
715 common = common->common_refpoint (s, a);
726 common_refpoint_of_array (Link_array<Grob> const &arr, Grob *common, Axis a)
728 for (int i = arr.size() ; i--; )
735 common = common->common_refpoint (s, a);
746 SCM meta = get_grob_property ("meta");
747 SCM nm = scm_assoc (ly_symbol2scm ("name"), meta);
748 nm = (gh_pair_p (nm)) ? ly_cdr (nm) : SCM_EOL;
749 return gh_symbol_p (nm) ? ly_symbol2string (nm) : classname (this);
753 Grob::add_offset_callback (SCM cb, Axis a)
755 if (!has_offset_callback_b (cb, a))
757 dim_cache_[a].offset_callbacks_ = gh_cons (cb, dim_cache_[a].offset_callbacks_);
758 dim_cache_[a].offsets_left_ ++;
763 Grob::has_extent_callback_b (SCM cb, Axis a)const
765 return scm_equal_p (cb, dim_cache_[a].dimension_) == SCM_BOOL_T;
770 Grob::has_offset_callback_b (SCM cb, Axis a)const
772 return scm_memq (cb, dim_cache_[a].offset_callbacks_) != SCM_BOOL_F;
776 Grob::set_extent (SCM dc, Axis a)
778 dim_cache_[a].dimension_ =dc;
782 Grob::set_parent (Grob *g, Axis a)
784 dim_cache_[a].parent_l_ = g;
787 MAKE_SCHEME_CALLBACK (Grob,fixup_refpoint,1);
789 Grob::fixup_refpoint (SCM smob)
791 Grob *me = unsmob_grob (smob);
792 for (int a = X_AXIS; a < NO_AXES; a ++)
795 Grob * parent = me->get_parent (ax);
800 if (parent->line_l () != me->line_l () && me->line_l ())
802 Grob * newparent = parent->find_broken_piece (me->line_l ());
803 me->set_parent (newparent, ax);
806 if (Item * i = dynamic_cast<Item*> (me))
808 Item *parenti = dynamic_cast<Item*> (parent);
812 Direction my_dir = i->break_status_dir () ;
813 if (my_dir!= parenti->break_status_dir ())
815 Item *newparent = parenti->find_prebroken_piece (my_dir);
816 me->set_parent (newparent, ax);
825 Grob::warning (String s)const
827 SCM cause = self_scm();
828 while (cause != SCM_EOL && !unsmob_music (cause))
830 Grob * g = unsmob_grob (cause);
831 cause = g->get_grob_property ("cause");
834 if (Music *m = unsmob_music (cause))
836 m->origin()->warning (s);
843 Grob::programming_error (String s)const
845 s = "Programming error: " + s;
850 /****************************************************
852 ****************************************************/
856 IMPLEMENT_SMOBS (Grob);
857 IMPLEMENT_DEFAULT_EQUAL_P (Grob);
860 Grob::mark_smob (SCM ses)
862 Grob * s = (Grob*) SCM_CELL_WORD_1 (ses);
863 scm_gc_mark (s->immutable_property_alist_);
864 scm_gc_mark (s->mutable_property_alist_);
866 for (int a =0 ; a < 2; a++)
868 scm_gc_mark (s->dim_cache_[a].offset_callbacks_);
869 scm_gc_mark (s->dim_cache_[a].dimension_);
870 Grob *p = s->get_parent (Y_AXIS);
872 scm_gc_mark (p->self_scm ());
876 scm_gc_mark (s->original_l_->self_scm ());
878 return s->do_derived_mark ();
882 Grob::print_smob (SCM s, SCM port, scm_print_state *)
884 Grob *sc = (Grob *) ly_cdr (s);
886 scm_puts ("#<Grob ", port);
887 scm_puts ((char *)sc->name ().ch_C (), port);
890 don't try to print properties, that is too much hassle.
892 scm_puts (" >", port);
897 Grob::do_derived_mark ()
902 LY_DEFINE(ly_set_grob_property,"ly-set-grob-property", 3, 0, 0,
903 (SCM grob, SCM sym, SCM val),
905 Set @var{sym} in grob @var{grob} to value @var{val}")
907 Grob * sc = unsmob_grob (grob);
908 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
909 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
911 if (!type_check_assignment (sym, val, ly_symbol2scm ("backend-type?")))
912 error ("typecheck failed");
914 sc->internal_set_grob_property (sym, val);
915 return SCM_UNSPECIFIED;
918 LY_DEFINE(ly_get_grob_property,
919 "ly-get-grob-property", 2, 0, 0, (SCM grob, SCM sym),
920 " Get the value of a value in grob @var{g} of property @var{sym}. It
921 will return @code{'()} (end-of-list) if @var{g} doesn't have @var{sym} set.
924 Grob * sc = unsmob_grob (grob);
925 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
926 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
928 return sc->internal_get_grob_property (sym);
933 Grob::discretionary_processing ()
938 LY_DEFINE(spanner_get_bound, "ly-get-spanner-bound", 2 , 0, 0,
940 "Get one of the bounds of @var{spanner}. @var{dir} may be @code{-1} for
941 left, and @code{1} for right.
944 Spanner * sl = dynamic_cast<Spanner*> (unsmob_grob (slur));
945 SCM_ASSERT_TYPE(sl, slur, SCM_ARG1, __FUNCTION__, "spanner grob");
946 SCM_ASSERT_TYPE(ly_dir_p (dir), slur, SCM_ARG2, __FUNCTION__, "dir");
947 return sl->get_bound (to_dir (dir))->self_scm ();
950 LY_DEFINE(ly_get_paper_var,"ly-get-paper-variable", 2, 0, 0,
952 "Get a variable from the \\paper block.")
954 Grob * sc = unsmob_grob (grob);
955 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
956 SCM_ASSERT_TYPE(gh_symbol_p(sym), sym, SCM_ARG2, __FUNCTION__, "symbol");
958 return sc->paper_l() ->get_scmvar_scm (sym);
963 LY_DEFINE(ly_get_extent, "ly-get-extent", 3, 0, 0,
964 (SCM grob, SCM refp, SCM axis),
965 "Get the extent in @var{axis} direction of @var{grob} relative to the
968 Grob * sc = unsmob_grob (grob);
969 Grob * ref = unsmob_grob (refp);
970 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
971 SCM_ASSERT_TYPE(ref, refp, SCM_ARG2, __FUNCTION__, "grob");
973 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG3, __FUNCTION__, "axis");
975 return ly_interval2scm ( sc->extent (ref, Axis (gh_scm2int (axis))));
978 LY_DEFINE (ly_get_parent, "ly-get-parent", 2, 0, 0, (SCM grob, SCM axis),
979 "Get the parent of @var{grob}. @var{axis} can be 0 for the X-axis, 1
982 Grob * sc = unsmob_grob (grob);
983 SCM_ASSERT_TYPE(sc, grob, SCM_ARG1, __FUNCTION__, "grob");
984 SCM_ASSERT_TYPE(ly_axis_p(axis), axis, SCM_ARG2, __FUNCTION__, "axis");
986 return sc->get_parent (Axis (gh_scm2int (axis)))->self_scm();
991 Grob::internal_has_interface (SCM k)
993 SCM ifs = get_grob_property ("interfaces");
995 return scm_memq (k, ifs) != SCM_BOOL_F;
998 IMPLEMENT_TYPE_P (Grob, "ly-grob?");
1000 ADD_INTERFACE (Grob, "grob-interface",
1001 "All grobs support this",
1002 "X-offset-callbacks Y-offset-callbacks X-extent-callback molecule cause
1003 Y-extent-callback molecule-callback extra-offset
1005 staff-symbol interfaces dependencies extra-extent-X causes meta
1006 layer before-line-breaking-callback after-line-breaking-callback extra-extent-Y minimum-extent-X minimum-extent-Y transparent");