use alloc::vec::Vec;
use crate::render::scene::SceneOp;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CodecError {
UnexpectedEof,
BadMagic,
UnknownVersion(u8),
UnknownTag(u8),
CrcMismatch { expected: u32, actual: u32 },
BadFillRule(u8),
BadResourceKind(u8),
BadUtf8,
UnbalancedGroup,
BadSkipOffset,
UnsupportedScale(u8),
UnknownFlags(u8),
BadComposite(u8),
}
impl From<mirx::CodecError> for CodecError {
fn from(e: mirx::CodecError) -> Self {
match e {
mirx::CodecError::UnexpectedEof => Self::UnexpectedEof,
mirx::CodecError::BadMagic => Self::BadMagic,
mirx::CodecError::UnknownVersion(v) => Self::UnknownVersion(v),
mirx::CodecError::UnknownTag(t) => Self::UnknownTag(t),
mirx::CodecError::CrcMismatch { expected, actual } => {
Self::CrcMismatch { expected, actual }
}
mirx::CodecError::BadFillRule(b) => Self::BadFillRule(b),
mirx::CodecError::BadResourceKind(b) => Self::BadResourceKind(b),
mirx::CodecError::BadUtf8 => Self::BadUtf8,
mirx::CodecError::UnbalancedGroup => Self::UnbalancedGroup,
mirx::CodecError::BadSkipOffset => Self::BadSkipOffset,
mirx::CodecError::UnsupportedScale(s) => Self::UnsupportedScale(s),
mirx::CodecError::UnknownFlags(f) => Self::UnknownFlags(f),
mirx::CodecError::BadComposite(b) => Self::BadComposite(b),
}
}
}
pub fn encode_scene(ops: &[SceneOp]) -> Result<Vec<u8>, CodecError> {
let mirx_ops: alloc::vec::Vec<mirx::SceneOp> = ops.iter().cloned().map(Into::into).collect();
mirx::Scene::from_ops(mirx_ops).encode().map_err(Into::into)
}
pub fn decode_scene(payload: &[u8]) -> Result<Vec<SceneOp>, CodecError> {
let scene = mirx::Scene::decode(payload).map_err(CodecError::from)?;
Ok(scene.ops.into_iter().map(Into::into).collect())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::render::command::CompositeMode;
use crate::render::path::Path;
use crate::render::raster::FillRule;
use crate::render::scene::ResourceRef;
use crate::types::{Color, Fixed, Point, Rect, Transform};
use alloc::vec;
fn red() -> Color {
Color::rgb(255, 0, 0)
}
fn roundtrip(ops: vec::Vec<SceneOp>) {
let bytes = encode_scene(&ops).unwrap();
let back = decode_scene(&bytes).unwrap();
assert_eq!(back, ops);
}
#[test]
fn fill_rect_identity_roundtrips() {
roundtrip(vec![SceneOp::FillRect {
area: Rect::new(
Fixed::from_int(1),
Fixed::from_int(2),
Fixed::from_int(3),
Fixed::from_int(4),
),
transform: Transform::IDENTITY,
quad: None,
color: red(),
radius: Fixed::ZERO,
opa: 200,
}]);
}
#[test]
fn golden_bytes_are_stable() {
let ops = vec![
SceneOp::FillRect {
area: Rect::new(
Fixed::from_int(1),
Fixed::from_int(2),
Fixed::from_int(3),
Fixed::from_int(4),
),
transform: Transform::IDENTITY,
quad: None,
color: Color::rgba(0x11, 0x22, 0x33, 0x44),
radius: Fixed::ZERO,
opa: 0xAB,
},
SceneOp::Line {
p1: Point::ZERO,
p2: Point::new(Fixed::from_int(5), Fixed::ZERO),
transform: Transform::IDENTITY,
color: Color::rgba(1, 2, 3, 4),
width: Fixed::from_int(1),
opa: 0xFF,
},
];
let golden: &[u8] = &[
0x03, 0x01, 0x08, 0x00, 0x84, 0xcc, 0x5e, 0xd3, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x11, 0x22,
0x33, 0x44, 0xab, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03,
0x04, 0xff, 0x00,
];
assert_eq!(encode_scene(&ops).unwrap(), golden);
assert_eq!(decode_scene(golden).unwrap(), ops);
}
#[test]
fn fill_path_roundtrips() {
roundtrip(vec![SceneOp::FillPath {
path: Path::from_owned(vec![
crate::render::path::PathCmd::MoveTo(Point::ZERO),
crate::render::path::PathCmd::LineTo(Point::new(Fixed::from_int(4), Fixed::ZERO)),
crate::render::path::PathCmd::QuadTo {
ctrl: Point::new(Fixed::from_int(4), Fixed::from_int(4)),
end: Point::new(Fixed::ZERO, Fixed::from_int(4)),
},
crate::render::path::PathCmd::Close,
]),
transform: Transform::IDENTITY,
color: red(),
opa: 255,
fill_rule: FillRule::NonZero,
}]);
}
#[test]
fn blit_per_composite_mode_roundtrips() {
for m in [
CompositeMode::Add,
CompositeMode::Screen,
CompositeMode::Multiply,
CompositeMode::Darken,
CompositeMode::Lighten,
CompositeMode::Difference,
] {
roundtrip(vec![SceneOp::Blit {
texture: ResourceRef::Index(3),
pos: Point::ZERO,
size: Point::new(Fixed::from_int(8), Fixed::from_int(8)),
transform: Transform::IDENTITY,
quad: None,
opa: 255,
radius: Fixed::ZERO,
composite: m,
}]);
}
}
#[test]
fn corrupt_crc_is_rejected() {
let ops = vec![SceneOp::Line {
p1: Point::ZERO,
p2: Point::ZERO,
transform: Transform::IDENTITY,
color: red(),
width: Fixed::from_int(1),
opa: 255,
}];
let mut bytes = encode_scene(&ops).unwrap();
let last = bytes.len() - 1;
bytes[last] ^= 0xFF;
assert!(matches!(
decode_scene(&bytes),
Err(CodecError::CrcMismatch { .. })
));
}
#[test]
fn unbalanced_group_end_is_rejected_at_encode() {
assert!(matches!(
encode_scene(&[SceneOp::GroupEnd]),
Err(CodecError::UnbalancedGroup)
));
}
}