use super::stroke_engine::RenderCheckpoint;
use crate::coord::CanvasRect;
#[derive(Clone)]
pub struct DabSavePoint {
pub cumulative_canvas_bbox: CanvasRect,
pub vector_index: usize,
pub render_state: RenderCheckpoint,
}
pub struct SavePointStore {
points: Vec<DabSavePoint>,
}
impl Default for SavePointStore {
fn default() -> Self {
Self::new()
}
}
impl SavePointStore {
pub fn new() -> Self {
Self {
points: Vec::with_capacity(512),
}
}
pub fn push(
&mut self,
dab_bbox: CanvasRect,
vector_index: usize,
render_state: RenderCheckpoint,
) {
let cumulative = if let Some(prev) = self.points.last() {
prev.cumulative_canvas_bbox.union(dab_bbox)
} else {
dab_bbox
};
self.points.push(DabSavePoint {
cumulative_canvas_bbox: cumulative,
vector_index,
render_state,
});
}
pub fn full_bbox(&self) -> Option<CanvasRect> {
self.points.last().map(|sp| sp.cumulative_canvas_bbox)
}
pub fn truncate(&mut self, n: usize) {
self.points.truncate(n);
}
pub fn clear(&mut self) {
self.points.clear();
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn points(&self) -> &[DabSavePoint] {
&self.points
}
pub fn finalize_render_state(&mut self, vector_index: usize, render_state: RenderCheckpoint) {
for sp in self.points.iter_mut().rev() {
if sp.vector_index == vector_index {
sp.render_state = render_state.clone();
} else if sp.vector_index < vector_index {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_checkpoint() -> RenderCheckpoint {
RenderCheckpoint {
last_point: None,
accumulated_distance: 0.0,
leftover_distance: 0.0,
last_dab_size: [10.0, 10.0],
last_dab_pos: None,
dab_count: 0,
}
}
fn r(x: i32, y: i32, w: u32, h: u32) -> CanvasRect {
CanvasRect::from_xywh(x, y, w, h)
}
#[test]
fn single_dab() {
let mut store = SavePointStore::new();
store.push(r(10, 20, 30, 40), 0, dummy_checkpoint());
assert_eq!(store.len(), 1);
assert_eq!(store.full_bbox(), Some(r(10, 20, 30, 40)));
}
#[test]
fn cumulative_bbox_grows() {
let mut store = SavePointStore::new();
store.push(r(10, 10, 5, 5), 0, dummy_checkpoint());
store.push(r(20, 20, 5, 5), 1, dummy_checkpoint());
assert_eq!(store.full_bbox(), Some(r(10, 10, 15, 15)));
}
#[test]
fn truncate_removes_tail() {
let mut store = SavePointStore::new();
for i in 0..5 {
store.push(r(i * 10, 0, 5, 5), i as usize, dummy_checkpoint());
}
assert_eq!(store.len(), 5);
store.truncate(3);
assert_eq!(store.len(), 3);
}
#[test]
fn clear_empties() {
let mut store = SavePointStore::new();
store.push(r(0, 0, 10, 10), 0, dummy_checkpoint());
store.clear();
assert!(store.is_empty());
assert!(store.full_bbox().is_none());
}
#[test]
fn vector_index_preserved() {
let mut store = SavePointStore::new();
store.push(r(0, 0, 5, 5), 42, dummy_checkpoint());
store.push(r(10, 10, 5, 5), 99, dummy_checkpoint());
let pts = store.points();
assert_eq!(pts[0].vector_index, 42);
assert_eq!(pts[1].vector_index, 99);
}
}