Skip to main content

apk/res/
value.rs

1//! `Res_value`: typed values stored in resource tables and binary XML.
2
3use std::fmt;
4use std::str::FromStr;
5
6use binrw::binrw;
7
8use super::id::{ResourceId, ResourceRef};
9
10/// The raw, 8-byte `Res_value` record.
11#[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    /// Reads a record from the start of `bytes`.
33    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
39/// `Res_value::dataType` codes.
40pub(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    /// `TYPE_NULL` data marking an explicitly empty value (`@empty`).
57    pub const DATA_NULL_EMPTY: u32 = 1;
58}
59
60/// A resource or attribute value.
61///
62/// References may be symbolic ([`ResourceRef::Named`]) while building; they
63/// are resolved to IDs when encoded. Decoded values always carry IDs.
64#[derive(Clone, Debug, PartialEq)]
65#[non_exhaustive]
66pub enum Value {
67    String(String),
68    Bool(bool),
69    /// A decimal integer (`TYPE_INT_DEC`); also used for enum attributes.
70    Int(i32),
71    /// A hexadecimal integer (`TYPE_INT_HEX`); also used for flag attributes.
72    Hex(u32),
73    Float(f32),
74    Dimension(Dimension),
75    Fraction(Fraction),
76    Color(Color),
77    /// `@type/name`: the value of another resource.
78    Reference(ResourceRef),
79    /// `?type/name`: the value of a theme attribute.
80    Attribute(ResourceRef),
81    /// `@null`: no resource, which clears an attribute (a reference to ID 0).
82    Null,
83    /// `@empty`: an explicitly empty value.
84    Empty,
85    /// A value type this crate does not interpret, kept verbatim.
86    Raw {
87        data_type: u8,
88        data: u32,
89    },
90}
91
92impl Value {
93    /// Decodes a raw value; `string` looks up string values by pool index.
94    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    /// Encodes this value. `string` interns a string; `resolve` maps a
118    /// symbolic reference to its ID.
119    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            // As aapt2 compiles it; `TYPE_NULL` would leave the attribute
137            // undefined, so a style's value would apply instead.
138            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    /// The integer content of `Int`, `Hex`, or `Bool` values.
152    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    /// Formats the value using Android XML notation.
219    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/// A malformed color, dimension, or fraction literal.
239#[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/// How a color literal was written, which selects its `Res_value` type.
253#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
254pub enum ColorFormat {
255    /// `#AARRGGBB`
256    #[default]
257    Argb8,
258    /// `#RRGGBB`
259    Rgb8,
260    /// `#ARGB`
261    Argb4,
262    /// `#RGB`
263    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/// A color, stored as 32-bit ARGB regardless of its literal form.
278#[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    /// Parses `#RGB`, `#ARGB`, `#RRGGBB`, or `#AARRGGBB`.
298    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        // Each 4-bit digit of a short form stands for a repeated 8-bit pair.
304        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/// The unit of a [`Dimension`].
334#[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/// A dimension such as `16dp`, held in Android's complex-number encoding,
354/// so decoded values re-encode exactly.
355#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
356pub struct Dimension(u32);
357
358impl Dimension {
359    /// Encodes `value` in `unit`, which must be finite and below 2^23 in magnitude.
360    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    /// The unit, or `None` for a unit code Android does not define.
369    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    /// Parses `<number><unit>`, accepting `dip` as an alias of `dp`.
378    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/// A fraction such as `50%` (of the element) or `50%p` (of its parent), held
399/// in Android's complex-number encoding.
400#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
401pub struct Fraction(u32);
402
403impl Fraction {
404    /// Encodes the ratio `value` (`0.5` is `50%`).
405    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    /// The fraction as a ratio; `0.5` is `50%`.
410    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;
443/// Values must be smaller than this in magnitude: the mantissa is a 24-bit
444/// two's complement integer.
445const COMPLEX_LIMIT: f32 = (1 << 23) as f32;
446
447/// `complex_value` from AOSP `ResourceTypes.cpp`.
448fn 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
460/// The mantissa and radix bits of `value`, as AOSP `ResTable::stringToFloat`
461/// computes them.
462fn complex(value: f32) -> Result<u32, LiteralError> {
463    if !value.is_finite() || value.abs() >= COMPLEX_LIMIT {
464        return Err(LiteralError::OutOfRange { value });
465    }
466    // Single precision, exactly as aapt2 computes it.
467    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        // Matches aapt2's encoding of 16dp.
494        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        // A unit code Android does not define survives.
511        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        // `@null` is what aapt2 writes: a reference to nothing.
546        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}