diff options
author | Minijackson <minijackson@riseup.net> | 2022-12-22 12:19:59 +0100 |
---|---|---|
committer | Minijackson <minijackson@riseup.net> | 2022-12-22 12:19:59 +0100 |
commit | 92a02c34628343153b33602eae00cef46e28d191 (patch) | |
tree | 8622ec528d24e456be22d984d93aa9bcafc97399 /core | |
download | diaphragm-92a02c34628343153b33602eae00cef46e28d191.tar.gz diaphragm-92a02c34628343153b33602eae00cef46e28d191.zip |
WIP
Diffstat (limited to 'core')
-rw-r--r-- | core/Cargo.toml | 10 | ||||
-rw-r--r-- | core/src/colors.rs | 34 | ||||
-rw-r--r-- | core/src/complex_shapes.rs | 285 | ||||
-rw-r--r-- | core/src/core_shapes.rs | 86 | ||||
-rw-r--r-- | core/src/lib.rs | 19 | ||||
-rw-r--r-- | core/src/mod.rs | 90 | ||||
-rw-r--r-- | core/src/rendering.rs | 86 | ||||
-rw-r--r-- | core/src/runtime.rs | 95 | ||||
-rw-r--r-- | core/src/solving.rs | 279 | ||||
-rw-r--r-- | core/src/styles.rs | 161 | ||||
-rw-r--r-- | core/src/text.rs | 81 | ||||
-rw-r--r-- | core/src/types.rs | 316 |
12 files changed, 1542 insertions, 0 deletions
diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000..37fb207 --- /dev/null +++ b/core/Cargo.toml | |||
@@ -0,0 +1,10 @@ | |||
1 | [package] | ||
2 | name = "diaphragm-core" | ||
3 | version = "0.1.0" | ||
4 | authors = ["Minijackson <minijackson@riseup.net>"] | ||
5 | edition = "2021" | ||
6 | |||
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
8 | |||
9 | [dependencies] | ||
10 | palette = "0.5" | ||
diff --git a/core/src/colors.rs b/core/src/colors.rs new file mode 100644 index 0000000..ba78fa3 --- /dev/null +++ b/core/src/colors.rs | |||
@@ -0,0 +1,34 @@ | |||
1 | type Srgba = palette::Srgba<f64>; | ||
2 | |||
3 | #[derive(Copy, Clone, PartialEq, Debug, Default)] | ||
4 | pub struct Color(Srgba); | ||
5 | |||
6 | impl Color { | ||
7 | pub fn transparent() -> Self { | ||
8 | Color(Srgba::new(0., 0., 0., 0.)) | ||
9 | } | ||
10 | |||
11 | pub fn white() -> Self { | ||
12 | Color(Srgba::new(1., 1., 1., 1.)) | ||
13 | } | ||
14 | |||
15 | pub fn black() -> Self { | ||
16 | Color(Srgba::new(0., 0., 0., 1.)) | ||
17 | } | ||
18 | |||
19 | pub fn from_rgb(r: f64, g: f64, b: f64) -> Self { | ||
20 | Color(Srgba::new(r, g, b, 1.0)) | ||
21 | } | ||
22 | |||
23 | pub fn from_rgba(r: f64, g: f64, b: f64, a: f64) -> Self { | ||
24 | Color(Srgba::new(r, g, b, a)) | ||
25 | } | ||
26 | |||
27 | pub fn rgb(&self) -> (f64, f64, f64) { | ||
28 | (self.0.red, self.0.green, self.0.blue) | ||
29 | } | ||
30 | |||
31 | pub fn rgba(&self) -> (f64, f64, f64, f64) { | ||
32 | (self.0.red, self.0.green, self.0.blue, self.0.alpha) | ||
33 | } | ||
34 | } | ||
diff --git a/core/src/complex_shapes.rs b/core/src/complex_shapes.rs new file mode 100644 index 0000000..e23881d --- /dev/null +++ b/core/src/complex_shapes.rs | |||
@@ -0,0 +1,285 @@ | |||
1 | use super::core_shapes::CoreShape; | ||
2 | use super::solving::SolverContext; | ||
3 | use super::styles::{FillStyle, StrokeStyle}; | ||
4 | use super::types::{Bounds, ShapeContext, ShapeContextBuilder}; | ||
5 | |||
6 | pub trait ComplexShape: DynClone { | ||
7 | fn as_core_shape(&self) -> Option<&dyn CoreShape> { | ||
8 | None | ||
9 | } | ||
10 | |||
11 | fn draw(&self, context: &ShapeContext, solver: &mut dyn SolverContext) -> DrawResult; | ||
12 | } | ||
13 | |||
14 | pub trait DynClone { | ||
15 | fn dyn_clone(&self) -> Box<dyn ComplexShape>; | ||
16 | } | ||
17 | |||
18 | impl<T: Clone + ComplexShape + 'static> DynClone for T { | ||
19 | fn dyn_clone(&self) -> Box<dyn ComplexShape> { | ||
20 | Box::new(self.clone()) | ||
21 | } | ||
22 | } | ||
23 | |||
24 | #[derive(Debug, Clone)] | ||
25 | pub struct Drawable<T: ComplexShape> { | ||
26 | pub(crate) context: ShapeContext, | ||
27 | pub(crate) shape: T, | ||
28 | } | ||
29 | |||
30 | impl<T: ComplexShape> Drawable<T> { | ||
31 | pub fn new(shape: T, context: ShapeContext) -> Self { | ||
32 | Self { context, shape } | ||
33 | } | ||
34 | |||
35 | pub fn from_shape(shape: T, solver: &mut dyn SolverContext) -> Self { | ||
36 | Self { | ||
37 | context: ShapeContext::builder().build(solver), | ||
38 | shape, | ||
39 | } | ||
40 | } | ||
41 | |||
42 | pub fn builder(shape: T) -> DrawableBuilder<T> | ||
43 | where | ||
44 | T: Clone, | ||
45 | { | ||
46 | DrawableBuilder::new(shape) | ||
47 | } | ||
48 | |||
49 | pub fn shape(&self) -> &T { | ||
50 | &self.shape | ||
51 | } | ||
52 | |||
53 | pub fn shape_mut(&mut self) -> &mut T { | ||
54 | &mut self.shape | ||
55 | } | ||
56 | |||
57 | pub fn bounds(&self) -> &Bounds { | ||
58 | &self.context.bounds | ||
59 | } | ||
60 | |||
61 | pub fn bounds_mut(&mut self) -> &mut Bounds { | ||
62 | &mut self.context.bounds | ||
63 | } | ||
64 | |||
65 | pub fn fill_style(&mut self) -> &mut FillStyle { | ||
66 | &mut self.context.fill | ||
67 | } | ||
68 | |||
69 | pub fn stroke_style(&mut self) -> &mut StrokeStyle { | ||
70 | &mut self.context.stroke | ||
71 | } | ||
72 | } | ||
73 | |||
74 | pub struct DrawableBuilder<T: ComplexShape> { | ||
75 | context: ShapeContextBuilder, | ||
76 | shape: T, | ||
77 | } | ||
78 | |||
79 | impl<T: ComplexShape + Clone> DrawableBuilder<T> { | ||
80 | pub fn new(shape: T) -> Self { | ||
81 | Self { | ||
82 | context: ShapeContext::builder(), | ||
83 | shape, | ||
84 | } | ||
85 | } | ||
86 | |||
87 | pub fn bounds(&mut self, bounds: Bounds) -> &mut Self { | ||
88 | self.context.bounds(bounds); | ||
89 | self | ||
90 | } | ||
91 | |||
92 | pub fn fill(&mut self, fill: FillStyle) -> &mut Self { | ||
93 | self.context.fill(fill); | ||
94 | self | ||
95 | } | ||
96 | |||
97 | pub fn stroke(&mut self, stroke: StrokeStyle) -> &mut Self { | ||
98 | self.context.stroke(stroke); | ||
99 | self | ||
100 | } | ||
101 | |||
102 | pub fn build(&self, solver: &mut dyn SolverContext) -> Drawable<T> { | ||
103 | Drawable { | ||
104 | shape: self.shape.clone(), | ||
105 | context: self.context.build(solver), | ||
106 | } | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub struct DynDrawable { | ||
111 | pub(crate) context: ShapeContext, | ||
112 | pub(crate) shape: Box<dyn ComplexShape>, | ||
113 | } | ||
114 | |||
115 | // TODO: copy paste is bad? | ||
116 | impl DynDrawable { | ||
117 | pub fn new<T: ComplexShape + 'static>(shape: T, context: ShapeContext) -> Self { | ||
118 | Self { | ||
119 | context, | ||
120 | shape: Box::new(shape), | ||
121 | } | ||
122 | } | ||
123 | |||
124 | pub fn new_dyn(shape: Box<dyn ComplexShape>, context: ShapeContext) -> Self { | ||
125 | Self { context, shape } | ||
126 | } | ||
127 | |||
128 | pub fn from_shape<T: ComplexShape + 'static>(shape: T, solver: &mut dyn SolverContext) -> Self { | ||
129 | Self { | ||
130 | context: ShapeContext::builder().build(solver), | ||
131 | shape: Box::new(shape), | ||
132 | } | ||
133 | } | ||
134 | |||
135 | pub fn from_dyn(shape: Box<dyn ComplexShape>, solver: &mut dyn SolverContext) -> Self { | ||
136 | Self { | ||
137 | context: ShapeContext::builder().build(solver), | ||
138 | shape, | ||
139 | } | ||
140 | } | ||
141 | |||
142 | pub fn shape(&self) -> &dyn ComplexShape { | ||
143 | &*self.shape | ||
144 | } | ||
145 | |||
146 | pub fn shape_mut(&mut self) -> &mut dyn ComplexShape { | ||
147 | &mut *self.shape | ||
148 | } | ||
149 | |||
150 | pub fn bounds(&self) -> &Bounds { | ||
151 | &self.context.bounds | ||
152 | } | ||
153 | |||
154 | pub fn bounds_mut(&mut self) -> &mut Bounds { | ||
155 | &mut self.context.bounds | ||
156 | } | ||
157 | |||
158 | pub fn fill_style(&mut self) -> &mut FillStyle { | ||
159 | &mut self.context.fill | ||
160 | } | ||
161 | |||
162 | pub fn stroke_mut(&mut self) -> &mut StrokeStyle { | ||
163 | &mut self.context.stroke | ||
164 | } | ||
165 | } | ||
166 | |||
167 | impl Clone for DynDrawable { | ||
168 | fn clone(&self) -> Self { | ||
169 | DynDrawable { | ||
170 | context: self.context.clone(), | ||
171 | shape: self.shape.dyn_clone(), | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | |||
176 | impl<T: ComplexShape + 'static> From<Drawable<T>> for DynDrawable { | ||
177 | fn from(drawable: Drawable<T>) -> Self { | ||
178 | DynDrawable { | ||
179 | context: drawable.context, | ||
180 | shape: Box::new(drawable.shape), | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | pub struct DrawResult { | ||
186 | pub(crate) subshapes: Vec<DynDrawable>, | ||
187 | pub(crate) waiting_on: Vec<()>, | ||
188 | } | ||
189 | |||
190 | impl DrawResult { | ||
191 | pub fn new() -> Self { | ||
192 | Self { | ||
193 | subshapes: Vec::new(), | ||
194 | waiting_on: Vec::new(), | ||
195 | } | ||
196 | } | ||
197 | |||
198 | /* | ||
199 | pub fn push_shape<T: ComplexShape + 'static>(&mut self, shape: T, context: ShapeContext) { | ||
200 | self.subshapes.push(DynDrawable { | ||
201 | context, | ||
202 | shape: Box::new(shape), | ||
203 | }) | ||
204 | } | ||
205 | |||
206 | pub fn push_boxed_shape(&mut self, shape: Box<dyn ComplexShape>, context: ShapeContext) { | ||
207 | self.subshapes.push(DynDrawable { context, shape }) | ||
208 | } | ||
209 | */ | ||
210 | |||
211 | pub fn push<T: ComplexShape + 'static>(&mut self, drawable: Drawable<T>) { | ||
212 | self.subshapes.push(drawable.into()) | ||
213 | } | ||
214 | |||
215 | pub fn push_dyn(&mut self, drawable: DynDrawable) { | ||
216 | self.subshapes.push(drawable) | ||
217 | } | ||
218 | } | ||
219 | |||
220 | /* | ||
221 | pub trait Drawable { | ||
222 | fn draw<'z3>(&self, bounds: &Bounds<'z3>, z3_ctx: &z3::Context) -> DrawResult; | ||
223 | } | ||
224 | |||
225 | /* | ||
226 | impl Drawable for Box<dyn Drawable> { | ||
227 | fn draw<'z3>(&self, bounds: &Bounds<'z3>, z3_ctx: &z3::Context) -> DrawResult { | ||
228 | (**self).draw(bounds, z3_ctx) | ||
229 | } | ||
230 | } | ||
231 | */ | ||
232 | |||
233 | impl Drawable for CoreShape { | ||
234 | fn draw<'z3>( | ||
235 | &self, | ||
236 | bounds: &Bounds<'z3>, | ||
237 | ) -> ( | ||
238 | Vec<Bounded<'z3, CoreShape>>, | ||
239 | Vec<Bounded<'z3, Box<dyn Drawable>>>, | ||
240 | ) { | ||
241 | // TODO: clone, really? | ||
242 | ( | ||
243 | vec![Bounded::<CoreShape> { | ||
244 | bounds: bounds.clone(), | ||
245 | shape: self.clone(), | ||
246 | }], | ||
247 | vec![], | ||
248 | ) | ||
249 | } | ||
250 | |||
251 | fn constraints(&self, _bounds: &Bounds, _z3_ctx: &z3::Context) -> Vec<z3::ast::Bool> { | ||
252 | vec![] | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /* | ||
257 | impl<'a, T: Drawable> Drawable for Vec<Bounded<'a, T>> { | ||
258 | fn draw<'z3>( | ||
259 | &self, | ||
260 | _bounds: &Bounds<'z3>, | ||
261 | as_render Vec<Bounded<'z3, CoreShape>>, | ||
262 | Vec<Bounded<'z3, Box<dyn Drawable>>>, | ||
263 | ) { | ||
264 | let mut core_shapes = Vec::new(); | ||
265 | let mut complex_drawables = Vec::new(); | ||
266 | |||
267 | for drawable in self.iter() { | ||
268 | let (mut inner_core_shapes, mut inner_complex_drawables) = | ||
269 | drawable.shape.draw(&drawable.bounds); | ||
270 | |||
271 | core_shapes.append(&mut inner_core_shapes); | ||
272 | complex_drawables.append(&mut inner_complex_drawables); | ||
273 | } | ||
274 | |||
275 | (core_shapes, complex_drawables) | ||
276 | } | ||
277 | |||
278 | fn constraints(&self, _bounds: &Bounds, z3_ctx: &z3::Context) -> Vec<z3::ast::Bool> { | ||
279 | self.iter() | ||
280 | .flat_map(|drawable| drawable.shape.constraints(&drawable.bounds, z3_ctx)) | ||
281 | .collect() | ||
282 | } | ||
283 | } | ||
284 | */ | ||
285 | */ | ||
diff --git a/core/src/core_shapes.rs b/core/src/core_shapes.rs new file mode 100644 index 0000000..c3d3cae --- /dev/null +++ b/core/src/core_shapes.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | use super::complex_shapes::{ComplexShape, DrawResult}; | ||
2 | use super::rendering::{Render, Renderer}; | ||
3 | use super::solving::{Constrainable, SolverContext, SolverModel}; | ||
4 | use super::types::*; | ||
5 | |||
6 | pub trait CoreShape { | ||
7 | fn constrain( | ||
8 | &self, | ||
9 | _context: &ShapeContext, | ||
10 | _solver: &mut dyn SolverContext, | ||
11 | _renderer: &dyn Renderer, | ||
12 | ) { | ||
13 | } | ||
14 | fn to_render(&self, model: &dyn SolverModel) -> Option<Box<dyn Render>>; | ||
15 | } | ||
16 | |||
17 | impl<T: CoreShape + Clone + 'static> ComplexShape for T { | ||
18 | fn as_core_shape(&self) -> Option<&dyn CoreShape> { | ||
19 | Some(self) | ||
20 | } | ||
21 | |||
22 | fn draw(&self, _context: &ShapeContext, _solver: &mut dyn SolverContext) -> DrawResult { | ||
23 | panic!("Tried to decompose core shape") | ||
24 | } | ||
25 | } | ||
26 | |||
27 | // TODO: add default | ||
28 | #[derive(Copy, Clone, Debug, Default)] | ||
29 | pub struct Rectangle {} | ||
30 | |||
31 | impl CoreShape for Rectangle { | ||
32 | fn to_render(&self, _model: &dyn SolverModel) -> Option<Box<dyn Render>> { | ||
33 | Some(Box::new(self.clone())) | ||
34 | } | ||
35 | } | ||
36 | |||
37 | pub use super::text::{DefinedText, Text}; | ||
38 | |||
39 | impl CoreShape for Text { | ||
40 | fn constrain( | ||
41 | &self, | ||
42 | context: &ShapeContext, | ||
43 | solver: &mut dyn SolverContext, | ||
44 | renderer: &dyn Renderer, | ||
45 | ) { | ||
46 | let height_constraint = solver.float_eq(context.bounds.height, self.font.size); | ||
47 | solver.constrain(height_constraint); | ||
48 | |||
49 | // TODO: handle multiline | ||
50 | let (width, height) = renderer.text_extents(&self.content, &self.font); | ||
51 | dbg!(height); | ||
52 | |||
53 | let scale = solver.float_div(self.font.size, Float::Fixed(height)); | ||
54 | |||
55 | let calculated_width = solver.float_mul(&[Float::Fixed(width), scale]); | ||
56 | let width_constraint = solver.float_eq(context.bounds.width, calculated_width); | ||
57 | solver.constrain(width_constraint); | ||
58 | } | ||
59 | |||
60 | fn to_render(&self, model: &dyn SolverModel) -> Option<Box<dyn Render>> { | ||
61 | self.fixate(model) | ||
62 | .map(|path| -> Box<dyn Render> { Box::new(path) }) | ||
63 | } | ||
64 | } | ||
65 | |||
66 | #[derive(Clone, Debug, Default)] | ||
67 | pub struct StraightPath { | ||
68 | pub(crate) points: Vec<Point2D>, | ||
69 | } | ||
70 | |||
71 | impl StraightPath { | ||
72 | pub fn new(points: Vec<Point2D>) -> Self { | ||
73 | Self { points } | ||
74 | } | ||
75 | } | ||
76 | |||
77 | pub struct DefinedStraightPath { | ||
78 | pub(crate) points: Vec<DefinedPoint2D>, | ||
79 | } | ||
80 | |||
81 | impl CoreShape for StraightPath { | ||
82 | fn to_render(&self, model: &dyn SolverModel) -> Option<Box<dyn Render>> { | ||
83 | self.fixate(model) | ||
84 | .map(|path| -> Box<dyn Render> { Box::new(path) }) | ||
85 | } | ||
86 | } | ||
diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000..57e45df --- /dev/null +++ b/core/src/lib.rs | |||
@@ -0,0 +1,19 @@ | |||
1 | #![warn(clippy::all)] | ||
2 | #![deny(unsafe_code)] | ||
3 | |||
4 | pub mod colors; | ||
5 | mod complex_shapes; | ||
6 | pub mod core_shapes; | ||
7 | mod rendering; | ||
8 | mod runtime; | ||
9 | pub mod solving; | ||
10 | pub mod styles; | ||
11 | pub mod text; | ||
12 | pub mod types; | ||
13 | |||
14 | pub use complex_shapes::{ | ||
15 | ComplexShape, DrawResult, Drawable, DrawableBuilder, DynClone, DynDrawable, | ||
16 | }; | ||
17 | pub use rendering::Renderer; | ||
18 | pub use runtime::Runtime; | ||
19 | pub use solving::{SolverContext, SolverModel}; | ||
diff --git a/core/src/mod.rs b/core/src/mod.rs new file mode 100644 index 0000000..40fe74f --- /dev/null +++ b/core/src/mod.rs | |||
@@ -0,0 +1,90 @@ | |||
1 | pub mod constraints; | ||
2 | pub mod drawable; | ||
3 | pub mod shapes; | ||
4 | pub mod text; | ||
5 | pub mod types; | ||
6 | |||
7 | pub use self::drawable::Drawable; | ||
8 | pub use self::shapes::Shape as CoreShape; | ||
9 | pub use self::types::{Bounded, Bounds, DefinitelyBounded}; | ||
10 | |||
11 | use self::constraints::Constrainable; | ||
12 | |||
13 | const RECURSION_LIMIT: u64 = 10_000; | ||
14 | |||
15 | pub struct Context<'z3> { | ||
16 | drawables: Vec<Bounded<'z3, Box<dyn Drawable>>>, | ||
17 | } | ||
18 | |||
19 | impl<'z3> Context<'z3> { | ||
20 | pub fn new() -> Self { | ||
21 | Self { drawables: vec![] } | ||
22 | } | ||
23 | |||
24 | pub fn add_shape(&mut self, shape: Bounded<'z3, Box<dyn Drawable>>) { | ||
25 | self.drawables.push(shape); | ||
26 | } | ||
27 | |||
28 | pub fn set_constraints(&self, z3_ctx: &z3::Context, solver: &z3::Solver) { | ||
29 | for drawable in &self.drawables { | ||
30 | for constraint in drawable.shape.constraints(&drawable.bounds, &z3_ctx) { | ||
31 | solver.assert(&constraint); | ||
32 | } | ||
33 | } | ||
34 | } | ||
35 | |||
36 | // TODO: simplify until not possible? | ||
37 | |||
38 | pub fn draw(&self, model: &z3::Model) -> Vec<DefinitelyBounded<CoreShape>> { | ||
39 | let mut acc_core_drawables = Vec::new(); | ||
40 | let mut acc_complex_drawables = Vec::new(); | ||
41 | |||
42 | for drawable in &self.drawables { | ||
43 | let bounds = &drawable.bounds; | ||
44 | let shape = &drawable.shape; | ||
45 | |||
46 | let (core_drawables, mut complex_drawables) = shape.draw(bounds); | ||
47 | |||
48 | acc_core_drawables.extend(core_drawables.iter().map(|Bounded { bounds, shape }| { | ||
49 | DefinitelyBounded { | ||
50 | bounds: bounds.fixate(model), | ||
51 | shape: shape.clone(), | ||
52 | } | ||
53 | })); | ||
54 | |||
55 | acc_complex_drawables.append(&mut complex_drawables); | ||
56 | } | ||
57 | |||
58 | let mut recursion_count = 0; | ||
59 | |||
60 | while !acc_complex_drawables.is_empty() { | ||
61 | recursion_count += 1; | ||
62 | |||
63 | let mut tmp_complex_drawables = Vec::new(); | ||
64 | |||
65 | for drawable in acc_complex_drawables.drain(..) { | ||
66 | let bounds = drawable.bounds; | ||
67 | let shape = drawable.shape; | ||
68 | |||
69 | let (core_drawables, mut complex_drawables) = shape.draw(&bounds); | ||
70 | |||
71 | acc_core_drawables.extend(core_drawables.into_iter().map( | ||
72 | |Bounded { bounds, shape }| DefinitelyBounded { | ||
73 | bounds: bounds.fixate(model), | ||
74 | shape, | ||
75 | }, | ||
76 | )); | ||
77 | |||
78 | tmp_complex_drawables.append(&mut complex_drawables); | ||
79 | } | ||
80 | |||
81 | acc_complex_drawables = tmp_complex_drawables; | ||
82 | |||
83 | if recursion_count > RECURSION_LIMIT { | ||
84 | panic!("Recursion limit reached"); | ||
85 | } | ||
86 | } | ||
87 | |||
88 | acc_core_drawables | ||
89 | } | ||
90 | } | ||
diff --git a/core/src/rendering.rs b/core/src/rendering.rs new file mode 100644 index 0000000..f7a0189 --- /dev/null +++ b/core/src/rendering.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | use super::core_shapes::*; | ||
2 | use super::styles::{DefinedDashStyle, DefinedStrokeStyle, FillStyle, Pattern}; | ||
3 | use super::text::{DefinedFontDescription, FontDescription}; | ||
4 | use super::types::DefinedShapeContext; | ||
5 | |||
6 | pub trait Renderer { | ||
7 | fn move_to(&mut self, x: f64, y: f64); | ||
8 | fn stroke(&mut self); | ||
9 | fn fill(&mut self); | ||
10 | fn fill_preserve(&mut self); | ||
11 | fn set_pattern(&mut self, pattern: &Pattern); | ||
12 | fn set_dash(&mut self, dash: &DefinedDashStyle); | ||
13 | fn clear_dash(&mut self); | ||
14 | fn set_line_width(&mut self, width: f64); | ||
15 | fn line_to(&mut self, x: f64, y: f64); | ||
16 | fn rectangle(&mut self, x: f64, y: f64, width: f64, height: f64); | ||
17 | // For a font of size 1. | ||
18 | fn text_extents(&self, text: &str, font: &FontDescription) -> (f64, f64); | ||
19 | fn show_text(&mut self, text: &str, font: &DefinedFontDescription); | ||
20 | } | ||
21 | |||
22 | pub trait Render { | ||
23 | fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer); | ||
24 | } | ||
25 | |||
26 | fn draw(fill: &FillStyle, stroke: &DefinedStrokeStyle, renderer: &mut dyn Renderer) { | ||
27 | let stroking = !stroke.pattern.is_none(); | ||
28 | |||
29 | if !fill.pattern.is_none() { | ||
30 | renderer.set_pattern(&fill.pattern); | ||
31 | if stroking { | ||
32 | renderer.fill_preserve(); | ||
33 | } else { | ||
34 | renderer.fill(); | ||
35 | } | ||
36 | } | ||
37 | |||
38 | if !stroke.pattern.is_none() { | ||
39 | renderer.set_pattern(&stroke.pattern); | ||
40 | renderer.set_line_width(stroke.line_width); | ||
41 | if let Some(dash) = &stroke.dash { | ||
42 | renderer.set_dash(dash); | ||
43 | } | ||
44 | renderer.stroke(); | ||
45 | renderer.clear_dash(); | ||
46 | } | ||
47 | } | ||
48 | |||
49 | impl Render for Rectangle { | ||
50 | fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { | ||
51 | let bounds = &context.bounds; | ||
52 | renderer.rectangle(bounds.left, bounds.top, bounds.width, bounds.height); | ||
53 | draw(&context.fill, &context.stroke, renderer); | ||
54 | } | ||
55 | } | ||
56 | |||
57 | impl Render for DefinedText { | ||
58 | fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { | ||
59 | // TODO: select font, style, text shaping (renderer specific), etc. | ||
60 | let bounds = &context.bounds; | ||
61 | //renderer.move_to(bounds.left, bounds.top + self.font.size); | ||
62 | renderer.move_to(bounds.left, bounds.top); | ||
63 | // TODO: ??? | ||
64 | //draw(&context.fill, &context.stroke, renderer); | ||
65 | renderer.show_text(&self.content, &self.font); | ||
66 | } | ||
67 | } | ||
68 | |||
69 | impl Render for DefinedStraightPath { | ||
70 | fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { | ||
71 | let mut iter = self.points.iter(); | ||
72 | |||
73 | let first_point = match iter.next() { | ||
74 | Some(point) => point, | ||
75 | None => return, | ||
76 | }; | ||
77 | |||
78 | renderer.move_to(first_point.x, first_point.y); | ||
79 | |||
80 | for point in iter { | ||
81 | renderer.line_to(point.x, point.y); | ||
82 | } | ||
83 | |||
84 | draw(&context.fill, &context.stroke, renderer); | ||
85 | } | ||
86 | } | ||
diff --git a/core/src/runtime.rs b/core/src/runtime.rs new file mode 100644 index 0000000..bfb31fa --- /dev/null +++ b/core/src/runtime.rs | |||
@@ -0,0 +1,95 @@ | |||
1 | use super::complex_shapes::{ComplexShape, Drawable, DynDrawable}; | ||
2 | use super::rendering::Renderer; | ||
3 | use super::solving::{Constrainable, SolverContext}; | ||
4 | |||
5 | const RECURSION_LIMIT: u64 = 10_000; | ||
6 | |||
7 | pub struct Runtime<'a> { | ||
8 | solver_ctx: Box<dyn SolverContext + 'a>, | ||
9 | renderer: Box<dyn Renderer>, | ||
10 | drawables: Vec<DynDrawable>, | ||
11 | } | ||
12 | |||
13 | impl<'a> Runtime<'a> { | ||
14 | pub fn new(solver_ctx: Box<dyn SolverContext + 'a>, renderer: Box<dyn Renderer>) -> Self { | ||
15 | Self { | ||
16 | solver_ctx, | ||
17 | renderer, | ||
18 | drawables: Vec::new(), | ||
19 | } | ||
20 | } | ||
21 | |||
22 | pub fn add_drawable<T: ComplexShape + 'static>(&mut self, drawable: Drawable<T>) { | ||
23 | self.drawables.push(drawable.into()) | ||
24 | } | ||
25 | |||
26 | pub fn solver_ctx(&mut self) -> &mut (dyn SolverContext + 'a) { | ||
27 | &mut *self.solver_ctx | ||
28 | } | ||
29 | |||
30 | // TODO: preserve ordering of shapes | ||
31 | pub fn render(mut self) { | ||
32 | let mut drawables = self.drawables; | ||
33 | let mut waited_on_variables = Vec::new(); | ||
34 | let mut core_shapes = Vec::new(); | ||
35 | |||
36 | /* | ||
37 | for drawable in &self.shapes { | ||
38 | let bounds = &drawable.bounds; | ||
39 | let shape = &drawable.shape; | ||
40 | |||
41 | if let Some(core_shape) = shape.to_render() { | ||
42 | drawables.push((*drawable).clone()); | ||
43 | continue; | ||
44 | } | ||
45 | |||
46 | let mut result = shape.draw(bounds); | ||
47 | drawables.append(&mut result.subshapes); | ||
48 | waited_on_variables.append(&mut result.waiting_on); | ||
49 | } | ||
50 | */ | ||
51 | |||
52 | let mut recursion_count = 0; | ||
53 | |||
54 | while !drawables.is_empty() { | ||
55 | recursion_count += 1; | ||
56 | |||
57 | if recursion_count > RECURSION_LIMIT { | ||
58 | panic!("Recursion limit reached"); | ||
59 | } | ||
60 | |||
61 | let mut tmp_drawables = Vec::new(); | ||
62 | |||
63 | for drawable in drawables.drain(..) { | ||
64 | let shape_ctx = &drawable.context; | ||
65 | let shape = &drawable.shape; | ||
66 | |||
67 | if let Some(core_shape) = shape.as_core_shape() { | ||
68 | core_shape.constrain(shape_ctx, &mut *self.solver_ctx, &*self.renderer); | ||
69 | core_shapes.push((shape.dyn_clone(), shape_ctx.clone())); // Better to Arc? Cow? | ||
70 | continue; | ||
71 | } | ||
72 | |||
73 | let mut result = shape.draw(shape_ctx, &mut *self.solver_ctx); | ||
74 | tmp_drawables.append(&mut result.subshapes); | ||
75 | waited_on_variables.append(&mut result.waiting_on); | ||
76 | } | ||
77 | |||
78 | drawables = tmp_drawables; | ||
79 | } | ||
80 | |||
81 | let model = self.solver_ctx.solve(); | ||
82 | |||
83 | // Delay rendering core shapes until later to have all the constraints | ||
84 | for (core_shape, shape_ctx) in core_shapes { | ||
85 | let core_shape = core_shape.as_core_shape().unwrap(); | ||
86 | |||
87 | match (core_shape.to_render(&*model), shape_ctx.fixate(&*model)) { | ||
88 | (Some(defined_shape), Some(shape_ctx)) => { | ||
89 | defined_shape.render(shape_ctx, &mut *self.renderer) | ||
90 | } | ||
91 | _ => panic!("Failed to fixate core shape"), | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | } | ||
diff --git a/core/src/solving.rs b/core/src/solving.rs new file mode 100644 index 0000000..c7e94ba --- /dev/null +++ b/core/src/solving.rs | |||
@@ -0,0 +1,279 @@ | |||
1 | use super::core_shapes::*; | ||
2 | use super::styles::*; | ||
3 | use super::text::*; | ||
4 | use super::types::{Bool, Float}; | ||
5 | |||
6 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] | ||
7 | pub struct VariableHandle(usize); | ||
8 | |||
9 | impl VariableHandle { | ||
10 | pub fn new(id: usize) -> Self { | ||
11 | Self(id) | ||
12 | } | ||
13 | |||
14 | pub fn id(&self) -> usize { | ||
15 | self.0 | ||
16 | } | ||
17 | } | ||
18 | |||
19 | /* | ||
20 | pub trait VariableFloat<'a> { | ||
21 | fn id(&self) -> usize; | ||
22 | fn dyn_clone(&self) -> Box<dyn VariableFloat<'a> + 'a>; | ||
23 | |||
24 | fn eq(&self, other: &dyn VariableFloat) -> Bool<'a>; | ||
25 | fn neq(&self, other: &dyn VariableFloat) -> Bool<'a>; | ||
26 | fn gt(&self, other: &dyn VariableFloat) -> Bool<'a>; | ||
27 | fn ge(&self, other: &dyn VariableFloat) -> Bool<'a>; | ||
28 | fn lt(&self, other: &dyn VariableFloat) -> Bool<'a>; | ||
29 | fn le(&self, other: &dyn VariableFloat) -> Bool<'a>; | ||
30 | |||
31 | fn add(&self, other: &dyn VariableFloat) -> Float<'a>; | ||
32 | fn sub(&self, other: &dyn VariableFloat) -> Float<'a>; | ||
33 | fn mul(&self, other: &dyn VariableFloat) -> Float<'a>; | ||
34 | fn div(&self, other: &dyn VariableFloat) -> Float<'a>; | ||
35 | |||
36 | fn neg(&self) -> Float<'a>; | ||
37 | } | ||
38 | |||
39 | pub trait VariableBool<'a> { | ||
40 | fn id(&self) -> usize; | ||
41 | fn dyn_clone(&self) -> Box<dyn VariableBool<'a> + 'a>; | ||
42 | |||
43 | fn eq(&self, other: &dyn VariableBool) -> Bool<'a>; | ||
44 | fn neq(&self, other: &dyn VariableBool) -> Bool<'a>; | ||
45 | |||
46 | fn and(&self, other: &dyn VariableBool) -> Bool<'a>; | ||
47 | fn or(&self, other: &dyn VariableBool) -> Bool<'a>; | ||
48 | |||
49 | fn not(&self) -> Bool<'a>; | ||
50 | } | ||
51 | */ | ||
52 | |||
53 | pub trait SolverContext { | ||
54 | fn solve<'a>(&'a self) -> Box<dyn SolverModel + 'a>; | ||
55 | fn constrain(&mut self, assertion: Bool); | ||
56 | |||
57 | // Floats | ||
58 | |||
59 | fn new_free_float(&mut self) -> Float; | ||
60 | fn new_fixed_float(&mut self, value: f64) -> Float; | ||
61 | |||
62 | fn float_add(&mut self, values: &[Float]) -> Float; | ||
63 | fn float_sub(&mut self, values: &[Float]) -> Float; | ||
64 | fn float_mul(&mut self, values: &[Float]) -> Float; | ||
65 | fn float_div(&mut self, lhs: Float, rhs: Float) -> Float; | ||
66 | fn float_neg(&mut self, value: Float) -> Float; | ||
67 | |||
68 | fn float_eq(&mut self, lhs: Float, rhs: Float) -> Bool; | ||
69 | fn float_ne(&mut self, lhs: Float, rhs: Float) -> Bool; | ||
70 | fn float_gt(&mut self, lhs: Float, rhs: Float) -> Bool; | ||
71 | fn float_ge(&mut self, lhs: Float, rhs: Float) -> Bool; | ||
72 | fn float_lt(&mut self, lhs: Float, rhs: Float) -> Bool; | ||
73 | fn float_le(&mut self, lhs: Float, rhs: Float) -> Bool; | ||
74 | |||
75 | fn float_max(&mut self, values: &[Float]) -> Float { | ||
76 | let result = self.new_free_float(); | ||
77 | |||
78 | for (index, &candidate) in values.iter().enumerate() { | ||
79 | let comparisons: Vec<_> = values | ||
80 | .iter() | ||
81 | .enumerate() | ||
82 | .filter(|(other_index, _)| *other_index != index) | ||
83 | .map(|(_, &other)| self.float_ge(candidate, other)) | ||
84 | .collect(); | ||
85 | |||
86 | let premise = self.bool_and(&comparisons); | ||
87 | let conclusion = self.float_eq(candidate, result); | ||
88 | let implication = self.bool_implies(premise, conclusion); | ||
89 | self.constrain(implication); | ||
90 | } | ||
91 | |||
92 | result | ||
93 | } | ||
94 | |||
95 | fn float_min(&mut self, values: &[Float]) -> Float { | ||
96 | let result = self.new_free_float(); | ||
97 | |||
98 | for (index, &candidate) in values.iter().enumerate() { | ||
99 | let comparisons: Vec<_> = values | ||
100 | .iter() | ||
101 | .enumerate() | ||
102 | .filter(|(other_index, _)| *other_index != index) | ||
103 | .map(|(_, &other)| self.float_le(candidate, other)) | ||
104 | .collect(); | ||
105 | |||
106 | let premise = self.bool_and(&comparisons); | ||
107 | let conclusion = self.float_eq(candidate, result); | ||
108 | let implication = self.bool_implies(premise, conclusion); | ||
109 | self.constrain(implication); | ||
110 | } | ||
111 | |||
112 | result | ||
113 | } | ||
114 | |||
115 | // Bools | ||
116 | |||
117 | fn new_free_bool(&mut self) -> Bool; | ||
118 | fn new_fixed_bool(&mut self, value: bool) -> Bool; | ||
119 | |||
120 | fn bool_eq(&mut self, lhs: Bool, rhs: Bool) -> Bool; | ||
121 | fn bool_ne(&mut self, lhs: Bool, rhs: Bool) -> Bool; | ||
122 | |||
123 | fn bool_and(&mut self, values: &[Bool]) -> Bool; | ||
124 | fn bool_or(&mut self, values: &[Bool]) -> Bool; | ||
125 | fn bool_not(&mut self, value: Bool) -> Bool; | ||
126 | |||
127 | fn bool_implies(&mut self, lhs: Bool, rhs: Bool) -> Bool; | ||
128 | } | ||
129 | |||
130 | pub trait SolverModel { | ||
131 | fn eval_float(&self, f: Float) -> Option<f64>; | ||
132 | fn eval_bool(&self, b: Bool) -> Option<bool>; | ||
133 | } | ||
134 | |||
135 | /* | ||
136 | pub trait Solver { | ||
137 | fn constrain(&mut self, assertion: &Bool); | ||
138 | fn solve(&self) -> Box<dyn SolverModel>; | ||
139 | } | ||
140 | */ | ||
141 | |||
142 | use super::types::*; | ||
143 | |||
144 | pub trait Constrainable { | ||
145 | type Fixated; | ||
146 | |||
147 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated>; | ||
148 | } | ||
149 | |||
150 | impl Constrainable for Float { | ||
151 | type Fixated = f64; | ||
152 | |||
153 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
154 | model.eval_float(*self) | ||
155 | } | ||
156 | |||
157 | /* | ||
158 | fn fixate(&self, model: &Model) -> Self::Fixated { | ||
159 | match self { | ||
160 | Float::Defined(float) => *float, | ||
161 | Float::Constrained(variable) => { | ||
162 | let (num, den) = model | ||
163 | .eval::<z3::ast::Real>(variable) | ||
164 | .expect("Couldn't eval variable") | ||
165 | .as_real() | ||
166 | .expect("Couldn't get value from variable"); | ||
167 | |||
168 | num as f64 / den as f64 | ||
169 | } | ||
170 | Float::Undefined => panic!("Undefined float"), | ||
171 | } | ||
172 | } | ||
173 | */ | ||
174 | } | ||
175 | |||
176 | impl Constrainable for Bounds { | ||
177 | type Fixated = DefinedBounds; | ||
178 | |||
179 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
180 | Some(DefinedBounds { | ||
181 | top: self.top.fixate(model)?, | ||
182 | left: self.left.fixate(model)?, | ||
183 | width: self.width.fixate(model)?, | ||
184 | height: self.height.fixate(model)?, | ||
185 | }) | ||
186 | } | ||
187 | } | ||
188 | |||
189 | impl Constrainable for StrokeStyle { | ||
190 | type Fixated = DefinedStrokeStyle; | ||
191 | |||
192 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
193 | let dash = match &self.dash { | ||
194 | Some(dash) => Some(dash.fixate(model)?), | ||
195 | None => None, | ||
196 | }; | ||
197 | |||
198 | Some(DefinedStrokeStyle { | ||
199 | pattern: self.pattern.clone(), | ||
200 | dash, | ||
201 | line_width: self.line_width.fixate(model)?, | ||
202 | }) | ||
203 | } | ||
204 | } | ||
205 | |||
206 | impl Constrainable for DashStyle { | ||
207 | type Fixated = DefinedDashStyle; | ||
208 | |||
209 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
210 | Some(DefinedDashStyle { | ||
211 | dashes: self | ||
212 | .dashes | ||
213 | .iter() | ||
214 | .map(|value| value.fixate(model)) | ||
215 | .collect::<Option<_>>()?, | ||
216 | offset: self.offset.fixate(model)?, | ||
217 | }) | ||
218 | } | ||
219 | } | ||
220 | |||
221 | impl Constrainable for ShapeContext { | ||
222 | type Fixated = DefinedShapeContext; | ||
223 | |||
224 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
225 | Some(DefinedShapeContext { | ||
226 | bounds: self.bounds.fixate(model)?, | ||
227 | fill: self.fill.clone(), | ||
228 | stroke: self.stroke.fixate(model)?, | ||
229 | }) | ||
230 | } | ||
231 | } | ||
232 | |||
233 | impl Constrainable for Point2D { | ||
234 | type Fixated = DefinedPoint2D; | ||
235 | |||
236 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
237 | Some(DefinedPoint2D { | ||
238 | x: self.x.fixate(model)?, | ||
239 | y: self.y.fixate(model)?, | ||
240 | }) | ||
241 | } | ||
242 | } | ||
243 | |||
244 | impl Constrainable for FontDescription { | ||
245 | type Fixated = DefinedFontDescription; | ||
246 | |||
247 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
248 | Some(DefinedFontDescription { | ||
249 | family: self.family.clone(), | ||
250 | style: self.style, | ||
251 | weight: self.weight, | ||
252 | size: self.size.fixate(&*model)?, | ||
253 | }) | ||
254 | } | ||
255 | } | ||
256 | |||
257 | impl Constrainable for Text { | ||
258 | type Fixated = DefinedText; | ||
259 | |||
260 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
261 | Some(DefinedText { | ||
262 | content: self.content.clone(), | ||
263 | font: self.font.fixate(&*model)?, | ||
264 | }) | ||
265 | } | ||
266 | } | ||
267 | |||
268 | impl Constrainable for StraightPath { | ||
269 | type Fixated = DefinedStraightPath; | ||
270 | |||
271 | fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated> { | ||
272 | let points: Option<_> = self | ||
273 | .points | ||
274 | .iter() | ||
275 | .map(|point| point.fixate(model)) | ||
276 | .collect(); | ||
277 | Some(DefinedStraightPath { points: points? }) | ||
278 | } | ||
279 | } | ||
diff --git a/core/src/styles.rs b/core/src/styles.rs new file mode 100644 index 0000000..c061c58 --- /dev/null +++ b/core/src/styles.rs | |||
@@ -0,0 +1,161 @@ | |||
1 | use super::colors::Color; | ||
2 | use super::types::Float; | ||
3 | |||
4 | #[derive(Copy, Clone, PartialEq, Debug)] | ||
5 | #[non_exhaustive] | ||
6 | pub enum Pattern { | ||
7 | Solid(Color), | ||
8 | None, | ||
9 | } | ||
10 | |||
11 | impl Pattern { | ||
12 | pub fn is_none(&self) -> bool { | ||
13 | match self { | ||
14 | Pattern::None => true, | ||
15 | _ => false, | ||
16 | } | ||
17 | } | ||
18 | } | ||
19 | |||
20 | impl Default for Pattern { | ||
21 | fn default() -> Self { | ||
22 | Pattern::Solid(Color::default()) | ||
23 | } | ||
24 | } | ||
25 | |||
26 | #[derive(Clone, PartialEq, Debug)] | ||
27 | pub struct FillStyle { | ||
28 | pub(crate) pattern: Pattern, | ||
29 | } | ||
30 | |||
31 | impl FillStyle { | ||
32 | pub fn solid(color: Color) -> Self { | ||
33 | FillStyle { | ||
34 | pattern: Pattern::Solid(color), | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | impl Default for FillStyle { | ||
40 | fn default() -> Self { | ||
41 | FillStyle { | ||
42 | pattern: Pattern::None, | ||
43 | } | ||
44 | } | ||
45 | } | ||
46 | |||
47 | // TODO: probably move Float into an enum with Defined(f64) and Variable(handle) | ||
48 | #[derive(Clone, Debug)] | ||
49 | pub struct StrokeStyle { | ||
50 | pub(crate) pattern: Pattern, | ||
51 | pub(crate) dash: Option<DashStyle>, | ||
52 | pub(crate) line_width: Float, | ||
53 | } | ||
54 | |||
55 | impl StrokeStyle { | ||
56 | pub fn solid(color: Color) -> Self { | ||
57 | StrokeStyle { | ||
58 | pattern: Pattern::Solid(color), | ||
59 | dash: None, | ||
60 | line_width: Float::Fixed(0.), | ||
61 | } | ||
62 | } | ||
63 | |||
64 | pub fn builder() -> StrokeStyleBuilder { | ||
65 | StrokeStyleBuilder::default() | ||
66 | } | ||
67 | } | ||
68 | |||
69 | impl Default for StrokeStyle { | ||
70 | fn default() -> Self { | ||
71 | Self::builder().build() | ||
72 | } | ||
73 | } | ||
74 | |||
75 | #[derive(Clone, Default)] | ||
76 | pub struct StrokeStyleBuilder { | ||
77 | pattern: Option<Pattern>, | ||
78 | dash: Option<DashStyle>, | ||
79 | line_width: Option<Float>, | ||
80 | } | ||
81 | |||
82 | impl StrokeStyleBuilder { | ||
83 | pub fn pattern(&mut self, pattern: Pattern) -> &mut Self { | ||
84 | self.pattern = Some(pattern); | ||
85 | self | ||
86 | } | ||
87 | |||
88 | pub fn dash(&mut self, dash: DashStyle) -> &mut Self { | ||
89 | self.dash = Some(dash); | ||
90 | self | ||
91 | } | ||
92 | |||
93 | pub fn line_width(&mut self, width: Float) -> &mut Self { | ||
94 | self.line_width = Some(width); | ||
95 | self | ||
96 | } | ||
97 | |||
98 | pub fn build(&mut self) -> StrokeStyle { | ||
99 | StrokeStyle { | ||
100 | pattern: self.pattern.clone().unwrap_or_default(), | ||
101 | dash: self.dash.clone(), | ||
102 | line_width: self.line_width.unwrap_or(Float::Fixed(2.)), | ||
103 | } | ||
104 | } | ||
105 | |||
106 | } | ||
107 | |||
108 | |||
109 | |||
110 | |||
111 | #[derive(Clone, Debug, Default)] | ||
112 | pub struct DashStyle { | ||
113 | pub(crate) dashes: Vec<Float>, | ||
114 | pub(crate) offset: Float, | ||
115 | } | ||
116 | |||
117 | impl DashStyle { | ||
118 | pub fn new(dashes: Vec<Float>, offset: Float) -> Self { | ||
119 | Self { dashes, offset } | ||
120 | } | ||
121 | |||
122 | pub fn dashes(&self) -> &[Float] { | ||
123 | &self.dashes | ||
124 | } | ||
125 | |||
126 | pub fn dashes_mut(&mut self) -> &mut Vec<Float> { | ||
127 | &mut self.dashes | ||
128 | } | ||
129 | |||
130 | pub fn offset(&self) -> Float { | ||
131 | self.offset | ||
132 | } | ||
133 | |||
134 | // TODO: does this makes sense? | ||
135 | pub fn offset_mut(&mut self) -> &mut Float { | ||
136 | &mut self.offset | ||
137 | } | ||
138 | } | ||
139 | |||
140 | #[derive(Clone, PartialEq, Debug, Default)] | ||
141 | pub struct DefinedStrokeStyle { | ||
142 | pub(crate) pattern: Pattern, | ||
143 | pub(crate) dash: Option<DefinedDashStyle>, | ||
144 | pub(crate) line_width: f64, | ||
145 | } | ||
146 | |||
147 | #[derive(Clone, PartialEq, Debug, Default)] | ||
148 | pub struct DefinedDashStyle { | ||
149 | pub(crate) dashes: Vec<f64>, | ||
150 | pub(crate) offset: f64, | ||
151 | } | ||
152 | |||
153 | impl DefinedDashStyle { | ||
154 | pub fn dashes(&self) -> &[f64] { | ||
155 | &self.dashes | ||
156 | } | ||
157 | |||
158 | pub fn offset(&self) -> f64 { | ||
159 | self.offset | ||
160 | } | ||
161 | } | ||
diff --git a/core/src/text.rs b/core/src/text.rs new file mode 100644 index 0000000..3110030 --- /dev/null +++ b/core/src/text.rs | |||
@@ -0,0 +1,81 @@ | |||
1 | use super::types::Float; | ||
2 | |||
3 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] | ||
4 | #[non_exhaustive] | ||
5 | pub enum FontStyle { | ||
6 | Normal, | ||
7 | Oblique, | ||
8 | Italic, | ||
9 | } | ||
10 | |||
11 | impl Default for FontStyle { | ||
12 | fn default() -> Self { | ||
13 | Self::Normal | ||
14 | } | ||
15 | } | ||
16 | |||
17 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] | ||
18 | #[non_exhaustive] | ||
19 | pub enum FontVariant { | ||
20 | Normal, | ||
21 | SmallCaps, | ||
22 | } | ||
23 | |||
24 | impl Default for FontVariant { | ||
25 | fn default() -> Self { | ||
26 | Self::Normal | ||
27 | } | ||
28 | } | ||
29 | |||
30 | #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] | ||
31 | #[non_exhaustive] | ||
32 | pub enum FontWeight { | ||
33 | Thin, | ||
34 | Ultralight, | ||
35 | Light, | ||
36 | Semilight, | ||
37 | Book, | ||
38 | Normal, | ||
39 | Medium, | ||
40 | Semibold, | ||
41 | Bold, | ||
42 | Ultrabold, | ||
43 | Heavy, | ||
44 | Ultraheavy, | ||
45 | } | ||
46 | |||
47 | impl Default for FontWeight { | ||
48 | fn default() -> Self { | ||
49 | Self::Normal | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[derive(Clone, Debug)] | ||
54 | pub struct FontDescription { | ||
55 | pub family: String, | ||
56 | pub style: FontStyle, | ||
57 | pub weight: FontWeight, | ||
58 | // TODO: handles weirdly with bounds that specifies both top and bottom | ||
59 | pub size: Float, | ||
60 | } | ||
61 | |||
62 | // TODO: automate creation of Defined* structs | ||
63 | #[derive(Clone, Debug)] | ||
64 | pub struct DefinedFontDescription { | ||
65 | pub family: String, | ||
66 | pub style: FontStyle, | ||
67 | pub weight: FontWeight, | ||
68 | pub size: f64, | ||
69 | } | ||
70 | |||
71 | #[derive(Clone, Debug)] | ||
72 | pub struct Text { | ||
73 | pub content: String, | ||
74 | pub font: FontDescription, | ||
75 | } | ||
76 | |||
77 | #[derive(Clone, Debug)] | ||
78 | pub struct DefinedText { | ||
79 | pub content: String, | ||
80 | pub font: DefinedFontDescription, | ||
81 | } | ||
diff --git a/core/src/types.rs b/core/src/types.rs new file mode 100644 index 0000000..1b7623a --- /dev/null +++ b/core/src/types.rs | |||
@@ -0,0 +1,316 @@ | |||
1 | use super::solving::{SolverContext, VariableHandle}; | ||
2 | use super::styles::{DefinedStrokeStyle, FillStyle, StrokeStyle}; | ||
3 | |||
4 | // TODO: if we're so dependent on lifetimes, it's probably better to remove Box, and use raw | ||
5 | // references instead | ||
6 | |||
7 | // Not Eq / Ord to prevent cofusion | ||
8 | #[derive(Copy, Clone, Debug)] | ||
9 | pub enum Float { | ||
10 | Fixed(f64), | ||
11 | Variable(VariableHandle), | ||
12 | } | ||
13 | |||
14 | impl Default for Float { | ||
15 | fn default() -> Self { | ||
16 | Float::Fixed(0.) | ||
17 | } | ||
18 | } | ||
19 | |||
20 | impl Float { | ||
21 | pub fn from_handle(handle: VariableHandle) -> Self { | ||
22 | Float::Variable(handle) | ||
23 | } | ||
24 | |||
25 | /* | ||
26 | pub fn handle(&self) -> Option<VariableHandle> { | ||
27 | match self { | ||
28 | Float::Fixed(_) => None, | ||
29 | Float::Variable(handle) => Some(handle), | ||
30 | } | ||
31 | } | ||
32 | */ | ||
33 | } | ||
34 | |||
35 | #[derive(Clone, Default)] | ||
36 | struct FloatBuilder(Option<Float>); | ||
37 | |||
38 | impl FloatBuilder { | ||
39 | pub fn set(&mut self, value: Float) { | ||
40 | self.0 = Some(value); | ||
41 | } | ||
42 | |||
43 | pub fn build(&self, solver: &mut dyn SolverContext) -> Float { | ||
44 | self.0.unwrap_or_else(|| solver.new_free_float()) | ||
45 | } | ||
46 | } | ||
47 | |||
48 | // Not Eq / Ord to prevent cofusion | ||
49 | #[derive(Copy, Clone, Hash, Debug)] | ||
50 | pub struct Bool(VariableHandle); | ||
51 | |||
52 | impl Bool { | ||
53 | pub fn new(handle: VariableHandle) -> Self { | ||
54 | Bool(handle) | ||
55 | } | ||
56 | |||
57 | pub fn handle(&self) -> VariableHandle { | ||
58 | self.0 | ||
59 | } | ||
60 | } | ||
61 | |||
62 | #[derive(Clone, Debug)] | ||
63 | pub struct Point2D { | ||
64 | pub(crate) x: Float, | ||
65 | pub(crate) y: Float, | ||
66 | } | ||
67 | |||
68 | impl Point2D { | ||
69 | pub fn new(x: Float, y: Float) -> Self { | ||
70 | Point2D { x, y } | ||
71 | } | ||
72 | |||
73 | pub fn x(&self) -> Float { | ||
74 | self.x | ||
75 | } | ||
76 | |||
77 | pub fn y(&self) -> Float { | ||
78 | self.y | ||
79 | } | ||
80 | } | ||
81 | |||
82 | #[derive(Clone, PartialEq, PartialOrd, Debug)] | ||
83 | pub struct DefinedPoint2D { | ||
84 | pub(crate) x: f64, | ||
85 | pub(crate) y: f64, | ||
86 | } | ||
87 | |||
88 | #[derive(Clone, Debug)] | ||
89 | pub struct Bounds { | ||
90 | // TODO: really pub? That allows modification, when one might expect constraints | ||
91 | pub(crate) top: Float, | ||
92 | pub(crate) left: Float, | ||
93 | pub(crate) width: Float, | ||
94 | pub(crate) height: Float, | ||
95 | } | ||
96 | |||
97 | impl Bounds { | ||
98 | pub fn builder() -> BoundsBuilder { | ||
99 | BoundsBuilder::default() | ||
100 | } | ||
101 | |||
102 | pub fn top(&self, _ctx: &mut dyn SolverContext) -> Float { | ||
103 | self.top | ||
104 | } | ||
105 | |||
106 | pub fn left(&self, _ctx: &mut dyn SolverContext) -> Float { | ||
107 | self.left | ||
108 | } | ||
109 | |||
110 | pub fn width(&self, _ctx: &mut dyn SolverContext) -> Float { | ||
111 | self.width | ||
112 | } | ||
113 | |||
114 | pub fn height(&self, _ctx: &mut dyn SolverContext) -> Float { | ||
115 | self.height | ||
116 | } | ||
117 | |||
118 | pub fn right(&self, solver: &mut dyn SolverContext) -> Float { | ||
119 | solver.float_add(&[self.left, self.width]) | ||
120 | } | ||
121 | |||
122 | pub fn bottom(&self, solver: &mut dyn SolverContext) -> Float { | ||
123 | solver.float_add(&[self.top, self.height]) | ||
124 | } | ||
125 | |||
126 | pub fn vert_center(&self, solver: &mut dyn SolverContext) -> Float { | ||
127 | let half_height = solver.float_div(self.height, Float::Fixed(2.)); | ||
128 | solver.float_add(&[self.top, half_height]) | ||
129 | } | ||
130 | |||
131 | pub fn horiz_center(&self, solver: &mut dyn SolverContext) -> Float { | ||
132 | let half_width = solver.float_div(self.width, Float::Fixed(2.)); | ||
133 | solver.float_add(&[self.left, half_width]) | ||
134 | } | ||
135 | |||
136 | pub fn top_left(&self, _ctx: &mut dyn SolverContext) -> Point2D { | ||
137 | Point2D::new(self.left, self.top) | ||
138 | } | ||
139 | |||
140 | pub fn top_right(&self, solver: &mut dyn SolverContext) -> Point2D { | ||
141 | Point2D::new(self.right(solver), self.top) | ||
142 | } | ||
143 | |||
144 | pub fn bottom_left(&self, solver: &mut dyn SolverContext) -> Point2D { | ||
145 | Point2D::new(self.left, self.bottom(solver)) | ||
146 | } | ||
147 | |||
148 | pub fn bottom_right(&self, solver: &mut dyn SolverContext) -> Point2D { | ||
149 | Point2D::new(self.right(solver), self.bottom(solver)) | ||
150 | } | ||
151 | |||
152 | pub fn center(&self, solver: &mut dyn SolverContext) -> Point2D { | ||
153 | Point2D::new(self.horiz_center(solver), self.vert_center(solver)) | ||
154 | } | ||
155 | |||
156 | pub fn with_margin(&self, margin: Float, solver: &mut dyn SolverContext) -> Self { | ||
157 | let neg_margin = solver.float_neg(margin); | ||
158 | Bounds { | ||
159 | top: solver.float_add(&[self.top, neg_margin]), | ||
160 | left: solver.float_add(&[self.top, neg_margin]), | ||
161 | height: solver.float_add(&[self.top, margin]), | ||
162 | width: solver.float_add(&[self.top, margin]), | ||
163 | } | ||
164 | } | ||
165 | } | ||
166 | |||
167 | #[derive(Clone, Default)] | ||
168 | pub struct BoundsBuilder { | ||
169 | top: FloatBuilder, | ||
170 | left: FloatBuilder, | ||
171 | width: FloatBuilder, | ||
172 | height: FloatBuilder, | ||
173 | } | ||
174 | |||
175 | impl BoundsBuilder { | ||
176 | pub fn top(&mut self, top: Float) -> &mut Self { | ||
177 | self.top.set(top); | ||
178 | self | ||
179 | } | ||
180 | |||
181 | pub fn left(&mut self, left: Float) -> &mut Self { | ||
182 | self.left.set(left); | ||
183 | self | ||
184 | } | ||
185 | |||
186 | pub fn width(&mut self, width: Float) -> &mut Self { | ||
187 | self.width.set(width); | ||
188 | self | ||
189 | } | ||
190 | |||
191 | pub fn height(&mut self, height: Float) -> &mut Self { | ||
192 | self.height.set(height); | ||
193 | self | ||
194 | } | ||
195 | |||
196 | pub fn build(&self, solver: &mut dyn SolverContext) -> Bounds { | ||
197 | let _build_float = | ||
198 | |maybe_float: Option<Float>| maybe_float.unwrap_or_else(|| solver.new_free_float()); | ||
199 | |||
200 | Bounds { | ||
201 | top: self.top.build(solver), | ||
202 | left: self.left.build(solver), | ||
203 | width: self.width.build(solver), | ||
204 | height: self.height.build(solver), | ||
205 | } | ||
206 | } | ||
207 | } | ||
208 | |||
209 | #[derive(Clone, Debug)] | ||
210 | pub struct Bounded<Shape> { | ||
211 | pub bounds: Bounds, | ||
212 | pub shape: Shape, | ||
213 | } | ||
214 | |||
215 | #[derive(Clone, PartialEq, PartialOrd, Debug)] | ||
216 | pub struct DefinedBounds { | ||
217 | pub top: f64, | ||
218 | pub left: f64, | ||
219 | pub width: f64, | ||
220 | pub height: f64, | ||
221 | } | ||
222 | |||
223 | #[derive(Clone, PartialEq, PartialOrd, Debug)] | ||
224 | pub struct DefinitelyBounded<Shape> { | ||
225 | pub bounds: DefinedBounds, | ||
226 | pub shape: Shape, | ||
227 | } | ||
228 | |||
229 | #[derive(Clone, Debug)] | ||
230 | pub struct ShapeContext { | ||
231 | pub(crate) bounds: Bounds, | ||
232 | pub(crate) fill: FillStyle, | ||
233 | pub(crate) stroke: StrokeStyle, | ||
234 | } | ||
235 | |||
236 | impl ShapeContext { | ||
237 | pub fn new(solver: &mut dyn SolverContext) -> Self { | ||
238 | Self { | ||
239 | bounds: Bounds { | ||
240 | top: solver.new_free_float(), | ||
241 | left: solver.new_free_float(), | ||
242 | width: solver.new_free_float(), | ||
243 | height: solver.new_free_float(), | ||
244 | }, | ||
245 | fill: FillStyle::default(), | ||
246 | stroke: StrokeStyle::default(), | ||
247 | } | ||
248 | } | ||
249 | |||
250 | pub(crate) fn builder() -> ShapeContextBuilder { | ||
251 | ShapeContextBuilder::default() | ||
252 | } | ||
253 | |||
254 | pub fn bounds(&self) -> &Bounds { | ||
255 | &self.bounds | ||
256 | } | ||
257 | |||
258 | pub fn bounds_mut(&mut self) -> &mut Bounds { | ||
259 | &mut self.bounds | ||
260 | } | ||
261 | |||
262 | pub fn fill(&self) -> &FillStyle { | ||
263 | &self.fill | ||
264 | } | ||
265 | |||
266 | pub fn fill_mut(&mut self) -> &mut FillStyle { | ||
267 | &mut self.fill | ||
268 | } | ||
269 | |||
270 | pub fn stroke(&self) -> &StrokeStyle { | ||
271 | &self.stroke | ||
272 | } | ||
273 | |||
274 | pub fn stroke_mut(&mut self) -> &mut StrokeStyle { | ||
275 | &mut self.stroke | ||
276 | } | ||
277 | } | ||
278 | |||
279 | #[derive(Clone, Default)] | ||
280 | pub(crate) struct ShapeContextBuilder { | ||
281 | bounds: Option<Bounds>, | ||
282 | fill: Option<FillStyle>, | ||
283 | stroke: Option<StrokeStyle>, | ||
284 | } | ||
285 | |||
286 | impl ShapeContextBuilder { | ||
287 | pub fn bounds(&mut self, bounds: Bounds) -> &mut Self { | ||
288 | self.bounds = Some(bounds); | ||
289 | self | ||
290 | } | ||
291 | |||
292 | pub fn fill(&mut self, fill: FillStyle) -> &mut Self { | ||
293 | self.fill = Some(fill); | ||
294 | self | ||
295 | } | ||
296 | |||
297 | pub fn stroke(&mut self, stroke: StrokeStyle) -> &mut Self { | ||
298 | self.stroke = Some(stroke); | ||
299 | self | ||
300 | } | ||
301 | |||
302 | pub fn build(&self, solver: &mut dyn SolverContext) -> ShapeContext { | ||
303 | ShapeContext { | ||
304 | bounds: self.bounds.clone().unwrap_or_else(|| Bounds::builder().build(solver)), | ||
305 | fill: self.fill.clone().unwrap_or_default(), | ||
306 | stroke: self.stroke.clone().unwrap_or_default(), | ||
307 | } | ||
308 | } | ||
309 | } | ||
310 | |||
311 | #[derive(Clone, Debug)] | ||
312 | pub struct DefinedShapeContext { | ||
313 | pub bounds: DefinedBounds, | ||
314 | pub fill: FillStyle, | ||
315 | pub stroke: DefinedStrokeStyle, | ||
316 | } | ||