diff --git a/.gitignore b/.gitignore
index 0369a8b..e662361 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,6 @@
/Cargo.lock
/.vscode
/*.code-workspace
-/ink-stroke-modeler/build
+/docs/*.pdf
/test
/expand
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index a9bfba9..0c22efb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,3 +12,4 @@ svg = "0.16.0"
tracing = "0.1.40"
[dependencies]
+thiserror = "1.0.61"
diff --git a/README.md b/README.md
index 014ac6e..eae4298 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,8 @@
[![main docs](https://img.shields.io/badge/docs-main-informational)](https://flxzt.github.io/ink-stroke-modeler-rs/ink_stroke_modeler_rs/)
[![CI](https://github.com/flxzt/ink-stroke-modeler-rs/actions/workflows/ci.yaml/badge.svg)](https://github.com/flxzt/ink-stroke-modeler-rs/actions/workflows/ci.yaml)
-Partial rust rewrite of [https://github.com/google/ink-stroke-modeler](https://github.com/google/ink-stroke-modeler). Beware that not all functionalities are implemented (no kalman-based prediction) and the API is not identical either.
+Partial rust rewrite of [https://github.com/google/ink-stroke-modeler](https://github.com/google/ink-stroke-modeler).
+Beware that not all functionalities are implemented (no kalman-based prediction) and the API is not identical either.
# Usage
diff --git a/docs/position_modeling.html b/docs/position_modeling.html
index 221782c..ff0ecc2 100644
--- a/docs/position_modeling.html
+++ b/docs/position_modeling.html
@@ -13,7 +13,7 @@
Position modeling
The position of the pen is modeled as a weight connected by a spring
to an anchor.
The anchor moves along the resampled dewobbled inputs ,
-pulling the weight along with it accross a surface, with some amount of
+pulling the weight along with it across a surface, with some amount of
friction. Euler integration is used to solve for the position of the
pen.
diff --git a/docs/position_modeling.typ b/docs/position_modeling.typ
index 66248d9..e911012 100644
--- a/docs/position_modeling.typ
+++ b/docs/position_modeling.typ
@@ -17,7 +17,7 @@
#definition[
The position of the pen is modeled as a weight connected by a spring to an anchor.
-The anchor moves along the _resampled dewobbled inputs_, pulling the weight along with it accross a surface, with some
+The anchor moves along the _resampled dewobbled inputs_, pulling the weight along with it across a surface, with some
amount of friction. Euler integration is used to solve for the position of the pen.
#figure(image("position_model.svg"))
diff --git a/justfile b/justfile
index 4fd0f18..f646e7d 100644
--- a/justfile
+++ b/justfile
@@ -1,5 +1,9 @@
-# build docs
-doc:
+# justfile for ink-stroke-modeler-rs crate
+
+default:
+ just --list
+
+docs-build:
pandoc docs/notations.typ -o docs/notations.html --mathml
pandoc docs/position_modeling.typ -o docs/position_modeling.html --mathml
pandoc docs/resampling.typ -o docs/resampling.html --mathml
@@ -9,8 +13,7 @@ doc:
cargo doc --open
cp docs/position_model.svg target/doc/ink_stroke_modeler_rs/position_model.svg
-
-remove_html:
+docs-remove-html:
rm docs/notations.html
rm docs/position_modeling.html
rm docs/resampling.html
diff --git a/src/engine.rs b/src/engine.rs
index fae2adc..d06089a 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1,9 +1,11 @@
-use std::vec;
-
-use super::*;
-
+use crate::error::{ElementError, ElementOrderError};
+use crate::position_modeler::PositionModeler;
+use crate::state_modeler::StateModeler;
use crate::utils::interp;
use crate::utils::normalize01_64;
+use crate::{ModelerError, ModelerInput, ModelerInputEventType, ModelerParams, ModelerResult};
+use std::collections::VecDeque;
+use std::vec;
/// smooth out the input position from high frequency noise
/// uses a moving average of position and interpolating between this
@@ -58,23 +60,6 @@ pub struct StrokeModeler {
pub(crate) state_modeler: StateModeler,
}
-/// errors
-#[derive(Debug)]
-pub enum Errors {
- /// when a duplicate element is sent
- DuplicateElement,
- /// when a new event has a time that's less than the previous one
- NegativeTimeDelta,
- /// When order of element is not correct.
- /// Either when
- /// - down event is not the first event or a down event occured after another one
- /// - no Down event occurred before a Move event
- /// - No event occured before an up event
- ElementOrderError,
- /// When the time delta is too large between the input provided and the previous one
- TooFarApart,
-}
-
impl Default for StrokeModeler {
fn default() -> Self {
let params = ModelerParams::suggested();
@@ -157,11 +142,15 @@ impl StrokeModeler {
///
/// If this does not return an error, results will contain at least one Result, and potentially
/// more if the inputs are slower than the minimum output rate
- pub fn update(&mut self, input: ModelerInput) -> Result, Errors> {
+ pub fn update(&mut self, input: ModelerInput) -> Result, ModelerError> {
match input.event_type {
ModelerInputEventType::Down => {
if self.last_event.is_some() {
- return Err(Errors::ElementOrderError);
+ return Err(ModelerError::Element {
+ src: ElementError::Order {
+ src: ElementOrderError::UnexpectedDown,
+ },
+ });
}
self.wobble_update(&input); // first event is "as is"
@@ -183,7 +172,11 @@ impl StrokeModeler {
ModelerInputEventType::Move => {
// get the latest element
if self.last_event.is_none() {
- return Err(Errors::ElementOrderError);
+ return Err(ModelerError::Element {
+ src: ElementError::Order {
+ src: ElementOrderError::UnexpectedMove,
+ },
+ });
}
let latest_time = self.last_event.as_ref().unwrap().time;
let new_time = input.time;
@@ -191,10 +184,14 @@ impl StrokeModeler {
// validate before doing anything
// if the input is incorrect, return an error and leave the engine unmodified
if new_time - latest_time < 0.0 {
- return Err(Errors::NegativeTimeDelta);
+ return Err(ModelerError::Element {
+ src: ElementError::NegativeTimeDelta,
+ });
}
if input == *self.last_event.as_ref().unwrap() {
- return Err(Errors::DuplicateElement);
+ return Err(ModelerError::Element {
+ src: ElementError::Duplicate,
+ });
}
self.state_modeler.update(input.clone());
@@ -207,7 +204,9 @@ impl StrokeModeler {
// this errors if the number of steps is larger than
// [ModelParams::sampling_max_outputs_per_call]
if n_tsteps as usize > self.params.sampling_max_outputs_per_call {
- return Err(Errors::TooFarApart);
+ return Err(ModelerError::Element {
+ src: ElementError::TooFarApart,
+ });
}
let p_start = self.last_corrected_event.unwrap();
@@ -238,17 +237,25 @@ impl StrokeModeler {
ModelerInputEventType::Up => {
// get the latest element
if self.last_event.is_none() {
- return Err(Errors::ElementOrderError);
+ return Err(ModelerError::Element {
+ src: ElementError::Order {
+ src: ElementOrderError::UnexpectedUp,
+ },
+ });
}
let latest_time = self.last_event.as_ref().unwrap().time;
let new_time = input.time;
// validate before doing any changes to the modeler
if new_time - latest_time < 0.0 {
- return Err(Errors::NegativeTimeDelta);
+ return Err(ModelerError::Element {
+ src: ElementError::NegativeTimeDelta,
+ });
}
if input == *self.last_event.as_ref().unwrap() {
- return Err(Errors::DuplicateElement);
+ return Err(ModelerError::Element {
+ src: ElementError::Duplicate,
+ });
}
self.state_modeler.update(input.clone());
@@ -261,7 +268,9 @@ impl StrokeModeler {
// this errors if the number of steps is larger than
// [ModelParams::sampling_max_outputs_per_call]
if n_tsteps as usize > self.params.sampling_max_outputs_per_call {
- return Err(Errors::TooFarApart);
+ return Err(ModelerError::Element {
+ src: ElementError::TooFarApart,
+ });
}
let p_start = self.last_corrected_event.unwrap();
@@ -426,7 +435,7 @@ impl StrokeModeler {
if self.wobble_duration_sum < 1e-12 {
event.pos
} else {
- // calulate the average position
+ // calculate the average position
let avg_position = (
self.wobble_weighted_pos_sum.0 / self.wobble_duration_sum,
@@ -983,7 +992,7 @@ mod tests {
]
));
- // we get more strokes as the model catches up to the anchor postion
+ // we get more strokes as the model catches up to the anchor position
time += delta_time;
let update = engine.update(ModelerInput {
event_type: ModelerInputEventType::Up,
@@ -1096,7 +1105,7 @@ mod tests {
]
));
- // the stroke is finised, we get an error if we predict it
+ // the stroke is finished, we get an error if we predict it
assert!(engine.predict().is_err());
}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..5c8ca92
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,37 @@
+#[derive(Debug, Clone, thiserror::Error)]
+#[non_exhaustive]
+pub enum ElementError {
+ #[error("A duplicate element is sent to the modeler")]
+ Duplicate,
+ #[error("A sent element has a time earlier than the previous one")]
+ NegativeTimeDelta,
+ #[error("Sent element order is incorrect")]
+ Order {
+ #[from]
+ src: ElementOrderError,
+ },
+ #[error("Sent element's time is too far apart from the previous one")]
+ TooFarApart,
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+#[non_exhaustive]
+#[allow(clippy::enum_variant_names)]
+pub enum ElementOrderError {
+ #[error("Down Event is not the first or occurred after a different event")]
+ UnexpectedDown,
+ #[error("Move event occurred before a initial down event")]
+ UnexpectedMove,
+ #[error("No other event occurred before an up event")]
+ UnexpectedUp,
+}
+
+#[derive(Debug, Clone, thiserror::Error)]
+#[non_exhaustive]
+pub enum ModelerError {
+ #[error("Input element error")]
+ Element {
+ #[from]
+ src: ElementError,
+ },
+}
diff --git a/src/lib.rs b/src/lib.rs
index 80a8807..1e3c383 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,11 +1,6 @@
-// imports
-use std::collections::VecDeque;
-
-#[cfg(test)]
-extern crate approx;
-
-// modules
+// Modules
mod engine;
+pub mod error;
mod input;
mod params;
mod position_modeler;
@@ -13,12 +8,13 @@ mod results;
mod state_modeler;
mod utils;
-pub use engine::Errors;
+#[cfg(test)]
+extern crate approx;
+
+// Re-Exports
pub use engine::StrokeModeler;
+pub use error::ModelerError;
pub use input::ModelerInput;
pub use input::ModelerInputEventType;
pub use params::ModelerParams;
-use position_modeler::PositionModeler;
-use results::ModelerPartial;
pub use results::ModelerResult;
-use state_modeler::StateModeler;
diff --git a/src/params.rs b/src/params.rs
index 6b41099..3f992df 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -137,13 +137,10 @@ impl ModelerParams {
Ok(self)
} else {
//Collect errors
- let error_acc = parameter_tests
- .iter()
- .zip(errors)
- .filter(|x| !*(x.0))
- .fold(String::from("the following errors occured : "), |acc, x| {
- acc + x.1
- });
+ let error_acc = parameter_tests.iter().zip(errors).filter(|x| !*(x.0)).fold(
+ String::from("the following errors occurred : "),
+ |acc, x| acc + x.1,
+ );
Err(error_acc)
}
diff --git a/src/position_modeler.rs b/src/position_modeler.rs
index 09b8481..94922e6 100644
--- a/src/position_modeler.rs
+++ b/src/position_modeler.rs
@@ -1,5 +1,6 @@
+use crate::results::ModelerPartial;
use crate::utils::{dist, nearest_point_on_segment};
-use crate::{ModelerInput, ModelerParams, ModelerPartial};
+use crate::{ModelerInput, ModelerParams};
/// This struct models the movement of the pen tip based on the laws of motion.
/// The pen tip is represented as a mass, connected by a spring to a moving
@@ -514,7 +515,7 @@ fn test_update_linear_path() {
}
#[test]
-fn model_end_of_stroke_stationnary() {
+fn model_end_of_stroke_stationary() {
let mut model = PositionModeler::new(
ModelerParams::suggested(),
ModelerInput {
diff --git a/src/state_modeler.rs b/src/state_modeler.rs
index 1e8434c..b732940 100644
--- a/src/state_modeler.rs
+++ b/src/state_modeler.rs
@@ -1,14 +1,13 @@
use crate::utils::{dist, interp, interp2, nearest_point_on_segment};
use crate::ModelerInput;
+use std::collections::VecDeque;
// only imported for docstrings
#[allow(unused)]
-use crate::ModelerPartial;
+use crate::results::ModelerPartial;
#[allow(unused)]
use crate::ModelerResult;
-use std::collections::VecDeque;
-
/// Get the pressure for a position by querying
/// information from the raw input strokes
///