1 /*
2   This file is part of LilyPond, the GNU music typesetter.
3 
4   Copyright (C) 1996--2021 Han-Wen Nienhuys <hanwen@xs4all.nl>
5   Jan Nieuwenhuizen <janneke@gnu.org>
6 
7   LilyPond is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11 
12   LilyPond is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16 
17   You should have received a copy of the GNU General Public License
18   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "stem.hh"
22 
23 #include "directional-element-interface.hh"
24 #include "font-interface.hh"
25 #include "grob.hh"
26 #include "international.hh"
27 #include "output-def.hh"
28 #include "staff-symbol-referencer.hh"
29 #include "stencil.hh"
30 #include "warn.hh"
31 
32 using std::string;
33 
34 class Flag
35 {
36 public:
37   DECLARE_SCHEME_CALLBACK (print, (SCM));
38   DECLARE_SCHEME_CALLBACK (glyph_name, (SCM));
39   DECLARE_SCHEME_CALLBACK (width, (SCM));
40   DECLARE_SCHEME_CALLBACK (calc_y_offset, (SCM));
41   DECLARE_SCHEME_CALLBACK (pure_calc_y_offset, (SCM, SCM, SCM));
42   DECLARE_SCHEME_CALLBACK (calc_x_offset, (SCM));
43 
44   static SCM internal_calc_y_offset (SCM smob, bool pure);
45 };
46 
47 MAKE_SCHEME_CALLBACK (Flag, width, 1);
48 SCM
width(SCM smob)49 Flag::width (SCM smob)
50 {
51   Grob *me = unsmob<Grob> (smob);
52   auto *sten = unsmob<const Stencil> (get_property (me, "stencil"));
53   if (!sten)
54     return to_scm (Interval (0.0, 0.0));
55 
56   Grob *stem = me->get_x_parent ();
57 
58   /*
59     TODO:
60     This reproduces a bad hard-coding that has been in the code for quite some time:
61     the bounding boxes for the flags are slightly off and need to be fixed.
62   */
63 
64   return to_scm (sten->extent (X_AXIS) - stem->extent (stem, X_AXIS)[RIGHT]);
65 }
66 
67 MAKE_SCHEME_CALLBACK (Flag, glyph_name, 1);
68 SCM
glyph_name(SCM smob)69 Flag::glyph_name (SCM smob)
70 {
71   Grob *me = unsmob<Grob> (smob);
72   Grob *stem = me->get_x_parent ();
73 
74   Direction d = get_grob_direction (stem);
75   int log = Stem::duration_log (stem);
76   string flag_style;
77 
78   SCM flag_style_scm = get_property (me, "style");
79   if (scm_is_symbol (flag_style_scm))
80     flag_style = ly_symbol2string (flag_style_scm);
81 
82   bool adjust = true;
83 
84   string staffline_offs;
85   if (flag_style == "mensural")
86     /* Mensural notation: For notes on staff lines, use different
87        flags than for notes between staff lines.  The idea is that
88        flags are always vertically aligned with the staff lines,
89        regardless if the note head is on a staff line or between two
90        staff lines.  In other words, the inner end of a flag always
91        touches a staff line.
92     */
93     {
94       if (adjust)
95         {
96           Real ss = Staff_symbol_referencer::staff_space (me);
97           const auto p = static_cast<int>
98                          (rint (stem->extent (stem, Y_AXIS)[d] * 2 / ss));
99           staffline_offs
100             = Staff_symbol_referencer::on_line (stem, p) ? "0" : "1";
101         }
102       else
103         staffline_offs = "2";
104     }
105   else
106     staffline_offs = "";
107 
108   char dir = (d == UP) ? 'u' : 'd';
109   string font_char = flag_style + dir + staffline_offs + std::to_string (log);
110   return ly_string2scm ("flags." + font_char);
111 }
112 
113 MAKE_SCHEME_CALLBACK (Flag, print, 1);
114 SCM
print(SCM smob)115 Flag::print (SCM smob)
116 {
117   Grob *me = unsmob<Grob> (smob);
118   Grob *stem = me->get_x_parent ();
119 
120   Direction d = get_grob_direction (stem);
121   string flag_style;
122 
123   SCM flag_style_scm = get_property (me, "style");
124   if (scm_is_symbol (flag_style_scm))
125     flag_style = ly_symbol2string (flag_style_scm);
126 
127   if (flag_style == "no-flag")
128     return Stencil ().smobbed_copy ();
129 
130   char dir = (d == UP) ? 'u' : 'd';
131   Font_metric *fm = Font_interface::get_default_font (me);
132   string font_char = robust_scm2string (get_property (me, "glyph-name"), "");
133   Stencil flag = fm->find_by_name (font_char);
134   if (flag.is_empty ())
135     me->warning (_f ("flag `%s' not found", font_char));
136 
137   /*
138     TODO: maybe property stroke-style should take different values,
139     e.g. "" (i.e. no stroke), "single" and "double" (currently, it's
140     '() or "grace").  */
141   SCM stroke_style_scm = get_property (me, "stroke-style");
142   if (scm_is_string (stroke_style_scm))
143     {
144       string stroke_style = ly_scm2string (stroke_style_scm);
145       if (!stroke_style.empty ())
146         {
147           string font_char = flag_style + dir + stroke_style;
148           Stencil stroke = fm->find_by_name ("flags." + font_char);
149           if (stroke.is_empty ())
150             {
151               font_char = dir + stroke_style;
152               stroke = fm->find_by_name ("flags." + font_char);
153             }
154           if (stroke.is_empty ())
155             me->warning (_f ("flag stroke `%s' not found", font_char));
156           else
157             flag.add_stencil (stroke);
158         }
159     }
160 
161   return flag.smobbed_copy ();
162 }
163 
164 MAKE_SCHEME_CALLBACK (Flag, pure_calc_y_offset, 3);
165 SCM
pure_calc_y_offset(SCM smob,SCM,SCM)166 Flag::pure_calc_y_offset (SCM smob,
167                           SCM /* beg */,
168                           SCM /* end */)
169 {
170   return internal_calc_y_offset (smob, true);
171 }
172 
173 MAKE_SCHEME_CALLBACK (Flag, calc_y_offset, 1);
174 SCM
calc_y_offset(SCM smob)175 Flag::calc_y_offset (SCM smob)
176 {
177   return internal_calc_y_offset (smob, false);
178 }
179 
180 SCM
internal_calc_y_offset(SCM smob,bool pure)181 Flag::internal_calc_y_offset (SCM smob, bool pure)
182 {
183   Grob *me = unsmob<Grob> (smob);
184   Grob *stem = me->get_x_parent ();
185   Direction d = get_grob_direction (stem);
186 
187   Real blot
188     = me->layout ()->get_dimension (ly_symbol2scm ("blot-diameter"));
189 
190   Interval stem_extent = pure
191                          ? stem->pure_y_extent (stem, 0, INT_MAX)
192                          : stem->extent (stem, Y_AXIS);
193 
194   return to_scm (stem_extent.is_empty ()
195                  ? 0.0
196                  : stem_extent[d] - d * blot / 2);
197 }
198 
199 MAKE_SCHEME_CALLBACK (Flag, calc_x_offset, 1);
200 SCM
calc_x_offset(SCM smob)201 Flag::calc_x_offset (SCM smob)
202 {
203   Grob *me = unsmob<Grob> (smob);
204   Grob *stem = me->get_x_parent ();
205   return to_scm (stem->extent (stem, X_AXIS)[RIGHT]);
206 }
207 
208 ADD_INTERFACE (Flag,
209                "A flag that gets attached to a stem."
210                "The style property is  symbol determining"
211                " what style of flag glyph is typeset on a"
212                " @code{Stem}.  Valid options include @code{'()}"
213                " for standard flags, @code{'mensural} and"
214                " @code{'no-flag}, which switches off the flag.",
215 
216                /* properties */
217                "glyph-name "
218                "style "
219                "stroke-style "
220               );
221