1use embed_doc_image::embed_doc_image;
4use std::error::Error;
5
6use crate::context::line_filter::LineFilter;
7use crate::errors::ContextError;
9use crate::geo_types::clip::{try_to_geos_geometry, LineClip};
10use crate::geo_types::{shapes, ToGeos};
11use crate::prelude::{Arrangement, HatchPattern, LineHatch, ToSvg};
12use cubic_spline::{Points, SplineOpts};
13use font_kit::font::Font;
14use font_kit::hinting::HintingOptions;
15use geo::map_coords::MapCoords;
16use geo::prelude::BoundingRect;
17use geo_types::{
18 coord, Coord as Coordinate, Geometry, GeometryCollection, LineString, MultiLineString, Point,
19 Polygon, Rect,
20};
21use geos::{Geom, GeometryTypes};
22pub use kurbo::BezPath;
23use kurbo::PathEl;
24pub use kurbo::Point as BezPoint;
25use nalgebra::{Affine2, Matrix3};
26use nannou::prelude::PI_F64;
27use std::f64::consts::PI;
28use std::sync::Arc;
29use svg::Document;
30
31pub mod operation;
32
33use operation::{OPLayer, Operation};
34
35pub mod glyph_proxy;
36
37use glyph_proxy::GlyphProxy;
38
39pub mod typography;
40
41use crate::geo_types::flatten::FlattenPolygons;
42use typography::Typography;
43
44pub mod line_filter;
45
46#[embed_doc_image("context_basic", "images/context_basic.png")]
110#[derive(Clone, Debug)]
111pub struct Context {
112 operations: Vec<Operation>,
113 accuracy: f64,
114 font: Option<Font>,
115 transformation: Option<Affine2<f64>>,
116 stroke_color: Option<String>,
117 outline_stroke: Option<f64>,
118 fill_color: Option<String>,
119 line_join: String,
120 line_cap: String,
121 pen_width: f64,
122 mask: Option<Geometry<f64>>,
123 clip_previous: bool,
124 hatch_pattern: Arc<Box<dyn HatchPattern>>,
125 hatch_angle: f64,
126 hatch_scale: Option<f64>,
127 stroke_filter: Option<Arc<Box<dyn LineFilter>>>,
128 hatch_filter: Option<Arc<Box<dyn LineFilter>>>,
129 stack: Vec<Context>,
130}
131
132impl Context {
133 pub fn stroke_filter(
135 &mut self,
136 filter: Option<Arc<Box<dyn LineFilter>>>, ) -> &mut Self {
138 self.stroke_filter = filter;
143 self
144 }
145
146 pub fn hatch_filter(
147 &mut self,
148 filter: Option<Arc<Box<dyn LineFilter>>>, ) -> &mut Self {
150 self.hatch_filter = filter;
151 self
152 }
153
154 pub fn accuracy(&mut self, accuracy: f64) -> &mut Self {
156 self.accuracy = accuracy;
157 self
158 }
159
160 pub fn default_font() -> Font {
162 let font_data =
163 include_bytes!("../../resources/fonts/ReliefSingleLine-Regular.ttf").to_vec();
164 Font::from_bytes(Arc::new(font_data), 0).unwrap() }
166
167 pub fn finalize_arrangement(&self, arrangement: &Arrangement<f64>) -> Arrangement<f64> {
169 if let Ok(bounds) = self.bounds() {
170 arrangement.finalize(&bounds)
171 } else {
172 Arrangement::unit(&arrangement.viewbox())
173 }
174 }
175
176 pub fn viewbox(x0: f64, y0: f64, x1: f64, y1: f64) -> Rect<f64> {
179 Rect::new(coord! {x: x0, y: y0}, coord! {x: x1, y: y1})
180 }
181
182 pub fn scale_matrix(sx: f64, sy: f64) -> Affine2<f64> {
184 Affine2::from_matrix_unchecked(Matrix3::new(sx, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 1.0))
185 }
186
187 pub fn unit_matrix() -> Affine2<f64> {
189 Affine2::from_matrix_unchecked(Matrix3::new(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0))
190 }
191
192 pub fn translate_matrix(tx: f64, ty: f64) -> Affine2<f64> {
194 Affine2::from_matrix_unchecked(Matrix3::new(1.0, 0.0, tx, 0.0, 1.0, ty, 0.0, 0.0, 1.0))
195 }
196
197 pub fn rotate_matrix(degrees: f64) -> Affine2<f64> {
200 let angle = PI * (degrees / 180.0);
201 Affine2::from_matrix_unchecked(Matrix3::new(
202 angle.cos(),
203 -angle.sin(),
204 0.0,
205 angle.sin(),
206 angle.cos(),
207 0.0,
208 0.0,
209 0.0,
210 1.0,
211 ))
212 }
213
214 pub fn new() -> Context {
216 Context {
217 operations: vec![],
218 accuracy: 0.1, transformation: None,
220 font: Some(Context::default_font()),
221 stroke_color: Some("black".to_string()),
222 outline_stroke: None,
223 fill_color: Some("black".to_string()),
224 line_join: "round".to_string(),
225 line_cap: "round".to_string(),
226 pen_width: 0.5,
227 mask: None,
228 clip_previous: false,
229 hatch_pattern: Arc::new(Box::new(LineHatch {})), hatch_angle: 0.0,
231 hatch_scale: None,
232 stroke_filter: None,
233 hatch_filter: None,
234 stack: vec![],
235 }
236 }
237
238 pub fn bounds(&self) -> Result<Rect<f64>, Box<dyn Error>> {
242 let mut pmin = Point::new(f64::MAX, f64::MAX);
243 let mut pmax = Point::new(f64::MIN, f64::MIN);
244 for operation in &self.operations {
245 let tmp_bounds = operation.content.bounding_rect();
246 if let Some(bounds) = tmp_bounds {
247 pmin = Point::new(pmin.y().min(bounds.min().y), pmin.y().min(bounds.min().y));
248 pmax = Point::new(pmax.x().max(bounds.max().x), pmax.y().max(bounds.max().y));
249 }
250 }
251 if pmin == Point::new(f64::MAX, f64::MAX) || pmax == Point::new(f64::MIN, f64::MIN) {
252 Err(Box::new(geo_types::Error::MismatchedGeometry {
253 expected: "Context with content",
254 found: "Empty context",
255 }))
256 } else {
257 Ok(Rect::new(pmin.0, pmax.0))
258 }
259 }
260
261 pub fn mask_poly(
264 &mut self,
265 exterior: Vec<(f64, f64)>,
266 interiors: Vec<Vec<(f64, f64)>>,
267 ) -> &mut Self {
268 let mask = Geometry::Polygon(Polygon::<f64>::new(
269 LineString::new(exterior.iter().map(|(x, y)| coord! {x:*x, y:*y}).collect()),
270 interiors
271 .iter()
272 .map(|interior| {
273 LineString::<f64>::new(
274 interior
275 .iter()
276 .map(|(x, y)| coord! {x:*x, y:*y})
277 .collect::<Vec<Coordinate<f64>>>(),
278 )
279 })
280 .collect(),
281 ));
282 self.set_mask(&Some(mask));
283 self
284 }
285
286 pub fn mask_box(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) -> &mut Self {
287 self.mask_poly(
288 vec![(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)],
289 vec![],
290 )
291 }
292
293 pub fn set_mask(&mut self, mask: &Option<Geometry<f64>>) -> &mut Self {
295 self.mask = match mask {
296 Some(maskgeo) => Some(match &self.transformation {
297 Some(affine) => maskgeo.map_coords(|xy| Operation::xform_coord(&xy, affine)),
298 None => maskgeo.clone(),
299 }),
300 None => mask.clone(),
301 };
302 self
303 }
304
305 pub fn push(&mut self) -> &mut Self {
307 self.stack.push(Self {
308 operations: vec![],
309 accuracy: self.accuracy.clone(),
310 font: match &self.font {
311 Some(font) => Some(font.clone()),
312 None => None,
313 },
314 transformation: match self.transformation.clone() {
315 Some(transformation) => Some(transformation),
316 None => None,
317 },
318 stroke_color: self.stroke_color.clone(),
319 outline_stroke: self.outline_stroke.clone(),
320 fill_color: self.fill_color.clone(),
321 line_join: self.line_join.clone(),
322 line_cap: self.line_cap.clone(),
323 pen_width: self.pen_width.clone(),
324 mask: self.mask.clone(),
325 clip_previous: self.clip_previous.clone(),
326 hatch_pattern: self.hatch_pattern.clone(),
327 hatch_angle: self.hatch_angle,
328 hatch_scale: self.hatch_scale,
329 stroke_filter: self.stroke_filter.clone(),
330 hatch_filter: self.hatch_filter.clone(),
331 stack: vec![],
332 });
333 self
334 }
335
336 pub fn pop(&mut self) -> Result<&mut Self, ContextError> {
338 let other = self.stack.pop().ok_or(ContextError::PoppedEmptyStack)?;
339 self.transformation = match other.transformation.clone() {
340 Some(transformation) => Some(transformation),
341 None => None,
342 };
343 self.accuracy = other.accuracy.clone();
344 self.stroke_color = other.stroke_color.clone();
345 self.outline_stroke = other.outline_stroke.clone();
346 self.fill_color = other.fill_color.clone();
347 self.line_join = other.line_join.clone();
348 self.line_cap = other.line_cap.clone();
349 self.pen_width = other.pen_width.clone();
350 self.hatch_angle = other.hatch_angle;
351 self.clip_previous = other.clip_previous.clone();
352 self.stroke_filter = other.stroke_filter.clone();
353 self.hatch_filter = other.hatch_filter.clone();
354 Ok(self)
355 }
356
357 pub fn transform(&mut self, transformation: Option<&Affine2<f64>>) -> &mut Self {
366 self.transformation = match transformation {
367 Some(tx) => Some(tx.clone()),
368 None => None,
369 };
370 self
371 }
372
373 pub fn mul_transform(&mut self, transformation: &Affine2<f64>) -> &mut Self {
379 let base = match self.transformation.clone() {
380 Some(tx) => tx,
381 None => Context::unit_matrix(),
382 };
383
384 self.transformation = Some(transformation * base);
385 self
386 }
387
388 fn add_operation(&mut self, geometry: Geometry<f64>) {
390 let geometry = geometry.flatten();
391 let op = Operation {
392 content: geometry,
393 rendered: (MultiLineString::new(vec![]), MultiLineString::new(vec![])),
394 accuracy: self.accuracy.clone(),
395 transformation: self.transformation.clone(),
396 stroke_color: self.stroke_color.clone(),
397 outline_stroke: self.outline_stroke.clone(),
398 fill_color: self.fill_color.clone(),
399 line_join: self.line_join.clone(),
400 line_cap: self.line_cap.clone(),
401 pen_width: self.pen_width.clone(),
402 mask: self.mask.clone(),
403 clip_previous: self.clip_previous.clone(),
404 hatch_pattern: self.hatch_pattern.clone(),
405 hatch_angle: self.hatch_angle,
406 hatch_scale: self.hatch_scale,
407 stroke_filter: self.stroke_filter.clone(),
408 hatch_filter: self.hatch_filter.clone(),
409 };
410 let op = op.render();
411 self.operations.push(op);
412 }
413
414 pub fn geometry(&mut self, geometry: &Geometry<f64>) -> &mut Self {
417 match &geometry {
418 Geometry::LineString(_ls) => {
419 self.add_operation(geometry.clone());
420 }
421 Geometry::MultiLineString(_mls) => {
422 self.add_operation(geometry.clone());
423 }
424 Geometry::Polygon(_poly) => {
425 self.add_operation(geometry.clone());
426 }
427 Geometry::MultiPolygon(_mp) => {
428 self.add_operation(geometry.clone());
429 }
430 Geometry::Rect(rect) => self.add_operation(Geometry::Polygon(Polygon::new(
431 LineString::new(vec![
432 coord! {x: rect.min().x, y: rect.min().y},
433 coord! {x: rect.max().x, y: rect.min().y},
434 coord! {x: rect.max().x, y: rect.max().y},
435 coord! {x: rect.min().x, y: rect.max().y},
436 coord! {x: rect.min().x, y: rect.min().y},
437 ]),
438 vec![],
439 ))),
440 Geometry::Triangle(tri) => self.add_operation(Geometry::Polygon(Polygon::new(
441 LineString::new(vec![
442 coord! {x: tri.0.x, y:tri.0.y},
443 coord! {x: tri.1.x, y:tri.1.y},
444 coord! {x: tri.2.x, y:tri.2.y},
445 coord! {x: tri.0.x, y:tri.0.y},
446 ]),
447 vec![],
448 ))),
449 Geometry::GeometryCollection(collection) => {
450 for item in collection {
452 self.geometry(item);
454 }
455 }
456 Geometry::Line(line) => self.add_operation(Geometry::LineString(LineString(vec![
457 coord! {x: line.start.x, y: line.start.y},
458 coord! {x: line.end.x, y: line.end.y},
459 ]))),
460 Geometry::Point(pt) => {
461 self.circle(pt.0.x, pt.0.y, self.pen_width / 2.0);
462 }
463 Geometry::MultiPoint(points) => {
464 for pt in points {
465 self.circle(pt.0.x, pt.0.y, self.pen_width / 2.0);
466 }
467 }
468 };
469 self
470 }
471
472 pub fn line(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) -> &mut Self {
474 self.add_operation(Geometry::LineString(LineString::<f64>::new(vec![
475 coord! {x: x0, y: y0},
476 coord! {x: x1, y: y1},
477 ])));
478 self
479 }
480
481 pub fn typography(
483 &mut self,
484 text: &String,
485 x0: f64,
486 y0: f64,
487 typography: &Typography,
488 ) -> &mut Self {
489 let typ = typography.clone();
490 let geo = typ
491 .render(text, self.accuracy)
492 .unwrap_or(Geometry::GeometryCollection(GeometryCollection(vec![])))
493 .map_coords(|co| coord!(x: x0 + co.x.clone(), y: y0 - (co.y.clone())));
494 self.geometry(&geo);
496 self
497 }
498
499 pub fn glyph(&mut self, glyph: char, close: bool) -> &mut Self {
502 if let Some(font) = &self.font {
503 let mut proxy = GlyphProxy::new(close);
504 let glyph_id = font.glyph_for_char(glyph).unwrap_or(32);
505 font.outline(glyph_id, HintingOptions::None, &mut proxy)
506 .unwrap();
507 self.path(&proxy.path());
509 }
510 self
512 }
513
514 pub fn path(&mut self, bezier: &BezPath) -> &mut Self {
519 let mut segments: MultiLineString<f64> = MultiLineString::new(vec![]);
521 let mut lastpoint = kurbo::Point::new(0.0, 0.0);
522 let add_segment = |el: PathEl| match el {
523 PathEl::MoveTo(pos) => {
524 segments
525 .0
526 .push(LineString::new(vec![coord! {x: pos.x, y: pos.y}]));
527 lastpoint = pos.clone();
528 }
529 PathEl::LineTo(pos) => {
530 if let Some(line) = segments.0.last_mut() {
531 line.0.push(coord! {x: pos.x, y: pos.y});
532 }
533 }
534 PathEl::ClosePath => {
535 if let Some(line) = segments.0.last_mut() {
536 line.0.push(coord! {x: lastpoint.x, y: lastpoint.y});
537 }
538 }
539 _ => panic!("Unexpected/Impossible segment type interpolating a bezier path!"),
540 };
541
542 bezier.flatten(self.accuracy, add_segment);
543 let tmp_gtgeo = Geometry::MultiLineString(segments);
544 let tmp_geos = tmp_gtgeo.to_geos();
545 let out_gtgeo: Geometry<f64> = match tmp_geos {
546 Ok(geos_geom) => {
547 if let Ok((poly_geo, _cuts_geo, _dangles_geo, invalid_geo)) =
550 geos_geom.polygonize_full()
551 {
552 let out_gtgeo = match invalid_geo {
558 None => Geometry::try_from(&poly_geo).unwrap_or(tmp_gtgeo.clone()),
559 Some(invalid) => {
560 Geometry::GeometryCollection(GeometryCollection::new_from(vec![
562 Geometry::try_from(&poly_geo).unwrap_or(tmp_gtgeo.clone()),
563 Geometry::try_from(&invalid).unwrap_or(
564 Geometry::GeometryCollection(GeometryCollection::new_from(
565 vec![],
566 )),
567 ),
568 ]))
569 }
570 };
571 out_gtgeo
573 } else {
574 tmp_gtgeo.clone()
576 }
577 }
578 Err(_err) => {
579 tmp_gtgeo.clone()
581 }
582 };
583
584 self.add_operation(out_gtgeo);
586 self
587 }
588
589 pub fn spline(
595 &mut self,
596 points: &Vec<(f64, f64)>,
597 num_interpolated_segments: u32,
598 tension: f64,
599 ) -> &mut Self {
600 let spline_opts = SplineOpts::new()
601 .num_of_segments(num_interpolated_segments)
602 .tension(tension);
603 let points = match Points::try_from(points) {
604 Ok(pts) => pts,
605 Err(_e) => return self,
606 };
607 let spline = cubic_spline::calc_spline(&points, &spline_opts);
608 match spline {
609 Ok(spts) => {
610 self.add_operation(Geometry::LineString(LineString::<f64>::new(
611 spts.get_ref()
612 .iter()
613 .map(|pt| coord! {x: pt.x, y: pt.y})
614 .collect(),
615 )));
616 self
617 }
618 Err(_e) => self,
619 }
620 }
621
622 pub fn arc_center(&mut self, x0: f64, y0: f64, radius: f64, deg0: f64, deg1: f64) -> &mut Self {
626 let ls = crate::geo_types::shapes::arc_center(x0, y0, radius, deg0, deg1);
627 let ls = ls.map_coords(|co| coord!(x: co.x.clone(), y: -co.y.clone()));
628
629 self.add_operation(Geometry::LineString(ls));
631
632 self
633 }
634
635 pub fn rect(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) -> &mut Self {
637 self.add_operation(Geometry::Polygon(Polygon::<f64>::new(
638 LineString::new(vec![
639 coord! {x: x0, y: y0},
640 coord! {x: x1, y: y0},
641 coord! {x: x1, y: y1},
642 coord! {x: x0, y: y1},
643 coord! {x: x0, y: y0},
644 ]),
645 vec![],
646 )));
647 self
648 }
649
650 pub fn poly(
652 &mut self,
653 exterior: Vec<(f64, f64)>,
654 interiors: Vec<Vec<(f64, f64)>>,
655 ) -> &mut Self {
656 self.add_operation(Geometry::Polygon(Polygon::<f64>::new(
657 LineString::new(exterior.iter().map(|(x, y)| coord! {x:*x, y:*y}).collect()),
658 interiors
659 .iter()
660 .map(|interior| {
661 LineString::<f64>::new(
662 interior
663 .iter()
664 .map(|(x, y)| coord! {x:*x, y:*y})
665 .collect::<Vec<Coordinate<f64>>>(),
666 )
667 })
668 .collect(),
669 )));
670 self
671 }
672
673 pub fn circle(&mut self, x0: f64, y0: f64, radius: f64) -> &mut Self {
676 self.add_operation(shapes::circle(x0, y0, radius));
677 self
678 }
679
680 pub fn regular_poly(
683 &mut self,
684 sides: usize,
685 x: f64,
686 y: f64,
687 radius: f64,
688 rotation: f64,
689 ) -> &mut Self {
690 let geo = shapes::regular_poly(sides, x, y, radius, rotation);
691 self.add_operation(geo);
692 self
693 }
694
695 pub fn star_poly(
701 &mut self,
702 sides: usize,
703 x: f64,
704 y: f64,
705 inner_radius: f64,
706 outer_radius: f64,
707 rotation: f64,
708 ) -> &mut Self {
709 if sides < 3 {
710 return self;
711 };
712 let mut exterior = LineString::<f64>::new(vec![]);
713 for i in 0..sides {
714 let angle_a = rotation - PI_F64 / 2.0
715 + (f64::from(i as i32) / f64::from(sides as i32)) * (2.0 * PI_F64);
716 let angle_b = rotation - PI_F64 / 2.0
717 + ((f64::from(i as i32) + 0.5) / f64::from(sides as i32)) * (2.0 * PI_F64);
718 exterior.0.push(coord! {
719 x: x+angle_a.cos() * outer_radius,
720 y: y+angle_a.sin() * outer_radius});
721 exterior.0.push(coord! {
722 x: x+angle_b.cos() * inner_radius,
723 y: y+angle_b.sin() * inner_radius});
724 }
725 exterior.0.push(coord! {
727 x: x+(rotation - PI_F64/2.0).cos() * outer_radius,
728 y: y+(rotation - PI_F64/2.0).sin() * outer_radius});
729 self.add_operation(Geometry::Polygon(Polygon::new(exterior, vec![])));
730 self
731 }
732
733 pub fn clip(&mut self, clip: bool) -> &mut Self {
737 self.clip_previous = clip;
738 self
739 }
740
741 pub fn stroke(&mut self, color: &str) -> &mut Self {
743 self.stroke_color = Some(color.to_string());
744 self
745 }
746
747 pub fn no_stroke(&mut self) -> &mut Self {
748 self.stroke_color = None;
749 self
750 }
751
752 pub fn fill(&mut self, color: &str) -> &mut Self {
754 self.fill_color = Some(color.to_string());
755 self
756 }
757
758 pub fn no_fill(&mut self) -> &mut Self {
759 self.fill_color = None;
760 self
761 }
762
763 pub fn hatch(&mut self, angle: f64) -> &mut Self {
767 self.hatch_angle = angle;
768 self
769 }
770
771 pub fn pen(&mut self, width: f64) -> &mut Self {
773 self.pen_width = width;
774 self
775 }
776
777 pub fn pattern(&mut self, pattern: Arc<Box<dyn HatchPattern>>) -> &mut Self {
780 self.hatch_pattern = pattern;
781 self
782 }
783
784 pub fn hatch_scale(&mut self, scale: Option<f64>) -> &mut Self {
786 self.hatch_scale = scale;
787 self
788 }
789
790 pub fn outline(&mut self, stroke: Option<f64>) -> &mut Self {
795 self.outline_stroke = stroke;
796 self
797 }
798
799 pub fn flatten(&self) -> Self {
811 let mut new_ctx = Context::new();
812 new_ctx.add_operation(Geometry::MultiLineString(MultiLineString::new(vec![])));
813 let mut last_operation = new_ctx.operations[0].clone();
814 let tmp_gt_op = Geometry::GeometryCollection(GeometryCollection::new_from(vec![]));
815 let mut current_geometry = try_to_geos_geometry(&tmp_gt_op).unwrap_or(
816 geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
817 .expect("Failed to generate default geos::geometry"),
818 );
819 for operation in self.operations.iter() {
820 if operation.consistent(&last_operation) {
821 let cgeo = try_to_geos_geometry(&operation.content).unwrap_or(
823 geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
824 .expect("Failed to generate default geos::geometry"),
825 );
826 current_geometry =
827 geos::Geometry::create_geometry_collection(vec![current_geometry, cgeo])
828 .expect("Cannot append geometry into collection.");
829 } else {
830 new_ctx.stroke_color = operation.stroke_color.clone();
832 new_ctx.outline_stroke = operation.outline_stroke.clone();
833 new_ctx.fill_color = operation.fill_color.clone();
834 new_ctx.line_join = operation.line_join.clone();
835 new_ctx.line_cap = operation.line_cap.clone();
836 new_ctx.pen_width = operation.pen_width.clone();
837 new_ctx.clip_previous = operation.clip_previous.clone();
838 new_ctx.hatch_pattern = operation.hatch_pattern.clone();
839 new_ctx.hatch_angle = operation.hatch_angle;
840
841 current_geometry = current_geometry.unary_union().unwrap_or(current_geometry);
842
843 new_ctx.geometry(&geo_types::Geometry::try_from(current_geometry).unwrap_or(
844 Geometry::GeometryCollection(GeometryCollection::new_from(vec![])),
845 ));
846 last_operation = operation.clone();
847 current_geometry = try_to_geos_geometry(&operation.content).unwrap_or(
848 geos::Geometry::create_empty_collection(GeometryTypes::GeometryCollection)
849 .expect("If we failed to convert or fallback, something is very wrong."),
850 );
851 }
852 }
853 current_geometry = current_geometry.unary_union().unwrap_or(current_geometry);
855 new_ctx.geometry(&geo_types::Geometry::try_from(current_geometry).unwrap_or(
856 Geometry::GeometryCollection(GeometryCollection::new_from(vec![])),
857 ));
858 new_ctx
859 }
860
861 pub fn to_geo(&self) -> Result<Geometry<f64>, Box<dyn Error>> {
862 let mut all: Vec<Geometry<f64>> = vec![];
863 for operation in &self.operations {
864 all.push(operation.content.clone().into());
865 }
866 Ok(Geometry::GeometryCollection(
867 GeometryCollection::<f64>::new_from(all),
868 ))
869 }
870
871 pub fn to_layers(&self) -> Vec<OPLayer> {
873 let mut oplayers: Vec<OPLayer> = vec![];
874 for op in &self.operations {
875 let (mut stroke, mut fill) = op.rendered.clone();
876 stroke.0.retain(|item| item.0.len() > 1);
878 fill.0.retain(|item| item.0.len() > 1);
879
880 oplayers.push(OPLayer {
881 stroke_lines: stroke,
882 fill_lines: fill,
883 stroke: op.stroke_color.clone(),
884 fill: op.fill_color.clone(),
885 stroke_width: op.pen_width,
886 stroke_linejoin: op.line_join.clone(),
887 stroke_linecap: op.line_cap.clone(),
888 });
889 }
890 assert_eq!(&self.operations.len(), &oplayers.len());
891
892 if self.operations.len() > 1 {
895 for i in 0..(self.operations.len() - 1) {
896 for j in (i + 1)..self.operations.len() {
897 if self.operations[j].clip_previous {
898 oplayers[i].stroke_lines =
899 Geometry::MultiLineString(oplayers[i].stroke_lines.clone())
900 .clipwith(&self.operations[j].content)
901 .unwrap_or(MultiLineString::<f64>::new(vec![]));
902 oplayers[i].fill_lines =
903 Geometry::MultiLineString(oplayers[i].fill_lines.clone())
904 .clipwith(&self.operations[j].content)
905 .unwrap_or(oplayers[i].fill_lines.clone());
906 }
907 }
908 }
909 }
910 oplayers
911 }
912
913 pub fn to_svg(&self, arrangement: &Arrangement<f64>) -> Result<Document, ContextError> {
915 let oplayers = self.to_layers();
916
917 let mut svg =
918 arrangement
919 .create_svg_document()
920 .or(Err(ContextError::SvgGenerationError(
921 "Failed to create raw svg doc".into(),
922 )
923 .into()))?;
924
925 let mut id = 0;
926 for oplayer in oplayers {
927 if !oplayer.stroke_lines.0.is_empty() {
928 let optimizer = crate::optimizer::Optimizer::new(
929 oplayer.stroke_width * 2.,
930 crate::optimizer::OptimizationStrategy::Greedy,
931 );
932
933 let slines_opt = optimizer.optimize(&optimizer.merge(&oplayer.stroke_lines));
934 let slines = slines_opt.to_path(&arrangement);
935 svg = svg.add(
936 slines
937 .set("id", format!("outline-{}", id))
938 .set("fill", "none")
939 .set(
940 "stroke",
941 match &oplayer.stroke {
942 Some(color) => color.clone(),
943 None => "none".to_string(),
944 },
945 )
946 .set("stroke-width", oplayer.stroke_width)
947 .set("stroke-linejoin", oplayer.stroke_linejoin.clone())
948 .set("stroke-linecap", oplayer.stroke_linecap.clone()),
949 );
950 }
951 if !oplayer.fill_lines.0.is_empty() {
952 let optimizer = crate::optimizer::Optimizer::new(
953 oplayer.stroke_width,
954 crate::optimizer::OptimizationStrategy::Greedy,
955 );
956 let fill_opt = optimizer.optimize(&oplayer.fill_lines);
957 let flines = fill_opt.to_path(&arrangement);
958 svg = svg.add(
959 flines
960 .set("id", format!("fill-{}", id))
961 .set("fill", "none")
962 .set(
963 "stroke",
964 match &oplayer.stroke {
965 Some(color) => color.clone(),
966 None => "none".to_string(),
967 },
968 )
969 .set("stroke-width", oplayer.stroke_width)
970 .set("stroke-linejoin", oplayer.stroke_linejoin.clone())
971 .set("stroke-linecap", oplayer.stroke_linecap.clone()),
972 );
973 id = id + 1;
974 }
975 }
976 Ok(svg)
977 }
978}
979
980#[cfg(test)]
981mod test {
982 use crate::prelude::NoHatch;
983
984 use super::*;
985 use geo_types::{Rect, Triangle};
986
987 #[test]
988 fn test_context_new() {
989 let context = Context::new();
990 assert_eq!(context.transformation, None);
991 }
992
993 #[test]
994 fn test_minimal_rect() {
995 let mut context = Context::new();
996 context
997 .stroke("red")
998 .pen(0.8)
999 .fill("blue")
1000 .hatch(45.0)
1001 .rect(10.0, 10.0, 50.0, 50.0);
1002 let arrangement = Arrangement::FitCenterMargin(
1003 10.0,
1004 Rect::new(coord! {x: 0.0, y: 0.0}, coord! {x:100.0, y:100.0}),
1005 false,
1006 );
1007 context.to_svg(&arrangement).unwrap();
1008 }
1009
1010 #[test]
1011 fn test_arc_c() {
1012 let mut context = Context::new();
1013 context
1014 .stroke("red")
1015 .pen(0.8)
1016 .fill("blue")
1017 .hatch(45.0)
1018 .arc_center(0.0, 0.0, 10.0, 45.0, 180.0);
1019 let arrangement = Arrangement::unit(&Rect::new(
1020 coord! {x: 0.0, y: 0.0},
1021 coord! {x:100.0, y:100.0},
1022 ));
1023 let svg1 = context.to_svg(&arrangement).unwrap();
1024 let mut context = Context::new();
1025 context
1026 .stroke("red")
1027 .pen(0.8)
1028 .fill("blue")
1029 .hatch(45.0)
1030 .arc_center(0.0, 0.0, 10.0, 180.0, 45.0);
1031 let arrangement = Arrangement::unit(&Rect::new(
1032 coord! {x: 0.0, y: 0.0},
1033 coord! {x:100.0, y:100.0},
1034 ));
1035 let svg2 = context.to_svg(&arrangement).unwrap();
1036 assert_eq!(svg2.to_string(), svg1.to_string());
1038 }
1039
1040 #[test]
1041 fn test_minimal_poly() {
1042 let mut context = Context::new();
1043 context.stroke("red");
1044 context.pen(0.8);
1045 context.fill("blue");
1046 context.hatch(45.0);
1047 context.poly(vec![(10.0, 10.0), (50.0, 10.0), (25.0, 25.0)], vec![]);
1048 let arrangement = Arrangement::FitCenterMargin(
1049 10.0,
1050 Rect::new(coord! {x: 0.0, y: 0.0}, coord! {x:100.0, y:100.0}),
1051 false,
1052 );
1053 context.to_svg(&arrangement).unwrap();
1054 }
1055
1056 #[test]
1057 fn test_regular_poly() {
1058 let mut context = Context::new();
1059 context.stroke("red");
1060 context.pen(0.8);
1061 context.fill("blue");
1062 context.hatch(45.0);
1063 context.regular_poly(4, 50.0, 50.0, 100.0, 0.0);
1064 let arrangement = Arrangement::FitCenterMargin(
1065 10.0,
1066 Rect::new(coord! {x: 0.0, y: 0.0}, coord! {x:100.0, y:100.0}),
1067 false,
1068 );
1069 context.to_svg(&arrangement).unwrap();
1070 }
1071
1072 #[test]
1073 fn test_5_pointed_star() {
1074 let mut context = Context::new();
1075 context
1076 .stroke("red")
1077 .pen(0.8)
1078 .fill("blue")
1079 .hatch(45.0)
1080 .star_poly(5, 50.0, 50.0, 20.0, 40.0, 0.0);
1081 }
1082
1083 #[test]
1084 fn test_flatten_simple() {
1085 let mut context = Context::new();
1086 context.stroke("red");
1087 context.pen(0.5);
1088 context.fill("blue");
1089 context.pattern(Arc::new(Box::new(NoHatch {})));
1090 context.hatch(45.0);
1091 context.rect(10.0, 10.0, 30.0, 30.0);
1092 context.rect(20.0, 20.0, 40.0, 40.0);
1093 context = context.flatten();
1094 let arrangement = Arrangement::unit(&Rect::new(
1095 coord! {x: 0.0, y: 0.0},
1096 coord! {x:100.0, y:100.0},
1097 ));
1098 let svg = context.to_svg(&arrangement).unwrap();
1099 assert_eq!(svg.to_string(),
1100 concat!(
1101 "<svg height=\"100mm\" viewBox=\"0 0 100 100\" width=\"100mm\" xmlns=\"http://www.w3.org/2000/svg\">\n",
1102 "<path d=\"M30,10 L10,10 L10,30 L20,30 L20,40 L40,40 L40,20 L30,20 L30,10\" fill=\"none\" id=\"outline-0\" ",
1103 "stroke=\"red\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.5\"/>\n</svg>"
1104 ));
1105 }
1106
1107 #[test]
1108 fn test_flatten_complex() {
1109 let mut context = Context::new();
1110 context.stroke("red");
1111 context.pen(0.5);
1112 context.fill("blue");
1113 context.pattern(Arc::new(Box::new(NoHatch {})));
1116 context.rect(10.0, 10.0, 30.0, 30.0);
1117 context.rect(20.0, 20.0, 40.0, 40.0);
1118 context.rect(32.0, 32.0, 48.0, 48.0);
1119 context
1120 .stroke("black")
1121 .clip(true)
1122 .rect(22.0, 22.0, 38.0, 38.0);
1123
1124 context = context.flatten();
1125 let arrangement = Arrangement::unit(&Rect::new(
1126 coord! {x: 0.0, y: 0.0},
1127 coord! {x:100.0, y:100.0},
1128 ));
1129 let svg = context.to_svg(&arrangement).unwrap();
1130 println!("svg: {}", svg.to_string());
1131 assert_eq!(
1132 svg.to_string(),
1133 concat!(
1134 "<svg height=\"100mm\" viewBox=\"0 0 100 100\" width=\"100mm\" xmlns=\"http://www.w3.org/2000/svg\">\n",
1135 "<path d=\"\" fill=\"none\" id=\"outline-0\" stroke=\"black\" ",
1136 "stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.5\"/>\n<path d=\"\" fill=\"none\" id=\"fill-0\" stroke=\"black\" stroke-linecap=\"round\" ",
1137 "stroke-linejoin=\"round\" stroke-width=\"0.5\"/>\n<path d=\"M30,10 L10,10 L10,30 L20,30 L20,40 L32,40 L32,48 L48,48 L48,32 L40,32 L40,20 L30,20 L30,10\" ",
1138 "fill=\"none\" id=\"outline-1\" stroke=\"black\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.5\"/>\n<path d=\"\" fill=\"none\" ",
1139 "id=\"fill-1\" stroke=\"black\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.5\"/>\n<path d=\"M22,22 L38,22 L38,38 L22,38 L22,22\" ",
1140 "fill=\"none\" id=\"outline-2\" stroke=\"black\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.5\"/>\n</svg>",
1141 )
1142 );
1143 }
1155
1156 #[test]
1157 fn test_to_geo() {
1158 let mut context = Context::new();
1159 context
1160 .stroke("red")
1161 .pen(0.8)
1162 .fill("blue")
1163 .hatch(45.0)
1164 .rect(10.0, 10.0, 90.0, 90.0);
1165
1166 let foo = context.to_geo().unwrap();
1167 let mut context = Context::new();
1169 context.stroke("green");
1170 context.pen(0.8);
1171 context.fill("purple");
1172 context.geometry(&foo);
1173 let arrangement = Arrangement::unit(&Rect::new(
1174 coord! {x: 0.0, y: 0.0},
1175 coord! {x:100.0, y:100.0},
1176 ));
1177 context.to_svg(&arrangement).unwrap();
1179 }
1181
1182 #[test]
1183 fn test_various_geometry() {
1184 let mut context = Context::new();
1185 context.stroke("red");
1186 context.pen(0.8);
1187 context.fill("blue");
1188 context.pattern(Arc::new(Box::new(NoHatch {})));
1189 context
1190 .hatch(45.0)
1191 .geometry(&Geometry::Polygon(Polygon::new(
1192 LineString::new(vec![
1193 coord! {x: 0.0, y:0.0},
1194 coord! {x: 100.0, y:0.0},
1195 coord! {x:100.0, y:100.0},
1196 coord! {x:0.0, y:100.0},
1197 coord! {x: 0.0, y:0.0},
1198 ]),
1199 vec![],
1200 )))
1201 .geometry(&Geometry::Triangle(Triangle::new(
1202 coord! {x: 0.0, y:0.0},
1203 coord! {x: 100.0, y:0.0},
1204 coord! {x:100.0, y:100.0},
1205 )));
1206 let arrangement = Arrangement::unit(&Rect::new(
1207 coord! {x: 0.0, y: 0.0},
1208 coord! {x:100.0, y:100.0},
1209 ));
1210 let svg = context.to_svg(&arrangement).unwrap();
1211 assert_eq!(svg.to_string(), concat!(
1212 "<svg height=\"100mm\" viewBox=\"0 0 100 100\" width=\"100mm\" xmlns=\"http://www.w3.org/2000/svg\">\n",
1213 "<path d=\"M0,0 L100,0 L100,100 L0,100 L0,0\" fill=\"none\" id=\"outline-0\" stroke=\"red\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.8\"/>\n",
1214 "<path d=\"M0,0 L100,0 L100,100 L0,0\" fill=\"none\" id=\"outline-0\" stroke=\"red\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"0.8\"/>\n",
1215 "</svg>"
1216 ));
1217 }
1218
1219 #[test]
1220 fn test_bezier_path() {
1221 let mut context = Context::new();
1222 let mut path = BezPath::new();
1224 path.move_to(BezPoint::new(20.0, 20.0));
1225 path.line_to(BezPoint::new(80.0, 20.0));
1226 path.curve_to(
1227 BezPoint::new(80.0, 40.0),
1228 BezPoint::new(90.0, 50.0),
1229 BezPoint::new(80.0, 60.0),
1230 );
1231 path.line_to(BezPoint::new(50.0, 80.0));
1232 path.quad_to(BezPoint::new(30.0, 50.0), BezPoint::new(25.0, 30.0));
1233 path.close_path();
1234 context
1235 .stroke("red")
1236 .pen(0.8)
1237 .fill("blue")
1238 .pattern(Arc::new(Box::new(LineHatch {})))
1239 .hatch(45.0)
1240 .path(&path);
1241
1242 let arrangement = Arrangement::unit(&Rect::new(
1243 coord! {x: 0.0, y: 0.0},
1244 coord! {x:100.0, y:100.0},
1245 ));
1246 let _svg = context.to_svg(&arrangement).unwrap();
1247 }
1249
1250 #[test]
1251 fn test_single_glyph() {
1252 let mut context = Context::new();
1253 context
1255 .stroke("red")
1256 .pen(0.8)
1257 .fill("blue")
1258 .pattern(Arc::new(Box::new(LineHatch {})))
1259 .hatch(45.0)
1260 .glyph('X', false)
1261 .glyph('O', false);
1262
1263 let arrangement = Arrangement::unit(&Rect::new(
1264 coord! {x: 0.0, y: 0.0},
1265 coord! {x:100.0, y:100.0},
1266 ));
1267 let _svg = context.to_svg(&arrangement).unwrap();
1268 }
1269}