use crate::glyphs;
const BRACKET_CLEARANCE_U: f32 = 0.8; const HOOK_LEN_U: f32 = 0.6; const NUMBER_HALF_GAP_U: f32 = 1.0; const NUMBER_HEIGHT_U: f32 = 1.1;
pub(crate) struct TupletPlan {
pub svg: String,
}
pub(crate) fn plan_tuplet(xs: &[f32], ref_ys: &[f32], actual_notes: u8, stem_up: bool, beamed_fully: bool, space: f32) -> TupletPlan {
let dir = if stem_up { -1.0 } else { 1.0 };
let extreme = ref_ys.iter().copied().fold(ref_ys[0], |acc, y| if stem_up { acc.min(y) } else { acc.max(y) });
let bracket_y = extreme + dir * BRACKET_CLEARANCE_U * space;
let x_first = xs[0];
let x_last = xs[xs.len() - 1];
let x_mid = (x_first + x_last) / 2.0;
let mut svg = String::new();
if !beamed_fully {
let hook_end_y = bracket_y - dir * HOOK_LEN_U * space;
svg.push_str(&glyphs::tuplet_line(x_first, hook_end_y, x_first, bracket_y, space));
svg.push_str(&glyphs::tuplet_line(x_last, hook_end_y, x_last, bracket_y, space));
let gap = NUMBER_HALF_GAP_U * space;
if x_mid - gap > x_first {
svg.push_str(&glyphs::tuplet_line(x_first, bracket_y, x_mid - gap, bracket_y, space));
}
if x_mid + gap < x_last {
svg.push_str(&glyphs::tuplet_line(x_mid + gap, bracket_y, x_last, bracket_y, space));
}
}
let number_y = if beamed_fully { bracket_y + dir * NUMBER_HEIGHT_U * 0.5 * space } else { bracket_y };
svg.push_str(&glyphs::tuplet_number(actual_notes, x_mid, number_y, space));
TupletPlan { svg }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unbeamed_triplet_draws_bracket_and_number() {
let xs = vec![0.0, 20.0, 40.0];
let ref_ys = vec![50.0, 50.0, 50.0];
let plan = plan_tuplet(&xs, &ref_ys, 3, true, false, 20.0);
assert!(plan.svg.contains("acorde-tuplet-bracket"));
assert!(plan.svg.contains("acorde-tuplet-number"));
}
#[test]
fn beamed_tuplet_omits_bracket() {
let xs = vec![0.0, 20.0, 40.0];
let ref_ys = vec![50.0, 50.0, 50.0];
let plan = plan_tuplet(&xs, &ref_ys, 3, true, true, 20.0);
assert!(!plan.svg.contains("acorde-tuplet-bracket"));
assert!(plan.svg.contains("acorde-tuplet-number"));
}
#[test]
fn bracket_sits_above_for_stem_up_and_below_for_stem_down() {
let xs = vec![0.0, 20.0];
let ref_ys = vec![50.0, 50.0];
let up = plan_tuplet(&xs, &ref_ys, 3, true, false, 20.0);
let down = plan_tuplet(&xs, &ref_ys, 3, false, false, 20.0);
assert_ne!(up.svg, down.svg);
}
}