1use euclid::default::Point2D;
2use euclid::Trig;
3use num_traits::{Float, FromPrimitive};
4use palette::Srgba;
5use rand::random;
6use std::fmt;
7
8pub struct Space;
9
10pub struct Config {
11 #[allow(dead_code)]
12 options: Option<Options>,
13}
14
15pub struct DrawingSurface {
16 #[allow(dead_code)]
17 width: f32,
18 #[allow(dead_code)]
19 height: f32,
20}
21
22#[derive(Clone, PartialEq, Debug, Copy, Eq)]
23pub enum FillStyle {
24 Solid,
25 Hachure,
26 ZigZag,
27 CrossHatch,
28 Dots,
29 Dashed,
30 ZigZagLine,
31}
32
33impl fmt::Display for FillStyle {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 let s = match self {
36 FillStyle::Solid => "Solid",
37 FillStyle::Hachure => "Hachure",
38 FillStyle::ZigZag => "ZigZag",
39 FillStyle::CrossHatch => "CrossHatch",
40 FillStyle::Dots => "Dots",
41 FillStyle::Dashed => "Dashed",
42 FillStyle::ZigZagLine => "ZigZagLine",
43 };
44 f.write_str(s)
45 }
46}
47
48#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
49pub enum LineCap {
50 #[default]
51 Butt,
52 Round,
53 Square,
54}
55
56#[derive(Clone, Copy, PartialEq, Debug)]
58pub enum LineJoin {
59 Miter { limit: f64 },
60 Round,
61 Bevel,
62}
63impl LineJoin {
64 pub const DEFAULT_MITER_LIMIT: f64 = 10.0;
65}
66impl Default for LineJoin {
67 fn default() -> Self {
68 LineJoin::Miter {
69 limit: LineJoin::DEFAULT_MITER_LIMIT,
70 }
71 }
72}
73
74#[derive(Clone, Builder)]
75#[builder(setter(strip_option))]
76pub struct Options {
77 #[builder(default = "Some(2.0)")]
78 pub max_randomness_offset: Option<f32>,
79 #[builder(default = "Some(1.0)")]
80 pub roughness: Option<f32>,
81 #[builder(default = "Some(2.0)")]
82 pub bowing: Option<f32>,
83 #[builder(default = "Some(Srgba::new(0.0, 0.0, 0.0, 1.0))")]
84 pub stroke: Option<Srgba>,
85 #[builder(default = "Some(1.0)")]
86 pub stroke_width: Option<f32>,
87 #[builder(default = "Some(0.95)")]
88 pub curve_fitting: Option<f32>,
89 #[builder(default = "Some(0.0)")]
90 pub curve_tightness: Option<f32>,
91 #[builder(default = "Some(9.0)")]
92 pub curve_step_count: Option<f32>,
93 #[builder(default = "None")]
94 pub fill: Option<Srgba>,
95 #[builder(default = "None")]
96 pub fill_style: Option<FillStyle>,
97 #[builder(default = "Some(-1.0)")]
98 pub fill_weight: Option<f32>,
99 #[builder(default = "Some(-41.0)")]
100 pub hachure_angle: Option<f32>,
101 #[builder(default = "Some(-1.0)")]
102 pub hachure_gap: Option<f32>,
103 #[builder(default = "Some(1.0)")]
104 pub simplification: Option<f32>,
105 #[builder(default = "Some(-1.0)")]
106 pub dash_offset: Option<f32>,
107 #[builder(default = "Some(-1.0)")]
108 pub dash_gap: Option<f32>,
109 #[builder(default = "Some(-1.0)")]
110 pub zigzag_offset: Option<f32>,
111 #[builder(default = "Some(0_u64)")]
112 pub seed: Option<u64>,
113 #[builder(default = "None")]
114 pub stroke_line_dash: Option<Vec<f64>>,
115 #[builder(default = "None")]
116 pub stroke_line_dash_offset: Option<f64>,
117 #[builder(default = "None")]
118 pub line_cap: Option<LineCap>,
119 #[builder(default = "None")]
120 pub line_join: Option<LineJoin>,
121 #[builder(default = "None")]
122 pub fill_line_dash: Option<Vec<f64>>,
123 #[builder(default = "None")]
124 pub fill_line_dash_offset: Option<f64>,
125 #[builder(default = "Some(false)")]
126 pub disable_multi_stroke: Option<bool>,
127 #[builder(default = "Some(false)")]
128 pub disable_multi_stroke_fill: Option<bool>,
129 #[builder(default = "Some(false)")]
130 pub preserve_vertices: Option<bool>,
131 #[builder(default = "None")]
132 pub fixed_decimal_place_digits: Option<f32>,
133 #[builder(default = "None", setter(skip))]
136 pub(crate) randomizer: Option<i32>,
137}
138
139impl Default for Options {
140 fn default() -> Self {
141 Options {
142 max_randomness_offset: Some(2.0),
143 roughness: Some(1.0),
144 bowing: Some(1.0),
145 stroke: Some(Srgba::new(0.0, 0.0, 0.0, 1.0)),
146 stroke_width: Some(1.0),
147 curve_tightness: Some(0.0),
148 curve_fitting: Some(0.95),
149 curve_step_count: Some(9.0),
150 fill: None,
151 fill_style: None,
152 fill_weight: Some(-1.0),
153 hachure_angle: Some(-41.0),
154 hachure_gap: Some(-1.0),
155 dash_offset: Some(-1.0),
156 dash_gap: Some(-1.0),
157 zigzag_offset: Some(-1.0),
158 seed: Some(0_u64),
159 disable_multi_stroke: Some(false),
160 disable_multi_stroke_fill: Some(false),
161 preserve_vertices: Some(false),
162 simplification: Some(1.0),
163 stroke_line_dash: None,
164 stroke_line_dash_offset: None,
165 line_cap: None,
166 line_join: None,
167 fill_line_dash: None,
168 fill_line_dash_offset: None,
169 fixed_decimal_place_digits: None,
170 randomizer: None,
171 }
172 }
173}
174
175impl Options {
176 pub fn random(&mut self) -> f64 {
177 if self.randomizer.is_none() {
184 let seed_bits = self.seed.unwrap_or(0) as u32;
185 self.randomizer = Some(seed_bits as i32);
186 }
187
188 let state = self.randomizer.unwrap_or(0);
189 if state != 0 {
190 let next = state.wrapping_mul(48271);
198 self.randomizer = Some(next);
199 let out = next & 0x7fffffff;
200 return (out as f64) / 2147483648.0;
201 }
202
203 random::<f64>()
204 }
205
206 pub fn set_hachure_angle(&mut self, angle: Option<f32>) -> &mut Self {
207 self.hachure_angle = angle;
208 self
209 }
210
211 pub fn set_hachure_gap(&mut self, gap: Option<f32>) -> &mut Self {
212 self.hachure_gap = gap;
213 self
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::{Options, OptionsBuilder};
220
221 #[test]
222 fn roughjs_random_seed_1_matches_known_sequence() {
223 let denom = 2147483648.0_f64; let expected_out: [u32; 10] = [
226 48_271,
227 182_605_793,
228 1_291_342_511,
229 1_533_981_633,
230 1_591_223_503,
231 902_075_297,
232 1_698_214_639,
233 773_027_713,
234 144_866_575,
235 647_683_937,
236 ];
237 let expected: Vec<f64> = expected_out.iter().map(|&n| (n as f64) / denom).collect();
238
239 let mut opts: Options = OptionsBuilder::default().seed(1_u64).build().unwrap();
240 let got: Vec<f64> = (0..expected.len()).map(|_| opts.random()).collect();
241
242 assert_eq!(got, expected);
243 }
244}
245
246#[derive(Clone, PartialEq, Debug, Eq)]
247pub enum OpType {
248 Move,
249 BCurveTo,
250 LineTo,
251}
252
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub enum OpSetType {
255 Path,
256 FillPath,
257 FillSketch,
258}
259
260#[derive(Clone, Debug, PartialEq, Eq)]
261pub struct Op<F: Float + Trig> {
262 pub op: OpType,
263 pub data: Vec<F>,
264}
265
266#[derive(Clone, Debug, PartialEq, Eq)]
267pub struct OpSet<F: Float + Trig> {
268 pub op_set_type: OpSetType,
269 pub ops: Vec<Op<F>>,
270 pub size: Option<Point2D<F>>,
271 pub path: Option<String>,
272}
273
274pub struct Drawable<F: Float + Trig> {
275 pub shape: String,
276 pub options: Options,
277 pub sets: Vec<OpSet<F>>,
278}
279
280pub struct PathInfo {
281 pub d: String,
282 pub stroke: Option<Srgba>,
283 pub stroke_width: Option<f32>,
284 pub fill: Option<Srgba>,
285}
286
287pub fn _c<U: Float + FromPrimitive>(inp: f32) -> U {
288 U::from(inp).expect("can not parse from f32")
289}
290
291pub fn _cc<U: Float + FromPrimitive>(inp: f64) -> U {
292 U::from(inp).expect("can not parse from f64")
293}