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