rusvid_core 0.2.1

Core library for rusvid
Documentation
use std::collections::HashMap;
use std::rc::Rc;

use crate::holder::likes::color_like::ColorLike;
use crate::holder::likes::path_like::PathLike;
use crate::holder::stroke::Stroke;
use crate::holder::transform::{Transform, TransformError, TransformLogic};
use crate::holder::utils::{random_id, TranslateIntoResvgGeneric};
use crate::pixel::Pixel;
use crate::point::Point;

const BOUNDING_BOX_STEPS: i32 = 10;

// TODO move SvgItem into separate file
#[derive(Debug, Clone)]
pub struct SvgItem {
    pub(crate) id: String,
    pub(crate) path: Vec<PathLike>,

    pub(crate) fill_color: Option<ColorLike>,
    pub(crate) stroke: Option<Stroke>,

    pub(crate) visibility: bool,

    last_rotation: f64,
    last_scale: Point,
}

impl SvgItem {
    pub fn new_with_id(
        id: impl Into<String>,
        path: Vec<PathLike>,
        fill_color: Option<ColorLike>,
    ) -> Self {
        Self {
            id: id.into(),
            path,
            fill_color,
            stroke: Some(Stroke::default()),
            visibility: true,
            last_rotation: 0.0,
            last_scale: Point::new_symmetric(1.0),
        }
    }

    pub fn new(path: Vec<PathLike>, fill_color: Option<ColorLike>) -> Self {
        Self::new_with_id(random_id(), path, fill_color)
    }

    pub fn bounding_box(&self) -> (Point, Point) {
        let mut smaller_corner = match self.path[0] {
            PathLike::Move(p) => p,
            _ => todo!(),
        };
        let mut bigger_corner = smaller_corner;

        let mut last_point = smaller_corner;
        self.path.iter().for_each(|path| {
            fn compare_and_set(value: &Point, smaller: &mut Point, bigger: &mut Point) {
                if smaller.x() > value.x() {
                    *smaller.x_mut() = value.x();
                }
                if smaller.y() > value.y() {
                    *smaller.y_mut() = value.y();
                }

                if bigger.x() < value.x() {
                    *bigger.x_mut() = value.x();
                }
                if bigger.y() < value.y() {
                    *bigger.y_mut() = value.y();
                }
            }

            last_point = match path {
                PathLike::Move(p) => {
                    compare_and_set(p, &mut smaller_corner, &mut bigger_corner);
                    *p
                }
                PathLike::Line(p) => {
                    compare_and_set(p, &mut smaller_corner, &mut bigger_corner);
                    *p
                }
                PathLike::CurveTo(p, c1, c2) => {
                    use flo_curves::bezier::Curve;
                    use flo_curves::*;

                    fn point_to_coord2(p: &Point) -> Coord2 {
                        Coord2(p.x(), p.y())
                    }

                    fn coord2_to_point(c: &Coord2) -> Point {
                        Point::new(c.x(), c.y())
                    }

                    compare_and_set(p, &mut smaller_corner, &mut bigger_corner);

                    let curve = Curve::from_points(
                        point_to_coord2(&last_point),
                        (point_to_coord2(c1), point_to_coord2(c2)),
                        point_to_coord2(p),
                    );

                    for i in 0..BOUNDING_BOX_STEPS {
                        let t = (i as f64) / (BOUNDING_BOX_STEPS as f64);
                        let pos = curve.point_at_pos(t);
                        let as_point = coord2_to_point(&pos);
                        compare_and_set(&as_point, &mut smaller_corner, &mut bigger_corner);
                    }

                    *p
                }
                _ => last_point,
            };
        });

        (smaller_corner, bigger_corner)
    }

    pub fn bounding_box_rect(&self) -> SvgItem {
        let (smaller_corner, bigger_corner) = self.bounding_box();

        // TODO optimize math
        let a = PathLike::Move(smaller_corner);
        let b = PathLike::Line(
            smaller_corner + Point::new(bigger_corner.x() - smaller_corner.x(), 0.0),
        );
        let c = PathLike::Line(
            smaller_corner
                + Point::new(
                    bigger_corner.x() - smaller_corner.x(),
                    bigger_corner.y() - smaller_corner.y(),
                ),
        );
        let d = PathLike::Line(Point::new(
            smaller_corner.x(),
            (smaller_corner
                + Point::new(
                    bigger_corner.x() - smaller_corner.x(),
                    bigger_corner.y() - smaller_corner.y(),
                ))
            .y(),
        ));

        let mut my_box = SvgItem::new(vec![a, b, c, d, PathLike::Close], None);
        my_box.stroke = Some(Stroke {
            paint: ColorLike::Color(Pixel::new(0, 0, 0, 255)),
            width: 3.0,
            ..Default::default()
        });

        my_box
    }
}

