1use crate::display::Drawn;
8use crate::notation::Placement;
9
10pub use crate::stream::Expander;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum RepeatExpressionKind {
16 Coda,
18 Segno,
20 Fine,
22 DaCapo,
24 DaCapoAlFine,
26 DaCapoAlCoda,
28 AlSegno,
30 DalSegno,
32 DalSegnoAlFine,
34 DalSegnoAlCoda,
36}
37
38impl RepeatExpressionKind {
39 pub const ALL: [Self; 10] = [
41 Self::Coda,
42 Self::Segno,
43 Self::Fine,
44 Self::DaCapo,
45 Self::DaCapoAlFine,
46 Self::DaCapoAlCoda,
47 Self::AlSegno,
48 Self::DalSegno,
49 Self::DalSegnoAlFine,
50 Self::DalSegnoAlCoda,
51 ];
52
53 pub fn class_name(self) -> &'static str {
55 match self {
56 Self::Coda => "Coda",
57 Self::Segno => "Segno",
58 Self::Fine => "Fine",
59 Self::DaCapo => "DaCapo",
60 Self::DaCapoAlFine => "DaCapoAlFine",
61 Self::DaCapoAlCoda => "DaCapoAlCoda",
62 Self::AlSegno => "AlSegno",
63 Self::DalSegno => "DalSegno",
64 Self::DalSegnoAlFine => "DalSegnoAlFine",
65 Self::DalSegnoAlCoda => "DalSegnoAlCoda",
66 }
67 }
68
69 pub fn from_class_name(name: &str) -> Option<Self> {
71 Self::ALL.into_iter().find(|kind| kind.class_name() == name)
72 }
73
74 pub fn text_alternatives(self) -> &'static [&'static str] {
77 match self {
78 Self::Coda => &["Coda", "to Coda", "al Coda"],
79 Self::Segno => &["Segno"],
80 Self::Fine => &["fine"],
81 Self::DaCapo => &["Da Capo", "D.C."],
82 Self::DaCapoAlFine => &["Da Capo al fine", "D.C. al fine"],
83 Self::DaCapoAlCoda => &["Da Capo al Coda", "D.C. al Coda"],
84 Self::AlSegno => &["al Segno"],
85 Self::DalSegno => &["Dal Segno", "D.S."],
86 Self::DalSegnoAlFine => &["Dal Segno al fine", "D.S. al fine"],
87 Self::DalSegnoAlCoda => &["Dal Segno al Coda", "D.S. al Coda"],
88 }
89 }
90
91 pub fn is_marker(self) -> bool {
95 matches!(self, Self::Coda | Self::Segno | Self::Fine)
96 }
97
98 pub fn justify(self) -> &'static str {
101 match self {
102 Self::Coda | Self::Segno => "center",
103 _ => "right",
104 }
105 }
106
107 pub fn has_symbol(self) -> bool {
110 matches!(self, Self::Coda | Self::Segno)
111 }
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Hash)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130#[must_use]
131pub struct RepeatExpression {
132 kind: RepeatExpressionKind,
133 text: String,
134 use_symbol: bool,
135 #[cfg_attr(feature = "serde", serde(default))]
136 placement: Drawn<Option<Placement>>,
137}
138
139impl RepeatExpression {
140 pub fn new(kind: RepeatExpressionKind) -> Self {
144 Self {
145 kind,
146 text: kind.text_alternatives()[0].to_string(),
147 use_symbol: kind.has_symbol(),
148 placement: Drawn(None),
149 }
150 }
151
152 pub fn with_text(kind: RepeatExpressionKind, text: &str) -> Self {
158 let mut mark = Self::new(kind);
159 let takes_text = !matches!(
160 kind,
161 RepeatExpressionKind::Segno | RepeatExpressionKind::Fine
162 );
163 if takes_text && mark.is_valid_text(text) {
164 mark.text = text.to_string();
165 mark.use_symbol = false;
166 }
167 mark
168 }
169
170 pub fn kind(&self) -> RepeatExpressionKind {
172 self.kind
173 }
174
175 pub fn text(&self) -> &str {
177 &self.text
178 }
179
180 pub fn set_text(&mut self, text: impl Into<String>) {
183 self.text = text.into();
184 }
185
186 pub fn is_valid_text(&self, text: &str) -> bool {
190 let strip = |text: &str| -> String {
191 text.trim()
192 .chars()
193 .filter(|character| *character != ' ' && *character != '.')
194 .flat_map(char::to_lowercase)
195 .collect()
196 };
197 let text = strip(text);
198 self.kind
199 .text_alternatives()
200 .iter()
201 .any(|candidate| strip(candidate) == text)
202 }
203
204 pub fn use_symbol(&self) -> bool {
206 self.use_symbol
207 }
208
209 pub fn set_use_symbol(&mut self, use_symbol: bool) {
212 self.use_symbol = use_symbol;
213 }
214
215 pub fn placement(&self) -> Option<Placement> {
217 self.placement.0
218 }
219
220 pub fn set_placement(&mut self, placement: Option<Placement>) {
222 self.placement.0 = placement;
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn a_mark_takes_only_its_own_words() {
232 let da_capo = RepeatExpression::with_text(RepeatExpressionKind::DaCapo, "D.C.");
234 assert_eq!(da_capo.text(), "D.C.");
235 let wrong = RepeatExpression::with_text(RepeatExpressionKind::DaCapo, "D.S.");
236 assert_eq!(wrong.text(), "Da Capo");
237 assert!(da_capo.is_valid_text("da capo"));
238 }
239
240 #[test]
241 fn a_coda_is_a_sign_until_it_is_given_words() {
242 assert!(RepeatExpression::new(RepeatExpressionKind::Coda).use_symbol());
243 assert!(!RepeatExpression::with_text(RepeatExpressionKind::Coda, "al Coda").use_symbol());
244 assert!(RepeatExpression::new(RepeatExpressionKind::Segno).use_symbol());
245 assert_eq!(RepeatExpressionKind::Fine.justify(), "right");
246 assert_eq!(RepeatExpressionKind::Segno.justify(), "center");
247 }
248}