]> git.donarmstrong.com Git - lilypond.git/blob - lily/bar-line.cc
Web-it: replace "open source" with "software libero"
[lilypond.git] / lily / bar-line.cc
1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3
4   Copyright (C) 1997--2012 Han-Wen Nienhuys <hanwen@xs4all.nl>
5
6   LilyPond is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10
11   LilyPond is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "bar-line.hh"
21
22 #include "all-font-metrics.hh"
23 #include "font-interface.hh"
24 #include "line-interface.hh"
25 #include "lookup.hh"
26 #include "output-def.hh"
27 #include "paper-column.hh"
28 #include "staff-symbol.hh"
29 #include "staff-symbol-referencer.hh"
30
31 #include <set>
32
33
34 MAKE_SCHEME_CALLBACK (Bar_line, calc_bar_extent, 1)
35 SCM
36 Bar_line::calc_bar_extent (SCM smob)
37 {
38   Interval result;
39   Grob *me = unsmob_grob (smob);
40   if (Grob *staff = Staff_symbol_referencer::get_staff_symbol (me))
41     {
42       result = staff->extent (staff, Y_AXIS);
43
44       /* Due to rounding problems, bar lines extending to the outermost edges
45          of the staff lines appear wrongly in on-screen display
46          (and, to a lesser extent, in print) - they stick out a pixel.
47          The solution is to extend bar lines only to the middle
48          of the staff line - unless they have different colors,
49          when it would be undesirable.
50       */
51       SCM bar_line_color = me->get_property ("color");
52       SCM staff_color = staff->get_property ("color");
53       Real radius = Staff_symbol_referencer::staff_radius (me);
54       if (bar_line_color == staff_color && radius)
55         result *= (1 - 0.5 * (Staff_symbol_referencer::line_thickness (me) / radius));
56     }
57   return ly_interval2scm (result);
58 }
59
60 Interval
61 Bar_line::bar_y_extent (Grob *me, Grob *refpoint)
62 {
63   Interval iv = robust_scm2interval (me->get_property ("bar-extent"), Interval ());
64
65   iv.translate (me->relative_coordinate (refpoint, Y_AXIS));
66   return iv;
67 }
68
69 bool
70 Bar_line::non_empty_barline (Grob *me)
71 {
72   return has_interface (me) && !me->extent (me, X_AXIS).is_empty ();
73 }
74
75 MAKE_SCHEME_CALLBACK (Bar_line, print, 1);
76 SCM
77 Bar_line::print (SCM smob)
78 {
79   Grob *me = unsmob_grob (smob);
80
81   SCM s = me->get_property ("glyph-name");
82   SCM extent = me->get_property ("bar-extent");
83
84   if (scm_is_string (s) && is_number_pair (extent))
85     {
86       string str = ly_scm2string (s);
87       Interval ex = ly_scm2interval (extent);
88       if (ex.length () > 0)
89         {
90           Stencil result = compound_barline (me, str, ex, false);
91
92           return result.smobbed_copy ();
93         }
94     }
95   return SCM_EOL;
96 }
97
98 Stencil
99 Bar_line::compound_barline (Grob *me, string str, Interval const &extent,
100                             bool rounded)
101 {
102   Real kern = robust_scm2double (me->get_property ("kern"), 1);
103   Real thinkern = robust_scm2double (me->get_property ("thin-kern"), 1);
104   Real hair = robust_scm2double (me->get_property ("hair-thickness"), 1);
105   Real fatline = robust_scm2double (me->get_property ("thick-thickness"), 1);
106
107   Real staffline = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
108   Real staff_space = Staff_symbol_referencer::staff_space (me);
109
110   kern *= staffline;
111   thinkern *= staffline;
112   hair *= staffline;
113   fatline *= staffline;
114
115   Stencil thin = simple_barline (me, hair, extent, rounded);
116   Stencil thick = simple_barline (me, fatline, extent, rounded);
117   Stencil dot = Font_interface::get_default_font (me)->find_by_name ("dots.dot");
118
119   /*
120     the two dots of the repeat sign should be centred at the middle of
121     the staff and both should avoid staff lines
122   */
123   Real centre = 0.0, dist = 1.0;
124   if (staff_space)
125     {
126       if (Grob *staff = Staff_symbol_referencer::get_staff_symbol (me))
127         {
128           std::vector<Real> linepos = Staff_symbol::line_positions (staff);
129           if (!linepos.empty ())
130             {
131               centre = Staff_symbol::line_span (staff).center ();
132
133               /*
134                 fold the staff into two at centre and find the first gap
135                 big enough to hold a dot and some space below and above
136               */
137               std::set<Real> half_staff;
138               half_staff.insert (0.0);
139               for (std::vector<Real>::const_iterator
140                      i = linepos.begin (), e = linepos.end ();
141                    i != e;
142                    ++i)
143                 half_staff.insert (fabs (*i - centre));
144
145               /*
146                 gap is measured like line-positions;
147                 1.0 for dot diameter, twice the staffline width for the
148                 gap above and below and one more staffline width for the
149                 two half stafflines
150               */
151               Real const gap_to_find = (1.0 + 3 * staffline) / staff_space;
152               dist = *half_staff.rbegin () * 2 + gap_to_find;
153               for (std::set<Real>::const_iterator
154                      i0 = half_staff.begin (), i1 = i0, e = half_staff.end ();
155                    ++i1 != e;
156                    i0 = i1)
157                 if (*i1 - *i0 > gap_to_find)
158                   {
159                     dist = *i0 + *i1;
160                     break;
161                   }
162             }
163         }
164     }
165   else
166     dist += 3 * staffline;
167
168   if (staff_space == 0.0)
169     staff_space = 1.0;
170
171   Stencil colon (dot);
172   colon.translate_axis (dist * staff_space / 2, Y_AXIS);
173   colon.add_stencil (dot);
174   colon.translate_axis ((centre - dist / 2) * staff_space / 2, Y_AXIS);
175
176   Real const h = extent.length ();
177   Stencil m;
178
179   if (str == "||:")
180     str = "|:";
181
182   if (str == "|S" || str == "S|")
183     str = "S";
184
185   if (str == "")
186     {
187       Stencil empty = Lookup::blank (Box (Interval (0, 0), extent));
188       return empty;
189     }
190   else if (str == "|")
191     return thin;
192   else if (str == ".")
193     return thick;
194   else if (str == "|." || (h == 0 && str == ":|"))
195     {
196       m.add_at_edge (X_AXIS, LEFT, thick, 0);
197       m.add_at_edge (X_AXIS, LEFT, thin, kern);
198     }
199   else if (str == ".|" || (h == 0 && str == "|:"))
200     {
201       m.add_at_edge (X_AXIS, RIGHT, thick, 0);
202       m.add_at_edge (X_AXIS, RIGHT, thin, kern);
203     }
204   else if (str == ":|")
205     {
206       m.add_at_edge (X_AXIS, LEFT, thick, 0);
207       m.add_at_edge (X_AXIS, LEFT, thin, kern);
208       m.add_at_edge (X_AXIS, LEFT, colon, kern);
209     }
210   else if (str == "|:")
211     {
212       m.add_at_edge (X_AXIS, RIGHT, thick, 0);
213       m.add_at_edge (X_AXIS, RIGHT, thin, kern);
214       m.add_at_edge (X_AXIS, RIGHT, colon, kern);
215     }
216   else if (str == ":|:")
217     {
218       m.add_at_edge (X_AXIS, LEFT, thick, thinkern);
219       m.add_at_edge (X_AXIS, LEFT, colon, kern);
220       m.add_at_edge (X_AXIS, RIGHT, thick, kern);
221       m.add_at_edge (X_AXIS, RIGHT, colon, kern);
222     }
223   else if (str == ":|.|:")
224     {
225       m.add_at_edge (X_AXIS, LEFT, thick, 0);
226       m.add_at_edge (X_AXIS, LEFT, thin, kern);
227       m.add_at_edge (X_AXIS, LEFT, colon, kern);
228       m.add_at_edge (X_AXIS, RIGHT, thin, kern);
229       m.add_at_edge (X_AXIS, RIGHT, colon, kern);
230     }
231   else if (str == ":|.:")
232     {
233       m.add_at_edge (X_AXIS, LEFT, thick, 0);
234       m.add_at_edge (X_AXIS, LEFT, thin, kern);
235       m.add_at_edge (X_AXIS, LEFT, colon, kern);
236       m.add_at_edge (X_AXIS, RIGHT, colon, kern);
237     }
238   else if (str == ".|.")
239     {
240       m.add_at_edge (X_AXIS, LEFT, thick, thinkern);
241       m.add_at_edge (X_AXIS, RIGHT, thick, kern);
242     }
243   else if (str == "|.|")
244     {
245       m.add_at_edge (X_AXIS, LEFT, thick, 0);
246       m.add_at_edge (X_AXIS, LEFT, thin, kern);
247       m.add_at_edge (X_AXIS, RIGHT, thin, kern);
248     }
249   else if (str == "||")
250     {
251       /*
252         should align to other side? this never appears
253         on the system-start?
254         m.add_at_edge (X_AXIS, RIGHT, thin, 0);
255         m.add_at_edge (X_AXIS, RIGHT, thin, thinkern);
256       */
257       m.add_at_edge (X_AXIS, LEFT, thin, thinkern);
258       m.add_at_edge (X_AXIS, RIGHT, thin, thinkern);
259     }
260   else if (str.find ("S") != NPOS || str == "|._.|")
261     {
262       //  Handle all varsegno stuff
263       Stencil segno;
264       segno.add_at_edge (X_AXIS, LEFT, thin, thinkern);
265       segno.add_at_edge (X_AXIS, RIGHT, thin, thinkern);
266       segno.add_stencil (Font_interface::get_default_font (me)->find_by_name ("scripts.varsegno"));
267
268       if (str == "S")
269         m.add_stencil (segno);
270       else if (str == "S|:" || str == ".S|:")
271         {
272           m.add_at_edge (X_AXIS, RIGHT, thick, 0);
273           m.add_at_edge (X_AXIS, RIGHT, thin, kern);
274           m.add_at_edge (X_AXIS, RIGHT, colon, kern);
275           m.add_at_edge (X_AXIS, LEFT, segno, thinkern);
276         }
277       else if (str == ":|S" || str == ":|S.")
278         {
279           m.add_at_edge (X_AXIS, LEFT, thick, 0);
280           m.add_at_edge (X_AXIS, LEFT, thin, kern);
281           m.add_at_edge (X_AXIS, LEFT, colon, kern);
282           m.add_at_edge (X_AXIS, RIGHT, segno, thinkern);
283         }
284       else if (str == ":|S|:" || str == ":|S.|:")
285         {
286           m.add_at_edge (X_AXIS, LEFT, thick, 0);
287           m.add_at_edge (X_AXIS, LEFT, thin, kern);
288           m.add_at_edge (X_AXIS, LEFT, colon, kern);
289           m.add_at_edge (X_AXIS, RIGHT, segno, thinkern);
290           m.add_at_edge (X_AXIS, RIGHT, thick, thinkern);
291           m.add_at_edge (X_AXIS, RIGHT, thin, kern);
292           m.add_at_edge (X_AXIS, RIGHT, colon, kern);
293         }
294       else if (str == "|._.|") // :|S|: or :|S.|: without segno and colon
295         {
296           // get the width of the segno sign
297           Real segno_width = segno.extent (X_AXIS).length ();
298           m.add_at_edge (X_AXIS, LEFT, thick, 0);
299           m.add_at_edge (X_AXIS, LEFT, thin, kern);
300           m.add_at_edge (X_AXIS, RIGHT, thick, segno_width + 2 * thinkern);
301           m.add_at_edge (X_AXIS, RIGHT, thin, kern);
302         }
303       // end varsegno block
304     }
305   else if (str == ":")
306     {
307       if (Grob *staff = Staff_symbol_referencer::get_staff_symbol (me))
308         {
309           Interval staff_extent = staff->extent (staff, Y_AXIS);
310
311           /*
312             assume staff lines are disposed equally at unit space;
313             put a dot into each space within extent (may extend staff_extent).
314
315             staff_extent is an interval of two integers or two half-integers;
316             in the former case dots are to be placed at half-integers,
317             in the latter at integers.
318
319             these integers are not exact due to staff line thickness.
320           */
321           int const pos = int (rint (staff_extent.at (UP) * 2));
322           Real const correction = pos & 1 ? 0.0 : 0.5;
323
324           for (int i = int (rint (extent.at (DOWN) + (0.5 - correction))),
325                e = int (rint (extent.at (UP) + (0.5 - correction)));
326                i < e;
327                ++i)
328             {
329               Stencil d (dot);
330
331               d.translate_axis (i + correction, Y_AXIS);
332               m.add_stencil (d);
333             }
334         }
335     }
336   else if (str == "dashed")
337     m = dashed_bar_line (me, extent, hair);
338   else if (str == "'")
339     m = tick_bar_line (me, extent.at (UP), rounded);
340   else if (str == "kievan")
341    {
342     me->set_property ("layer", scm_from_int (1));
343     m.add_stencil (Font_interface::get_default_font (me)->find_by_name ("scripts.barline.kievan"));
344     m = *unsmob_stencil (scm_call_1 (ly_lily_module_constant ("stencil-whiteout"), m.smobbed_copy ()));
345    }
346   return m;
347 }
348
349 Stencil
350 Bar_line::simple_barline (Grob *me,
351                           Real w,
352                           Interval const &extent,
353                           bool rounded)
354 {
355   Real blot
356     = rounded
357       ? me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"))
358       : 0.0;
359
360   return Lookup::round_filled_box (Box (Interval (0, w), extent), blot);
361 }
362
363 Stencil
364 Bar_line::tick_bar_line (Grob *me, Real h, bool rounded)
365 {
366   Real th = Staff_symbol_referencer::staff_space (me) / 2;
367   Real line_thick = Staff_symbol_referencer::line_thickness (me);
368
369   Real blot
370     = rounded
371       ? me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"))
372       : 0.0;
373
374   return Lookup::round_filled_box (Box (Interval (0, line_thick),
375                                         Interval (h - th, h + th)), blot);
376 }
377
378 Stencil
379 Bar_line::dashed_bar_line (Grob *me, Interval const &extent, Real thick)
380 {
381   Real dash_size
382     = 1.0 - robust_scm2double (me->get_property ("gap"), 0.3);
383   /*
384     this is a tad complex for what we want to achieve, but with a
385     simple line, the round blotting interferes with staff line
386     connections.
387   */
388   Real ss = Staff_symbol_referencer::staff_space (me);
389   Real const h = extent.length ();
390   int dashes = int (rint (h / ss));
391
392   /*
393     there are two concerns:
394     1. one dash plus one space should be one staff space
395     2. the line should begin and end with half a dash
396
397     both can be satisfied, if the extent is (roughly) an integer
398     multiple of staff space.
399   */
400   if (fabs (h / ss - dashes) < 0.1)
401     {
402       Real blot
403         = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
404
405       Real const half_dash = dash_size / 2;
406       Stencil bar;
407
408       for (int i = 0; i <= dashes; ++i)
409         {
410           Real top_y = extent.at (DOWN)
411                        + (i == dashes ? h : (i + half_dash) * ss);
412           Real bot_y = extent.at (DOWN) + (i ? (i - half_dash) * ss : 0.0);
413
414           bar.add_stencil (Lookup::round_filled_box (Box (Interval (0, thick),
415                                                           Interval (bot_y, top_y)),
416                                                      blot));
417         }
418       return bar;
419     }
420   else
421     {
422       /*
423         We have to scale the dashing so it starts and ends with half a
424         dash exactly.
425       */
426       Real total_dash_size = h / dashes;
427       Real factor = (dash_size - thick) / ss;
428
429       SCM at = scm_list_n (ly_symbol2scm ("dashed-line"),
430                            scm_from_double (thick),
431                            scm_from_double (factor * total_dash_size),
432                            scm_from_double ((1 - factor) * total_dash_size),
433                            scm_from_double (0),
434                            scm_from_double (h),
435                            scm_from_double (factor * total_dash_size * 0.5),
436                            SCM_UNDEFINED);
437
438       Box box;
439       box.add_point (Offset (0, 0));
440       box.add_point (Offset (0, h));
441
442       Stencil s (box, at);
443       s.translate (Offset (thick / 2, extent.at (DOWN)));
444       return s;
445     }
446   return Stencil ();
447 }
448
449 MAKE_SCHEME_CALLBACK (Bar_line, calc_anchor, 1)
450 SCM
451 Bar_line::calc_anchor (SCM smob)
452 {
453   Grob *me = unsmob_grob (smob);
454   Real kern = robust_scm2double (me->get_property ("kern"), 1);
455   Real staffline = me->layout ()->get_dimension (ly_symbol2scm ("line-thickness"));
456   string str = robust_scm2string (me->get_property ("glyph-name"), "");
457
458   /* we put the anchor in the center of the barline, unless we are
459      a repeat bar, in which case we put the anchor in the center of
460      the barline without the dots. */
461   Interval ext = me->extent (me, X_AXIS);
462   if (ext.is_empty ())
463     return scm_from_double (0);
464
465   Real anchor = ext.center ();
466
467   Stencil dot = Font_interface::get_default_font (me)->find_by_name ("dots.dot");
468   Real dot_width = dot.extent (X_AXIS).length () + kern * staffline;
469   if (str == "|:")
470     anchor -= dot_width / 2;
471   else if (str == ":|")
472     anchor += dot_width / 2;
473
474   return scm_from_double (anchor);
475 }
476
477 ADD_INTERFACE (Bar_line,
478                "Bar line.\n"
479                "\n"
480                "Print a special bar symbol.  It replaces the regular bar"
481                " symbol with a special symbol.  The argument @var{bartype}"
482                " is a string which specifies the kind of bar line to print."
483                "  Options are @code{|}, @code{:|}, @code{|:}, @code{:|:}, @code{:|.|:},"
484                " @code{:|.:}, @code{.}, @code{||}, @code{|.}, @code{.|}, @code{.|.},"
485                " @code{|.|}, @code{:}, @code{dashed}, @code{'} and @code{S}.\n"
486                "\n"
487                "These produce, respectively, a normal bar line, a right repeat, a left repeat,"
488                " a thick double repeat, a thin-thick-thin double repeat,"
489                " a thin-thick double repeat, a thick bar, a double bar, a start bar,"
490                " an end bar, a thick double bar, a thin-thick-thin bar,"
491                " a dotted bar, a dashed bar, a tick as bar line and a segno bar.\n"
492                "\n"
493                "In addition, there is an option"
494                " @code{||:} which is equivalent to @code{|:} except at line"
495                " breaks, where it produces a double bar (@code{||}) at the"
496                " end of the line and a repeat sign (@code{|:}) at the"
497                " beginning of the new line.\n"
498                "\n"
499                "For segno, @code{S} produces a segno sign except at line breaks,"
500                " where it produces a double bar (@code{||}) at the"
501                " end of the line and a segno sign at the beginning of the new line."
502                " @code{|S} is equivalent to @code{S} but produces a simple bar line"
503                " (@code{|}) instead of a double bar line (@code{||}) at line breaks."
504                " @code{S|} produces the segno sign at line breaks and starts the following"
505                " line without special bar lines.\n"
506                "\n"
507                "@code{S|:} and @code{:|S} are used for repeat/segno combinations that are"
508                " separated at line breaks.  Alternatively, @code{.S|:} and @code{:|S.}"
509                " may be used which combine repeat signs and segno at the same line in"
510                " case of a line break.  @code{:|S|:} is a combination of a left repeat"
511                " (@code{:|}), a segno (@code{S}) and a right repeat @code{|:} which"
512                " splits before the segno at line breaks; @code{:|S.|:} splits after"
513                " the segno sign.\n"
514                "\n"
515                "If @var{bartype} is set to @code{empty} then nothing is"
516                " printed, but a line break is allowed at that spot.\n"
517                "\n"
518                "@code{gap} is used for the gaps in dashed bar lines.",
519
520                /* properties */
521                "allow-span-bar "
522                "gap "
523                "kern "
524                "thin-kern "
525                "hair-thickness "
526                "has-span-bar "
527                "thick-thickness "
528                "glyph "
529                "glyph-name "
530                "bar-extent "
531               );