diff --git a/Cargo.lock b/Cargo.lock index 323df3f..3d7b096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +dependencies = [ + "memchr", +] + [[package]] name = "askama" version = "0.12.0" @@ -77,8 +86,18 @@ version = "0.1.0" dependencies = [ "askama", "derive_more", + "lazy_static", + "regex", + "strum", + "strum_macros", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "humansize" version = "2.1.3" @@ -88,6 +107,12 @@ dependencies = [ "libm", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libm" version = "0.2.7" @@ -165,6 +190,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -174,6 +228,12 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "semver" version = "1.0.18" @@ -200,6 +260,25 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.28", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/dyesub-tool/Cargo.toml b/dyesub-tool/Cargo.toml index 1ff22f0..a99af79 100644 --- a/dyesub-tool/Cargo.toml +++ b/dyesub-tool/Cargo.toml @@ -6,3 +6,7 @@ edition = "2021" [dependencies] askama = "0.12.0" derive_more = "0.99.17" +lazy_static = "1.4.0" +regex = "1.9.3" +strum = "0.25.0" +strum_macros = "0.25.2" diff --git a/dyesub-tool/src/main.rs b/dyesub-tool/src/main.rs index 0a4c50d..ba9def8 100644 --- a/dyesub-tool/src/main.rs +++ b/dyesub-tool/src/main.rs @@ -8,6 +8,12 @@ use std::{fs::OpenOptions, io::Write}; #[derive(Template)] #[template(path = "document.xml")] struct DocumentTemplate { + dimensions: DocumentDimensions, + box_size: SVGMeasure, + positions: Vec<(SVGMeasure, SVGMeasure)>, +} + +struct DocumentDimensions { width: SVGMeasure, height: SVGMeasure, } @@ -18,16 +24,42 @@ enum Error { Template(askama::Error), } +fn positions( + size: SVGMeasure, + padding: SVGMeasure, + dimensions: &DocumentDimensions, + count: u32, +) -> Vec<(SVGMeasure, SVGMeasure)> { + let grid = size + padding; + let row_count = ((dimensions.width - padding * 2.0) / grid) as u32; + (0..count) + .map(|i| { + ( + grid * (i % row_count).into() + padding, + grid * (i / row_count).into() + padding, + ) + }) + .collect() +} + fn main() -> Result<(), Error> { let mut file = OpenOptions::new() .write(true) .truncate(true) .create(true) .open("output.svg")?; - let document = DocumentTemplate { + let dimensions = DocumentDimensions { width: SVGMeasure::new(8.5, SVGUnit::Inch), height: SVGMeasure::new(11.0, SVGUnit::Inch), }; + let box_size = SVGMeasure::new(14.0, SVGUnit::Millimeter); + let boxes = 54; + let padding = SVGMeasure::new(2.5, SVGUnit::Millimeter); + let document = DocumentTemplate { + positions: positions(box_size, padding, &dimensions, boxes), + dimensions, + box_size, + }; write!(file, "{}", document.render()?)?; Ok(()) } diff --git a/dyesub-tool/src/svg/measure.rs b/dyesub-tool/src/svg/measure.rs index 4a63dd0..fd59c9f 100644 --- a/dyesub-tool/src/svg/measure.rs +++ b/dyesub-tool/src/svg/measure.rs @@ -1,9 +1,16 @@ use super::SVGUnit; +use derive_more::From; +use lazy_static::lazy_static; +use regex::Regex; +use std::cmp::Ordering; use std::fmt; +use std::num::ParseFloatError; +use std::str::FromStr; use std::{ fmt::Display, - ops::{Add, Div, Mul, Sub}, + ops::{Add, Div, Mul, Rem, Sub}, }; +use strum::ParseError; #[derive(Debug, Clone, Copy)] pub struct SVGMeasure { @@ -16,11 +23,11 @@ impl SVGMeasure { Self { measure, unit } } - fn to_user_units(&self) -> f64 { + fn to_user_units(self) -> f64 { self.measure * self.unit.to_user_units() } - fn to_unit(&self, unit: SVGUnit) -> Self { + fn to_unit(self, unit: SVGUnit) -> Self { SVGMeasure { measure: self.to_user_units() / unit.to_user_units(), unit, @@ -28,15 +35,49 @@ impl SVGMeasure { } } +#[derive(From, Debug)] +pub enum SVGUnitParseError { + ParseMeasure(ParseFloatError), + ParseUnit(ParseError), + Invalid, +} + +impl FromStr for SVGMeasure { + type Err = SVGUnitParseError; + fn from_str(s: &str) -> Result { + if s == "0" { + return Ok(SVGMeasure::new(0.0, SVGUnit::Pixel)); + } + lazy_static! { + static ref RE: Regex = Regex::new(r"^([\d.]+)([a-zA-Z]+)$").unwrap(); + } + if let Some(captures) = RE.captures(s) { + let measure = captures[1].parse::()?; + let unit = captures[2].parse::()?; + Ok(SVGMeasure::new(measure, unit)) + } else { + Err(SVGUnitParseError::Invalid) + } + } +} + const EQ_TOLERANCE: f64 = 0.00001; impl PartialEq for SVGMeasure { fn eq(&self, other: &Self) -> bool { (self.to_user_units() - other.to_user_units()).abs() < EQ_TOLERANCE } +} - fn ne(&self, other: &Self) -> bool { - (self.to_user_units() - other.to_user_units()).abs() > EQ_TOLERANCE +impl PartialOrd for SVGMeasure { + fn partial_cmp(&self, other: &Self) -> Option { + Some(if self == other { + Ordering::Equal + } else if self.to_user_units() > other.to_user_units() { + Ordering::Greater + } else { + Ordering::Less + }) } } @@ -73,6 +114,14 @@ impl Mul for SVGMeasure { } } +impl Div for SVGMeasure { + type Output = f64; + + fn div(self, other: Self) -> Self::Output { + self.measure / other.to_unit(self.unit).measure + } +} + impl Div for SVGMeasure { type Output = Self; @@ -81,3 +130,12 @@ impl Div for SVGMeasure { self } } + +impl Rem for SVGMeasure { + type Output = Self; + + fn rem(mut self, other: Self) -> Self::Output { + self.measure %= other.to_unit(self.unit).measure; + self + } +} diff --git a/dyesub-tool/src/svg/tests.rs b/dyesub-tool/src/svg/tests.rs index 559e599..c8690b9 100644 --- a/dyesub-tool/src/svg/tests.rs +++ b/dyesub-tool/src/svg/tests.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use crate::svg::{SVGMeasure, SVGUnit}; #[test] @@ -44,3 +46,21 @@ fn ne() { let b = SVGMeasure::new(10.0, SVGUnit::Pixel); assert_ne!(a, b); } + +#[test] +fn cmp() { + let m_10cm = SVGMeasure::new(10.0, SVGUnit::Centimeter); + let m_5mm = SVGMeasure::new(5.0, SVGUnit::Millimeter); + assert!(m_10cm > m_5mm); + assert!(m_5mm < m_10cm); +} + +#[test] +fn from_str() { + let m_10cm = SVGMeasure::new(10.0, SVGUnit::Centimeter); + let m_5mm = SVGMeasure::new(5.0, SVGUnit::Millimeter); + let m_0 = SVGMeasure::new(0.0, SVGUnit::Pixel); + assert_eq!(m_10cm, SVGMeasure::from_str("10cm").unwrap()); + assert_eq!(m_5mm, SVGMeasure::from_str("5mm").unwrap()); + assert_eq!(m_0, SVGMeasure::from_str("0").unwrap()); +} diff --git a/dyesub-tool/src/svg/unit.rs b/dyesub-tool/src/svg/unit.rs index 9c67baf..c653555 100644 --- a/dyesub-tool/src/svg/unit.rs +++ b/dyesub-tool/src/svg/unit.rs @@ -1,50 +1,38 @@ -use std::fmt::{self, Display}; +use strum_macros::{Display, EnumString, IntoStaticStr}; /// https://oreillymedia.github.io/Using_SVG/guide/units.html -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, EnumString, IntoStaticStr)] pub enum SVGUnit { /// Pixel units, directly equivalent to SVG user units. + #[strum(serialize = "px")] Pixel, /// Inches. + #[strum(serialize = "in")] Inch, /// Centimeters. + #[strum(serialize = "cm")] Centimeter, /// Millimeters. + #[strum(serialize = "mm")] Millimeter, /// Points. + #[strum(serialize = "pt")] Point, // Picas. + #[strum(serialize = "pc")] Pica, } impl SVGUnit { - pub fn to_user_units(&self) -> f64 { + pub const fn to_user_units(&self) -> f64 { use SVGUnit::*; match self { Pixel => 1.0, Inch => 96.0, Centimeter => 37.795, Millimeter => 3.7795, - Point => 4.0 / 3.0, // 1.3333 + Point => 1.3333, Pica => 16.0, } } } - -impl Display for SVGUnit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use SVGUnit::*; - write!( - f, - "{}", - match &self { - Pixel => "px", - Inch => "in", - Centimeter => "cm", - Millimeter => "mm", - Point => "pt", - Pica => "pc", - } - ) - } -} diff --git a/dyesub-tool/templates/document.xml b/dyesub-tool/templates/document.xml index d555166..7804fe5 100644 --- a/dyesub-tool/templates/document.xml +++ b/dyesub-tool/templates/document.xml @@ -1,5 +1,7 @@ - + {% for (x, y) in positions %} + + {% endfor %} diff --git a/flake.nix b/flake.nix index 98edfdf..7abf022 100644 --- a/flake.nix +++ b/flake.nix @@ -51,7 +51,7 @@ Updating `cargoHash`: pname = "dyesub-tool"; version = "0.1.0"; buildAndTestSubdir = "dyesub-tool"; - cargoHash = "sha256-owoTOMY/u7K3Y96FIHXMJeqILAOFR2wSYozzsT+0p64="; + cargoHash = "sha256-QlLyKDZZ+/bB4xBv0j9GoL6Ur3Okdskkazi4WlHhx6Y="; meta = meta // { description = "A tool for generating dye sublimation transfer sheet SVGs for Japanese thumb shift 拇指シフト keycaps."; };