sdocx 1.1.0

Reverse-engineered tooling and SDK for converting Samsung Notes (.sdocx) files
Documentation
use super::{Node, Path, Polyline, decimal};

#[derive(Clone)]
pub struct Data {
    inner: svg::node::element::path::Data,
    valid: bool,
}
impl Default for Data {
    fn default() -> Self {
        Self::new()
    }
}
impl Data {
    pub fn new() -> Self {
        Self {
            inner: Default::default(),
            valid: true,
        }
    }
    fn parameters<const N: usize>(&mut self, values: [f64; N]) -> Vec<f32> {
        let values: Vec<_> = values.into_iter().map(|value| value as f32).collect();
        self.valid &= values.iter().all(|value| value.is_finite());
        values
    }
    pub fn move_to(mut self, point: (impl Into<f64>, impl Into<f64>)) -> Self {
        let values = self.parameters([point.0.into(), point.1.into()]);
        self.inner = self.inner.move_to(values);
        self
    }
    pub fn line_to(mut self, point: (impl Into<f64>, impl Into<f64>)) -> Self {
        let values = self.parameters([point.0.into(), point.1.into()]);
        self.inner = self.inner.line_to(values);
        self
    }
    pub fn horizontal_line_to(mut self, x: impl Into<f64>) -> Self {
        let values = self.parameters([x.into()]);
        self.inner = self.inner.horizontal_line_to(values);
        self
    }
    pub fn horizontal_line_by(mut self, x: impl Into<f64>) -> Self {
        let values = self.parameters([x.into()]);
        self.inner = self.inner.horizontal_line_by(values);
        self
    }
    pub fn vertical_line_by(mut self, y: impl Into<f64>) -> Self {
        let values = self.parameters([y.into()]);
        self.inner = self.inner.vertical_line_by(values);
        self
    }
    pub fn quadratic_curve_to(mut self, p: (f32, f32, f32, f32)) -> Self {
        let values = self.parameters([p.0.into(), p.1.into(), p.2.into(), p.3.into()]);
        self.inner = self.inner.quadratic_curve_to(values);
        self
    }
    pub fn quadratic_curve_by(mut self, p: (f32, f32, f32, f32)) -> Self {
        let values = self.parameters([p.0.into(), p.1.into(), p.2.into(), p.3.into()]);
        self.inner = self.inner.quadratic_curve_by(values);
        self
    }
    pub fn cubic_curve_to(mut self, p: (f32, f32, f32, f32, f32, f32)) -> Self {
        let values = self.parameters([
            p.0.into(),
            p.1.into(),
            p.2.into(),
            p.3.into(),
            p.4.into(),
            p.5.into(),
        ]);
        self.inner = self.inner.cubic_curve_to(values);
        self
    }
    pub fn elliptical_arc_by(mut self, p: (f32, f32, f32, bool, bool, f32, f32)) -> Self {
        self.valid &= p.0 >= 0. && p.1 >= 0.;
        let values = self.parameters([
            p.0.into(),
            p.1.into(),
            p.2.into(),
            u8::from(p.3).into(),
            u8::from(p.4).into(),
            p.5.into(),
            p.6.into(),
        ]);
        self.inner = self.inner.elliptical_arc_by(values);
        self
    }
    pub fn close(mut self) -> Self {
        self.inner = self.inner.close();
        self
    }
    fn text(self) -> Option<String> {
        self.valid
            .then(|| svg::node::Value::from(self.inner).into())
    }
}
impl Path {
    pub fn data(mut self, data: Data) -> Self {
        self.0.optional("d", data.text());
        self
    }
}

pub struct ReplayPath {
    data: String,
    lengths: Vec<usize>,
    valid: bool,
}
impl Default for ReplayPath {
    fn default() -> Self {
        Self {
            data: String::new(),
            lengths: Vec::new(),
            valid: true,
        }
    }
}
impl ReplayPath {
    pub fn push(&mut self, part: Data, replay: bool) {
        if let Some(part) = part.text() {
            self.data.push_str(&part);
        } else {
            self.valid = false;
        }
        if replay {
            self.lengths.push(self.data.len());
        }
    }
    pub fn finish(self) -> Path {
        let mut path = Path::new();
        path.0.valid = self.valid;
        path.0.attr("d", self.data);
        replay_lengths(&mut path.0, &self.lengths);
        path
    }
}

pub fn polyline(points: &[crate::Point], replay: bool) -> Polyline {
    let mut node = Polyline::new();
    let mut value = String::new();
    let mut offsets = Vec::new();
    for point in points {
        if !value.is_empty() {
            value.push(' ');
        }
        let (Some(x), Some(y)) = (decimal(point.x, 2), decimal(point.y, 2)) else {
            node.0.valid = false;
            break;
        };
        value.push_str(&svg::node::Value::from((x.text(), y.text())));
        if replay {
            offsets.push(value.len());
        }
    }
    node.0.attr("points", value);
    replay_lengths(&mut node.0, &offsets);
    node
}
fn replay_lengths(node: &mut Node, lengths: &[usize]) {
    if !lengths.is_empty() {
        node.attr(
            "data-replay-lengths",
            lengths
                .iter()
                .map(usize::to_string)
                .collect::<Vec<_>>()
                .join(","),
        );
    }
}

pub fn coordinate(value: f64, places: usize) -> f32 {
    decimal(value, places)
        .and_then(|value| value.text().parse().ok())
        .unwrap_or(f32::NAN)
}