use super::node::{
Align, AttrValue, BorderStyle, ContentAlign, Dim, Display, FlexDir, FlexWrap, Kind, Lp,
Overflow, Position, Style, TextStyle, TextWrap,
};
use super::op::Op;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError {
UnexpectedEof,
UnknownOpcode(u8),
UnknownTag(u8),
UnknownFieldId(u8),
InvalidBool(u8),
InvalidUtf8,
}
impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DecodeError::UnexpectedEof => write!(f, "unexpected end of op buffer"),
DecodeError::UnknownOpcode(b) => write!(f, "unknown opcode 0x{b:02X}"),
DecodeError::UnknownTag(b) => write!(f, "unknown enum tag 0x{b:02X}"),
DecodeError::UnknownFieldId(b) => write!(f, "unknown style field id {b}"),
DecodeError::InvalidBool(b) => write!(f, "invalid bool byte 0x{b:02X}"),
DecodeError::InvalidUtf8 => write!(f, "invalid utf-8 in length-prefixed string"),
}
}
}
impl std::error::Error for DecodeError {}
type Result<T> = core::result::Result<T, DecodeError>;
struct Reader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn at_end(&self) -> bool {
self.pos >= self.buf.len()
}
fn take(&mut self, n: usize) -> Result<&'a [u8]> {
let end = self.pos.checked_add(n).ok_or(DecodeError::UnexpectedEof)?;
let slice = self
.buf
.get(self.pos..end)
.ok_or(DecodeError::UnexpectedEof)?;
self.pos = end;
Ok(slice)
}
fn u8(&mut self) -> Result<u8> {
Ok(self.take(1)?[0])
}
fn u32(&mut self) -> Result<u32> {
let b = self.take(4)?;
Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn f32(&mut self) -> Result<f32> {
let b = self.take(4)?;
Ok(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn f64(&mut self) -> Result<f64> {
let b = self.take(8)?;
Ok(f64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn bool(&mut self) -> Result<bool> {
match self.u8()? {
0 => Ok(false),
1 => Ok(true),
other => Err(DecodeError::InvalidBool(other)),
}
}
fn string(&mut self) -> Result<String> {
let len = self.u32()? as usize;
let bytes = self.take(len)?;
core::str::from_utf8(bytes)
.map(str::to_owned)
.map_err(|_| DecodeError::InvalidUtf8)
}
}
pub fn decode_ops(buf: &[u8]) -> Result<Vec<Op>> {
let mut r = Reader::new(buf);
let mut ops = Vec::new();
while !r.at_end() {
ops.push(decode_op(&mut r)?);
}
Ok(ops)
}
fn decode_op(r: &mut Reader<'_>) -> Result<Op> {
let opcode = r.u8()?;
let op = match opcode {
0x00 => Op::Create {
id: r.u32()?,
kind: decode_kind(r)?,
},
0x01 => Op::AppendChild {
parent: r.u32()?,
child: r.u32()?,
},
0x02 => Op::InsertBefore {
parent: r.u32()?,
child: r.u32()?,
before: r.u32()?,
},
0x03 => Op::RemoveChild {
parent: r.u32()?,
child: r.u32()?,
},
0x04 => Op::SetText {
id: r.u32()?,
text: r.string()?,
},
0x05 => Op::SetStyle {
id: r.u32()?,
style: Box::new(decode_style(r)?),
},
0x06 => Op::SetAttribute {
id: r.u32()?,
key: r.string()?,
value: decode_attr_value(r)?,
},
0x07 => Op::SetTransform {
id: r.u32()?,
has: r.bool()?,
},
0x08 => Op::SetStatic {
id: r.u32()?,
value: r.bool()?,
},
0x09 => Op::Hide { id: r.u32()? },
0x0A => Op::Unhide { id: r.u32()? },
0x0B => Op::Free { id: r.u32()? },
0x0C => Op::SetTextStyle {
id: r.u32()?,
style: decode_text_style(r)?,
},
0x0D => Op::ClearTextStyle { id: r.u32()? },
other => return Err(DecodeError::UnknownOpcode(other)),
};
Ok(op)
}
fn decode_kind(r: &mut Reader<'_>) -> Result<Kind> {
match r.u8()? {
0x00 => Ok(Kind::Root),
0x01 => Ok(Kind::Box),
0x02 => Ok(Kind::Text),
0x03 => Ok(Kind::VirtualText),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_attr_value(r: &mut Reader<'_>) -> Result<AttrValue> {
match r.u8()? {
0x00 => Ok(AttrValue::Bool(r.bool()?)),
0x01 => Ok(AttrValue::Str(r.string()?)),
0x02 => Ok(AttrValue::Number(r.f64()?)),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_dim(r: &mut Reader<'_>) -> Result<Dim> {
match r.u8()? {
0x00 => Ok(Dim::Points(r.f32()?)),
0x01 => Ok(Dim::Percent(r.f32()?)),
0x02 => Ok(Dim::Auto),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_lp(r: &mut Reader<'_>) -> Result<Lp> {
match r.u8()? {
0x00 => Ok(Lp::Points(r.f32()?)),
0x01 => Ok(Lp::Percent(r.f32()?)),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_position(r: &mut Reader<'_>) -> Result<Position> {
match r.u8()? {
0x00 => Ok(Position::Relative),
0x01 => Ok(Position::Absolute),
0x02 => Ok(Position::Static),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_flex_dir(r: &mut Reader<'_>) -> Result<FlexDir> {
match r.u8()? {
0x00 => Ok(FlexDir::Row),
0x01 => Ok(FlexDir::Column),
0x02 => Ok(FlexDir::RowReverse),
0x03 => Ok(FlexDir::ColumnReverse),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_flex_wrap(r: &mut Reader<'_>) -> Result<FlexWrap> {
match r.u8()? {
0x00 => Ok(FlexWrap::NoWrap),
0x01 => Ok(FlexWrap::Wrap),
0x02 => Ok(FlexWrap::WrapReverse),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_align(r: &mut Reader<'_>) -> Result<Align> {
match r.u8()? {
0x00 => Ok(Align::Stretch),
0x01 => Ok(Align::FlexStart),
0x02 => Ok(Align::Center),
0x03 => Ok(Align::FlexEnd),
0x04 => Ok(Align::Baseline),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_content_align(r: &mut Reader<'_>) -> Result<ContentAlign> {
match r.u8()? {
0x00 => Ok(ContentAlign::FlexStart),
0x01 => Ok(ContentAlign::Center),
0x02 => Ok(ContentAlign::FlexEnd),
0x03 => Ok(ContentAlign::SpaceBetween),
0x04 => Ok(ContentAlign::SpaceAround),
0x05 => Ok(ContentAlign::SpaceEvenly),
0x06 => Ok(ContentAlign::Stretch),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_display(r: &mut Reader<'_>) -> Result<Display> {
match r.u8()? {
0x00 => Ok(Display::Flex),
0x01 => Ok(Display::None),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_text_wrap(r: &mut Reader<'_>) -> Result<TextWrap> {
match r.u8()? {
0x00 => Ok(TextWrap::Wrap),
0x01 => Ok(TextWrap::Hard),
0x02 => Ok(TextWrap::TruncateEnd),
0x03 => Ok(TextWrap::TruncateMiddle),
0x04 => Ok(TextWrap::TruncateStart),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_overflow(r: &mut Reader<'_>) -> Result<Overflow> {
match r.u8()? {
0x00 => Ok(Overflow::Visible),
0x01 => Ok(Overflow::Hidden),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_border_style(r: &mut Reader<'_>) -> Result<BorderStyle> {
match r.u8()? {
0x00 => Ok(BorderStyle::Named(r.string()?)),
0x01 => Ok(BorderStyle::Custom {
top_left: r.string()?,
top: r.string()?,
top_right: r.string()?,
right: r.string()?,
bottom_right: r.string()?,
bottom: r.string()?,
bottom_left: r.string()?,
left: r.string()?,
}),
other => Err(DecodeError::UnknownTag(other)),
}
}
fn decode_style(r: &mut Reader<'_>) -> Result<Style> {
let mut style = Style::default();
let field_count = r.u32()?;
for _ in 0..field_count {
let field_id = r.u8()?;
match field_id {
0 => style.position = Some(decode_position(r)?),
1 => style.top = Some(decode_dim(r)?),
2 => style.right = Some(decode_dim(r)?),
3 => style.bottom = Some(decode_dim(r)?),
4 => style.left = Some(decode_dim(r)?),
5 => style.margin = Some(decode_lp(r)?),
6 => style.margin_x = Some(decode_lp(r)?),
7 => style.margin_y = Some(decode_lp(r)?),
8 => style.margin_top = Some(decode_lp(r)?),
9 => style.margin_right = Some(decode_lp(r)?),
10 => style.margin_bottom = Some(decode_lp(r)?),
11 => style.margin_left = Some(decode_lp(r)?),
12 => style.padding = Some(decode_lp(r)?),
13 => style.padding_x = Some(decode_lp(r)?),
14 => style.padding_y = Some(decode_lp(r)?),
15 => style.padding_top = Some(decode_lp(r)?),
16 => style.padding_right = Some(decode_lp(r)?),
17 => style.padding_bottom = Some(decode_lp(r)?),
18 => style.padding_left = Some(decode_lp(r)?),
19 => style.flex_direction = Some(decode_flex_dir(r)?),
20 => style.flex_wrap = Some(decode_flex_wrap(r)?),
21 => style.flex_grow = Some(r.f32()?),
22 => style.flex_shrink = Some(r.f32()?),
23 => style.flex_basis = Some(decode_dim(r)?),
24 => style.align_items = Some(decode_align(r)?),
25 => style.align_self = Some(decode_align(r)?),
26 => style.align_content = Some(decode_content_align(r)?),
27 => style.justify_content = Some(decode_content_align(r)?),
28 => style.width = Some(decode_dim(r)?),
29 => style.height = Some(decode_dim(r)?),
30 => style.min_width = Some(decode_dim(r)?),
31 => style.min_height = Some(decode_dim(r)?),
32 => style.max_width = Some(decode_dim(r)?),
33 => style.max_height = Some(decode_dim(r)?),
34 => style.aspect_ratio = Some(r.f32()?),
35 => style.display = Some(decode_display(r)?),
36 => style.border_style = Some(decode_border_style(r)?),
37 => style.border_top = Some(r.bool()?),
38 => style.border_right = Some(r.bool()?),
39 => style.border_bottom = Some(r.bool()?),
40 => style.border_left = Some(r.bool()?),
41 => style.gap = Some(r.f32()?),
42 => style.column_gap = Some(r.f32()?),
43 => style.row_gap = Some(r.f32()?),
44 => style.text_wrap = Some(decode_text_wrap(r)?),
45 => style.overflow_x = Some(decode_overflow(r)?),
46 => style.overflow_y = Some(decode_overflow(r)?),
47 => style.background_color = Some(r.string()?),
48 => style.border_color = Some(r.string()?),
49 => style.border_top_color = Some(r.string()?),
50 => style.border_right_color = Some(r.string()?),
51 => style.border_bottom_color = Some(r.string()?),
52 => style.border_left_color = Some(r.string()?),
53 => style.border_background_color = Some(r.string()?),
54 => style.border_top_background_color = Some(r.string()?),
55 => style.border_right_background_color = Some(r.string()?),
56 => style.border_bottom_background_color = Some(r.string()?),
57 => style.border_left_background_color = Some(r.string()?),
58 => style.border_dim_color = Some(r.bool()?),
59 => style.border_top_dim_color = Some(r.bool()?),
60 => style.border_right_dim_color = Some(r.bool()?),
61 => style.border_bottom_dim_color = Some(r.bool()?),
62 => style.border_left_dim_color = Some(r.bool()?),
other => return Err(DecodeError::UnknownFieldId(other)),
}
}
Ok(style)
}
fn decode_text_style(r: &mut Reader<'_>) -> Result<TextStyle> {
let mut style = TextStyle::default();
let field_count = r.u32()?;
for _ in 0..field_count {
let field_id = r.u8()?;
match field_id {
0 => style.color = Some(r.string()?),
1 => style.background_color = Some(r.string()?),
2 => style.bold = r.bool()?,
3 => style.italic = r.bool()?,
4 => style.underline = r.bool()?,
5 => style.strikethrough = r.bool()?,
6 => style.inverse = r.bool()?,
7 => style.dim_color = r.bool()?,
other => return Err(DecodeError::UnknownFieldId(other)),
}
}
Ok(style)
}
#[cfg(test)]
#[path = "decode_tests.rs"]
mod decode_tests;