use super::{MAX_STRETCH, TAG_SIZE, model::Model};
use crate::{
metrics::TextMetrics,
spec::{FragmentSpec, SequenceSpec},
};
pub(super) fn make_room_for_guards(model: &Model, slots: &mut Slots, metrics: &impl TextMetrics) {
let track = Track::new(model, slots, 1.0);
let guard = |label: Option<&str>| {
label.map_or(0.0, |label| {
metrics.width(&format!("[{label}]"), TAG_SIZE) + 12.0
})
};
for (index, fragment) in model.sequence.fragments.iter().enumerate() {
let end = track.frames[index].1;
let starts = &track.branches[index];
let needed = tab_width(fragment, metrics) + 6.0 + guard(fragment.label.as_deref());
let room = starts.first().copied().unwrap_or(end) - track.frames[index].0;
slots.head[index] += (needed - room).max(0.0);
for (number, branch) in fragment.branches.iter().enumerate() {
let next = starts.get(number + 1).copied().unwrap_or(end);
let needed = 6.0 + guard(Some(branch.label.as_deref().unwrap_or("")));
slots.branch[index][number] += (needed - (next - starts[number])).max(0.0);
}
}
}
pub(super) fn tab_width(fragment: &FragmentSpec, metrics: &impl TextMetrics) -> f64 {
metrics.width(fragment.kind.keyword(), TAG_SIZE) + 12.0
}
pub(super) struct Track {
pub(super) arrows: Vec<f64>,
pub(super) frames: Vec<(f64, f64)>,
pub(super) branches: Vec<Vec<f64>>,
pub(super) length: f64,
}
pub(super) struct Slots {
pub(super) arrow: Vec<f64>,
pub(super) slot: Vec<f64>,
pub(super) head: Vec<f64>,
pub(super) branch: Vec<Vec<f64>>,
pub(super) foot: f64,
}
impl Slots {
pub(super) fn uniform(
sequence: &SequenceSpec,
head: f64,
branch: f64,
) -> (Vec<f64>, Vec<Vec<f64>>) {
(
vec![head; sequence.fragments.len()],
sequence
.fragments
.iter()
.map(|fragment| vec![branch; fragment.branches.len()])
.collect(),
)
}
}
impl Track {
fn new(model: &Model, slots: &Slots, scale: f64) -> Self {
let sequence = model.sequence;
let fragments = &sequence.fragments;
let mut frames = vec![(0.0, 0.0); fragments.len()];
let mut branches: Vec<Vec<f64>> = fragments
.iter()
.map(|fragment| vec![0.0; fragment.branches.len()])
.collect();
let mut arrows = Vec::with_capacity(sequence.messages.len());
let mut at = 0.0;
let mut by_depth: Vec<usize> = (0..fragments.len()).collect();
crate::sort::by_key(&mut by_depth, |index| (model.depths[*index], *index));
for index in 0..sequence.messages.len() {
for &fragment in &by_depth {
if fragments[fragment].from == index {
frames[fragment].0 = at;
at += slots.head[fragment] * scale;
}
if let Some(branch) = fragments[fragment]
.branches
.iter()
.position(|branch| branch.from == index)
{
branches[fragment][branch] = at;
at += slots.branch[fragment][branch] * scale;
}
}
arrows.push(at + slots.arrow[index] * scale);
at += slots.slot[index] * scale;
for &fragment in by_depth.iter().rev() {
if fragments[fragment].to == index {
frames[fragment].1 = at;
at += slots.foot * scale;
}
}
}
Self {
arrows,
frames,
branches,
length: at,
}
}
pub(super) fn fitted(model: &Model, slots: &Slots, room: f64) -> Self {
let natural = Self::new(model, slots, 1.0);
if natural.length >= room || natural.length <= 0.0 {
return natural;
}
Self::new(model, slots, (room / natural.length).min(MAX_STRETCH))
}
}