1use std::fmt;
4use std::str::FromStr;
5
6use binrw::binrw;
7
8use super::id::{ResourceId, ResourceRef};
9
10#[binrw]
12#[brw(little)]
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub(crate) struct RawValue {
15 #[br(temp)]
16 #[bw(calc = 8)]
17 _size: u16,
18 #[br(temp)]
19 #[bw(calc = 0)]
20 _reserved: u8,
21 pub data_type: u8,
22 pub data: u32,
23}
24
25impl RawValue {
26 pub const SIZE: usize = 8;
27
28 pub const fn new(data_type: u8, data: u32) -> Self {
29 Self { data_type, data }
30 }
31
32 pub fn read(bytes: &[u8]) -> Option<Self> {
34 let bytes = bytes.get(..Self::SIZE)?;
35 Some(Self::new(bytes[3], u32::from_le_bytes(bytes[4..8].try_into().ok()?)))
36 }
37}
38
39pub(crate) mod data_type {
41 pub const NULL: u8 = 0x00;
42 pub const REFERENCE: u8 = 0x01;
43 pub const ATTRIBUTE: u8 = 0x02;
44 pub const STRING: u8 = 0x03;
45 pub const FLOAT: u8 = 0x04;
46 pub const DIMENSION: u8 = 0x05;
47 pub const FRACTION: u8 = 0x06;
48 pub const INT_DEC: u8 = 0x10;
49 pub const INT_HEX: u8 = 0x11;
50 pub const INT_BOOLEAN: u8 = 0x12;
51 pub const INT_COLOR_ARGB8: u8 = 0x1c;
52 pub const INT_COLOR_RGB8: u8 = 0x1d;
53 pub const INT_COLOR_ARGB4: u8 = 0x1e;
54 pub const INT_COLOR_RGB4: u8 = 0x1f;
55
56 pub const DATA_NULL_EMPTY: u32 = 1;
58}
59
60#[derive(Clone, Debug, PartialEq)]
65#[non_exhaustive]
66pub enum Value {
67 String(String),
68 Bool(bool),
69 Int(i32),
71 Hex(u32),
73 Float(f32),
74 Dimension(Dimension),
75 Fraction(Fraction),
76 Color(Color),
77 Reference(ResourceRef),
79 Attribute(ResourceRef),
81 Null,
83 Empty,
85 Raw {
87 data_type: u8,
88 data: u32,
89 },
90}
91
92impl Value {
93 pub(crate) fn from_raw<E>(raw: RawValue, string: impl FnOnce(u32) -> Result<String, E>) -> Result<Self, E> {
95 use data_type::*;
96 let data = raw.data;
97 Ok(match raw.data_type {
98 NULL if data == DATA_NULL_EMPTY => Self::Empty,
99 REFERENCE if data == 0 => Self::Null,
100 REFERENCE => Self::Reference(ResourceRef::Id(ResourceId(data))),
101 ATTRIBUTE => Self::Attribute(ResourceRef::Id(ResourceId(data))),
102 STRING => Self::String(string(data)?),
103 FLOAT => Self::Float(f32::from_bits(data)),
104 DIMENSION => Self::Dimension(Dimension(data)),
105 FRACTION => Self::Fraction(Fraction(data)),
106 INT_DEC => Self::Int(data as i32),
107 INT_HEX => Self::Hex(data),
108 INT_BOOLEAN => Self::Bool(data != 0),
109 INT_COLOR_ARGB8 => Self::Color(Color { argb: data, format: ColorFormat::Argb8 }),
110 INT_COLOR_RGB8 => Self::Color(Color { argb: data, format: ColorFormat::Rgb8 }),
111 INT_COLOR_ARGB4 => Self::Color(Color { argb: data, format: ColorFormat::Argb4 }),
112 INT_COLOR_RGB4 => Self::Color(Color { argb: data, format: ColorFormat::Rgb4 }),
113 data_type => Self::Raw { data_type, data },
114 })
115 }
116
117 pub(crate) fn to_raw<'v, E>(
120 &'v self,
121 string: impl FnOnce(&'v str) -> u32,
122 resolve: impl FnOnce(&ResourceRef) -> Result<ResourceId, E>,
123 ) -> Result<RawValue, E> {
124 use data_type::*;
125 Ok(match self {
126 Self::String(s) => RawValue::new(STRING, string(s)),
127 Self::Bool(b) => RawValue::new(INT_BOOLEAN, if *b { u32::MAX } else { 0 }),
128 Self::Int(i) => RawValue::new(INT_DEC, *i as u32),
129 Self::Hex(h) => RawValue::new(INT_HEX, *h),
130 Self::Float(f) => RawValue::new(FLOAT, f.to_bits()),
131 Self::Dimension(d) => RawValue::new(DIMENSION, d.0),
132 Self::Fraction(f) => RawValue::new(FRACTION, f.0),
133 Self::Color(c) => RawValue::new(c.format.data_type(), c.argb),
134 Self::Reference(r) => RawValue::new(REFERENCE, resolve(r)?.0),
135 Self::Attribute(r) => RawValue::new(ATTRIBUTE, resolve(r)?.0),
136 Self::Null => RawValue::new(REFERENCE, 0),
139 Self::Empty => RawValue::new(NULL, DATA_NULL_EMPTY),
140 Self::Raw { data_type, data } => RawValue::new(*data_type, *data),
141 })
142 }
143
144 pub fn as_str(&self) -> Option<&str> {
145 match self {
146 Self::String(s) => Some(s),
147 _ => None,
148 }
149 }
150
151 pub fn as_int(&self) -> Option<i64> {
153 match *self {
154 Self::Int(i) => Some(i.into()),
155 Self::Hex(h) => Some(h.into()),
156 Self::Bool(b) => Some(b.into()),
157 _ => None,
158 }
159 }
160
161 pub fn as_bool(&self) -> Option<bool> {
162 match *self {
163 Self::Bool(b) => Some(b),
164 _ => None,
165 }
166 }
167}
168
169impl From<&str> for Value {
170 fn from(s: &str) -> Self {
171 Self::String(s.to_owned())
172 }
173}
174
175impl From<String> for Value {
176 fn from(s: String) -> Self {
177 Self::String(s)
178 }
179}
180
181impl From<bool> for Value {
182 fn from(b: bool) -> Self {
183 Self::Bool(b)
184 }
185}
186
187impl From<i32> for Value {
188 fn from(i: i32) -> Self {
189 Self::Int(i)
190 }
191}
192
193impl From<ResourceRef> for Value {
194 fn from(r: ResourceRef) -> Self {
195 Self::Reference(r)
196 }
197}
198
199impl From<Color> for Value {
200 fn from(c: Color) -> Self {
201 Self::Color(c)
202 }
203}
204
205impl From<Dimension> for Value {
206 fn from(d: Dimension) -> Self {
207 Self::Dimension(d)
208 }
209}
210
211impl From<Fraction> for Value {
212 fn from(f: Fraction) -> Self {
213 Self::Fraction(f)
214 }
215}
216
217impl fmt::Display for Value {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 Self::String(s) => f.write_str(s),
222 Self::Bool(b) => write!(f, "{b}"),
223 Self::Int(i) => write!(f, "{i}"),
224 Self::Hex(h) => write!(f, "0x{h:08x}"),
225 Self::Float(x) => write!(f, "{x:?}"),
226 Self::Dimension(d) => write!(f, "{d}"),
227 Self::Fraction(x) => write!(f, "{x}"),
228 Self::Color(c) => write!(f, "{c}"),
229 Self::Reference(r) => write!(f, "{r}"),
230 Self::Attribute(r) => write!(f, "?{}", r.to_string().trim_start_matches('@')),
231 Self::Null => f.write_str("@null"),
232 Self::Empty => f.write_str("@empty"),
233 Self::Raw { data_type, data } => write!(f, "(type 0x{data_type:02x})0x{data:08x}"),
234 }
235 }
236}
237
238#[derive(Clone, Debug, PartialEq, thiserror::Error)]
240#[non_exhaustive]
241pub enum LiteralError {
242 #[error("{text:?} is not a color (#RGB, #ARGB, #RRGGBB, or #AARRGGBB)")]
243 Color { text: String },
244 #[error("{text:?} is not a dimension such as `16dp`")]
245 Dimension { text: String },
246 #[error("{text:?} is not a fraction such as `50%` or `50%p`")]
247 Fraction { text: String },
248 #[error("{value} is not finite with a magnitude below 2^23, so it has no complex encoding")]
249 OutOfRange { value: f32 },
250}
251
252#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
254pub enum ColorFormat {
255 #[default]
257 Argb8,
258 Rgb8,
260 Argb4,
262 Rgb4,
264}
265
266impl ColorFormat {
267 const fn data_type(self) -> u8 {
268 match self {
269 Self::Argb8 => data_type::INT_COLOR_ARGB8,
270 Self::Rgb8 => data_type::INT_COLOR_RGB8,
271 Self::Argb4 => data_type::INT_COLOR_ARGB4,
272 Self::Rgb4 => data_type::INT_COLOR_RGB4,
273 }
274 }
275}
276
277#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
279pub struct Color {
280 pub argb: u32,
281 pub format: ColorFormat,
282}
283
284impl Color {
285 pub const fn argb(argb: u32) -> Self {
286 Self { argb, format: ColorFormat::Argb8 }
287 }
288
289 pub const fn rgb(rgb: u32) -> Self {
290 Self { argb: 0xff00_0000 | (rgb & 0x00ff_ffff), format: ColorFormat::Rgb8 }
291 }
292}
293
294impl FromStr for Color {
295 type Err = LiteralError;
296
297 fn from_str(text: &str) -> Result<Self, LiteralError> {
299 let invalid = || LiteralError::Color { text: text.to_owned() };
300 let hex =
301 text.strip_prefix('#').filter(|hex| hex.bytes().all(|b| b.is_ascii_hexdigit())).ok_or_else(invalid)?;
302 let value = u32::from_str_radix(hex, 16).map_err(|_| invalid())?;
303 let widen = |v: u32| (0..4).rev().fold(0, |acc, i| (acc << 8) | (((v >> (i * 4)) & 0xf) * 0x11));
305 Ok(match hex.len() {
306 3 => Self { argb: 0xff00_0000 | widen(value), format: ColorFormat::Rgb4 },
307 4 => Self { argb: widen(value), format: ColorFormat::Argb4 },
308 6 => Self { argb: 0xff00_0000 | value, format: ColorFormat::Rgb8 },
309 8 => Self { argb: value, format: ColorFormat::Argb8 },
310 _ => return Err(invalid()),
311 })
312 }
313}
314
315impl fmt::Display for Color {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 let opaque = self.argb >> 24 == 0xff;
318 let short = (self.argb & 0x0f0f_0f0f) == ((self.argb >> 4) & 0x0f0f_0f0f);
319 match self.format {
320 ColorFormat::Rgb8 if opaque => write!(f, "#{:06x}", self.argb & 0x00ff_ffff),
321 ColorFormat::Argb4 | ColorFormat::Rgb4 if short && (self.format == ColorFormat::Argb4 || opaque) => {
322 let short = (0..4).rev().fold(0, |value, i| (value << 4) | ((self.argb >> (i * 8 + 4)) & 0xf));
323 match self.format {
324 ColorFormat::Argb4 => write!(f, "#{short:04x}"),
325 _ => write!(f, "#{:03x}", short & 0xfff),
326 }
327 }
328 _ => write!(f, "#{:08x}", self.argb),
329 }
330 }
331}
332
333#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
335pub enum DimensionUnit {
336 Px,
337 Dp,
338 Sp,
339 Pt,
340 In,
341 Mm,
342}
343
344impl DimensionUnit {
345 const ALL: [(Self, &'static str); 6] =
346 [(Self::Px, "px"), (Self::Dp, "dp"), (Self::Sp, "sp"), (Self::Pt, "pt"), (Self::In, "in"), (Self::Mm, "mm")];
347
348 fn suffix(self) -> &'static str {
349 Self::ALL[self as usize].1
350 }
351}
352
353#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
356pub struct Dimension(u32);
357
358impl Dimension {
359 pub fn new(value: f32, unit: DimensionUnit) -> Result<Self, LiteralError> {
361 Ok(Self(complex(value)? | unit as u32))
362 }
363
364 pub fn value(self) -> f32 {
365 complex_value(self.0)
366 }
367
368 pub fn unit(self) -> Option<DimensionUnit> {
370 DimensionUnit::ALL.get((self.0 & UNIT_MASK) as usize).map(|(unit, _)| *unit)
371 }
372}
373
374impl FromStr for Dimension {
375 type Err = LiteralError;
376
377 fn from_str(text: &str) -> Result<Self, LiteralError> {
379 let invalid = || LiteralError::Dimension { text: text.to_owned() };
380 let (unit, number) = DimensionUnit::ALL
381 .into_iter()
382 .chain([(DimensionUnit::Dp, "dip")])
383 .find_map(|(unit, suffix)| text.strip_suffix(suffix).map(|number| (unit, number)))
384 .ok_or_else(invalid)?;
385 Self::new(number.trim().parse().map_err(|_| invalid())?, unit)
386 }
387}
388
389impl fmt::Display for Dimension {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 match self.unit() {
392 Some(unit) => write!(f, "{}{}", self.value(), unit.suffix()),
393 None => write!(f, "{}(unit {})", self.value(), self.0 & UNIT_MASK),
394 }
395 }
396}
397
398#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
401pub struct Fraction(u32);
402
403impl Fraction {
404 pub fn new(value: f32, relative_to_parent: bool) -> Result<Self, LiteralError> {
406 Ok(Self(complex(value)? | u32::from(relative_to_parent)))
407 }
408
409 pub fn value(self) -> f32 {
411 complex_value(self.0)
412 }
413
414 pub fn relative_to_parent(self) -> bool {
415 self.0 & UNIT_MASK == 1
416 }
417}
418
419impl FromStr for Fraction {
420 type Err = LiteralError;
421
422 fn from_str(text: &str) -> Result<Self, LiteralError> {
423 let invalid = || LiteralError::Fraction { text: text.to_owned() };
424 let (number, relative_to_parent) = match text.strip_suffix("%p") {
425 Some(number) => (number, true),
426 None => (text.strip_suffix('%').ok_or_else(invalid)?, false),
427 };
428 let percent: f32 = number.trim().parse().map_err(|_| invalid())?;
429 Self::new(percent / 100.0, relative_to_parent)
430 }
431}
432
433impl fmt::Display for Fraction {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 write!(f, "{}%{}", self.value() * 100.0, if self.relative_to_parent() { "p" } else { "" })
436 }
437}
438
439const UNIT_MASK: u32 = 0xf;
440const RADIX_SHIFT: u32 = 4;
441const MANTISSA_SHIFT: u32 = 8;
442const MANTISSA_MASK: u32 = 0x00ff_ffff;
443const COMPLEX_LIMIT: f32 = (1 << 23) as f32;
446
447fn complex_value(data: u32) -> f32 {
449 const MANTISSA_MULT: f32 = 1.0 / (1 << MANTISSA_SHIFT) as f32;
450 const RADIX_MULTS: [f32; 4] = [
451 MANTISSA_MULT,
452 MANTISSA_MULT / (1 << 7) as f32,
453 MANTISSA_MULT / (1 << 15) as f32,
454 MANTISSA_MULT / (1 << 23) as f32,
455 ];
456 let mantissa = (data & (MANTISSA_MASK << MANTISSA_SHIFT)) as i32;
457 mantissa as f32 * RADIX_MULTS[((data >> RADIX_SHIFT) & 0x3) as usize]
458}
459
460fn complex(value: f32) -> Result<u32, LiteralError> {
463 if !value.is_finite() || value.abs() >= COMPLEX_LIMIT {
464 return Err(LiteralError::OutOfRange { value });
465 }
466 let bits = (value.abs() * (1u32 << 23) as f32 + 0.5) as u64;
468 let (radix, shift) = match bits {
469 _ if bits & 0x7f_ffff == 0 => (0, 23),
470 _ if bits & !0x7f_ffff == 0 => (3, 0),
471 _ if bits & !0x7fff_ffff == 0 => (2, 8),
472 _ if bits & !0x7f_ffff_ffff == 0 => (1, 16),
473 _ => (0, 23),
474 };
475 let mut mantissa = ((bits >> shift) as u32) & MANTISSA_MASK;
476 if value < 0.0 {
477 mantissa = mantissa.wrapping_neg() & MANTISSA_MASK;
478 }
479 Ok((radix << RADIX_SHIFT) | (mantissa << MANTISSA_SHIFT))
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
487 fn complex_numbers_round_trip() {
488 for value in [0.0, 1.0, 16.0, 0.5, -3.25, 1234.5, 0.001, 8_388_607.0, -8_388_607.0] {
489 let dimension = Dimension::new(value, DimensionUnit::Dp).unwrap();
490 assert!((dimension.value() - value).abs() < 1e-3, "{value} decoded as {}", dimension.value());
491 assert_eq!(dimension.unit(), Some(DimensionUnit::Dp));
492 }
493 assert_eq!(Dimension::new(16.0, DimensionUnit::Dp).unwrap().0, 0x0000_1001);
495 }
496
497 #[test]
498 fn unrepresentable_numbers_are_rejected() {
499 for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 8_388_608.0, -8_388_608.0, 1e30] {
500 assert!(
501 matches!(Dimension::new(value, DimensionUnit::Dp), Err(LiteralError::OutOfRange { .. })),
502 "{value}"
503 );
504 }
505 assert!(matches!("1000000000%".parse::<Fraction>(), Err(LiteralError::OutOfRange { .. })));
506 }
507
508 #[test]
509 fn decoded_values_re_encode_exactly() {
510 let raw = RawValue::new(data_type::DIMENSION, 0x0000_100f);
512 let value = Value::from_raw::<()>(raw, |_| unreachable!()).unwrap();
513 assert_eq!(value.to_raw::<()>(|_| unreachable!(), |_| unreachable!()).unwrap(), raw);
514 }
515
516 #[test]
517 fn parses_literals() {
518 let argb = |text: &str| text.parse::<Color>().unwrap().argb;
519 assert_eq!(argb("#f00"), 0xffff_0000);
520 assert_eq!(argb("#8f00"), 0x88ff_0000);
521 assert_eq!(argb("#123456"), 0xff12_3456);
522 assert_eq!(argb("#80123456"), 0x8012_3456);
523 for format in [ColorFormat::Rgb4, ColorFormat::Argb4, ColorFormat::Rgb8] {
524 let color = Color { argb: 0x7f12_3456, format };
525 assert_eq!(color.to_string(), "#7f123456", "unrepresentable formats must preserve the ARGB value");
526 }
527 for invalid in ["#12345", "#+1234", "123456"] {
528 assert!(matches!(invalid.parse::<Color>(), Err(LiteralError::Color { .. })), "{invalid}");
529 }
530 assert_eq!("4.5dip".parse::<Dimension>().unwrap(), Dimension::new(4.5, DimensionUnit::Dp).unwrap());
531 for (text, value) in [("1e3dp", 1000.0), ("-2.5E-1dp", -0.25), ("1e+2dip", 100.0)] {
532 assert_eq!(text.parse::<Dimension>().unwrap(), Dimension::new(value, DimensionUnit::Dp).unwrap());
533 }
534 assert!(matches!("4.5parsecs".parse::<Dimension>(), Err(LiteralError::Dimension { .. })));
535 let fraction: Fraction = "50%p".parse().unwrap();
536 assert_eq!((fraction.value(), fraction.relative_to_parent()), (0.5, true));
537 }
538
539 #[test]
540 fn null_empty_and_undefined_are_distinct() {
541 for value in [Value::Empty, Value::Null] {
542 let raw = value.to_raw::<()>(|_| 0, |_| unreachable!()).unwrap();
543 assert_eq!(Value::from_raw::<()>(raw, |_| unreachable!()).unwrap(), value);
544 }
545 assert_eq!(
547 Value::Null.to_raw::<()>(|_| 0, |_| unreachable!()).unwrap(),
548 RawValue::new(data_type::REFERENCE, 0)
549 );
550 let undefined = RawValue::new(data_type::NULL, 0);
551 assert_eq!(Value::from_raw::<()>(undefined, |_| unreachable!()).unwrap(), Value::Raw { data_type: 0, data: 0 });
552 }
553}