#![deny(unsafe_code)]
use serde::{Deserialize, Serialize};
pub mod items;
pub mod primitives;
pub mod specs;
pub use items::*;
pub use primitives::*;
pub use specs::*;
pub trait PaintList: Clone + std::fmt::Debug + Serialize + for<'de> Deserialize<'de> {
fn engine_id(&self) -> EngineId;
fn viewport(&self) -> DeviceIntSize;
fn generation_id(&self) -> u64;
fn commands(&self) -> &[PaintCmd];
fn fonts(&self) -> &[FontResource] {
&[]
}
fn images(&self) -> &[ImageResource] {
&[]
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FontResource {
pub key: FontInstanceKey,
pub data: std::sync::Arc<Vec<u8>>,
pub index: u32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ImageResource {
pub key: ImageKey,
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct EngineId(pub u32);
impl EngineId {
pub const GENET: Self = Self(0);
pub const NEMATIC: Self = Self(1);
pub const SCRYING: Self = Self(2);
pub const INKER: Self = Self(3);
pub const UNASSIGNED: Self = Self(u32::MAX);
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum PaintCmd {
PushClip(ClipSpec),
PopClip,
PushTransform(TransformSpec),
PopTransform,
PushLayer(LayerSpec),
PopLayer,
DrawRect(RectItem),
DrawStroke(StrokeItem),
DrawLine(LineItem),
DrawPath(PathItem),
DrawBorder(BorderItem),
DrawLinearGradient(LinearGradientItem),
DrawRadialGradient(RadialGradientItem),
DrawConicGradient(ConicGradientItem),
DrawText(TextRunItem),
DrawImage(ImageItem),
DrawRepeatingImage(RepeatingImageItem),
DrawExternalTexture(ExternalTextureItem),
DrawShadow(ShadowItem),
PushShadow(ShadowSpec),
PopAllShadows,
HitTest(HitTestItem),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PaintEnvelope {
pub engine: EngineId,
pub viewport: DeviceIntSize,
pub generation: u64,
pub commands: Vec<PaintCmd>,
pub fonts: Vec<FontResource>,
pub images: Vec<ImageResource>,
}
impl PaintEnvelope {
pub fn from_list<L: PaintList>(list: &L) -> Self {
Self {
engine: list.engine_id(),
viewport: list.viewport(),
generation: list.generation_id(),
commands: list.commands().to_vec(),
fonts: list.fonts().to_vec(),
images: list.images().to_vec(),
}
}
}
impl PaintList for PaintEnvelope {
fn engine_id(&self) -> EngineId {
self.engine
}
fn viewport(&self) -> DeviceIntSize {
self.viewport
}
fn generation_id(&self) -> u64 {
self.generation
}
fn commands(&self) -> &[PaintCmd] {
&self.commands
}
fn fonts(&self) -> &[FontResource] {
&self.fonts
}
fn images(&self) -> &[ImageResource] {
&self.images
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct PrimitiveFlags(pub u32);
impl PrimitiveFlags {
pub const HIT_TESTABLE: Self = Self(1 << 0);
pub const IS_BACKFACE: Self = Self(1 << 1);
pub const ANTIALIASED: Self = Self(1 << 2);
pub const fn empty() -> Self {
Self(0)
}
pub fn contains(&self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
}
impl std::ops::BitOr for PrimitiveFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for PrimitiveFlags {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct CommonPlacement {
pub bounds: LayoutRect,
pub flags: PrimitiveFlags,
}
impl CommonPlacement {
pub fn new(bounds: LayoutRect) -> Self {
Self {
bounds,
flags: PrimitiveFlags::empty(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
struct StubPaintList {
viewport: DeviceIntSize,
commands: Vec<PaintCmd>,
generation: u64,
}
impl PaintList for StubPaintList {
fn engine_id(&self) -> EngineId {
EngineId::UNASSIGNED
}
fn viewport(&self) -> DeviceIntSize {
self.viewport
}
fn generation_id(&self) -> u64 {
self.generation
}
fn commands(&self) -> &[PaintCmd] {
&self.commands
}
}
fn box2d(x: f32, y: f32, w: f32, h: f32) -> LayoutRect {
LayoutRect::new(LayoutPoint::new(x, y), LayoutPoint::new(x + w, y + h))
}
#[test]
fn primitive_flags_or_combines() {
let f = PrimitiveFlags::HIT_TESTABLE | PrimitiveFlags::ANTIALIASED;
assert!(f.contains(PrimitiveFlags::HIT_TESTABLE));
assert!(f.contains(PrimitiveFlags::ANTIALIASED));
assert!(!f.contains(PrimitiveFlags::IS_BACKFACE));
}
#[test]
fn stub_paint_list_satisfies_trait_bounds() {
fn assert_paint_list<L: PaintList>(_: &L) {}
let list = StubPaintList::default();
assert_paint_list(&list);
}
#[test]
fn paint_cmd_round_trips_through_json() {
let cmd = PaintCmd::DrawRect(RectItem {
placement: CommonPlacement::new(box2d(0.0, 0.0, 100.0, 50.0)),
color: ColorF::default(),
});
let serialized = serde_json::to_string(&cmd).expect("serialize");
let parsed: PaintCmd = serde_json::from_str(&serialized).expect("deserialize");
match parsed {
PaintCmd::DrawRect(_) => {}
other => panic!("round-trip lost variant: {other:?}"),
}
}
#[test]
fn external_texture_content_generation_defaults_none() {
let item = ExternalTextureItem {
placement: CommonPlacement::new(box2d(0.0, 0.0, 200.0, 200.0)),
texture_key: 0xDEADBEEF,
opacity: 1.0,
content_generation: None,
};
assert_eq!(item.content_generation, None);
}
#[test]
fn paint_envelope_preserves_list_fields() {
let viewport = DeviceIntSize::new(800, 600);
let stub = StubPaintList {
viewport,
commands: vec![
PaintCmd::DrawRect(RectItem {
placement: CommonPlacement::new(box2d(0.0, 0.0, 100.0, 50.0)),
color: ColorF::default(),
}),
PaintCmd::PopLayer,
],
generation: 42,
};
let envelope = PaintEnvelope::from_list(&stub);
assert_eq!(envelope.engine_id(), EngineId::UNASSIGNED);
assert_eq!(envelope.viewport(), viewport);
assert_eq!(envelope.generation_id(), 42);
assert_eq!(envelope.commands().len(), 2);
}
#[test]
fn paint_envelope_round_trips_through_serde() {
let envelope = PaintEnvelope {
engine: EngineId::GENET,
viewport: DeviceIntSize::new(1024, 768),
generation: 7,
commands: vec![PaintCmd::PopLayer],
fonts: Vec::new(),
images: Vec::new(),
};
let json = serde_json::to_string(&envelope).expect("serialize");
let parsed: PaintEnvelope = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.engine, EngineId::GENET);
assert_eq!(parsed.viewport, envelope.viewport);
assert_eq!(parsed.generation, 7);
assert_eq!(parsed.commands.len(), 1);
}
#[test]
fn engine_id_sentinels_are_stable() {
assert_eq!(EngineId::GENET.0, 0);
assert_eq!(EngineId::NEMATIC.0, 1);
assert_eq!(EngineId::SCRYING.0, 2);
assert_eq!(EngineId::INKER.0, 3);
}
}