1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
//! A length type with a unit.
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;
/// A length with a unit.
#[derive(Copy, Clone, PartialEq)]
pub struct Length {
/// The length in the given unit.
pub val: f64,
/// The unit of measurement.
pub unit: Unit,
}
/// Different units of measurement.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Unit {
/// Points.
Pt,
/// Millimeters.
Mm,
/// Centimeters.
Cm,
/// Inches.
In,
/// Raw units (the implicit unit of all bare `f64` lengths).
Raw,
}
impl Length {
/// Create a length from a value with a unit.
pub const fn new(val: f64, unit: Unit) -> Self {
Self { val, unit }
}
/// Create a length from a number of points.
pub const fn pt(pt: f64) -> Self {
Self::new(pt, Unit::Pt)
}
/// Create a length from a number of millimeters.
pub const fn mm(mm: f64) -> Self {
Self::new(mm, Unit::Mm)
}
/// Create a length from a number of centimeters.
pub const fn cm(cm: f64) -> Self {
Self::new(cm, Unit::Cm)
}
/// Create a length from a number of inches.
pub const fn inches(inches: f64) -> Self {
Self::new(inches, Unit::In)
}
/// Create a length from a number of raw units.
pub const fn raw(raw: f64) -> Self {
Self::new(raw, Unit::Raw)
}
/// Convert this to a number of points.
pub fn as_pt(self) -> f64 {
self.with_unit(Unit::Pt).val
}
/// Convert this to a number of millimeters.
pub fn as_mm(self) -> f64 {
self.with_unit(Unit::Mm).val
}
/// Convert this to a number of centimeters.
pub fn as_cm(self) -> f64 {
self.with_unit(Unit::Cm).val
}
/// Convert this to a number of inches.
pub fn as_inches(self) -> f64 {
self.with_unit(Unit::In).val
}
/// Get the value of this length in raw units.
pub fn as_raw(self) -> f64 {
self.with_unit(Unit::Raw).val
}
/// Convert this to a length with a different unit.
pub fn with_unit(self, unit: Unit) -> Self {
Self {
val: self.val * self.unit.raw_scale() / unit.raw_scale(),
unit,
}
}
}
impl Unit {
/// How many raw units correspond to a value of `1.0` in this unit.
fn raw_scale(self) -> f64 {
match self {
Unit::Pt => 1.0,
Unit::Mm => 2.83465,
Unit::Cm => 28.3465,
Unit::In => 72.0,
Unit::Raw => 1.0,
}
}
}
impl Display for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{:.2}{}", self.val, self.unit)
}
}
impl Debug for Length {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Display for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self {
Unit::Mm => "mm",
Unit::Pt => "pt",
Unit::Cm => "cm",
Unit::In => "in",
Unit::Raw => "rw",
})
}
}
impl Debug for Unit {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl FromStr for Length {
type Err = ParseLengthError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let len = src.len();
// We need at least some number and the unit.
if len <= 2 {
return Err(ParseLengthError);
}
// We can view the string as bytes since a multibyte UTF-8 char cannot
// have valid ASCII chars as subbytes.
let split = len - 2;
let bytes = src.as_bytes();
let unit = match &bytes[split ..] {
b"pt" => Unit::Pt,
b"mm" => Unit::Mm,
b"cm" => Unit::Cm,
b"in" => Unit::In,
_ => return Err(ParseLengthError),
};
src[.. split]
.parse::<f64>()
.map(|val| Self::new(val, unit))
.map_err(|_| ParseLengthError)
}
}
/// The error when parsing a length fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParseLengthError;
impl std::error::Error for ParseLengthError {}
impl Display for ParseLengthError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("invalid string for length")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_length_from_str_parses_correct_value_and_unit() {
assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5)));
}
#[test]
fn test_length_from_str_works_with_non_ascii_chars() {
assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError));
}
#[test]
fn test_length_formats_correctly() {
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
}
#[test]
fn test_length_unit_conversion() {
assert!((Length::mm(150.0).as_cm() - 15.0) < 1e-4);
}
}
|