mod cleanup;
pub mod filter;
mod mixed;
mod relative;
pub use crate::convert::cleanup::{cleanup, cleanup_unpositioned};
pub use crate::convert::filter::filter;
pub use crate::convert::mixed::{mixed, to_absolute};
pub use crate::convert::relative::relative;
use crate::geometry::MakeArcs;
use crate::math::to_fixed;
use crate::{command, Path};
bitflags! {
#[derive(Debug)]
pub struct StyleInfo: usize {
const has_marker_mid = 0b0_0001;
const maybe_has_stroke = 0b0010;
const maybe_has_linecap = 0b100;
const is_safe_to_use_z = 0b1000;
const has_marker = 0b_0001_0000;
}
}
bitflags! {
#[derive(Debug)]
pub struct Flags: usize {
const remove_useless_flag= 0b0000_0000_0000_0001;
const smart_arc_rounding_flag= 0b_0000_0000_0010;
const straight_curves_flag = 0b00_0000_0000_0100;
const convert_to_q_flag = 0b_0000_0000_0000_1000;
const line_shorthands_flag = 0b00_0000_0001_0000;
const collapse_repeated_flag = 0b_0000_0010_0000;
const curve_smooth_shorthands_flag = 0b0100_0000;
const convert_to_z_flag = 0b_0000_0000_1000_0000;
const force_absolute_path_flag = 0b001_0000_0000;
const negative_extra_space_flag = 0b10_0000_0000;
const utilize_absolute_flag = 0b0_0100_0000_0000;
}
}
#[cfg_attr(feature = "napi", napi)]
#[derive(Debug, Copy, Clone, Default)]
pub enum Precision {
#[default]
None,
Disabled,
Enabled(i32),
}
#[derive(Debug, Default)]
pub struct Options {
pub flags: Flags,
pub make_arcs: MakeArcs,
pub precision: Precision,
}
pub fn run(path: &mut Path, options: &Options, style_info: &StyleInfo) {
let includes_vertices = path
.0
.iter()
.any(|c| !matches!(c, command::Data::MoveBy(_) | command::Data::MoveTo(_)));
log::debug!("convert::run: converting path: {path}");
let mut positioned_path = relative(std::mem::take(path));
let mut state = filter::State::new(&positioned_path, options, style_info);
positioned_path = filter(positioned_path, options, &mut state, style_info);
if options.flags.utilize_absolute() {
positioned_path = mixed(positioned_path, options);
}
positioned_path = cleanup(positioned_path);
for command in &mut positioned_path.0 {
if command.command.is_by() {
options.round_data(command.command.args_mut(), options.error());
} else {
options.round_absolute_command_data(
command.command.args_mut(),
options.error(),
&command.start.0,
);
}
}
*path = positioned_path.take();
let has_marker = style_info.contains(StyleInfo::has_marker);
let is_markers_only_path = has_marker
&& includes_vertices
&& path
.0
.iter()
.all(|c| matches!(c, command::Data::MoveBy(_) | command::Data::MoveTo(_)));
if is_markers_only_path {
path.0.push(command::Data::ClosePath);
}
log::debug!("convert::run: done: {path}");
}
impl StyleInfo {
pub fn conservative() -> Self {
let mut result = Self::all();
result.set(Self::is_safe_to_use_z, false);
result
}
}
impl Default for StyleInfo {
fn default() -> Self {
Self::empty()
}
}
impl Flags {
fn remove_useless(&self) -> bool {
self.contains(Self::remove_useless_flag)
}
fn smart_arc_rounding(&self) -> bool {
self.contains(Self::smart_arc_rounding_flag)
}
fn straight_curves(&self) -> bool {
self.contains(Self::straight_curves_flag)
}
fn convert_to_q(&self) -> bool {
self.contains(Self::convert_to_q_flag)
}
fn line_shorthands(&self) -> bool {
self.contains(Self::line_shorthands_flag)
}
fn collapse_repeated(&self) -> bool {
self.contains(Self::collapse_repeated_flag)
}
fn curve_smooth_shorthands(&self) -> bool {
self.contains(Self::curve_smooth_shorthands_flag)
}
fn convert_to_z(&self) -> bool {
self.contains(Self::convert_to_z_flag)
}
fn force_absolute_path(&self) -> bool {
self.contains(Self::force_absolute_path_flag)
}
fn negative_extra_space(&self) -> bool {
self.contains(Self::negative_extra_space_flag)
}
fn utilize_absolute(&self) -> bool {
self.contains(Self::utilize_absolute_flag)
}
}
impl Default for Flags {
fn default() -> Self {
let mut flags = Self::all();
flags.set(Self::force_absolute_path_flag, false);
flags
}
}
impl Options {
pub fn error(&self) -> f64 {
match self.precision.inner() {
Some(precision) => {
let trunc_by = f64::powi(10.0, precision);
f64::trunc(f64::powi(0.1, precision) * trunc_by) / trunc_by
}
None => 1e-2,
}
}
pub fn round(&self, data: f64, error: f64) -> f64 {
let precision = self.precision.unwrap_or(0);
if precision > 0 && precision < 20 {
let fixed = to_fixed(data, precision);
if (fixed - data).abs() == 0.0 {
return data;
}
let rounded = to_fixed(data, precision - 1);
if to_fixed((rounded - data).abs(), precision + 1) >= error {
fixed
} else {
rounded
}
} else {
data.round()
}
}
pub fn round_data(&self, data: &mut [f64], error: f64) {
data.iter_mut().enumerate().for_each(|(i, d)| {
let result = self.round(*d, error);
if i > 4 && result == 0.0 {
return;
}
*d = result;
});
}
pub fn round_absolute_command_data(&self, data: &mut [f64], error: f64, start: &[f64; 2]) {
data.iter_mut().enumerate().for_each(|(i, d)| {
let result = self.round(*d, error);
if (i == 5 && result == start[0]) || (i == 6 && result == start[1]) {
return;
}
*d = result;
});
}
pub fn round_path(&self, path: &mut Path, error: f64) {
path.0
.iter_mut()
.for_each(|c| self.round_data(c.args_mut(), error));
}
pub fn conservative() -> Self {
Self {
flags: Flags::default(),
make_arcs: MakeArcs::default(),
precision: Precision::conservative(),
}
}
}
impl Precision {
fn is_disabled(self) -> bool {
matches!(self, Self::Disabled)
}
fn unwrap_or(self, default: i32) -> i32 {
match self.inner() {
Some(x) => x,
None => default,
}
}
fn inner(self) -> Option<i32> {
match self {
Self::Enabled(x) => Some(x),
Self::None => Some(3),
Self::Disabled => None,
}
}
pub fn conservative() -> Self {
Self::Enabled(19)
}
}
#[test]
fn test_convert() {
use crate::Path;
use oxvg_parse::Parse as _;
let mut path = Path::parse_string("m 1208.23,1821.01 c 74.07,14.24 196.57,17.09 293.43,-14.24 122.5,-42.74 22.79,-199.42 48.43,-207.97 25.64,-8.55 59.83,108.25 287.73,96.86 230.75,-11.39 256.39,-113.95 287.73,-96.86 31.34,17.09 -31.34,284.88 313.37,222.21 0,0 -361.8,96.86 -344.71,-165.23 0,0 -207.96,159.53 -498.54,17.09 2.85,0 76.92,245 -387.44,148.14").unwrap();
run(&mut path, &Options::default(), &StyleInfo::default());
assert_eq!(
String::from(path),
String::from("M1208.23 1821.01c74.07 14.24 196.57 17.09 293.43-14.24 122.5-42.74 22.79-199.42 48.43-207.97s59.83 108.25 287.73 96.86c230.75-11.39 256.39-113.95 287.73-96.86s-31.34 284.88 313.37 222.21c0 0-361.8 96.86-344.71-165.23 0 0-207.96 159.53-498.54 17.09 2.85 0 76.92 245-387.44 148.14")
);
}