(Which properties to
[lilypond.git] / lily / stem.cc
blobbe9c8a265be0562c90985e0333d6d067a6964f1d
1 /*
2 stem.cc -- implement Stem
4 source file of the GNU LilyPond music typesetter
6 (c) 1996--2004 Han-Wen Nienhuys <hanwen@cs.uu.nl>
7 Jan Nieuwenhuizen <janneke@gnu.org>
9 TODO: This is way too hairy
11 TODO: fix naming.
13 Stem-end, chord-start, etc. is all confusing naming.
16 #include <math.h> // rint
18 #include "lookup.hh"
19 #include "directional-element-interface.hh"
20 #include "note-head.hh"
21 #include "stem.hh"
22 #include "warn.hh"
23 #include "paper-def.hh"
24 #include "rhythmic-head.hh"
25 #include "font-interface.hh"
26 #include "stencil.hh"
27 #include "paper-column.hh"
28 #include "misc.hh"
29 #include "beam.hh"
30 #include "rest.hh"
31 #include "group-interface.hh"
32 #include "staff-symbol-referencer.hh"
33 #include "spanner.hh"
34 #include "side-position-interface.hh"
35 #include "dot-column.hh"
36 #include "stem-tremolo.hh"
38 void
39 Stem::set_beaming (Grob*me, int beam_count, Direction d)
41 SCM pair = me->get_property ("beaming");
43 if (!gh_pair_p (pair))
45 pair = gh_cons (SCM_EOL, SCM_EOL);
46 me->set_property ("beaming", pair);
49 SCM l = index_get_cell (pair, d);
50 for (int i = 0; i< beam_count; i++)
52 l = gh_cons (gh_int2scm (i), l);
54 index_set_cell (pair, d, l);
58 Interval
59 Stem::head_positions (Grob*me)
61 if (!head_count (me))
63 Interval iv;
64 return iv;
67 Drul_array<Grob*> e (extremal_heads (me));
69 return Interval (Staff_symbol_referencer::get_position (e[DOWN]),
70 Staff_symbol_referencer::get_position (e[UP]));
74 Real
75 Stem::chord_start_y (Grob*me)
77 return head_positions (me)[get_direction (me)]
78 * Staff_symbol_referencer::staff_space (me)/2.0;
81 Real
82 Stem::stem_end_position (Grob*me)
84 SCM p =me->get_property ("stem-end-position");
85 Real pos;
86 if (!gh_number_p (p))
88 pos = get_default_stem_end_position (me);
89 me->set_property ("stem-end-position", gh_double2scm (pos));
91 else
92 pos = gh_scm2double (p);
94 return pos;
97 Direction
98 Stem::get_direction (Grob*me)
100 Direction d = get_grob_direction (me);
102 if (!d)
104 d = get_default_dir (me);
105 // urg, AAARGH!
106 set_grob_direction (me, d);
108 return d ;
112 void
113 Stem::set_stemend (Grob*me, Real se)
115 // todo: margins
116 Direction d= get_direction (me);
118 if (d && d * head_positions (me)[get_direction (me)] >= se*d)
119 me->warning (_ ("Weird stem size; check for narrow beams"));
121 me->set_property ("stem-end-position", gh_double2scm (se));
126 Note head that determines hshift for upstems
128 WARNING: triggers direction
130 Grob*
131 Stem::support_head (Grob*me)
133 if (head_count (me) == 1)
136 UGH.
139 return unsmob_grob (ly_car (me->get_property ("note-heads")));
141 else
142 return first_head (me);
147 Stem::head_count (Grob*me)
149 return Pointer_group_interface::count (me, "note-heads");
153 The note head which forms one end of the stem.
155 WARNING: triggers direction
157 Grob*
158 Stem::first_head (Grob*me)
160 Direction d = get_direction (me);
161 if (!d)
162 return 0;
163 return extremal_heads (me)[-d];
167 The note head opposite to the first head.
169 Grob*
170 Stem::last_head (Grob*me)
172 Direction d = get_direction (me);
173 if (!d)
174 return 0;
175 return extremal_heads (me)[d];
179 START is part where stem reaches `last' head.
181 Drul_array<Grob*>
182 Stem::extremal_heads (Grob*me)
184 const int inf = 1000000;
185 Drul_array<int> extpos;
186 extpos[DOWN] = inf;
187 extpos[UP] = -inf;
189 Drul_array<Grob *> exthead;
190 exthead[LEFT] = exthead[RIGHT] =0;
192 for (SCM s = me->get_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
194 Grob * n = unsmob_grob (ly_car (s));
197 int p = Staff_symbol_referencer::get_rounded_position (n);
199 Direction d = LEFT;
200 do {
201 if (d* p > d* extpos[d])
203 exthead[d] = n;
204 extpos[d] = p;
206 } while (flip (&d) != DOWN);
209 return exthead;
212 static int
213 icmp (int const &a, int const &b)
215 return a-b;
219 The positions, in ascending order.
221 Array<int>
222 Stem::note_head_positions (Grob *me)
224 Array<int> ps ;
225 for (SCM s = me->get_property ("note-heads"); gh_pair_p (s); s = ly_cdr (s))
227 Grob * n = unsmob_grob (ly_car (s));
228 int p = Staff_symbol_referencer::get_rounded_position (n);
230 ps.push (p);
233 ps.sort (icmp);
234 return ps;
238 void
239 Stem::add_head (Grob*me, Grob *n)
241 n->set_property ("stem", me->self_scm ());
242 n->add_dependency (me);
245 TODO: why not store Rest pointers?
247 if (Note_head::has_interface (n))
249 Pointer_group_interface::add_grob (me, ly_symbol2scm ("note-heads"), n);
253 bool
254 Stem::is_invisible (Grob*me)
256 return ! (head_count (me)
257 && gh_scm2int (me->get_property ("duration-log")) >= 1);
260 Direction
261 Stem::get_default_dir (Grob*me)
263 int staff_center = 0;
264 Interval hp = head_positions (me);
265 if (hp.is_empty ())
267 return CENTER;
270 int udistance = (int) (UP * hp[UP] - staff_center);
271 int ddistance = (int) (DOWN* hp[DOWN] - staff_center);
273 if (sign (ddistance - udistance))
274 return Direction (sign (ddistance -udistance));
276 return to_dir (me->get_property ("neutral-direction"));
279 Real
280 Stem::get_default_stem_end_position (Grob*me)
282 Real ss = Staff_symbol_referencer::staff_space (me);
284 int durlog = duration_log (me);
286 SCM s;
287 Array<Real> a;
290 Real length = 7; // WARNING: IN HALF SPACES
291 SCM scm_len = me->get_property ("length");
292 if (gh_number_p (scm_len))
294 length = gh_scm2double (scm_len);
296 else
298 s = me->get_property ("lengths");
299 if (gh_pair_p (s))
301 length = 2* gh_scm2double (robust_list_ref (durlog -2, s));
305 /* URGURGURG
306 'set-default-stemlen' sets direction too
308 Direction dir = get_direction (me);
309 if (!dir)
311 dir = get_default_dir (me);
312 set_grob_direction (me, dir);
315 /* stems in unnatural (forced) direction should be shortened,
316 according to [Roush & Gourlay] */
317 if (dir * head_positions (me)[dir] >= 0)
319 SCM sshorten = me->get_property ("stem-shorten");
320 SCM scm_shorten = gh_pair_p (sshorten) ?
321 robust_list_ref ((duration_log (me) - 2) >? 0, sshorten): SCM_EOL;
322 Real shorten = 2* robust_scm2double (scm_shorten,0);
325 /* On boundary: shorten only half */
326 if (abs (head_positions (me)[dir]) <= 1)
327 shorten *= 0.5;
329 length -= shorten;
333 Tremolo stuff:
335 Grob * trem = unsmob_grob (me->get_property ("tremolo-flag"));
336 if (trem && !unsmob_grob (me->get_property ("beam")))
339 Crude hack: add extra space if tremolo flag is there.
341 We can't do this for the beam, since we get into a loop
342 (Stem_tremolo::raw_stencil () looks at the beam.)
344 --hwn
347 Real minlen =
348 1.0 + 2 * Stem_tremolo::raw_stencil (trem).extent (Y_AXIS).length () / ss;
350 if (durlog >= 3)
352 Interval flag_ext = flag (me).extent (Y_AXIS) ;
353 if (!flag_ext.is_empty ())
354 minlen += 2 * flag_ext.length () / ss ;
357 The clash is smaller for down stems (since the tremolo is
358 angled up.)
360 if (dir == DOWN)
361 minlen -= 1.0;
364 length = length >? (minlen + 1.0);
367 Interval hp = head_positions (me);
368 Real st = hp[dir] + dir * length;
371 TODO: change name to extend-stems to staff/center/'()
373 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
374 if (!no_extend_b && dir * st < 0) // junkme?
375 st = 0.0;
378 Make a little room if we have a upflag and there is a dot.
379 previous approach was to lengthen the stem. This is not
380 good typesetting practice.
383 if (!get_beam (me) && dir == UP
384 && durlog > 2)
386 Grob * closest_to_flag = extremal_heads (me)[dir];
387 Grob * dots = closest_to_flag
388 ? Rhythmic_head::get_dots (closest_to_flag ) : 0;
390 if (dots)
392 Real dp = Staff_symbol_referencer::get_position (dots);
393 Real flagy = flag (me).extent (Y_AXIS)[-dir] * 2
394 / ss;
397 Very gory: add myself to the X-support of the parent,
398 which should be a dot-column.
400 if (dir * (st + flagy - dp) < 0.5)
402 Grob *par = dots->get_parent (X_AXIS);
404 if (Dot_column::has_interface (par))
406 Side_position_interface::add_support (par, me);
409 TODO: apply some better logic here. The flag is
410 curved inwards, so this will typically be too
411 much.
419 return st;
426 the log of the duration (Number of hooks on the flag minus two)
429 Stem::duration_log (Grob*me)
431 SCM s = me->get_property ("duration-log");
432 return (gh_number_p (s)) ? gh_scm2int (s) : 2;
435 void
436 Stem::position_noteheads (Grob*me)
438 if (!head_count (me))
439 return;
441 Link_array<Grob> heads =
442 Pointer_group_interface__extract_grobs (me, (Grob*)0, "note-heads");
444 heads.sort (compare_position);
445 Direction dir =get_direction (me);
447 if (dir < 0)
448 heads.reverse ();
451 Real thick = thickness (me);
453 Grob *hed = support_head (me);
454 Real w = Note_head::head_extent (hed,X_AXIS)[dir];
455 for (int i=0; i < heads.size (); i++)
457 heads[i]->translate_axis (w - Note_head::head_extent (heads[i],X_AXIS)[dir],
458 X_AXIS);
461 bool parity= true;
462 Real lastpos = Real (Staff_symbol_referencer::get_position (heads[0]));
463 for (int i=1; i < heads.size (); i ++)
465 Real p = Staff_symbol_referencer::get_position (heads[i]);
466 Real dy =fabs (lastpos- p);
469 dy should always be 0.5, 0.0, 1.0, but provide safety margin
470 for rounding errors.
472 if (dy < 1.1)
474 if (parity)
476 Real l = Note_head::head_extent (heads[i], X_AXIS).length ();
478 Direction d = get_direction (me);
480 Reversed head should be shifted l-thickness, but this
481 looks too crowded, so we only shift l-0.5*thickness.
483 This leads to assymetry: Normal heads overlap the
484 stem 100% whereas reversed heads only overlaps the
485 stem 50%
489 Real reverse_overlap =0.5;
490 heads[i]->translate_axis ((l-thick*reverse_overlap) * d, X_AXIS);
492 if (is_invisible (me))
493 heads[i]->translate_axis (-thick*(2 - reverse_overlap) * d , X_AXIS);
496 /* TODO:
498 For some cases we should kern some more: when the
499 distance between the next or prev note is too large, we'd
500 get large white gaps, eg.
504 |X <- kern this.
510 parity = !parity;
512 else
513 parity = true;
515 lastpos = int (p);
519 MAKE_SCHEME_CALLBACK (Stem,before_line_breaking,1);
521 Stem::before_line_breaking (SCM smob)
523 Grob*me = unsmob_grob (smob);
527 Do the calculations for visible stems, but also for invisible stems
528 with note heads (i.e. half notes.)
530 if (head_count (me))
532 stem_end_position (me); // ugh. Trigger direction calc.
533 position_noteheads (me);
535 else
537 me->set_property ("print-function", SCM_EOL);
540 return SCM_UNSPECIFIED;
544 ugh.
545 When in a beam with tuplet brackets, brew_mol is called early,
546 caching a wrong value.
548 MAKE_SCHEME_CALLBACK (Stem, height, 2);
550 Stem::height (SCM smob, SCM ax)
552 Axis a = (Axis)gh_scm2int (ax);
553 Grob * me = unsmob_grob (smob);
554 assert (a == Y_AXIS);
556 SCM mol = me->get_uncached_stencil ();
557 Interval iv;
558 if (mol != SCM_EOL)
559 iv = unsmob_stencil (mol)->extent (a);
560 if (Grob *b =get_beam (me))
562 Direction d = get_direction (me);
563 iv[d] += d * Beam::get_thickness (b) /2.0 ;
566 return ly_interval2scm (iv);
570 Stencil
571 Stem::flag (Grob*me)
573 /* TODO: maybe property stroke-style should take different values,
574 e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
575 '() or "grace"). */
576 String flag_style;
578 SCM flag_style_scm = me->get_property ("flag-style");
579 if (gh_symbol_p (flag_style_scm))
581 flag_style = ly_symbol2string (flag_style_scm);
584 if (flag_style == "no-flag")
586 return Stencil ();
589 bool adjust = true;
591 String staffline_offs;
592 if (String::compare (flag_style, "mensural") == 0)
593 /* Mensural notation: For notes on staff lines, use different
594 flags than for notes between staff lines. The idea is that
595 flags are always vertically aligned with the staff lines,
596 regardless if the note head is on a staff line or between two
597 staff lines. In other words, the inner end of a flag always
598 touches a staff line.
601 if (adjust)
603 /* Urrgh! We have to detect wether this stem ends on a staff
604 line or between two staff lines. But we can not call
605 stem_end_position (me) or get_default_stem_end_position (me),
606 since this encounters the flag and hence results in an
607 infinite recursion. However, in pure mensural notation,
608 there are no multiple note heads attached to a single stem,
609 neither is there usually need for using the stem_shorten
610 property (except for 32th and 64th notes, but that is not a
611 problem since the stem length in this case is augmented by
612 an integral multiple of staff_space). Hence, it should be
613 sufficient to just take the first note head, assume it's
614 the only one, look if it's on a staff line, and select the
615 flag's shape accordingly. In the worst case, the shape
616 looks slightly misplaced, but that will usually be the
617 programmer's fault (e.g. when trying to attach multiple
618 note heads to a single stem in mensural notation).
622 perhaps the detection whether this correction is needed should
623 happen in a different place to avoid the recursion.
625 --hwn.
627 int p = Staff_symbol_referencer::get_rounded_position (me);
628 staffline_offs = Staff_symbol_referencer::on_staffline (me, p) ?
629 "1" : "0";
631 else
633 staffline_offs = "2";
636 else
638 staffline_offs = "";
641 char dir = (get_direction (me) == UP) ? 'u' : 'd';
642 String font_char =
643 flag_style + to_string (dir) + staffline_offs + to_string (duration_log (me));
644 Font_metric *fm = Font_interface::get_default_font (me);
645 Stencil flag = fm->find_by_name ("flags-" + font_char);
646 if (flag.is_empty ())
648 me->warning (_f ("flag `%s' not found", font_char));
651 SCM stroke_style_scm = me->get_property ("stroke-style");
652 if (gh_string_p (stroke_style_scm))
654 String stroke_style = ly_scm2string (stroke_style_scm);
655 if (!stroke_style.is_empty ())
657 String font_char = to_string (dir) + stroke_style;
658 Stencil stroke = fm->find_by_name ("flags-" + font_char);
659 if (stroke.is_empty ())
661 me->warning (_f ("flag stroke `%s' not found", font_char));
663 else
665 flag.add_stencil (stroke);
670 return flag;
673 MAKE_SCHEME_CALLBACK (Stem,dim_callback,2);
675 Stem::dim_callback (SCM e, SCM ax)
677 Axis a = (Axis) gh_scm2int (ax);
678 assert (a == X_AXIS);
679 Grob *me = unsmob_grob (e);
680 Interval r (0, 0);
681 if (unsmob_grob (me->get_property ("beam")) || abs (duration_log (me)) <= 2)
682 ; // TODO!
683 else
685 r = flag (me).extent (X_AXIS)
686 + thickness (me)/2;
688 return ly_interval2scm (r);
691 Real
692 Stem::thickness (Grob* me)
694 return gh_scm2double (me->get_property ("thickness"))
695 * Staff_symbol_referencer::line_thickness (me);
698 MAKE_SCHEME_CALLBACK (Stem,print,1);
701 Stem::print (SCM smob)
703 Grob*me = unsmob_grob (smob);
704 Stencil mol;
705 Direction d = get_direction (me);
708 TODO: make the stem start a direction ?
710 This is required to avoid stems passing in tablature chords...
712 Grob *lh = to_boolean (me->get_property ("avoid-note-head"))
713 ? last_head (me) : lh = first_head (me);
715 if (!lh)
716 return SCM_EOL;
718 if (is_invisible (me))
719 return SCM_EOL;
721 Real y1 = Staff_symbol_referencer::get_position (lh);
722 Real y2 = stem_end_position (me);
724 Interval stem_y (y1 <? y2,y2 >? y1);
727 // dy?
728 Real dy = Staff_symbol_referencer::staff_space (me) * 0.5;
730 if (Grob *hed = support_head (me))
733 must not take ledgers into account.
735 Interval head_height = Note_head::head_extent (hed,Y_AXIS);
736 Real y_attach = Note_head::stem_attachment_coordinate (hed, Y_AXIS);
738 y_attach = head_height.linear_combination (y_attach);
739 stem_y[Direction (-d)] += d * y_attach/dy;
743 // URG
744 Real stem_width = thickness (me);
745 Real blot =
746 me->get_paper ()->get_realvar (ly_symbol2scm ("blotdiameter"));
748 Box b = Box (Interval (-stem_width/2, stem_width/2),
749 Interval (stem_y[DOWN]*dy, stem_y[UP]*dy));
751 Stencil ss = Lookup::round_filled_box (b, blot);
752 mol.add_stencil (ss);
754 if (!get_beam (me) && abs (duration_log (me)) > 2)
756 Stencil fl = flag (me);
757 fl.translate_axis (stem_y[d]*dy - d * blot/2, Y_AXIS);
758 fl.translate_axis (stem_width/2, X_AXIS);
759 mol.add_stencil (fl);
762 return mol.smobbed_copy ();
766 move the stem to right of the notehead if it is up.
768 MAKE_SCHEME_CALLBACK (Stem,off_callback,2);
770 Stem::off_callback (SCM element_smob, SCM)
772 Grob *me = unsmob_grob (element_smob);
774 Real r=0;
776 if (head_count (me) == 0)
778 return gh_double2scm (0.0);
781 if (Grob * f = first_head (me))
783 Interval head_wid = Note_head::head_extent (f, X_AXIS);
785 Real attach =0.0;
787 if (is_invisible (me))
789 attach = 0.0;
791 else
792 attach = Note_head::stem_attachment_coordinate (f, X_AXIS);
794 Direction d = get_direction (me);
796 Real real_attach = head_wid.linear_combination (d * attach);
798 r = real_attach;
801 If not centered: correct for stem thickness.
803 if (attach)
805 Real rule_thick
806 = thickness (me);
808 r += - d * rule_thick * 0.5;
811 return gh_double2scm (r);
815 Grob*
816 Stem::get_beam (Grob*me)
818 SCM b= me->get_property ("beam");
819 return unsmob_grob (b);
822 Stem_info
823 Stem::get_stem_info (Grob *me)
825 /* Return cached info if available */
826 SCM scm_info = me->get_property ("stem-info");
827 if (!gh_pair_p (scm_info))
829 calc_stem_info (me);
830 scm_info = me->get_property ("stem-info");
833 Stem_info si;
834 si.dir_ = get_grob_direction (me);
835 si.ideal_y_ = gh_scm2double (gh_car (scm_info));
836 si.shortest_y_ = gh_scm2double (gh_cadr (scm_info));
837 return si;
842 TODO: add extra space for tremolos!
844 void
845 Stem::calc_stem_info (Grob *me)
847 Direction my_dir = get_grob_direction (me);
849 if (!my_dir)
851 programming_error ("No stem dir set?");
852 my_dir = UP;
855 Real staff_space = Staff_symbol_referencer::staff_space (me);
856 Grob *beam = get_beam (me);
857 Real beam_translation = Beam::get_beam_translation (beam);
858 Real beam_thickness = Beam::get_thickness (beam);
859 int beam_count = Beam::get_direction_beam_count (beam, my_dir);
862 /* Simple standard stem length */
863 SCM lengths = me->get_property ("beamed-lengths");
864 Real ideal_length =
865 gh_scm2double (robust_list_ref (beam_count - 1,lengths))
867 * staff_space
868 /* stem only extends to center of beam */
869 - 0.5 * beam_thickness;
871 /* Condition: sane minimum free stem length (chord to beams) */
872 lengths = me->get_property ("beamed-minimum-free-lengths");
873 Real ideal_minimum_free =
874 gh_scm2double (robust_list_ref (beam_count - 1, lengths))
875 * staff_space;
878 /* UGH
879 It seems that also for ideal minimum length, we must use
880 the maximum beam count (for this direction):
882 \score{ \notes\relative c''{ [a8 a32] }}
884 must be horizontal. */
885 Real height_of_my_beams = beam_thickness
886 + (beam_count - 1) * beam_translation;
888 Real ideal_minimum_length = ideal_minimum_free
889 + height_of_my_beams
890 /* stem only extends to center of beam */
891 - 0.5 * beam_thickness;
893 ideal_length = ideal_length >? ideal_minimum_length;
896 /* Convert to Y position, calculate for dir == UP */
897 Real note_start =
898 /* staff positions */
899 head_positions (me)[my_dir] * 0.5
900 * my_dir * staff_space;
901 Real ideal_y = note_start + ideal_length;
904 /* Conditions for Y position */
906 /* Lowest beam of (UP) beam must never be lower than second staffline
908 Reference?
910 Although this (additional) rule is probably correct,
911 I expect that highest beam (UP) should also never be lower
912 than middle staffline, just as normal stems.
914 Reference?
916 Obviously not for grace beams.
918 Also, not for knees. Seems to be a good thing. */
919 bool no_extend_b = to_boolean (me->get_property ("no-stem-extend"));
920 bool is_knee = to_boolean (beam->get_property ("knee"));
921 if (!no_extend_b && !is_knee)
923 /* Highest beam of (UP) beam must never be lower than middle
924 staffline */
925 ideal_y = ideal_y >? 0;
926 /* Lowest beam of (UP) beam must never be lower than second staffline */
927 ideal_y = ideal_y >? (-staff_space
928 - beam_thickness + height_of_my_beams);
932 ideal_y -= robust_scm2double (beam->get_property ("shorten"), 0);
934 Real minimum_free =
935 gh_scm2double (robust_list_ref
936 (beam_count - 1,
937 me->get_property
938 ("beamed-extreme-minimum-free-lengths")))
939 * staff_space;
941 Real minimum_length = minimum_free
942 + height_of_my_beams
943 /* stem only extends to center of beam */
944 - 0.5 * beam_thickness;
946 Real minimum_y = note_start + minimum_length;
949 ideal_y *= my_dir;
950 Real shortest_y = minimum_y * my_dir;
952 me->set_property ("stem-info",
953 scm_list_n (gh_double2scm (ideal_y),
954 gh_double2scm (shortest_y),
955 SCM_UNDEFINED));
958 Slice
959 Stem::beam_multiplicity (Grob *stem)
961 SCM beaming= stem->get_property ("beaming");
962 Slice l = int_list_to_slice (gh_car (beaming));
963 Slice r = int_list_to_slice (gh_cdr (beaming));
964 l.unite (r);
966 return l;
971 these are too many props.
973 ADD_INTERFACE (Stem,"stem-interface",
974 "The stem represent the graphical stem. "
975 " In addition, it internally connects note heads, beams, tremolos. Rests "
976 " and whole notes have invisible stems."
980 "tremolo-flag french-beaming "
981 "avoid-note-head thickness "
982 "stem-info beamed-lengths beamed-minimum-free-lengths "
983 "beamed-extreme-minimum-free-lengths lengths beam stem-shorten "
984 "duration-log beaming neutral-direction stem-end-position "
985 "note-heads direction length flag-style "
986 "no-stem-extend stroke-style");
990 /****************************************************************/
992 Stem_info::Stem_info ()
994 ideal_y_ = shortest_y_ =0;
995 dir_ = CENTER;
998 void
999 Stem_info::scale (Real x)
1001 ideal_y_ *= x;
1002 shortest_y_ *= x;