From 18a599cf065d29f1478ff08e1af301cbc129976f Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 24 Nov 2025 09:09:37 -0800 Subject: [PATCH] rust: Add Length type to paper-sizes crate. --- rust/Cargo.lock | 2 +- rust/paper-sizes/Cargo.toml | 2 +- rust/paper-sizes/src/lib.rs | 133 ++++++++++++++++++++++++---------- rust/paper-sizes/src/serde.rs | 100 +++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 39 deletions(-) create mode 100644 rust/paper-sizes/src/serde.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4ad4518df2..9ed9ede56c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1571,7 +1571,7 @@ dependencies = [ [[package]] name = "paper-sizes" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bindgen", "libc", diff --git a/rust/paper-sizes/Cargo.toml b/rust/paper-sizes/Cargo.toml index 73611bff56..28b88e2ed1 100644 --- a/rust/paper-sizes/Cargo.toml +++ b/rust/paper-sizes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "paper-sizes" -version = "0.2.0" +version = "0.3.0" edition = "2024" license = "MIT OR Apache-2.0 OR LGPL-2.1-or-later" authors = [ "Ben Pfaff" ] diff --git a/rust/paper-sizes/src/lib.rs b/rust/paper-sizes/src/lib.rs index 3303ee1033..e312374bf3 100644 --- a/rust/paper-sizes/src/lib.rs +++ b/rust/paper-sizes/src/lib.rs @@ -42,6 +42,9 @@ use xdg::BaseDirectories; #[cfg(target_os = "linux")] mod locale; +#[cfg(feature = "serde")] +mod serde; + include!(concat!(env!("OUT_DIR"), "/paperspecs.rs")); static PAPERSIZE_FILENAME: &str = "papersize"; @@ -69,6 +72,14 @@ pub enum Unit { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct ParseUnitError; +impl Error for ParseUnitError {} + +impl Display for ParseUnitError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "unknown unit") + } +} + impl FromStr for Unit { type Err = ParseUnitError; @@ -98,7 +109,7 @@ impl Unit { /// /// To convert a quantity of unit `a` into unit `b`, multiply by /// `a.as_unit(b)`. - fn as_unit(&self, other: Unit) -> f64 { + pub fn as_unit(&self, other: Unit) -> f64 { match (*self, other) { (Unit::Point, Unit::Point) => 1.0, (Unit::Point, Unit::Inch) => 1.0 / 72.0, @@ -119,6 +130,80 @@ impl Display for Unit { } } +/// A physical length with a [Unit]. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Length { + /// The length. + pub value: f64, + + /// The length's unit. + pub unit: Unit, +} + +impl Length { + /// Constructs a new `Length` from `value` and `unit`. + pub fn new(value: f64, unit: Unit) -> Self { + Self { value, unit } + } + + /// Returns this length converted to `unit`. + pub fn as_unit(&self, unit: Unit) -> Self { + Self { + value: self.value * unit.as_unit(Unit::Inch), + unit, + } + } + + /// Returns the value of this length in `unit`. + pub fn into_unit(&self, unit: Unit) -> f64 { + self.as_unit(unit).value + } +} + +/// An error parsing a [Length]. +#[derive(Copy, Clone, Debug)] +pub enum ParseLengthError { + /// Missing unit. + MissingUnit, + /// Invalid unit. + InvalidUnit, + /// Invalid value + InvalidValue, +} + +impl Error for ParseLengthError {} + +impl Display for ParseLengthError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseLengthError::MissingUnit => write!(f, "Missing unit"), + ParseLengthError::InvalidUnit => write!(f, "Invalid unit of measurement"), + ParseLengthError::InvalidValue => write!(f, "Invalid length"), + } + } +} + +impl FromStr for Length { + type Err = ParseLengthError; + + fn from_str(s: &str) -> Result { + if let Some(index) = s.find(|c: char| c.is_alphabetic()) { + let (value, unit) = s.split_at(index); + let value = value.parse().map_err(|_| ParseLengthError::InvalidValue)?; + let unit = unit.parse().map_err(|_| ParseLengthError::InvalidUnit)?; + Ok(Self { value, unit }) + } else { + Err(ParseLengthError::MissingUnit) + } + } +} + +impl Display for Length { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}{}", self.value, self.unit) + } +} + /// The size of a piece of paper. #[derive(Copy, Clone, Debug, PartialEq)] pub struct PaperSize { @@ -170,6 +255,16 @@ impl PaperSize { let (bw, bh) = other.as_unit(unit).into_width_height(); aw.round() == bw.round() && ah.round() == bh.round() } + + /// Returns the paper's width as a [Length]. + pub fn width(&self) -> Length { + Length::new(self.width, self.unit) + } + + /// Returns the paper's height as a [Length]. + pub fn height(&self) -> Length { + Length::new(self.height, self.unit) + } } /// An error parsing a [PaperSize]. @@ -240,29 +335,6 @@ impl Display for PaperSize { } } -#[cfg(feature = "serde")] -impl serde::Serialize for PaperSize { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.to_string().serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for PaperSize { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - String::deserialize(deserializer)? - .parse() - .map_err(D::Error::custom) - } -} - /// An error parsing a [PaperSpec]. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ParsePaperSpecError { @@ -1028,17 +1100,4 @@ mod tests { ); } } - - #[cfg(feature = "serde")] - #[test] - fn test_serde() { - assert_eq!( - serde_json::to_string(&PaperSize::new(8.5, 11.0, Unit::Inch)).unwrap(), - "\"8.5x11in\"" - ); - assert_eq!( - serde_json::from_str::("\"8.5x11in\"").unwrap(), - PaperSize::new(8.5, 11.0, Unit::Inch) - ) - } } diff --git a/rust/paper-sizes/src/serde.rs b/rust/paper-sizes/src/serde.rs new file mode 100644 index 0000000000..278f423529 --- /dev/null +++ b/rust/paper-sizes/src/serde.rs @@ -0,0 +1,100 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; + +#[cfg(feature = "serde")] +use crate::PaperSize; +use crate::{Length, Unit}; + +impl<'de> Deserialize<'de> for Length { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(D::Error::custom) + } +} + +impl Serialize for Length { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl Serialize for PaperSize { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PaperSize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(D::Error::custom) + } +} + +impl Serialize for Unit { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Unit { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(D::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use crate::{Length, PaperSize, Unit}; + + #[test] + fn unit() { + assert_eq!(serde_json::to_string(&Unit::Point).unwrap(), "\"pt\""); + assert_eq!(serde_json::from_str::("\"pt\"").unwrap(), Unit::Point) + } + + #[test] + fn length() { + assert_eq!( + serde_json::to_string(&Length::new(123.0, Unit::Millimeter)).unwrap(), + "\"123mm\"" + ); + assert_eq!( + serde_json::from_str::("\"123mm\"").unwrap(), + Length::new(123.0, Unit::Millimeter) + ) + } + + #[test] + fn paper_size() { + assert_eq!( + serde_json::to_string(&PaperSize::new(8.5, 11.0, Unit::Inch)).unwrap(), + "\"8.5x11in\"" + ); + assert_eq!( + serde_json::from_str::("\"8.5x11in\"").unwrap(), + PaperSize::new(8.5, 11.0, Unit::Inch) + ) + } +} -- 2.30.2