impl TransformLogic for SvgItem {
    fn transform(&mut self, transformation: &Transform) -> Result<(), TransformError> {
        match &transformation {
            Transform::Visibility(value) => self.visibility = *value,
            Transform::Move(point) => {
                self.path = self
                    .path
                    .iter()
                    .map(|p| match *p {
                        PathLike::Move(og_p) => PathLike::Move(og_p + *point),
                        PathLike::Line(og_p) => PathLike::Line(og_p + *point),
                        PathLike::CurveTo(end, c1, c2) => {
                            PathLike::CurveTo(end + *point, c1 + *point, c2 + *point)
                        }
                        PathLike::Close => PathLike::Close,
                    })
                    .collect();
            }
            Transform::Position(position) => match self.path[0] {
                PathLike::Move(point) => {
                    let offset = *position - point;
                    self.transform(&Transform::Move(offset))?
                }
                _ => panic!("First element needs to be a `PathLike::Move`"),
            },
            Transform::Color(value) => {
                self.fill_color = value.clone();
            }
            Transform::Stroke(stroke) => {
                self.stroke = stroke.clone();
            }
            Transform::Scale(factor) => {
                let center = PathLike::get_center(&self.path);

                let factor_adjusted = *factor / self.last_scale;

                self.path = self
                    .path
                    .iter()
                    .map(|p| {
                        let formatted = match *p {
                            PathLike::Move(value) => {
                                let v = (value - center) * factor_adjusted;
                                let pos = center + v;
                                PathLike::Move(pos)
                            }
                            PathLike::Line(value) => {
                                let v = (value - center) * factor_adjusted;
                                let pos = center + v;
                                PathLike::Line(pos)
                            }
                            PathLike::CurveTo(end, c_s, c_e) => {
                                let p_e = center + (end - center) * factor_adjusted;
                                let p_c_s = center + (c_s - center) * factor_adjusted;
                                let p_c_e = center + (c_e - center) * factor_adjusted;

                                PathLike::CurveTo(p_e, p_c_s, p_c_e)
                            }
                            PathLike::Close => PathLike::Close,
                        };

                        formatted
                    })
                    .collect();

                self.last_scale = *factor;
            }
            Transform::Rotate(angle) => {
                let center = PathLike::get_center(&self.path);

                let angle_diff = self.last_rotation - *angle;

                self.path = self
                    .path
                    .iter()
                    .map(|p| {
                        // TODO make helper functions to remove duplicate code
                        let formatted = match *p {
                            PathLike::Move(value) => {
                                let v = value - center;

                                let x = angle_diff.cos() * v.x() - angle_diff.sin() * v.y();
                                let y = angle_diff.sin() * v.x() + angle_diff.cos() * v.y();

                                let pos = center + Point::new(x, y);
                                PathLike::Move(pos)
                            }
                            PathLike::Line(value) => {
                                let v = value - center;

                                let x = angle_diff.cos() * v.x() - angle_diff.sin() * v.y();
                                let y = angle_diff.sin() * v.x() + angle_diff.cos() * v.y();

                                let pos = center + Point::new(x, y);
                                PathLike::Line(pos)
                            }
                            PathLike::CurveTo(end, c_s, c_e) => {
                                let v_e = end - center;
                                let v_c_s = c_s - center;
                                let v_c_e = c_e - center;

                                let e = center
                                    + Point::new(
                                        angle_diff.cos() * v_e.x() - angle_diff.sin() * v_e.y(),
                                        angle_diff.sin() * v_e.x() + angle_diff.cos() * v_e.y(),
                                    );
                                let cs = center
                                    + Point::new(
                                        angle_diff.cos() * v_c_s.x() - angle_diff.sin() * v_c_s.y(),
                                        angle_diff.sin() * v_c_s.x() + angle_diff.cos() * v_c_s.y(),
                                    );
                                let ce = center
                                    + Point::new(
                                        angle_diff.cos() * v_c_e.x() - angle_diff.sin() * v_c_e.y(),
                                        angle_diff.sin() * v_c_e.x() + angle_diff.cos() * v_c_e.y(),
                                    );

                                PathLike::CurveTo(e, cs, ce)
                            }
                            PathLike::Close => PathLike::Close,
                        };

                        formatted
                    })
                    .collect();

                self.last_rotation = *angle;
            }
        };

        Ok(())
    }
}

impl TranslateIntoResvgGeneric<resvg::usvg::NodeKind> for SvgItem {
    fn translate(&self) -> resvg::usvg::NodeKind {
        use resvg::usvg::*;

        let mut path = PathData::new();
        PathLike::extend_path_from_slice(&mut path, &self.path);

        let fill = self
            .fill_color
            .as_ref()
            .map(|color_like| color_like.translate());

        let visibility = match self.visibility {
            true => Visibility::Visible,
            false => Visibility::Hidden,
        };

        resvg::usvg::NodeKind::Path(resvg::usvg::Path {
            id: self.id.clone(),
            visibility,
            fill,
            stroke: self.stroke.clone().map(|s| s.translate()),
            data: Rc::new(path),
            ..resvg::usvg::Path::default()
        })
    }
}

#[derive(Debug, Default, Clone)]
pub struct SvgHolder {
    pub(crate) items: HashMap<String, SvgItem>,
}

impl SvgHolder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn new_with_items(items: Vec<SvgItem>) -> Self {
        let mut svg_holder = Self::new();

        for item in items {
            svg_holder.add_item(item);
        }

        svg_holder
    }

    pub fn add_item(&mut self, item: SvgItem) -> String {
        let id = item.id.clone();
        self.items.insert(id.clone(), item);
        id
    }

    pub fn get_item(&self, key: impl Into<String>) -> Option<&SvgItem> {
        self.items.get(&key.into())
    }

    pub fn get_item_mut(&mut self, key: impl Into<String>) -> Option<&mut SvgItem> {
        self.items.get_mut(&key.into())
    }
}

impl TransformLogic for SvgHolder {
    fn transform(&mut self, transformation: &Transform) -> Result<(), TransformError> {
        for item in &mut self.items.values_mut() {
            item.transform(transformation)?;
        }

        Ok(())
    }

    fn transform_by_id(
        &mut self,
        id: impl Into<String>,
        transformation: &Transform,
    ) -> Result<(), TransformError> {
        let id: String = id.into();
        let item = self
            .get_item_mut(id.clone())
            .ok_or(TransformError::NoItem(id))?;

        item.transform(transformation)
    }
}