ass_renderer/pipeline/drawing/
mod.rs1mod parse;
4mod spline;
5
6pub use parse::parse_draw_commands;
7use spline::spline_to_bezier;
8
9use crate::utils::RenderError;
10use tiny_skia::{Path, PathBuilder};
11
12#[cfg(feature = "nostd")]
13use alloc::vec::Vec;
14#[cfg(not(feature = "nostd"))]
15use std::vec::Vec;
16
17#[derive(Debug, Clone)]
19pub enum DrawCommand {
20 MoveTo { x: f32, y: f32 },
22 MoveToNoDraw { x: f32, y: f32 },
24 LineTo { x: f32, y: f32 },
26 BezierTo {
28 x1: f32,
29 y1: f32,
30 x2: f32,
31 y2: f32,
32 x3: f32,
33 y3: f32,
34 },
35 Spline { points: Vec<(f32, f32)> },
37 ExtendSpline { points: Vec<(f32, f32)> },
39 ClosePath,
41}
42
43pub fn process_drawing_commands(commands: &str) -> Result<Option<Path>, RenderError> {
45 let draw_commands = match parse_draw_commands(commands) {
47 Ok(commands) => commands,
48 Err(_) => return Ok(None), };
50 if draw_commands.is_empty() {
51 return Ok(None);
52 }
53
54 let mut builder = PathBuilder::new();
55 let mut _current_pos = (0.0, 0.0);
56
57 for cmd in draw_commands {
58 match cmd {
59 DrawCommand::MoveTo { x, y } => {
60 builder.move_to(x, y);
61 _current_pos = (x, y);
62 }
63 DrawCommand::MoveToNoDraw { x, y } => {
64 builder.move_to(x, y);
65 _current_pos = (x, y);
66 }
67 DrawCommand::LineTo { x, y } => {
68 builder.line_to(x, y);
69 _current_pos = (x, y);
70 }
71 DrawCommand::BezierTo {
72 x1,
73 y1,
74 x2,
75 y2,
76 x3,
77 y3,
78 } => {
79 builder.cubic_to(x1, y1, x2, y2, x3, y3);
80 _current_pos = (x3, y3);
81 }
82 DrawCommand::Spline { ref points } => {
83 if points.len() >= 3 {
85 let beziers = spline_to_bezier(points, false);
86 for (c1, c2, end) in beziers {
87 builder.cubic_to(c1.0, c1.1, c2.0, c2.1, end.0, end.1);
88 _current_pos = end;
89 }
90 }
91 }
92 DrawCommand::ExtendSpline { ref points } => {
93 if points.len() >= 3 {
95 let beziers = spline_to_bezier(points, true);
96 for (c1, c2, end) in beziers {
97 builder.cubic_to(c1.0, c1.1, c2.0, c2.1, end.0, end.1);
98 _current_pos = end;
99 }
100 }
101 }
102 DrawCommand::ClosePath => {
103 builder.close();
104 }
105 }
106 }
107
108 Ok(builder.finish())
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn plain_drawing_parses() {
117 let path = process_drawing_commands("m 0 0 l 100 0 l 100 100 l 0 100")
118 .expect("ok")
119 .expect("some path");
120 let b = path.bounds();
121 assert!(b.width() > 50.0 && b.height() > 50.0);
122 }
123
124 #[test]
125 fn trailing_tag_does_not_discard_the_shape() {
126 let path = process_drawing_commands("m 0 0 l 100 0 l 100 100 l 0 100\\p0}")
131 .expect("ok")
132 .expect("some path");
133 let b = path.bounds();
134 assert!(
135 b.width() > 50.0 && b.height() > 50.0,
136 "shape with a trailing \\p0 tag was discarded: bounds {b:?}"
137 );
138
139 let clean = process_drawing_commands("m 0 0 l 100 0 l 100 100 l 0 100")
141 .expect("ok")
142 .expect("some");
143 assert_eq!(path.len(), clean.len());
144 }
145}