]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
Fix segfault triggered by invalid Stem 'details settings.
[lilypond.git] / lily / stem.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1996--2010 Han-Wen Nienhuys <hanwen@xs4all.nl>
5   Jan Nieuwenhuizen <janneke@gnu.org>
6
7   TODO: This is way too hairy
8
9   TODO: fix naming.
10
11   Stem-end, chord-start, etc. is all confusing naming.
12
13   LilyPond is free software: you can redistribute it and/or modify
14   it under the terms of the GNU General Public License as published by
15   the Free Software Foundation, either version 3 of the License, or
16   (at your option) any later version.
17
18   LilyPond is distributed in the hope that it will be useful,
19   but WITHOUT ANY WARRANTY; without even the implied warranty of
20   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21   GNU General Public License for more details.
22
23   You should have received a copy of the GNU General Public License
24   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
25 */
26
27 #include "stem.hh"
28 #include "spanner.hh"
29
30 #include <cmath>                // rint
31 using namespace std;
32
33 #include "beam.hh"
34 #include "directional-element-interface.hh"
35 #include "dot-column.hh"
36 #include "font-interface.hh"
37 #include "international.hh"
38 #include "lookup.hh"
39 #include "misc.hh"
40 #include "note-head.hh"
41 #include "output-def.hh"
42 #include "paper-column.hh"
43 #include "pointer-group-interface.hh"
44 #include "rest.hh"
45 #include "rhythmic-head.hh"
46 #include "side-position-interface.hh"
47 #include "staff-symbol-referencer.hh"
48 #include "stem-tremolo.hh"
49 #include "warn.hh"
50
51 void
52 Stem::set_beaming (Grob *me, int beam_count, Direction d)
53 {
54   SCM pair = me->get_property ("beaming");
55
56   if (!scm_is_pair (pair))
57     {
58       pair = scm_cons (SCM_EOL, SCM_EOL);
59       me->set_property ("beaming", pair);
60     }
61
62   SCM lst = index_get_cell (pair, d);
63   if (beam_count)
64     for (int i = 0; i < beam_count; i++)
65       lst = scm_cons (scm_from_int (i), lst);
66   else
67     lst = SCM_BOOL_F;
68   
69   index_set_cell (pair, d, lst);
70 }
71
72 int
73 Stem::get_beaming (Grob *me, Direction d)
74 {
75   SCM pair = me->get_property ("beaming");
76   if (!scm_is_pair (pair))
77     return 0;
78
79   SCM lst = index_get_cell (pair, d);
80
81   int len = scm_ilength (lst);
82   return max (len, 0);
83 }
84
85 Interval
86 Stem::head_positions (Grob *me)
87 {
88   if (head_count (me))
89     {
90       Drul_array<Grob *> e (extremal_heads (me));
91       return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
92                        Staff_symbol_referencer::get_position (e[UP]));
93     }
94   return Interval ();
95 }
96
97 Real
98 Stem::chord_start_y (Grob *me)
99 {
100   Interval hp = head_positions (me);
101   if (!hp.is_empty ())
102     return hp[get_grob_direction (me)] * Staff_symbol_referencer::staff_space (me)
103       * 0.5;
104   return 0;
105 }
106
107
108
109 void
110 Stem::set_stemend (Grob *me, Real se)
111 {
112   // todo: margins
113   Direction d = get_grob_direction (me);
114
115   if (d && d * head_positions (me)[get_grob_direction (me)] >= se * d)
116     me->warning (_ ("weird stem size, check for narrow beams"));
117
118   me->set_property ("stem-end-position", scm_from_double (se));
119 }
120
121 /* Note head that determines hshift for upstems
122    WARNING: triggers direction  */
123 Grob *
124 Stem::support_head (Grob *me)
125 {
126   extract_grob_set (me, "note-heads", heads);
127   if (heads.size () == 1)
128     return heads[0];
129
130   return first_head (me);
131 }
132
133 int
134 Stem::head_count (Grob *me)
135 {
136   return Pointer_group_interface::count (me, ly_symbol2scm ("note-heads"));
137 }
138
139 /* The note head which forms one end of the stem.
140    WARNING: triggers direction  */
141 Grob *
142 Stem::first_head (Grob *me)
143 {
144   Direction d = get_grob_direction (me);
145   if (d)
146     return extremal_heads (me)[-d];
147   return 0;
148 }
149
150 /* The note head opposite to the first head.  */
151 Grob *
152 Stem::last_head (Grob *me)
153 {
154   Direction d = get_grob_direction (me);
155   if (d)
156     return extremal_heads (me)[d];
157   return 0;
158 }
159
160 /*
161   START is part where stem reaches `last' head.
162
163   This function returns a drul with (bottom-head, top-head).
164 */
165 Drul_array<Grob *>
166 Stem::extremal_heads (Grob *me)
167 {
168   const int inf = INT_MAX;
169   Drul_array<int> extpos;
170   extpos[DOWN] = inf;
171   extpos[UP] = -inf;
172
173   Drul_array<Grob *> exthead (0, 0);
174   extract_grob_set (me, "note-heads", heads);
175
176   for (vsize i = heads.size (); i--;)
177     {
178       Grob *n = heads[i];
179       int p = Staff_symbol_referencer::get_rounded_position (n);
180
181       Direction d = LEFT;
182       do
183         {
184           if (d * p > d * extpos[d])
185             {
186               exthead[d] = n;
187               extpos[d] = p;
188             }
189         }
190       while (flip (&d) != DOWN);
191     }
192   return exthead;
193 }
194
195 /* The positions, in ascending order.  */
196 vector<int>
197 Stem::note_head_positions (Grob *me)
198 {
199   vector<int> ps;
200   extract_grob_set (me, "note-heads", heads);
201
202   for (vsize i = heads.size (); i--;)
203     {
204       Grob *n = heads[i];
205       int p = Staff_symbol_referencer::get_rounded_position (n);
206
207       ps.push_back (p);
208     }
209
210   vector_sort (ps, less<int> ());
211   return ps;
212 }
213
214 void
215 Stem::add_head (Grob *me, Grob *n)
216 {
217   n->set_object ("stem", me->self_scm ());
218
219   if (Note_head::has_interface (n))
220     Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
221   else if (Rest::has_interface (n))
222     Pointer_group_interface::add_grob (me, ly_symbol2scm ("rests"), n);
223 }
224
225 bool
226 Stem::is_invisible (Grob *me)
227 {
228   return !is_normal_stem (me)
229     && (robust_scm2double (me->get_property ("stemlet-length"),
230                            0.0) == 0.0);
231 }
232
233
234 bool
235 Stem::is_normal_stem (Grob *me)
236 {
237   return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
238 }
239
240
241 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
242 SCM
243 Stem::pure_height (SCM smob,
244                    SCM /* start */,
245                    SCM /* end */)
246 {
247   Grob *me = unsmob_grob (smob);
248   Interval iv;
249
250   if (!is_normal_stem (me))
251     return ly_interval2scm (iv);
252
253   Real ss = Staff_symbol_referencer::staff_space (me);
254   Real rad = Staff_symbol_referencer::staff_radius (me);
255
256   if (!to_boolean (me->get_property ("cross-staff")))
257     {
258       Real len = scm_to_double (calc_length (smob)) * ss / 2;
259       Direction dir = get_grob_direction (me);
260
261       Interval hp = head_positions (me);
262       if (dir == UP)
263         iv = Interval (0, len);
264       else
265         iv = Interval (-len, 0);
266
267       if (!hp.is_empty ())
268         {
269           iv.translate (hp[dir] * ss / 2);
270           iv.add_point (hp[-dir] * ss / 2);
271         }
272
273       /* extend the stem (away from the head) to cover the staff */
274       if (dir == UP)
275         iv[UP] = max (iv[UP], rad * ss);
276       else
277         iv[DOWN] = min (iv[DOWN], -rad * ss);
278     }
279   else
280     iv = Interval (-rad * ss, rad * ss);
281
282   return ly_interval2scm (iv);
283 }
284
285 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
286 SCM
287 Stem::calc_stem_end_position (SCM smob)
288 {
289   Grob *me = unsmob_grob (smob);
290
291   if (!head_count (me))
292     return scm_from_double (0.0);
293
294   if (Grob *beam = get_beam (me))
295     {
296       (void) beam->get_property ("quantized-positions");
297       return me->get_property ("stem-end-position");
298     }
299   
300   vector<Real> a;
301
302   /* WARNING: IN HALF SPACES */
303   Real length = robust_scm2double (me->get_property ("length"), 7);
304
305   Direction dir = get_grob_direction (me);
306   Interval hp = head_positions (me);
307   Real stem_end = dir ? hp[dir] + dir * length : 0;
308
309   /* TODO: change name  to extend-stems to staff/center/'()  */
310   bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
311   if (!no_extend && dir * stem_end < 0)
312     stem_end = 0.0;
313
314   return scm_from_double (stem_end);
315 }
316
317 /* Length is in half-spaces (or: positions) here. */
318 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
319 SCM
320 Stem::calc_length (SCM smob)
321 {
322   Grob *me = unsmob_grob (smob);
323   
324   SCM details = me->get_property ("details");
325   int durlog = duration_log (me);
326
327   Real ss = Staff_symbol_referencer::staff_space (me);
328   Real length = 7;
329   SCM s = ly_assoc_get (ly_symbol2scm ("lengths"), details, SCM_EOL);
330   if (scm_is_pair (s))
331     length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
332
333   Direction dir = get_grob_direction (me);
334
335   /* Stems in unnatural (forced) direction should be shortened,
336      according to [Roush & Gourlay] */
337   Interval hp = head_positions (me);
338   if (dir && dir * hp[dir] >= 0)
339     {
340       SCM sshorten = ly_assoc_get (ly_symbol2scm ("stem-shorten"), details, SCM_EOL);
341       SCM scm_shorten = scm_is_pair (sshorten)
342         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
343       Real shorten = 2* robust_scm2double (scm_shorten, 0);
344
345       /* On boundary: shorten only half */
346       if (abs (head_positions (me)[dir]) <= 1)
347         shorten *= 0.5;
348
349       length -= shorten;
350     }
351
352   length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
353
354   /* Tremolo stuff.  */
355   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
356   if (t_flag && !unsmob_grob (me->get_object ("beam")))
357     {
358       /* Crude hack: add extra space if tremolo flag is there.
359
360       We can't do this for the beam, since we get into a loop
361       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
362
363       Real minlen = 1.0
364         + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
365
366       /* We don't want to add the whole extent of the flag because the trem
367          and the flag can overlap partly. beam_translation gives a good
368          approximation */
369       if (durlog >= 3)
370         {
371           Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
372           /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
373           minlen += 2 * (durlog - 1.5) * beam_trans;
374
375           /* up-stems need even a little more space to avoid collisions. This
376              needs to be in sync with the tremolo positioning code in
377              Stem_tremolo::print */
378           if (dir == UP)
379             minlen += beam_trans;
380         }
381       length = max (length, minlen + 1.0);
382     }
383   
384   return scm_from_double (length);
385 }
386 /* The log of the duration (Number of hooks on the flag minus two)  */
387 int
388 Stem::duration_log (Grob *me)
389 {
390   SCM s = me->get_property ("duration-log");
391   return (scm_is_number (s)) ? scm_to_int (s) : 2;
392 }
393
394 MAKE_SCHEME_CALLBACK (Stem, calc_positioning_done, 1);
395 SCM
396 Stem::calc_positioning_done (SCM smob)
397 {
398   Grob *me = unsmob_grob (smob);  
399   if (!head_count (me))
400     return SCM_BOOL_T;
401
402   me->set_property ("positioning-done", SCM_BOOL_T);
403   
404   extract_grob_set (me, "note-heads", ro_heads);
405   vector<Grob*> heads (ro_heads);
406   vector_sort (heads, position_less);
407   Direction dir = get_grob_direction (me);
408
409   if (dir < 0)
410     reverse (heads);
411
412   Real thick = thickness (me);
413
414   Grob *hed = support_head (me);
415   if (!dir)
416     {
417       programming_error ("Stem dir must be up or down.");
418       dir = UP;
419       set_grob_direction (me, dir);
420     }
421
422   bool is_harmonic_centered = false;
423   for (vsize i = 0; i < heads.size (); i++)
424     is_harmonic_centered = is_harmonic_centered 
425       || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
426   is_harmonic_centered = is_harmonic_centered && is_invisible (me);
427
428   Real w = hed->extent (hed, X_AXIS)[dir];
429   for (vsize i = 0; i < heads.size (); i++)
430     {
431       Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
432
433       if (is_harmonic_centered)
434         amount =
435           hed->extent (hed, X_AXIS).linear_combination (CENTER)
436           - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
437       
438       heads[i]->translate_axis (amount, X_AXIS);
439     }
440   bool parity = true;
441   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
442   for (vsize i = 1; i < heads.size (); i++)
443     {
444       Real p = Staff_symbol_referencer::get_position (heads[i]);
445       Real dy = fabs (lastpos- p);
446
447       /*
448         dy should always be 0.5, 0.0, 1.0, but provide safety margin
449         for rounding errors.
450       */
451       if (dy < 1.1)
452         {
453           if (parity)
454             {
455               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
456
457               Direction d = get_grob_direction (me);
458               /*
459                 Reversed head should be shifted ell-thickness, but this
460                 looks too crowded, so we only shift ell-0.5*thickness.
461
462                 This leads to assymetry: Normal heads overlap the
463                 stem 100% whereas reversed heads only overlaps the
464                 stem 50%
465               */
466
467               Real reverse_overlap = 0.5;
468               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
469                                         X_AXIS);
470
471               if (is_invisible (me))
472                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
473                                           X_AXIS);
474
475               /* TODO:
476
477               For some cases we should kern some more: when the
478               distance between the next or prev note is too large, we'd
479               get large white gaps, eg.
480
481               |
482               X|
483               |X  <- kern this.
484               |
485               X
486
487               */
488             }
489           parity = !parity;
490         }
491       else
492         parity = true;
493
494       lastpos = int (p);
495     }
496
497   return SCM_BOOL_T;
498 }
499
500 MAKE_SCHEME_CALLBACK (Stem, calc_direction, 1);
501 SCM
502 Stem::calc_direction (SCM smob)
503 {
504   Grob *me = unsmob_grob (smob);
505   Direction dir = CENTER;
506   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
507     {
508       SCM ignore_me = beam->get_property ("direction");
509       (void) ignore_me;
510       dir = get_grob_direction (me);
511     }
512   else
513     {
514       SCM dd = me->get_property ("default-direction");
515       dir = to_dir (dd);
516       if (!dir)
517         return me->get_property ("neutral-direction");
518     }
519   
520   return scm_from_int (dir);
521 }
522
523 MAKE_SCHEME_CALLBACK (Stem, calc_default_direction, 1);
524 SCM
525 Stem::calc_default_direction (SCM smob)
526 {
527   Grob *me = unsmob_grob (smob);
528
529   Direction dir = CENTER;
530   int staff_center = 0;
531   Interval hp = head_positions (me);
532   if (!hp.is_empty ())
533     {
534       int udistance = (int) (UP * hp[UP] - staff_center);
535       int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
536       
537       dir = Direction (sign (ddistance - udistance));
538     }
539   
540   return scm_from_int (dir);
541 }
542
543
544 MAKE_SCHEME_CALLBACK (Stem, height, 1);
545 SCM
546 Stem::height (SCM smob)
547 {
548   Grob *me = unsmob_grob (smob);
549   if (!is_normal_stem (me))
550     return ly_interval2scm (Interval ());
551   
552   Direction dir = get_grob_direction (me);
553   
554   Grob *beam = get_beam (me);
555   if (beam)
556     {
557       /* trigger set-stem-lengths. */
558       beam->get_property ("quantized-positions");
559     }
560
561   /*
562     Can't get_stencil (), since that would cache stencils too early.
563     This causes problems with beams.
564    */
565   Stencil *stencil = unsmob_stencil (print (smob));
566   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval ();
567   if (beam)
568     {
569       if (dir == CENTER)
570         {
571           programming_error ("no stem direction");
572           dir = UP;
573         }
574       iv[dir] += dir * Beam::get_beam_thickness (beam) * 0.5;
575     }
576
577   return ly_interval2scm (iv);
578 }
579
580 Real
581 Stem::stem_end_position (Grob *me)
582 {
583   return robust_scm2double (me->get_property ("stem-end-position"), 0);
584 }
585
586 MAKE_SCHEME_CALLBACK (Stem, calc_flag, 1);
587 SCM
588 Stem::calc_flag (SCM smob)
589 {
590   Grob *me = unsmob_grob (smob);
591
592   int log = duration_log (me);
593   /*
594     TODO: maybe property stroke-style should take different values,
595     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
596     '() or "grace").  */
597   string flag_style;
598
599    SCM flag_style_scm = me->get_property ("flag-style");
600   if (scm_is_symbol (flag_style_scm))
601     flag_style = ly_symbol2string (flag_style_scm);
602
603   if (flag_style == "no-flag")
604     return Stencil ().smobbed_copy ();
605
606   bool adjust = true;
607
608   string staffline_offs;
609   if (flag_style == "mensural")
610     /* Mensural notation: For notes on staff lines, use different
611        flags than for notes between staff lines.  The idea is that
612        flags are always vertically aligned with the staff lines,
613        regardless if the note head is on a staff line or between two
614        staff lines.  In other words, the inner end of a flag always
615        touches a staff line.
616     */
617     {
618       if (adjust)
619         {
620           int p = (int) (rint (stem_end_position (me)));
621           staffline_offs
622             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
623         }
624       else
625         staffline_offs = "2";
626      }
627   else
628     staffline_offs = "";
629
630   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
631   string font_char = flag_style
632     + to_string (dir) + staffline_offs + to_string (log);
633   Font_metric *fm = Font_interface::get_default_font (me);
634   Stencil flag = fm->find_by_name ("flags." + font_char);
635   if (flag.is_empty ())
636     me->warning (_f ("flag `%s' not found", font_char));
637
638   SCM stroke_style_scm = me->get_property ("stroke-style");
639   if (scm_is_string (stroke_style_scm))
640     {
641       string stroke_style = ly_scm2string (stroke_style_scm);
642       if (!stroke_style.empty ())
643         {
644           string font_char = flag_style + to_string (dir) + stroke_style;
645           Stencil stroke = fm->find_by_name ("flags." + font_char);
646           if (stroke.is_empty ())
647             {
648               font_char = to_string (dir) + stroke_style;
649               stroke = fm->find_by_name ("flags." + font_char);
650             }
651           if (stroke.is_empty ())
652             me->warning (_f ("flag stroke `%s' not found", font_char));
653           else
654             flag.add_stencil (stroke);
655         }
656      }
657
658   return flag.smobbed_copy ();
659 }
660
661
662 Stencil
663 Stem::flag (Grob *me)
664 {
665   int log = duration_log (me);
666   if (log < 3
667       || unsmob_grob (me->get_object ("beam")))
668     return Stencil ();
669
670   if (!is_normal_stem (me))
671     return Stencil ();
672
673   // This get_property call already evaluates the scheme function with
674   // the grob passed as argument! Thus, we only have to check if a valid
675   // stencil is returned.
676   SCM flag_style_scm = me->get_property ("flag");
677   if (Stencil *flag = unsmob_stencil (flag_style_scm)) {
678     return *flag;
679   } else {
680     return Stencil ();
681   }
682 }
683
684 MAKE_SCHEME_CALLBACK (Stem, width, 1);
685 SCM
686 Stem::width (SCM e)
687 {
688   Grob *me = unsmob_grob (e);
689
690   Interval r;
691
692   if (is_invisible (me))
693     r.set_empty ();
694   else if (unsmob_grob (me->get_object ("beam"))
695            || abs (duration_log (me)) <= 2)
696     {
697       r = Interval (-1, 1);
698       r *= thickness (me) / 2;
699     }
700   else
701     {
702       r = Interval (-1, 1) * thickness (me) * 0.5;
703       r.unite (flag (me).extent (X_AXIS));
704     }
705   return ly_interval2scm (r);
706 }
707
708 Real
709 Stem::thickness (Grob *me)
710 {
711   return scm_to_double (me->get_property ("thickness"))
712     * Staff_symbol_referencer::line_thickness (me);
713 }
714
715 MAKE_SCHEME_CALLBACK (Stem, print, 1);
716 SCM
717 Stem::print (SCM smob)
718 {
719   Grob *me = unsmob_grob (smob);
720   Grob *beam = get_beam (me);
721     
722   Stencil mol;
723   Direction d = get_grob_direction (me);
724
725   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
726                                            0.0);
727   bool stemlet = stemlet_length > 0.0;
728
729   /* TODO: make the stem start a direction ?
730      This is required to avoid stems passing in tablature chords.  */
731   Grob *lh
732     = to_boolean (me->get_property ("avoid-note-head"))
733     ? last_head (me)
734     : first_head (me);
735
736   if (!lh && !stemlet)
737     return SCM_EOL;
738
739   if (!lh && stemlet && !beam)
740     return SCM_EOL;
741
742   if (lh && robust_scm2int (lh->get_property ("duration-log"), 0) < 1) 
743     return SCM_EOL;
744
745   if (is_invisible (me))
746     return SCM_EOL;
747
748   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
749   Real y1 = y2;
750   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
751
752   if (lh)
753     y2 = Staff_symbol_referencer::get_position (lh);
754   else if (stemlet)
755     {
756       Real beam_translation = Beam::get_beam_translation (beam);
757       Real beam_thickness = Beam::get_beam_thickness (beam);
758       int beam_count = beam_multiplicity (me).length () + 1;
759
760       y2 -= d
761         * (0.5 * beam_thickness
762            + beam_translation * max (0, (beam_count - 1))
763            + stemlet_length) / half_space;
764     }
765
766   Interval stem_y (min (y1, y2), max (y2, y1));
767
768   if (Grob *head = support_head (me))
769     {
770       /*
771         must not take ledgers into account.
772       */
773       Interval head_height = head->extent (head, Y_AXIS);
774       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
775
776       y_attach = head_height.linear_combination (y_attach);
777       stem_y[Direction (-d)] += d * y_attach / half_space;
778     }
779
780   // URG
781   Real stem_width = thickness (me);
782   Real blot
783     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
784
785   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
786                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
787
788   Stencil ss = Lookup::round_filled_box (b, blot);
789   mol.add_stencil (ss);
790
791   mol.add_stencil (get_translated_flag (me));
792
793   return mol.smobbed_copy ();
794 }
795
796 Stencil
797 Stem::get_translated_flag (Grob *me)
798 {
799   Stencil fl = flag (me);
800   if (!fl.is_empty ())
801     {
802       Direction d = get_grob_direction (me);
803       Real blot
804         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
805       Real stem_width = thickness (me);
806       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
807       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
808       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
809       fl.translate_axis (stem_width / 2, X_AXIS);
810     }
811   return fl;
812 }
813
814
815 /*
816   move the stem to right of the notehead if it is up.
817 */
818 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
819 SCM
820 Stem::offset_callback (SCM smob)
821 {
822   Grob *me = unsmob_grob (smob);
823
824   extract_grob_set (me, "rests", rests);
825   if (rests.size ())
826     {
827       Grob *rest = rests.back ();
828       Real r = rest->extent (rest, X_AXIS).center ();
829       return scm_from_double (r);
830     }
831
832   
833   if (Grob *f = first_head (me))
834     {
835       Interval head_wid = f->extent (f, X_AXIS);
836       Real attach = 0.0;
837
838       if (is_invisible (me))
839         attach = 0.0;
840       else
841         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
842
843       Direction d = get_grob_direction (me);
844       Real real_attach = head_wid.linear_combination (d * attach);
845       Real r = real_attach;
846
847       /* If not centered: correct for stem thickness.  */
848       if (attach)
849         {
850           Real rule_thick = thickness (me);
851           r += -d * rule_thick * 0.5;
852         }
853       return scm_from_double (r);
854     }
855
856   programming_error ("Weird stem.");
857   return scm_from_double (0.0);
858 }
859
860 Spanner *
861 Stem::get_beam (Grob *me)
862 {
863   SCM b = me->get_object ("beam");
864   return dynamic_cast<Spanner *> (unsmob_grob (b));
865 }
866
867 Stem_info
868 Stem::get_stem_info (Grob *me)
869 {
870   Stem_info si;
871   si.dir_ = get_grob_direction (me);
872   
873   SCM scm_info = me->get_property ("stem-info");
874   si.ideal_y_ = scm_to_double (scm_car (scm_info));
875   si.shortest_y_ = scm_to_double (scm_cadr (scm_info));
876   return si;
877 }
878
879 MAKE_SCHEME_CALLBACK (Stem, calc_stem_info, 1);
880 SCM
881 Stem::calc_stem_info (SCM smob)
882 {
883   Grob *me = unsmob_grob (smob);
884   Direction my_dir = get_grob_direction (me);
885
886   if (!my_dir)
887     {
888       programming_error ("no stem dir set");
889       my_dir = UP;
890     }
891
892   Real staff_space = Staff_symbol_referencer::staff_space (me);
893   Grob *beam = get_beam (me);
894
895   if (beam)
896     {
897       (void) beam->get_property ("beaming");
898     }
899   
900   Real beam_translation = Beam::get_beam_translation (beam);
901   Real beam_thickness = Beam::get_beam_thickness (beam);
902   int beam_count = Beam::get_direction_beam_count (beam, my_dir);
903   Real length_fraction
904     = robust_scm2double (me->get_property ("length-fraction"), 1.0);
905
906   /* Simple standard stem length */
907   SCM details = me->get_property ("details");
908   SCM lengths = ly_assoc_get (ly_symbol2scm ("beamed-lengths"), details, SCM_EOL);
909   
910   Real ideal_length
911     = (scm_is_pair (lengths)
912        ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
913           * staff_space
914           * length_fraction
915           /*
916             stem only extends to center of beam
917           */
918           - 0.5 * beam_thickness)
919        : 0.0);
920
921   /* Condition: sane minimum free stem length (chord to beams) */
922   lengths = ly_assoc_get (ly_symbol2scm ("beamed-minimum-free-lengths"),
923                           details, SCM_EOL);
924
925   Real ideal_minimum_free
926     = (scm_is_pair (lengths)
927        ? (scm_to_double (robust_list_ref (beam_count - 1, lengths))
928           * staff_space
929           * length_fraction)
930        : 0.0);
931
932   Real height_of_my_trem = 0.0;
933   Grob *trem = unsmob_grob (me->get_object ("tremolo-flag"));
934   if (trem)
935     {
936       height_of_my_trem
937         = Stem_tremolo::vertical_length (trem)
938         /* hack a bit of space around the trem. */
939         + beam_translation;
940     }
941
942   
943   /* UGH
944      It seems that also for ideal minimum length, we must use
945      the maximum beam count (for this direction):
946
947      \score { \relative c'' { a8[ a32] } }
948
949      must be horizontal. */
950   Real height_of_my_beams = beam_thickness
951     + (beam_count - 1) * beam_translation;
952
953   Real ideal_minimum_length = ideal_minimum_free
954     + height_of_my_beams
955     + height_of_my_trem
956     /* stem only extends to center of beam */
957     - 0.5 * beam_thickness;
958
959   ideal_length = max (ideal_length, ideal_minimum_length);
960
961   /* Convert to Y position, calculate for dir == UP */
962   Real note_start
963     =     /* staff positions */
964     head_positions (me)[my_dir] * 0.5
965     * my_dir * staff_space;
966   Real ideal_y = note_start + ideal_length;
967
968   /* Conditions for Y position */
969
970   /* Lowest beam of (UP) beam must never be lower than second staffline
971
972   Reference?
973
974   Although this (additional) rule is probably correct,
975   I expect that highest beam (UP) should also never be lower
976   than middle staffline, just as normal stems.
977
978   Reference?
979
980   Obviously not for grace beams.
981
982   Also, not for knees.  Seems to be a good thing. */
983   bool no_extend = to_boolean (me->get_property ("no-stem-extend"));
984   bool is_knee = to_boolean (beam->get_property ("knee"));
985   if (!no_extend && !is_knee)
986     {
987       /* Highest beam of (UP) beam must never be lower than middle
988          staffline */
989       ideal_y = max (ideal_y, 0.0);
990       /* Lowest beam of (UP) beam must never be lower than second staffline */
991       ideal_y = max (ideal_y, (-staff_space
992                                - beam_thickness + height_of_my_beams));
993     }
994
995   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
996
997   SCM bemfl = ly_assoc_get (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
998                             details, SCM_EOL);
999   
1000   Real minimum_free
1001     = (scm_is_pair (bemfl)
1002        ? (scm_to_double (robust_list_ref (beam_count - 1, bemfl))
1003           * staff_space
1004           * length_fraction)
1005        : 0.0);
1006
1007   Real minimum_length = max (minimum_free, height_of_my_trem)
1008     + height_of_my_beams
1009     /* stem only extends to center of beam */
1010     - 0.5 * beam_thickness;
1011
1012   ideal_y *= my_dir;
1013   Real minimum_y = note_start + minimum_length;
1014   Real shortest_y = minimum_y * my_dir;
1015
1016   return scm_list_2 (scm_from_double (ideal_y),
1017                      scm_from_double (shortest_y));
1018 }
1019
1020 Slice
1021 Stem::beam_multiplicity (Grob *stem)
1022 {
1023   SCM beaming = stem->get_property ("beaming");
1024   Slice le = int_list_to_slice (scm_car (beaming));
1025   Slice ri = int_list_to_slice (scm_cdr (beaming));
1026   le.unite (ri);
1027   return le;
1028 }
1029
1030 bool
1031 Stem::is_cross_staff (Grob *stem)
1032 {
1033   Grob *beam = unsmob_grob (stem->get_object ("beam"));
1034   return beam && Beam::is_cross_staff (beam);
1035 }
1036
1037 MAKE_SCHEME_CALLBACK (Stem, calc_cross_staff, 1)
1038 SCM
1039 Stem::calc_cross_staff (SCM smob)
1040 {
1041   return scm_from_bool (is_cross_staff (unsmob_grob (smob)));
1042 }
1043
1044 /* FIXME:  Too many properties  */
1045 ADD_INTERFACE (Stem,
1046                "The stem represents the graphical stem.  In addition, it"
1047                " internally connects note heads, beams, and tremolos.  Rests"
1048                " and whole notes have invisible stems.\n"
1049                "\n"
1050                "The following properties may be set in the @code{details}"
1051                " list.\n"
1052                "\n"
1053                "@table @code\n"
1054                "@item beamed-lengths\n"
1055                "List of stem lengths given beam multiplicity.\n"
1056                "@item beamed-minimum-free-lengths\n"
1057                "List of normal minimum free stem lengths (chord to beams)"
1058                " given beam multiplicity.\n"
1059                "@item beamed-extreme-minimum-free-lengths\n"
1060                "List of extreme minimum free stem lengths (chord to beams)"
1061                " given beam multiplicity.\n"
1062                "@item lengths\n"
1063                "Default stem lengths.  The list gives a length for each"
1064                " flag count.\n"
1065                "@item stem-shorten\n"
1066                "How much a stem in a forced direction should be shortened."
1067                "  The list gives an amount depending on the number of flags"
1068                " and beams.\n"
1069                "@end table\n",
1070
1071                /* properties */
1072                "avoid-note-head "
1073                "beam "
1074                "beaming "
1075                "beamlet-default-length "
1076                "beamlet-max-length-proportion "
1077                "default-direction "
1078                "details "
1079                "direction "
1080                "duration-log "
1081                "flag "
1082                "flag-style "
1083                "french-beaming "
1084                "length "
1085                "length-fraction "
1086                "max-beam-connect "
1087                "neutral-direction "
1088                "no-stem-extend "
1089                "note-heads "
1090                "positioning-done "
1091                "rests "
1092                "stem-end-position "
1093                "stem-info "
1094                "stemlet-length "
1095                "stroke-style "
1096                "thickness "
1097                "tremolo-flag "
1098                );
1099
1100 /****************************************************************/
1101
1102 Stem_info::Stem_info ()
1103 {
1104   ideal_y_ = shortest_y_ = 0;
1105   dir_ = CENTER;
1106 }
1107
1108 void
1109 Stem_info::scale (Real x)
1110 {
1111   ideal_y_ *= x;
1112   shortest_y_ *= x;
1113 }