summaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorMinijackson <minijackson@riseup.net>2022-12-22 12:19:59 +0100
committerMinijackson <minijackson@riseup.net>2022-12-22 12:19:59 +0100
commit92a02c34628343153b33602eae00cef46e28d191 (patch)
tree8622ec528d24e456be22d984d93aa9bcafc97399 /core/src
downloaddiaphragm-92a02c34628343153b33602eae00cef46e28d191.tar.gz
diaphragm-92a02c34628343153b33602eae00cef46e28d191.zip
WIP
Diffstat (limited to 'core/src')
-rw-r--r--core/src/colors.rs34
-rw-r--r--core/src/complex_shapes.rs285
-rw-r--r--core/src/core_shapes.rs86
-rw-r--r--core/src/lib.rs19
-rw-r--r--core/src/mod.rs90
-rw-r--r--core/src/rendering.rs86
-rw-r--r--core/src/runtime.rs95
-rw-r--r--core/src/solving.rs279
-rw-r--r--core/src/styles.rs161
-rw-r--r--core/src/text.rs81
-rw-r--r--core/src/types.rs316
11 files changed, 1532 insertions, 0 deletions
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 @@
1type Srgba = palette::Srgba<f64>;
2
3#[derive(Copy, Clone, PartialEq, Debug, Default)]
4pub struct Color(Srgba);
5
6impl 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 @@
1use super::core_shapes::CoreShape;
2use super::solving::SolverContext;
3use super::styles::{FillStyle, StrokeStyle};
4use super::types::{Bounds, ShapeContext, ShapeContextBuilder};
5
6pub 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
14pub trait DynClone {
15 fn dyn_clone(&self) -> Box<dyn ComplexShape>;
16}
17
18impl<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)]
25pub struct Drawable<T: ComplexShape> {
26 pub(crate) context: ShapeContext,
27 pub(crate) shape: T,
28}
29
30impl<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
74pub struct DrawableBuilder<T: ComplexShape> {
75 context: ShapeContextBuilder,
76 shape: T,
77}
78
79impl<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
110pub struct DynDrawable {
111 pub(crate) context: ShapeContext,
112 pub(crate) shape: Box<dyn ComplexShape>,
113}
114
115// TODO: copy paste is bad?
116impl 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
167impl 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
176impl<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
185pub struct DrawResult {
186 pub(crate) subshapes: Vec<DynDrawable>,
187 pub(crate) waiting_on: Vec<()>,
188}
189
190impl 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/*
221pub trait Drawable {
222 fn draw<'z3>(&self, bounds: &Bounds<'z3>, z3_ctx: &z3::Context) -> DrawResult;
223}
224
225/*
226impl 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
233impl 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/*
257impl<'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 @@
1use super::complex_shapes::{ComplexShape, DrawResult};
2use super::rendering::{Render, Renderer};
3use super::solving::{Constrainable, SolverContext, SolverModel};
4use super::types::*;
5
6pub 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
17impl<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)]
29pub struct Rectangle {}
30
31impl CoreShape for Rectangle {
32 fn to_render(&self, _model: &dyn SolverModel) -> Option<Box<dyn Render>> {
33 Some(Box::new(self.clone()))
34 }
35}
36
37pub use super::text::{DefinedText, Text};
38
39impl 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)]
67pub struct StraightPath {
68 pub(crate) points: Vec<Point2D>,
69}
70
71impl StraightPath {
72 pub fn new(points: Vec<Point2D>) -> Self {
73 Self { points }
74 }
75}
76
77pub struct DefinedStraightPath {
78 pub(crate) points: Vec<DefinedPoint2D>,
79}
80
81impl 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
4pub mod colors;
5mod complex_shapes;
6pub mod core_shapes;
7mod rendering;
8mod runtime;
9pub mod solving;
10pub mod styles;
11pub mod text;
12pub mod types;
13
14pub use complex_shapes::{
15 ComplexShape, DrawResult, Drawable, DrawableBuilder, DynClone, DynDrawable,
16};
17pub use rendering::Renderer;
18pub use runtime::Runtime;
19pub 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 @@
1pub mod constraints;
2pub mod drawable;
3pub mod shapes;
4pub mod text;
5pub mod types;
6
7pub use self::drawable::Drawable;
8pub use self::shapes::Shape as CoreShape;
9pub use self::types::{Bounded, Bounds, DefinitelyBounded};
10
11use self::constraints::Constrainable;
12
13const RECURSION_LIMIT: u64 = 10_000;
14
15pub struct Context<'z3> {
16 drawables: Vec<Bounded<'z3, Box<dyn Drawable>>>,
17}
18
19impl<'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 @@
1use super::core_shapes::*;
2use super::styles::{DefinedDashStyle, DefinedStrokeStyle, FillStyle, Pattern};
3use super::text::{DefinedFontDescription, FontDescription};
4use super::types::DefinedShapeContext;
5
6pub 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
22pub trait Render {
23 fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer);
24}
25
26fn 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
49impl 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
57impl 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
69impl 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 @@
1use super::complex_shapes::{ComplexShape, Drawable, DynDrawable};
2use super::rendering::Renderer;
3use super::solving::{Constrainable, SolverContext};
4
5const RECURSION_LIMIT: u64 = 10_000;
6
7pub struct Runtime<'a> {
8 solver_ctx: Box<dyn SolverContext + 'a>,
9 renderer: Box<dyn Renderer>,
10 drawables: Vec<DynDrawable>,
11}
12
13impl<'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 @@
1use super::core_shapes::*;
2use super::styles::*;
3use super::text::*;
4use super::types::{Bool, Float};
5
6#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
7pub struct VariableHandle(usize);
8
9impl 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/*
20pub 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
39pub 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
53pub 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
130pub trait SolverModel {
131 fn eval_float(&self, f: Float) -> Option<f64>;
132 fn eval_bool(&self, b: Bool) -> Option<bool>;
133}
134
135/*
136pub trait Solver {
137 fn constrain(&mut self, assertion: &Bool);
138 fn solve(&self) -> Box<dyn SolverModel>;
139}
140*/
141
142use super::types::*;
143
144pub trait Constrainable {
145 type Fixated;
146
147 fn fixate(&self, model: &dyn SolverModel) -> Option<Self::Fixated>;
148}
149
150impl 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
176impl 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
189impl 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
206impl 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
221impl 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
233impl 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
244impl 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
257impl 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
268impl 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 @@
1use super::colors::Color;
2use super::types::Float;
3
4#[derive(Copy, Clone, PartialEq, Debug)]
5#[non_exhaustive]
6pub enum Pattern {
7 Solid(Color),
8 None,
9}
10
11impl Pattern {
12 pub fn is_none(&self) -> bool {
13 match self {
14 Pattern::None => true,
15 _ => false,
16 }
17 }
18}
19
20impl Default for Pattern {
21 fn default() -> Self {
22 Pattern::Solid(Color::default())
23 }
24}
25
26#[derive(Clone, PartialEq, Debug)]
27pub struct FillStyle {
28 pub(crate) pattern: Pattern,
29}
30
31impl FillStyle {
32 pub fn solid(color: Color) -> Self {
33 FillStyle {
34 pattern: Pattern::Solid(color),
35 }
36 }
37}
38
39impl 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)]
49pub struct StrokeStyle {
50 pub(crate) pattern: Pattern,
51 pub(crate) dash: Option<DashStyle>,
52 pub(crate) line_width: Float,
53}
54
55impl 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
69impl Default for StrokeStyle {
70 fn default() -> Self {
71 Self::builder().build()
72 }
73}
74
75#[derive(Clone, Default)]
76pub struct StrokeStyleBuilder {
77 pattern: Option<Pattern>,
78 dash: Option<DashStyle>,
79 line_width: Option<Float>,
80}
81
82impl 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)]
112pub struct DashStyle {
113 pub(crate) dashes: Vec<Float>,
114 pub(crate) offset: Float,
115}
116
117impl 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)]
141pub 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)]
148pub struct DefinedDashStyle {
149 pub(crate) dashes: Vec<f64>,
150 pub(crate) offset: f64,
151}
152
153impl 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 @@
1use super::types::Float;
2
3#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
4#[non_exhaustive]
5pub enum FontStyle {
6 Normal,
7 Oblique,
8 Italic,
9}
10
11impl 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]
19pub enum FontVariant {
20 Normal,
21 SmallCaps,
22}
23
24impl 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]
32pub 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
47impl Default for FontWeight {
48 fn default() -> Self {
49 Self::Normal
50 }
51}
52
53#[derive(Clone, Debug)]
54pub 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)]
64pub struct DefinedFontDescription {
65 pub family: String,
66 pub style: FontStyle,
67 pub weight: FontWeight,
68 pub size: f64,
69}
70
71#[derive(Clone, Debug)]
72pub struct Text {
73 pub content: String,
74 pub font: FontDescription,
75}
76
77#[derive(Clone, Debug)]
78pub 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 @@
1use super::solving::{SolverContext, VariableHandle};
2use 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)]
9pub enum Float {
10 Fixed(f64),
11 Variable(VariableHandle),
12}
13
14impl Default for Float {
15 fn default() -> Self {
16 Float::Fixed(0.)
17 }
18}
19
20impl 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)]
36struct FloatBuilder(Option<Float>);
37
38impl 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)]
50pub struct Bool(VariableHandle);
51
52impl 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)]
63pub struct Point2D {
64 pub(crate) x: Float,
65 pub(crate) y: Float,
66}
67
68impl 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)]
83pub struct DefinedPoint2D {
84 pub(crate) x: f64,
85 pub(crate) y: f64,
86}
87
88#[derive(Clone, Debug)]
89pub 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
97impl 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)]
168pub struct BoundsBuilder {
169 top: FloatBuilder,
170 left: FloatBuilder,
171 width: FloatBuilder,
172 height: FloatBuilder,
173}
174
175impl 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)]
210pub struct Bounded<Shape> {
211 pub bounds: Bounds,
212 pub shape: Shape,
213}
214
215#[derive(Clone, PartialEq, PartialOrd, Debug)]
216pub 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)]
224pub struct DefinitelyBounded<Shape> {
225 pub bounds: DefinedBounds,
226 pub shape: Shape,
227}
228
229#[derive(Clone, Debug)]
230pub struct ShapeContext {
231 pub(crate) bounds: Bounds,
232 pub(crate) fill: FillStyle,
233 pub(crate) stroke: StrokeStyle,
234}
235
236impl 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)]
280pub(crate) struct ShapeContextBuilder {
281 bounds: Option<Bounds>,
282 fill: Option<FillStyle>,
283 stroke: Option<StrokeStyle>,
284}
285
286impl 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)]
312pub struct DefinedShapeContext {
313 pub bounds: DefinedBounds,
314 pub fill: FillStyle,
315 pub stroke: DefinedStrokeStyle,
316}