]> git.donarmstrong.com Git - lilypond.git/blob - lily/syllable-group.cc
* VERSION (MY_PATCH_LEVEL): make 1.7.0
[lilypond.git] / lily / syllable-group.cc
1 #include <string.h>
2
3 #include "lyric-phrasing-engraver.hh"
4 #include "note-head.hh"
5 #include "translator-group.hh"
6 #include "side-position-interface.hh"
7 #include "ly-smobs.icc"
8 #include "spanner.hh"
9 #include "paper-def.hh"
10
11
12 /*
13   Syllable_group is a class to be smobbed and entered as data in the
14   association list member of the Lyric_phrasing_engraver class.
15 */
16
17 Syllable_group::Syllable_group ()
18 {
19   first_in_phrase_b_=true;
20   melisma_b_ = false;
21   clear ();
22 }
23
24 void 
25 Syllable_group::clear ()
26 {
27   notehead_=0;
28   lyrics_.clear ();
29   longest_lyric_=0;
30   shortest_lyric_=0;
31   melisma_b_ = false;
32   group_translation_ = 0.0;
33 }
34   
35 void
36 Syllable_group::copy (Syllable_group *from)
37 {
38   notehead_ = from->notehead_;
39   lyrics_ = from->lyrics_;
40   longest_lyric_ = from->longest_lyric_;
41   shortest_lyric_ = from->shortest_lyric_;
42   melisma_b_ = from->melisma_b_;
43   alignment_ = from->alignment_;
44   first_in_phrase_b_ = from->first_in_phrase_b_;
45   group_translation_ = from->group_translation_;
46 }
47
48 void 
49 Syllable_group::set_first_in_phrase (bool f) 
50
51   first_in_phrase_b_ = f; 
52 }
53
54 void 
55 Syllable_group::set_notehead (Grob * notehead)
56 {
57   if (!notehead_)
58     {
59       /* there should only be a single notehead, so silently ignore
60          any extras */
61       notehead_=notehead;
62     }
63 }
64
65 void 
66 Syllable_group::add_lyric (Grob * lyric)
67 {
68   lyrics_.push (lyric);
69   /* record longest and shortest lyrics */
70   if (longest_lyric_)
71     {
72       if (lyric->extent (lyric,X_AXIS).length () > (longest_lyric_->extent (longest_lyric_, X_AXIS)).length ())
73         longest_lyric_ = lyric;
74       if (lyric->extent (lyric, X_AXIS).length () < (shortest_lyric_->extent (shortest_lyric_, X_AXIS)).length ())
75         shortest_lyric_ = lyric;
76     }
77   else
78     longest_lyric_ = shortest_lyric_ = lyric;
79 }
80
81 void 
82 Syllable_group::add_extender (Grob * extender)
83 {
84   if (notehead_ && melisma_b_)
85     {
86       dynamic_cast<Spanner*> (extender)->set_bound (RIGHT, notehead_);
87       // should the extender finish at the right of the last note of the melisma, or the left?
88       // Comments in lyric-extender.hh say left, but right looks better to me. GP.
89
90       // Left:
91       //     extender->set_grob_property ("right-trim-amount", gh_double2scm (0.0));
92
93       // Right:
94       Real ss = 1.0;
95       extender->set_grob_property ("right-trim-amount", 
96                                    gh_double2scm (-notehead_->extent (notehead_, X_AXIS).length ()/ss));
97     }
98 }
99
100 bool 
101 Syllable_group::set_lyric_align (const char *punc, Grob *default_notehead)
102 {
103   if (lyrics_.size () <= 0)
104     {
105       // No lyrics: nothing to do.
106       return true;
107     }
108   
109   Grob * lyric;
110   alignment_ = appropriate_alignment (punc);
111   
112   /* If there was no notehead in the matching voice context, use the
113    first notehead caught from any voice context (any port in a storm).
114
115
116    Is this wise? Can't the lyric simply be set on a the paper-column,
117    and be done with it. That's just as correct, and won't give strange
118    results if the port-in-the-storms happesn to be involved in a
119    note-collision? --hwn.
120   */
121   if (!notehead_)
122     {
123       notehead_ = default_notehead;
124     }
125
126   group_translation_ = amount_to_translate ();
127
128   // set the x alignment of each lyric
129   for (int l = 0; l < lyrics_.size (); l++)
130     {
131       lyric = lyrics_[l];
132       lyric->set_grob_property ("self-alignment-X", gh_int2scm (alignment_));
133       if (notehead_)
134         {
135           /*
136             Centering on parent is done by default (see
137             grob-description.scm); we only have to set the parent.
138           */
139           lyric->set_parent (notehead_, X_AXIS);
140           lyric->translate_axis (group_translation_, X_AXIS);
141         }
142     }
143   return (notehead_);
144 }
145
146 /** determine the distance to translate lyrics to get correct alignment
147     Rules: If alignment is centre, translate = 0
148     Otherwise,
149     If (length of longest lyric) < (property {begin,end}-alignment) * (length of shortest lyric),
150     - centre longest lyric on notehead
151     Otherwise
152     - move so shortest lyric just reaches notehead centre
153 */
154 Real 
155 Syllable_group::amount_to_translate ()
156 {
157   Real translate = 0.0;
158   if (alignment_ != CENTER)
159     {
160       switch (alignment_)
161         {
162           // FIXME: do we really know the lyric extent here? Some font sizing comes later?
163         case LEFT: 
164           translate =  longest_lyric_->extent (longest_lyric_, X_AXIS).length () / gh_scm2double (longest_lyric_->get_grob_property("begin-alignment"));
165           break;
166         case RIGHT: 
167           translate =   longest_lyric_->extent (longest_lyric_, X_AXIS).length () / gh_scm2double (longest_lyric_->get_grob_property("end-alignment"));
168           break;
169         }
170       if (!gh_scm2bool(longest_lyric_->get_grob_property("ignore-length-mismatch")))
171         {
172           Real l = shortest_lyric_->extent (shortest_lyric_, X_AXIS).length ();
173           translate = l <? translate;
174         }
175     
176       translate *= alignment_ ;
177     }
178   return translate;
179 }
180
181
182 /** determine what alignment we want.
183     Rules: if property alignment is set it specifies the alignment
184     if first_in_phrase_b_ is set, then alignment is LEFT.
185     otherwise if each syllable ends in punctuation, then alignment is RIGHT
186     otherwise alignment is centre.
187 */
188 int 
189 Syllable_group::appropriate_alignment (const char *punc)
190 {
191   SCM s=this->longest_lyric_->get_grob_property ("alignment");
192   if (s!=SCM_EOL)
193     {
194       return gh_scm2int (s);
195     }
196
197   if (first_in_phrase_b_)
198     return LEFT;
199
200   Grob * lyric;
201   bool end_phrase = true;
202
203   for (int l = 0; l < lyrics_.size () && end_phrase; l++)
204     {
205       lyric = lyrics_[l];
206       SCM lyric_scm = lyric->get_grob_property ("text");
207       String lyric_string = gh_string_p (lyric_scm)?ly_scm2string (lyric_scm):"";
208       char lastchar;
209       if (lyric_string.length ()>0)
210         {
211           lastchar = lyric_string[lyric_string.length ()-1];
212           /* If it doesn't end in punctuation then it ain't an end of phrase */
213           if (! strchr (punc, lastchar))
214             {
215               /*
216                 FIXME: Document this.
217           
218                 Special case: trailing space. Here examine the
219                 previous character and reverse the sense of the test
220                 (i.e. trailing space makes a break without
221                 punctuation, or suppresses a break with punctuation).
222                 This behaviour can be suppressed by including a space
223                 in the phrasingPunctuation property, in which case
224                 trailing space always means the same as punctuation.
225
226                 FIXME: The extra space throws alignment out a bit.
227               */
228               if (lastchar == ' ')
229                 {
230                   if (lyric_string.length ()>1)
231                     {
232                       lastchar = lyric_string[lyric_string.length ()-2];
233                       if (strchr (punc, lastchar))
234                         end_phrase=false;
235                     }
236                 }
237               else
238                 end_phrase=false;
239             }
240         }
241     }
242   if (end_phrase)
243     return RIGHT;
244
245   return CENTER;
246 }
247
248 /** We don't know about the melisma until after the initial alignment work is done, so go
249     back and fix the alignment when we DO know.
250 */
251 void
252 Syllable_group::adjust_melisma_align ()
253 {
254   if (notehead_ && lyrics_.size ())
255     {
256       // override any previous offset adjustments
257       Real translation = -group_translation_;
258       // melisma aligning:
259       switch (alignment_)
260         {
261           //  case LEFT: // that's all
262         case CENTER: // move right so smallest lyric is left-aligned on notehead
263           translation += (shortest_lyric_->extent (shortest_lyric_, X_AXIS)).length ()/2;
264           break;
265         case RIGHT: // move right so smallest lyric is left-aligned on notehead
266           translation += (shortest_lyric_->extent (shortest_lyric_, X_AXIS)).length ();
267           break;
268         }
269       group_translation_ += translation;
270       for (int l = 0; l < lyrics_.size (); l++)
271         {
272           lyrics_[l]->translate_axis (translation, X_AXIS);
273         }
274     }
275 }
276
277
278 bool
279 Syllable_group::is_empty ()
280 {
281   return lyrics_.size ()==0;
282 }
283
284 void
285 Syllable_group::next_lyric ()
286 {
287   first_in_phrase_b_ = (alignment_ == RIGHT);
288   clear ();
289 }
290
291 /* SMOB */
292 SCM
293 Syllable_group::mark_smob (SCM)
294 {
295   return SCM_EOL;
296 }
297
298 int
299 Syllable_group::print_smob (SCM, SCM port, scm_print_state *)
300 {
301   scm_puts ("#<Syllable_group>", port);
302   return 1;
303 }
304
305
306 IMPLEMENT_SIMPLE_SMOBS (Syllable_group);
307 IMPLEMENT_DEFAULT_EQUAL_P (Syllable_group);
308
309 SCM
310 Syllable_group::make_entry ()
311 {
312   Syllable_group *vi = new Syllable_group;
313   return vi->smobbed_self ();
314 }
315
316 struct Lyric_syllable
317 {
318   static bool has_interface (Grob*);
319 };
320 ADD_INTERFACE (Lyric_syllable,"lyric-syllable-interface",
321                "a single piece of lyrics",
322                "word-space alignment ignore-length-mismatch begin-alignment end-alignment");
323