]> git.donarmstrong.com Git - lilypond.git/blob - lily/stem.cc
fix pure stem height for invisible stems.
[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--2006 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   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
218                                            0.0);
219
220   return !((head_count (me)
221             || stemlet_length > 0.0)
222            && scm_to_int (me->get_property ("duration-log")) >= 1);
223 }
224
225
226 bool
227 Stem::is_normal_stem (Grob *me)
228 {
229   return head_count (me) && scm_to_int (me->get_property ("duration-log")) >= 1;
230 }
231
232
233 MAKE_SCHEME_CALLBACK (Stem, pure_height, 3)
234 SCM
235 Stem::pure_height (SCM smob, SCM start, SCM end)
236 {
237   (void) start;
238   (void) end;
239
240   Grob *me = unsmob_grob (smob);
241   Interval iv;
242
243   if (!is_normal_stem (me))
244     return ly_interval2scm (iv);
245   
246   Real ss = Staff_symbol_referencer::staff_space (me);
247   Real len = scm_to_double (calc_length (smob)) * ss / 2;
248   Direction dir = get_grob_direction (me);
249
250   Interval hp = head_positions (me);
251   if (dir == UP)
252     iv = Interval (0, len);
253   else
254     iv = Interval (-len, 0);
255
256   if (!hp.is_empty ())
257     iv.translate (hp[dir] * ss / 2);
258
259   return ly_interval2scm (iv);
260 }
261
262 MAKE_SCHEME_CALLBACK (Stem, calc_stem_end_position, 1)
263 SCM
264 Stem::calc_stem_end_position (SCM smob)
265 {
266   Grob *me = unsmob_grob (smob);
267
268   if (!head_count (me))
269     return scm_from_double (0.0);
270
271   if (Grob *beam = get_beam (me))
272     {
273       (void) beam->get_property ("quantized-positions");
274       return me->get_property ("stem-end-position");
275     }
276   
277   Real ss = Staff_symbol_referencer::staff_space (me);
278   int durlog = duration_log (me);
279   vector<Real> a;
280
281   /* WARNING: IN HALF SPACES */
282   Real length = robust_scm2double (me->get_property ("length"), 7);
283
284   Direction dir = get_grob_direction (me);
285   Interval hp = head_positions (me);
286   Real stem_end = dir ? hp[dir] + dir * length : 0;
287
288   /* TODO: change name  to extend-stems to staff/center/'()  */
289   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
290   if (!no_extend_b && dir * stem_end < 0)
291     stem_end = 0.0;
292
293   
294   /* Make a little room if we have a upflag and there is a dot.
295      previous approach was to lengthen the stem. This is not
296      good typesetting practice.  */
297   if (!get_beam (me) && dir == UP
298       && durlog > 2)
299     {
300       Grob *closest_to_flag = extremal_heads (me)[dir];
301       Grob *dots = closest_to_flag
302         ? Rhythmic_head::get_dots (closest_to_flag) : 0;
303
304       if (dots)
305         {
306           Real dp = Staff_symbol_referencer::get_position (dots);
307           Interval flag_yext = flag (me).extent (Y_AXIS) * (2 / ss) + stem_end;
308
309           /* Very gory: add myself to the X-support of the parent,
310              which should be a dot-column. */
311           
312           if (flag_yext.distance (dp) < 0.5)
313             {
314               Grob *par = dots->get_parent (X_AXIS);
315
316               if (Dot_column::has_interface (par))
317                 {
318                   Side_position_interface::add_support (par, me);
319
320                   /* TODO: apply some better logic here. The flag is
321                      curved inwards, so this will typically be too
322                      much. */
323                 }
324             }
325         }
326     }
327
328   return scm_from_double (stem_end);
329 }
330
331 /* Length is in half-spaces (or: positions) here. */
332 MAKE_SCHEME_CALLBACK (Stem, calc_length, 1)
333 SCM
334 Stem::calc_length (SCM smob)
335 {
336   Grob *me = unsmob_grob (smob);
337   
338   SCM details = me->get_property ("details");
339   int durlog = duration_log (me);
340
341   Real ss = Staff_symbol_referencer::staff_space (me);
342   Real length = 7;
343   SCM s = scm_cdr (scm_assq (ly_symbol2scm ("lengths"), details));
344   if (scm_is_pair (s))
345     length = 2 * scm_to_double (robust_list_ref (durlog - 2, s));
346
347   Direction dir = get_grob_direction (me);
348
349   /* Stems in unnatural (forced) direction should be shortened,
350      according to [Roush & Gourlay] */
351   Interval hp = head_positions (me);
352   if (dir && dir * hp[dir] >= 0)
353     {
354       SCM sshorten = scm_cdr (scm_assq (ly_symbol2scm ("stem-shorten"), details));
355       SCM scm_shorten = scm_is_pair (sshorten)
356         ? robust_list_ref (max (duration_log (me) - 2, 0), sshorten) : SCM_EOL;
357       Real shorten = 2* robust_scm2double (scm_shorten, 0);
358
359       /* On boundary: shorten only half */
360       if (abs (head_positions (me)[dir]) <= 1)
361         shorten *= 0.5;
362
363       length -= shorten;
364     }
365
366   length *= robust_scm2double (me->get_property ("length-fraction"), 1.0);
367
368   /* Tremolo stuff.  */
369   Grob *t_flag = unsmob_grob (me->get_object ("tremolo-flag"));
370   if (t_flag && !unsmob_grob (me->get_object ("beam")))
371     {
372       /* Crude hack: add extra space if tremolo flag is there.
373
374       We can't do this for the beam, since we get into a loop
375       (Stem_tremolo::raw_stencil () looks at the beam.) --hwn  */
376
377       Real minlen = 1.0
378         + 2 * Stem_tremolo::vertical_length (t_flag) / ss;
379
380       /* We don't want to add the whole extent of the flag because the trem
381          and the flag can overlap partly. beam_translation gives a good
382          approximation */
383       if (durlog >= 3)
384         {
385           Real beam_trans = Stem_tremolo::get_beam_translation (t_flag);
386           /* the obvious choice is (durlog - 2) here, but we need a bit more space. */
387           minlen += 2 * (durlog - 1.5) * beam_trans;
388
389           /* up-stems need even a little more space to avoid collisions. This
390              needs to be in sync with the tremolo positioning code in
391              Stem_tremolo::print */
392           if (dir == UP)
393             minlen += beam_trans;
394         }
395       length = max (length, minlen + 1.0);
396     }
397   
398   return scm_from_double (length);
399 }
400 /* The log of the duration (Number of hooks on the flag minus two)  */
401 int
402 Stem::duration_log (Grob *me)
403 {
404   SCM s = me->get_property ("duration-log");
405   return (scm_is_number (s)) ? scm_to_int (s) : 2;
406 }
407
408 MAKE_SCHEME_CALLBACK(Stem, calc_positioning_done, 1);
409 SCM
410 Stem::calc_positioning_done (SCM smob)
411 {
412   Grob *me = unsmob_grob (smob);  
413   if (!head_count (me))
414     return SCM_BOOL_T;
415
416   extract_grob_set (me, "note-heads", ro_heads);
417   vector<Grob*> heads (ro_heads);
418   vector_sort (heads, position_less);
419   Direction dir = get_grob_direction (me);
420
421   if (dir < 0)
422     reverse (heads);
423
424   Real thick = thickness (me);
425
426   Grob *hed = support_head (me);
427   if (!dir)
428     {
429       programming_error ("Stem dir must be up or down.");
430       dir = UP;
431       set_grob_direction (me, dir);
432     }
433
434   bool is_harmonic_centered = false;
435   for (vsize i = 0; i < heads.size (); i++)
436     is_harmonic_centered = is_harmonic_centered 
437       || heads[i]->get_property ("style") == ly_symbol2scm ("harmonic");
438   is_harmonic_centered = is_harmonic_centered && is_invisible (me);
439
440   Real w = hed->extent (hed, X_AXIS)[dir];
441   for (vsize i = 0; i < heads.size (); i++)
442     {
443       Real amount = w - heads[i]->extent (heads[i], X_AXIS)[dir];
444
445       if (is_harmonic_centered)
446         amount =
447           hed->extent (hed, X_AXIS).linear_combination (CENTER)
448           - heads[i]->extent (heads[i], X_AXIS).linear_combination (CENTER);
449       
450       heads[i]->translate_axis (amount, X_AXIS);
451     }
452   bool parity = true;
453   Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
454   for (vsize i = 1; i < heads.size (); i++)
455     {
456       Real p = Staff_symbol_referencer::get_position (heads[i]);
457       Real dy = fabs (lastpos- p);
458
459       /*
460         dy should always be 0.5, 0.0, 1.0, but provide safety margin
461         for rounding errors.
462       */
463       if (dy < 1.1)
464         {
465           if (parity)
466             {
467               Real ell = heads[i]->extent (heads[i], X_AXIS).length ();
468
469               Direction d = get_grob_direction (me);
470               /*
471                 Reversed head should be shifted ell-thickness, but this
472                 looks too crowded, so we only shift ell-0.5*thickness.
473
474                 This leads to assymetry: Normal heads overlap the
475                 stem 100% whereas reversed heads only overlaps the
476                 stem 50%
477               */
478
479               Real reverse_overlap = 0.5;
480               heads[i]->translate_axis ((ell - thick * reverse_overlap) * d,
481                                         X_AXIS);
482
483               if (is_invisible (me))
484                 heads[i]->translate_axis (-thick * (2 - reverse_overlap) * d,
485                                           X_AXIS);
486
487               /* TODO:
488
489               For some cases we should kern some more: when the
490               distance between the next or prev note is too large, we'd
491               get large white gaps, eg.
492
493               |
494               X|
495               |X  <- kern this.
496               |
497               X
498
499               */
500             }
501           parity = !parity;
502         }
503       else
504         parity = true;
505
506       lastpos = int (p);
507     }
508
509   return SCM_BOOL_T;
510 }
511
512 MAKE_SCHEME_CALLBACK(Stem, calc_direction, 1);
513 SCM
514 Stem::calc_direction (SCM smob)
515 {
516   Grob *me = unsmob_grob (smob);
517   Direction dir = CENTER;
518   if (Grob *beam = unsmob_grob (me->get_object ("beam")))
519     {
520       SCM ignore_me = beam->get_property ("direction");
521       (void) ignore_me;
522       dir = get_grob_direction (me);
523     }
524   else
525     {
526       SCM dd = me->get_property ("default-direction");
527       dir = to_dir (dd);
528       if (!dir)
529         return me->get_property ("neutral-direction");
530     }
531   
532   return scm_from_int (dir);
533 }
534
535 MAKE_SCHEME_CALLBACK(Stem, calc_default_direction, 1);
536 SCM
537 Stem::calc_default_direction (SCM smob)
538 {
539   Grob *me = unsmob_grob (smob);
540
541   Direction dir = CENTER;
542   int staff_center = 0;
543   Interval hp = head_positions (me);
544   if (!hp.is_empty ())
545     {
546       int udistance = (int) (UP * hp[UP] - staff_center);
547       int ddistance = (int) (DOWN * hp[DOWN] - staff_center);
548       
549       dir = Direction (sign (ddistance - udistance));
550     }
551   
552   return scm_from_int (dir);
553 }
554
555
556 MAKE_SCHEME_CALLBACK (Stem, height, 1);
557 SCM
558 Stem::height (SCM smob)
559 {
560   Grob *me = unsmob_grob (smob);
561
562   Direction dir = get_grob_direction (me);
563   
564   /* Trigger callback.
565
566   UGH. Should be automatic
567   */
568   Grob *beam = get_beam (me);
569   if (beam)
570     {
571       /* trigger set-stem-lengths. */
572       beam->get_property ("quantized-positions");
573     }
574
575   /*
576     Can't get_stencil(), since that would cache stencils too early.
577     This causes problems with beams.
578    */
579   Stencil *stencil = unsmob_stencil (print (smob));
580   Interval iv = stencil ? stencil->extent (Y_AXIS) : Interval();
581   if (beam)
582     {
583       if (dir == CENTER)
584         {
585           programming_error ("no stem direction");
586           dir = UP;
587         }
588       iv[dir] += dir * Beam::get_thickness (beam) * 0.5;
589     }
590
591   return ly_interval2scm (iv);
592 }
593
594 Real
595 Stem::stem_end_position (Grob *me)
596 {
597   return robust_scm2double (me->get_property ("stem-end-position"), 0);
598 }
599
600 Stencil
601 Stem::flag (Grob *me)
602 {
603   int log = duration_log (me);
604   if (log < 3
605       || unsmob_grob (me->get_object ("beam")))
606     return Stencil ();
607
608   /*
609     TODO: maybe property stroke-style should take different values,
610     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
611     '() or "grace").  */
612   string flag_style;
613
614   SCM flag_style_scm = me->get_property ("flag-style");
615   if (scm_is_symbol (flag_style_scm))
616     flag_style = ly_symbol2string (flag_style_scm);
617
618   if (flag_style == "no-flag")
619     return Stencil ();
620
621   bool adjust = true;
622
623   string staffline_offs;
624   if (flag_style == "mensural")
625     /* Mensural notation: For notes on staff lines, use different
626        flags than for notes between staff lines.  The idea is that
627        flags are always vertically aligned with the staff lines,
628        regardless if the note head is on a staff line or between two
629        staff lines.  In other words, the inner end of a flag always
630        touches a staff line.
631     */
632     {
633       if (adjust)
634         {
635           int p = (int) (rint (stem_end_position (me)));
636           staffline_offs
637             = Staff_symbol_referencer::on_line (me, p) ? "0" : "1";
638         }
639       else
640         staffline_offs = "2";
641     }
642   else
643     staffline_offs = "";
644
645   char dir = (get_grob_direction (me) == UP) ? 'u' : 'd';
646   string font_char = flag_style
647     + to_string (dir) + staffline_offs + to_string (log);
648   Font_metric *fm = Font_interface::get_default_font (me);
649   Stencil flag = fm->find_by_name ("flags." + font_char);
650   if (flag.is_empty ())
651     me->warning (_f ("flag `%s' not found", font_char));
652
653   SCM stroke_style_scm = me->get_property ("stroke-style");
654   if (scm_is_string (stroke_style_scm))
655     {
656       string stroke_style = ly_scm2string (stroke_style_scm);
657       if (!stroke_style.empty ())
658         {
659           string font_char = to_string (dir) + stroke_style;
660           Stencil stroke = fm->find_by_name ("flags." + font_char);
661           if (stroke.is_empty ())
662             me->warning (_f ("flag stroke `%s' not found", font_char));
663           else
664             flag.add_stencil (stroke);
665         }
666     }
667
668   return flag;
669 }
670
671 MAKE_SCHEME_CALLBACK (Stem, width, 1);
672 SCM
673 Stem::width (SCM e)
674 {
675   Grob *me = unsmob_grob (e);
676
677   Interval r;
678
679   if (is_invisible (me))
680     r.set_empty ();
681   else if (unsmob_grob (me->get_object ("beam"))
682            || abs (duration_log (me)) <= 2)
683     {
684       r = Interval (-1, 1);
685       r *= thickness (me) / 2;
686     }
687   else
688     {
689       r = Interval (-1, 1) * thickness (me) * 0.5;
690       r.unite (flag (me).extent (X_AXIS));
691     }
692   return ly_interval2scm (r);
693 }
694
695 Real
696 Stem::thickness (Grob *me)
697 {
698   return scm_to_double (me->get_property ("thickness"))
699     * Staff_symbol_referencer::line_thickness (me);
700 }
701
702 MAKE_SCHEME_CALLBACK (Stem, print, 1);
703 SCM
704 Stem::print (SCM smob)
705 {
706   Grob *me = unsmob_grob (smob);
707   Grob *beam = get_beam (me);
708     
709   Stencil mol;
710   Direction d = get_grob_direction (me);
711
712   Real stemlet_length = robust_scm2double (me->get_property ("stemlet-length"),
713                                            0.0);
714   bool stemlet = stemlet_length > 0.0;
715
716   /* TODO: make the stem start a direction ?
717      This is required to avoid stems passing in tablature chords.  */
718   Grob *lh
719     = to_boolean (me->get_property ("avoid-note-head"))
720     ? last_head (me)
721     : first_head (me);
722
723   if (!lh && !stemlet)
724     return SCM_EOL;
725
726   if (!lh && stemlet && !beam)
727     return SCM_EOL;
728
729   if (is_invisible (me))
730     return SCM_EOL;
731
732   Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
733   Real y1 = y2;
734   Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
735
736   if (lh)
737     y2 = Staff_symbol_referencer::get_position (lh);
738   else if (stemlet)
739     {
740       Real beam_translation = Beam::get_beam_translation (beam);
741       Real beam_thickness = Beam::get_thickness (beam);
742       int beam_count = beam_multiplicity (me).length () + 1;
743
744       y2 -= d
745         * (0.5 * beam_thickness
746            + beam_translation * max (0, (beam_count - 1))
747            + stemlet_length) / half_space;
748     }
749
750   Interval stem_y (min (y1, y2), max (y2, y1));
751
752   if (Grob *head = support_head (me))
753     {
754       /*
755         must not take ledgers into account.
756       */
757       Interval head_height = head->extent (head, Y_AXIS);
758       Real y_attach = Note_head::stem_attachment_coordinate (head, Y_AXIS);
759
760       y_attach = head_height.linear_combination (y_attach);
761       stem_y[Direction (-d)] += d * y_attach / half_space;
762     }
763
764   // URG
765   Real stem_width = thickness (me);
766   Real blot
767     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
768
769   Box b = Box (Interval (-stem_width / 2, stem_width / 2),
770                Interval (stem_y[DOWN] * half_space, stem_y[UP] * half_space));
771
772   Stencil ss = Lookup::round_filled_box (b, blot);
773   mol.add_stencil (ss);
774
775   mol.add_stencil (get_translated_flag (me));
776
777   return mol.smobbed_copy ();
778 }
779
780 Stencil
781 Stem::get_translated_flag (Grob *me)
782 {
783   Stencil fl = flag (me);
784   if (!fl.is_empty ())
785     {
786       Direction d = get_grob_direction (me);
787       Real blot
788         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
789       Real stem_width = thickness (me);
790       Real half_space = Staff_symbol_referencer::staff_space (me) * 0.5;
791       Real y2 = robust_scm2double (me->get_property ("stem-end-position"), 0.0);
792       fl.translate_axis (y2 * half_space - d * blot / 2, Y_AXIS);
793       fl.translate_axis (stem_width / 2, X_AXIS);
794     }
795   return fl;
796 }
797
798
799 /*
800   move the stem to right of the notehead if it is up.
801 */
802 MAKE_SCHEME_CALLBACK (Stem, offset_callback, 1);
803 SCM
804 Stem::offset_callback (SCM smob)
805 {
806   Grob *me = unsmob_grob (smob);
807   Real r = 0.0;
808
809   if (Grob *f = first_head (me))
810     {
811       Interval head_wid = f->extent (f, X_AXIS);
812       Real attach = 0.0;
813
814       if (is_invisible (me))
815         attach = 0.0;
816       else
817         attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
818
819       Direction d = get_grob_direction (me);
820       Real real_attach = head_wid.linear_combination (d * attach);
821       r = real_attach;
822
823       /* If not centered: correct for stem thickness.  */
824       if (attach)
825         {
826           Real rule_thick = thickness (me);
827           r += -d * rule_thick * 0.5;
828         }
829     }
830   else
831     {
832       extract_grob_set (me, "rests", rests);
833       if (rests.size ())
834         {
835           Grob *rest = rests.back ();
836           r = rest->extent (rest, X_AXIS).center ();
837         }
838     }
839   return scm_from_double (r);
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 = scm_cdr (scm_assq (ly_symbol2scm ("beamed-lengths"), details));
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 = scm_cdr (scm_assq (ly_symbol2scm ("beamed-minimum-free-lengths"), details));
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       height_of_my_trem = trem->extent (trem, Y_AXIS).length ()
913         /* hack a bit of space around the trem. */
914         + beam_translation;
915
916   /* UGH
917      It seems that also for ideal minimum length, we must use
918      the maximum beam count (for this direction):
919
920      \score{ \notes\relative c''{ [a8 a32] }}
921
922      must be horizontal. */
923   Real height_of_my_beams = beam_thickness
924     + (beam_count - 1) * beam_translation;
925
926   Real ideal_minimum_length = ideal_minimum_free
927     + height_of_my_beams
928     + height_of_my_trem
929     /* stem only extends to center of beam */
930     - 0.5 * beam_thickness;
931
932   ideal_length = max (ideal_length, ideal_minimum_length);
933
934   /* Convert to Y position, calculate for dir == UP */
935   Real note_start
936     =     /* staff positions */
937     head_positions (me)[my_dir] * 0.5
938     * my_dir * staff_space;
939   Real ideal_y = note_start + ideal_length;
940
941   /* Conditions for Y position */
942
943   /* Lowest beam of (UP) beam must never be lower than second staffline
944
945   Reference?
946
947   Although this (additional) rule is probably correct,
948   I expect that highest beam (UP) should also never be lower
949   than middle staffline, just as normal stems.
950
951   Reference?
952
953   Obviously not for grace beams.
954
955   Also, not for knees.  Seems to be a good thing. */
956   bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
957   bool is_knee = to_boolean (beam->get_property ("knee"));
958   if (!no_extend_b && !is_knee)
959     {
960       /* Highest beam of (UP) beam must never be lower than middle
961          staffline */
962       ideal_y = max (ideal_y, 0.0);
963       /* Lowest beam of (UP) beam must never be lower than second staffline */
964       ideal_y = max (ideal_y, (-staff_space
965                                - beam_thickness + height_of_my_beams));
966     }
967
968   ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
969
970   SCM bemfl = scm_cdr (scm_assq (ly_symbol2scm ("beamed-extreme-minimum-free-lengths"),
971                                  details));
972   
973   Real minimum_free
974     = scm_to_double (robust_list_ref (beam_count - 1, bemfl))
975     * staff_space
976     * length_fraction;
977
978   Real minimum_length = max (minimum_free, height_of_my_trem)
979     + height_of_my_beams
980     /* stem only extends to center of beam */
981     - 0.5 * beam_thickness;
982
983   ideal_y *= my_dir;
984   Real minimum_y = note_start + minimum_length;
985   Real shortest_y = minimum_y * my_dir;
986
987   return scm_list_2 (scm_from_double (ideal_y),
988                      scm_from_double (shortest_y));
989 }
990
991 Slice
992 Stem::beam_multiplicity (Grob *stem)
993 {
994   SCM beaming = stem->get_property ("beaming");
995   Slice le = int_list_to_slice (scm_car (beaming));
996   Slice ri = int_list_to_slice (scm_cdr (beaming));
997   le.unite (ri);
998   return le;
999 }
1000
1001 /* FIXME:  Too many properties  */
1002 ADD_INTERFACE (Stem,
1003                "The stem represent the graphical stem.  "
1004                "In addition, it internally connects note heads, beams and"
1005                "tremolos. "
1006                "Rests and whole notes have invisible stems."
1007
1008                "\n\nThe following properties may be set in the details list." 
1009                "@table @code\n"
1010                "@item  beamed-lengths \n"
1011                "list of stem lengths given beam multiplicity. \n"
1012                "@item beamed-minimum-free-lengths \n"
1013                "list of normal minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1014                "@item beamed-extreme-minimum-free-lengths\n"
1015                "list of extreme minimum free stem lengths (chord to beams) given beam multiplicity.\n"
1016                "@item lengths\n"
1017                "Default stem lengths. The list gives a length for each flag-count.\n"
1018                "@item stem-shorten\n"
1019                "How much a stem in a forced direction "
1020                "should be shortened. The list gives an amount depending on the number "
1021                "of flags/beams."
1022                "@end table\n"
1023                ,
1024
1025                /* properties */
1026                "avoid-note-head "
1027                "beam "
1028                "beaming "
1029                "default-direction "
1030                "details "
1031                "direction "
1032                "duration-log "
1033                "flag-style "
1034                "french-beaming "
1035                "length "
1036                "length-fraction "
1037                "max-beam-connect "
1038                "neutral-direction "
1039                "no-stem-extend "
1040                "note-heads "
1041                "positioning-done "
1042                "rests "
1043                "stem-end-position "
1044                "stem-info "
1045                "stemlet-length "
1046                "stroke-style "
1047                "thickness "
1048                "tremolo-flag "
1049                );
1050
1051 /****************************************************************/
1052
1053 Stem_info::Stem_info ()
1054 {
1055   ideal_y_ = shortest_y_ = 0;
1056   dir_ = CENTER;
1057 }
1058
1059 void
1060 Stem_info::scale (Real x)
1061 {
1062   ideal_y_ *= x;
1063   shortest_y_ *= x;
1064 }