use crate::{Glyph, NormalizedCoord, Paint, PaintRef, PaintScene};
use kurbo::{Affine, BezPath, Rect, Shape, Stroke};
use peniko::{BlendMode, Brush, Color, Fill, FontData, ImageBrush, ImageData, Style, StyleRef};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
const DEFAULT_TOLERANCE: f64 = 0.1;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RenderCommand<Font = FontData, Image = ImageData> {
PushLayer(LayerCommand),
PushClipLayer(ClipCommand),
PopLayer,
Stroke(StrokeCommand<Image>),
Fill(FillCommand<Image>),
GlyphRun(GlyphRunCommand<Font, Image>),
BoxShadow(BoxShadowCommand),
}
impl RenderCommand {
fn apply_transform(mut self, transform: Affine) -> Self {
match &mut self {
RenderCommand::PushLayer(cmd) => cmd.transform = transform * cmd.transform,
RenderCommand::PushClipLayer(cmd) => cmd.transform = transform * cmd.transform,
RenderCommand::PopLayer => {}
RenderCommand::Stroke(cmd) => cmd.transform = transform * cmd.transform,
RenderCommand::Fill(cmd) => cmd.transform = transform * cmd.transform,
RenderCommand::GlyphRun(cmd) => cmd.transform = transform * cmd.transform,
RenderCommand::BoxShadow(cmd) => cmd.transform = transform * cmd.transform,
};
self
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LayerCommand {
pub blend: BlendMode,
pub alpha: f32,
pub transform: Affine,
#[cfg_attr(feature = "serde", serde(with = "svg_path"))]
pub clip: BezPath, }
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ClipCommand {
pub transform: Affine,
#[cfg_attr(feature = "serde", serde(with = "svg_path"))]
pub clip: BezPath, }
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StrokeCommand<Image = ImageData> {
pub style: Stroke,
pub transform: Affine,
pub brush: Brush<ImageBrush<Image>>, pub brush_transform: Option<Affine>,
#[cfg_attr(feature = "serde", serde(with = "svg_path"))]
pub shape: BezPath, }
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FillCommand<Image = ImageData> {
pub fill: Fill,
pub transform: Affine,
pub brush: Brush<ImageBrush<Image>>, pub brush_transform: Option<Affine>,
#[cfg_attr(feature = "serde", serde(with = "svg_path"))]
pub shape: BezPath, }
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GlyphRunCommand<Font = FontData, Image = ImageData> {
pub font_data: Font,
pub font_size: f32,
pub hint: bool,
pub normalized_coords: Vec<NormalizedCoord>,
pub style: Style,
pub brush: Brush<ImageBrush<Image>>,
pub brush_alpha: f32,
pub transform: Affine,
pub glyph_transform: Option<Affine>,
pub glyphs: Vec<Glyph>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BoxShadowCommand {
pub transform: Affine,
pub rect: Rect,
pub brush: Color,
pub radius: f64,
pub std_dev: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Scene {
pub tolerance: f64,
pub commands: Vec<RenderCommand>,
}
impl Default for Scene {
fn default() -> Self {
Self {
tolerance: DEFAULT_TOLERANCE,
commands: Vec::new(),
}
}
}
impl Scene {
pub fn new() -> Self {
Self::default()
}
pub fn with_tolerance(tolerance: f64) -> Self {
Self {
tolerance,
commands: Vec::new(),
}
}
fn convert_paintref(&mut self, paint_ref: PaintRef<'_>) -> Brush {
match paint_ref {
Paint::Solid(color) => Brush::Solid(color),
Paint::Gradient(gradient) => Brush::Gradient(gradient.clone()),
Paint::Image(image) => Brush::Image(image.to_owned()),
Paint::Custom(_) => Brush::Solid(Color::TRANSPARENT),
}
}
}
impl PaintScene for Scene {
fn reset(&mut self) {
self.commands.clear()
}
fn push_layer(
&mut self,
blend: impl Into<BlendMode>,
alpha: f32,
transform: Affine,
clip: &impl Shape,
) {
let blend = blend.into();
let clip = clip.into_path(self.tolerance);
let layer = LayerCommand {
blend,
alpha,
transform,
clip,
};
self.commands.push(RenderCommand::PushLayer(layer));
}
fn push_clip_layer(&mut self, transform: Affine, clip: &impl Shape) {
let clip = clip.into_path(self.tolerance);
let layer = ClipCommand { transform, clip };
self.commands.push(RenderCommand::PushClipLayer(layer));
}
fn pop_layer(&mut self) {
self.commands.push(RenderCommand::PopLayer);
}
fn stroke<'a>(
&mut self,
style: &Stroke,
transform: Affine,
paint_ref: impl Into<PaintRef<'a>>,
brush_transform: Option<Affine>,
shape: &impl Shape,
) {
let shape = shape.into_path(self.tolerance);
let brush = self.convert_paintref(paint_ref.into());
let stroke = StrokeCommand {
style: style.clone(),
transform,
brush,
brush_transform,
shape,
};
self.commands.push(RenderCommand::Stroke(stroke));
}
fn fill<'a>(
&mut self,
style: Fill,
transform: Affine,
paint: impl Into<PaintRef<'a>>,
brush_transform: Option<Affine>,
shape: &impl Shape,
) {
let shape = shape.into_path(self.tolerance);
let brush = self.convert_paintref(paint.into());
let fill = FillCommand {
fill: style,
transform,
brush,
brush_transform,
shape,
};
self.commands.push(RenderCommand::Fill(fill));
}
fn draw_glyphs<'a, 's: 'a>(
&'a mut self,
font: &'a FontData,
font_size: f32,
hint: bool,
normalized_coords: &'a [NormalizedCoord],
style: impl Into<StyleRef<'a>>,
paint_ref: impl Into<PaintRef<'a>>,
brush_alpha: f32,
transform: Affine,
glyph_transform: Option<Affine>,
glyphs: impl Iterator<Item = Glyph>,
) {
let brush = self.convert_paintref(paint_ref.into());
let glyph_run = GlyphRunCommand {
font_data: font.clone(),
font_size,
hint,
normalized_coords: normalized_coords.to_vec(),
style: style.into().to_owned(),
brush,
brush_alpha,
transform,
glyph_transform,
glyphs: glyphs.into_iter().collect(),
};
self.commands.push(RenderCommand::GlyphRun(glyph_run));
}
fn draw_box_shadow(
&mut self,
transform: Affine,
rect: Rect,
brush: Color,
radius: f64,
std_dev: f64,
) {
let box_shadow = BoxShadowCommand {
transform,
rect,
brush,
radius,
std_dev,
};
self.commands.push(RenderCommand::BoxShadow(box_shadow));
}
fn append_scene(&mut self, scene: Scene, scene_transform: Affine) {
self.commands.extend(
scene
.commands
.into_iter()
.map(|cmd| cmd.apply_transform(scene_transform)),
);
}
}
#[cfg(feature = "serde")]
mod svg_path {
use kurbo::BezPath;
use serde::{self, Deserialize, Deserializer, Serializer};
use crate::svg_path_parser;
pub fn serialize<S>(path: &BezPath, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&path.to_svg())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<BezPath, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
svg_path_parser::parse_svg_path(&s).map_err(serde::de::Error::custom)
}
}