use std::{mem, string::ToString};
use failure::{format_err, Error};
const TOKEN_TYPE_REFERENCE_ID: u8 = 0x01;
const TOKEN_TYPE_ATTRIBUTE_REFERENCE_ID: u8 = 0x02;
const TOKEN_TYPE_STRING: u8 = 0x03;
const TOKEN_TYPE_FLOAT: u8 = 0x04;
const TOKEN_TYPE_DIMENSION: u8 = 0x05;
const TOKEN_TYPE_FRACTION: u8 = 0x06;
const TOKEN_TYPE_DYN_REFERENCE: u8 = 0x07;
const TOKEN_TYPE_DYN_ATTRIBUTE: u8 = 0x08;
const TOKEN_TYPE_INTEGER: u8 = 0x10;
const TOKEN_TYPE_FLAGS: u8 = 0x11;
const TOKEN_TYPE_BOOLEAN: u8 = 0x12;
const TOKEN_TYPE_ARGB8: u8 = 0x1C;
const TOKEN_TYPE_RGB8: u8 = 0x1D;
const TOKEN_TYPE_ARGB4: u8 = 0x1E;
const TOKEN_TYPE_RGB4: u8 = 0x1F;
#[derive(Debug)]
pub enum Value {
StringReference(u32),
Dimension(String),
Fraction(String),
Float(f32),
Integer(u32),
Flags(u32),
Boolean(bool),
ColorARGB8(String),
ColorRGB8(String),
ColorARGB4(String),
ColorRGB4(String),
ReferenceId(u32),
AttributeReferenceId(u32),
Unknown(u8, u32),
}
impl ToString for Value {
fn to_string(&self) -> String {
match self {
Self::StringReference(i) => format!("@string/{}", i),
Self::Dimension(s)
| Self::Fraction(s)
| Self::ColorARGB8(s)
| Self::ColorRGB8(s)
| Self::ColorARGB4(s)
| Self::ColorRGB4(s) => s.clone(),
Self::Float(f) => format!("{:.*}", 1, f),
Self::Integer(i) | Self::Flags(i) => i.to_string(),
Self::Boolean(b) => b.to_string(),
Self::ReferenceId(s) | Self::AttributeReferenceId(s) => format!("@id/0x{:x}", s),
_ => "Unknown".to_string(),
}
}
}
impl Value {
pub fn create(value_type: u8, data: u32) -> Result<Self, Error> {
let value = match value_type {
TOKEN_TYPE_REFERENCE_ID | TOKEN_TYPE_DYN_REFERENCE => Self::ReferenceId(data),
TOKEN_TYPE_ATTRIBUTE_REFERENCE_ID | TOKEN_TYPE_DYN_ATTRIBUTE => {
Self::AttributeReferenceId(data)
}
TOKEN_TYPE_STRING => Self::StringReference(data),
TOKEN_TYPE_DIMENSION => {
let units: [&str; 6] = ["px", "dip", "sp", "pt", "in", "mm"];
let value = Self::complex(data);
let unit_idx = data & 0xF;
if let Some(unit) = units.get(unit_idx as usize) {
let formatted = format!("{:.*}{}", 1, value, unit);
Self::Dimension(formatted)
} else {
return Err(format_err!(
"expected a valid unit index, got: {}",
unit_idx
));
}
}
TOKEN_TYPE_FRACTION => {
let units: [&str; 2] = ["%", "%p"];
let unit_idx = (data & 0xF) as usize;
let final_value = Self::complex(data) * 100.0;
if let Some(unit) = units.get(unit_idx) {
let integer = final_value.round();
let diff = final_value - integer;
let formatted_fraction = if diff > 0.0000001 {
format!("{:.*}{}", 6, final_value, unit)
} else {
format!("{:.*}{}", 1, final_value, unit)
};
Self::Fraction(formatted_fraction)
} else {
return Err(format_err!(
"expected a valid unit index, got: {}",
unit_idx
));
}
}
TOKEN_TYPE_INTEGER => {
Self::Integer(data)
}
TOKEN_TYPE_FLAGS => Self::Flags(data),
TOKEN_TYPE_FLOAT => Self::Float(f32::from_bits(data)),
TOKEN_TYPE_BOOLEAN => Self::Boolean(data > 0),
TOKEN_TYPE_ARGB8 => {
let formatted_color = format!("#{:08x}", data);
Self::ColorARGB8(formatted_color)
}
TOKEN_TYPE_RGB8 => {
let formatted_color = format!("#{:08x}", data);
Self::ColorRGB8(formatted_color)
}
TOKEN_TYPE_ARGB4 => {
let formatted_color = format!("#{:08x}", data);
Self::ColorARGB4(formatted_color)
}
TOKEN_TYPE_RGB4 => {
let formatted_color = format!("#{:08x}", data);
Self::ColorRGB4(formatted_color)
}
_ => Self::Unknown(value_type, data),
};
Ok(value)
}
#[allow(unsafe_code)]
fn complex(data: u32) -> f32 {
let mantissa = 0xffffff << 8;
let u_value = data & mantissa;
let i_value: i32 = unsafe { mem::transmute(u_value) };
let m = i_value as f32;
let mm = 1.0 / ((1 << 8) as f32);
let radix = [
1.0 * mm,
1.0 / ((1 << 7) as f32) * mm,
1.0 / ((1 << 15) as f32) * mm,
1.0 / ((1 << 23) as f32) * mm,
];
let idx = (data >> 4) & 0x3;
m * radix[idx as usize]
}
}
#[cfg(test)]
mod tests {
use super::{
ToString, Value, TOKEN_TYPE_ARGB4, TOKEN_TYPE_ARGB8, TOKEN_TYPE_ATTRIBUTE_REFERENCE_ID,
TOKEN_TYPE_BOOLEAN, TOKEN_TYPE_DIMENSION, TOKEN_TYPE_DYN_ATTRIBUTE,
TOKEN_TYPE_DYN_REFERENCE, TOKEN_TYPE_FLAGS, TOKEN_TYPE_FLOAT, TOKEN_TYPE_FRACTION,
TOKEN_TYPE_INTEGER, TOKEN_TYPE_REFERENCE_ID, TOKEN_TYPE_RGB4, TOKEN_TYPE_RGB8,
TOKEN_TYPE_STRING,
};
#[test]
fn it_can_generate_a_string_value() {
let value = Value::create(TOKEN_TYPE_STRING, 33);
assert_eq!("@string/33", value.unwrap().to_string());
}
#[test]
fn it_can_generate_reference_and_dyn_references() {
let value = Value::create(TOKEN_TYPE_REFERENCE_ID, 12345).unwrap();
let value2 = Value::create(TOKEN_TYPE_DYN_REFERENCE, 67890).unwrap();
assert_eq!("@id/0x3039", value.to_string());
assert_eq!("@id/0x10932", value2.to_string());
}
#[test]
fn it_can_generate_attribute_and_dyn_references() {
let value = Value::create(TOKEN_TYPE_ATTRIBUTE_REFERENCE_ID, 12345).unwrap();
let value2 = Value::create(TOKEN_TYPE_DYN_ATTRIBUTE, 67890).unwrap();
assert_eq!("@id/0x3039", value.to_string());
assert_eq!("@id/0x10932", value2.to_string());
}
#[test]
fn it_can_generate_a_positive_dimension() {
let dim = 1 << 30;
let units = 0x5;
let value = Value::create(TOKEN_TYPE_DIMENSION, dim | units);
let str_value = value.unwrap().to_string();
assert_eq!("4194304.0mm", str_value);
}
#[test]
fn it_can_generate_a_negative_dimension() {
let dim = 1 << 31;
let units = 0x0;
let value = Value::create(TOKEN_TYPE_DIMENSION, dim | units);
let str_value = value.unwrap().to_string();
assert_eq!("-8388608.0px", str_value);
}
#[test]
fn it_can_not_generate_a_dimension_if_units_are_out_of_range() {
let dim = 0;
let units = 0x6;
let value = Value::create(TOKEN_TYPE_DIMENSION, dim | units);
assert!(value.is_err());
}
#[test]
fn it_can_generate_a_positive_fraction() {
let dim = 1 << 25;
let units = 0x1;
let value = Value::create(TOKEN_TYPE_FRACTION, dim | units);
let str_value = value.unwrap().to_string();
assert_eq!("13107200.0%p", str_value);
}
#[test]
fn it_can_generate_a_negative_fraction() {
let dim = 1 << 31 | 1 << 5 | 1 << 10;
let units = 0x0;
let value = Value::create(TOKEN_TYPE_FRACTION, dim | units);
let str_value = value.unwrap().to_string();
assert_eq!("-25599.988281%", str_value);
}
#[test]
fn it_can_not_generate_a_fraction_if_units_are_out_of_range() {
let dim = 1 << 31 | 1 << 5 | 1 << 10;
let units = 0x2;
let value = Value::create(TOKEN_TYPE_FRACTION, dim | units);
assert!(value.is_err());
}
#[test]
fn it_can_generate_integer_values() {
let int = 12345;
let value = Value::create(TOKEN_TYPE_INTEGER, int);
assert_eq!("12345", value.unwrap().to_string());
}
#[test]
fn it_can_generate_flag_values() {
let int = 12345;
let value = Value::create(TOKEN_TYPE_FLAGS, int);
assert_eq!("12345", value.unwrap().to_string());
}
#[test]
fn it_can_generate_float_values() {
let float = 0;
let value = Value::create(TOKEN_TYPE_FLOAT, float);
assert_eq!("0.0", value.unwrap().to_string());
}
#[test]
fn it_can_generate_a_boolean_true_value() {
let data = 123;
let value = Value::create(TOKEN_TYPE_BOOLEAN, data);
assert_eq!("true", value.unwrap().to_string());
}
#[test]
fn it_can_generate_a_boolean_false_value() {
let data = 0;
let value = Value::create(TOKEN_TYPE_BOOLEAN, data);
assert_eq!("false", value.unwrap().to_string());
}
#[test]
fn it_can_generate_a_color_value() {
let data = 0x01AB23FE;
let value = Value::create(TOKEN_TYPE_ARGB8, data);
assert_eq!("#01ab23fe", value.unwrap().to_string());
}
#[test]
fn it_can_generate_a_color2_value() {
let data = 0x01AB23FE;
let value = Value::create(TOKEN_TYPE_RGB8, data);
assert_eq!("#01ab23fe", value.unwrap().to_string());
}
#[test]
fn it_can_generate_an_argb4_color_value() {
let data = 0x01AB23FE;
let value = Value::create(TOKEN_TYPE_ARGB4, data);
assert_eq!("#01ab23fe", value.unwrap().to_string());
}
#[test]
fn it_can_generate_a_rgb4_color_value() {
let data = 0x01AB23FE;
let value = Value::create(TOKEN_TYPE_RGB4, data);
assert_eq!("#01ab23fe", value.unwrap().to_string());
}
#[test]
fn it_generated_unknown_values_if_type_is_unkown() {
let data = 0x12345;
let value = Value::create(0x20, data);
assert_eq!("Unknown", value.unwrap().to_string());
}
}