1use crate::defaults::FloatType;
13
14#[derive(Clone, Copy, Debug, PartialEq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum Tenths {
20 Whole(i64),
22 Fraction(FloatType),
24}
25
26impl Tenths {
27 pub fn value(self) -> FloatType {
29 match self {
30 Self::Whole(whole) => whole as FloatType,
31 Self::Fraction(value) => value,
32 }
33 }
34
35 pub fn parse(text: &str) -> Option<Self> {
39 let value = text.trim().parse::<FloatType>().ok()?;
40 if value.is_finite() && value.fract() == 0.0 && value.abs() < 9.0e15 {
41 Some(Self::Whole(value as i64))
42 } else {
43 Some(Self::Fraction(value))
44 }
45 }
46}
47
48impl std::fmt::Display for Tenths {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 Self::Whole(whole) => write!(f, "{whole}"),
53 Self::Fraction(value) if value.fract() == 0.0 && value.is_finite() => {
54 write!(f, "{value:.1}")
55 }
56 Self::Fraction(value) => write!(f, "{value}"),
57 }
58 }
59}
60
61#[derive(Clone, Debug, Default, PartialEq)]
64#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65pub struct ScoreLayout {
66 pub scaling_millimeters: Option<Tenths>,
69 pub scaling_tenths: Option<Tenths>,
72 pub page_layout: Option<PageLayout>,
74 pub system_layout: Option<SystemLayout>,
76 pub staff_layouts: Vec<StaffLayout>,
78}
79
80impl ScoreLayout {
81 pub fn tenths_to_millimeters(&self, tenths: FloatType) -> FloatType {
85 let (Some(millimeters), Some(per)) = (self.scaling_millimeters, self.scaling_tenths) else {
86 return 0.0;
87 };
88 let value = millimeters.value() / per.value() * tenths;
89 (value * 1e6).round() / 1e6
90 }
91}
92
93#[derive(Clone, Debug, Default, PartialEq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97pub struct PageLayout {
98 pub page_number: Option<i64>,
100 pub left_margin: Option<Tenths>,
102 pub right_margin: Option<Tenths>,
104 pub top_margin: Option<Tenths>,
106 pub bottom_margin: Option<Tenths>,
108 pub page_height: Option<Tenths>,
110 pub page_width: Option<Tenths>,
112 pub is_new: Option<bool>,
114}
115
116#[derive(Clone, Debug, Default, PartialEq)]
119#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
120pub struct SystemLayout {
121 pub left_margin: Option<Tenths>,
123 pub right_margin: Option<Tenths>,
125 pub top_margin: Option<Tenths>,
127 pub bottom_margin: Option<Tenths>,
129 pub distance: Option<Tenths>,
131 pub top_distance: Option<Tenths>,
134 pub is_new: Option<bool>,
136}
137
138#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141pub enum StaffType {
142 #[default]
144 Regular,
145 Ossia,
147 Cue,
149 Editorial,
151 Alternate,
153 Other,
155}
156
157impl StaffType {
158 pub fn as_str(self) -> &'static str {
160 match self {
161 Self::Regular => "regular",
162 Self::Ossia => "ossia",
163 Self::Cue => "cue",
164 Self::Editorial => "editorial",
165 Self::Alternate => "alternate",
166 Self::Other => "other",
167 }
168 }
169
170 pub fn from_name(name: &str) -> Option<Self> {
172 Some(match name {
173 "regular" => Self::Regular,
174 "ossia" => Self::Ossia,
175 "cue" => Self::Cue,
176 "editorial" => Self::Editorial,
177 "alternate" => Self::Alternate,
178 "other" => Self::Other,
179 _ => return None,
180 })
181 }
182}
183
184#[derive(Clone, Debug, Default, PartialEq)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
189pub struct StaffLayout {
190 pub distance: Option<Tenths>,
192 pub staff_number: Option<i64>,
194 pub staff_size: Option<FloatType>,
196 pub staff_lines: Option<i64>,
198 pub hidden: Option<bool>,
200 pub staff_type: StaffType,
202}
203
204#[derive(Clone, Debug, PartialEq)]
206#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
207pub enum Layout {
208 Score(Box<ScoreLayout>),
210 Page(PageLayout),
212 System(SystemLayout),
214 Staff(StaffLayout),
216}
217
218impl Layout {
219 pub fn class_name(&self) -> &'static str {
221 match self {
222 Self::Score(_) => "ScoreLayout",
223 Self::Page(_) => "PageLayout",
224 Self::System(_) => "SystemLayout",
225 Self::Staff(_) => "StaffLayout",
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn a_length_is_read_and_written_as_music21_keeps_it() {
236 assert_eq!(Tenths::parse("120"), Some(Tenths::Whole(120)));
237 assert_eq!(Tenths::parse("30.25"), Some(Tenths::Fraction(30.25)));
238 assert_eq!(Tenths::parse("120.0"), Some(Tenths::Whole(120)));
239 assert_eq!(Tenths::parse("x"), None);
240 assert_eq!(Tenths::Whole(120).to_string(), "120");
241 assert_eq!(Tenths::Fraction(30.25).to_string(), "30.25");
242 assert_eq!(Tenths::Fraction(7.0).to_string(), "7.0");
243 }
244
245 #[test]
247 fn tenths_are_turned_into_millimeters() {
248 let layout = ScoreLayout {
249 scaling_millimeters: Some(Tenths::Whole(7)),
250 scaling_tenths: Some(Tenths::Whole(40)),
251 ..ScoreLayout::default()
252 };
253 assert_eq!(layout.tenths_to_millimeters(40.0), 7.0);
254 assert_eq!(ScoreLayout::default().tenths_to_millimeters(40.0), 0.0);
255 }
256}