planar_geo 0.4.0

A Rust library for 2D geometry: geometric objects, algorithms and visualization
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
/*!
Implementations of visualization functions for geometric types.

If the `draw` feature is enabled, all geometric types ([`Segment`],
[`Polysegment`], [`Contour`] and [`Shape`]) implement a drawing function with
the signature `draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error>`.
This function can be used to draw the type onto a [`cairo::Context`], using the
[`Style`] struct to define the appearance. The vertices / points of the type
are directly used in the coordinate system of the [`cairo::Context`]. For
example, if the [cairo transformation matrix](https://www.cairographics.org/manual/cairo-transformation.html)
matrix does not specify any scaling, a line from (0, 0) to (100, 0) is exactly
100 pixels long when the context is written to a .png file.

To refine the [`Style`] of a type, the following helper structs and enum are
provided by this module:
- [`Style`]: Defines the visual appearance of a type.
- [`LineStyle`]: Defines the stroking pattern for a line, e.g. solid, dashed or
dotted. This enum is used as a field of [`Style`].
- [`Text`]: Defines a text which is placed relative to the geometric type. This
struct is an optional field of [`Style`], but can also be used to display a
"standalone" text via its [`draw`](Text::draw) method (in that case, the text is
placed relative to the origin of the [`Context`](cairo::Context)).
- [`Anchor`]: Defines the orientation of a [`Text`] next to its geometric type
(e.g. on top of it).
*/

use bounding_box::BoundingBox;

use crate::Rotation2;
use crate::composite::Composite;
use crate::contour::Contour;
use crate::polysegment::Polysegment;
use crate::prelude::ArrowHeadSize;
use crate::segment::{ArcSegment, LineSegment, Segment};
use crate::shape::Shape;

impl Segment {
    /**
    Draws the [`Segment`] onto the [`cairo::Context`] with the given [`Style`].

    See the [module level documentation](crate::draw::geometry) for more.
     */
    pub fn draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
        match self {
            Segment::LineSegment(s) => s.draw(style, context),
            Segment::ArcSegment(s) => s.draw(style, context),
        }
    }
}

impl ArcSegment {
    /**
    Draws the [`ArcSegment`] onto the [`cairo::Context`] with the given [`Style`].

    See the [module level documentation](crate::draw::geometry) for more.
     */
    pub fn draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
        let vertex = self.start();
        context.move_to(vertex[0], vertex[1]);
        let radius = self.radius();
        let center = self.center();
        let start_angle = self.start_angle();
        let stop_angle = self.stop_angle();

        // Counter-clockwise arc
        if self.is_positive() {
            context.arc(center[0], center[1], radius, start_angle, stop_angle);
        } else {
            context.arc_negative(center[0], center[1], radius, start_angle, stop_angle);
        }

        // Code shared between line and arc segments
        stroke_line(style, context)?;

        if let Some(txt) = &style.text {
            let bb = if txt.anchor == crate::draw::Anchor::Centroid {
                let c = self.centroid();
                BoundingBox::new(c[0], c[0], c[1], c[1])
            } else {
                BoundingBox::from(self)
            };
            txt.draw_with_bounding_box::<true>(context, &bb)?;
        }
        return Ok(());
    }
}

impl LineSegment {
    /**
    Draws the [`LineSegment`] onto the [`cairo::Context`] with the given [`Style`].

    See the [module level documentation](crate::draw::geometry) for more.
     */
    pub fn draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
        context.move_to(self.start()[0], self.start()[1]);
        context.line_to(self.stop()[0], self.stop()[1]);

        // Code shared between line and arc segments
        stroke_line(style, context)?;

        if let Some(txt) = &style.text {
            let bb = if txt.anchor == crate::draw::Anchor::Centroid {
                let c = self.centroid();
                BoundingBox::new(c[0], c[0], c[1], c[1])
            } else {
                BoundingBox::from(self)
            };
            txt.draw_with_bounding_box::<true>(context, &bb)?;
        }
        return Ok(());
    }
}

/// Draws an uniform grid onto the `context`.
///
/// - `bounding_box` defines the extents of the grid.
/// - `spacing` defines the space between the grid lines. If this value is 0 or
/// negative, no lines are drawn.
/// - `offset` shifts the grid against the natural origin `[0, 0]`.
/// - `style` defines the style of the individual grid lines.
///
/// The image below was drawn using `examples/grid_and_coordinate_system.rs`.
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Grid][example_grid]")]
#[cfg_attr(
    feature = "doc-images",
    embed_doc_image::embed_doc_image("example_grid", "docs/img/example_grid.svg")
)]
#[cfg_attr(
    not(feature = "doc-images"),
    doc = "**Doc images not enabled**. Compile docs with `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
pub fn grid(
    bounding_box: &BoundingBox,
    spacing: f64,
    offset: [f64; 2],
    style: &Style,
    context: &cairo::Context,
) -> Result<(), cairo::Error> {
    fn draw_line(
        x1: f64,
        y1: f64,
        x2: f64,
        y2: f64,
        style: &Style,
        context: &cairo::Context,
    ) -> Result<(), cairo::Error> {
        context.move_to(x1, y1);
        context.line_to(x2, y2);
        stroke_line(style, context)
    }

    // Spacing must be positive, otherwise nothing is drawn
    if spacing <= 0.0 {
        return Ok(());
    }

    // Normalize the offset
    let o_x = offset[0].rem_euclid(spacing);
    let o_y = offset[1].rem_euclid(spacing);

    // Add a bit of additional extents to account for the normalized offset
    let xmin = bounding_box.xmin() - spacing;
    let xmax = bounding_box.xmax() + spacing;
    let ymin = bounding_box.ymin() - spacing;
    let ymax = bounding_box.ymax() + spacing;

    // Vertical lines
    let mut x = (xmin / spacing).ceil() * spacing + o_x;
    while x <= xmax {
        draw_line(x, ymin, x, ymax, style, context)?;
        x += spacing;
    }

    // Horizontal lines
    let mut y = (ymin / spacing).ceil() * spacing + o_y;
    while y <= ymax {
        draw_line(xmin, y, xmax, y, style, context)?;
        y += spacing;
    }

    return Ok(());
}

/// Draws a 2d-coordinate system (two perpendicular arrows aligned to the x- and
/// y-axis) onto the `context`.
///
/// - `origin` defines the origin of the drawn coordinate system in relation to
/// the context coordinate system.
/// - `x` defines the total length of the x-arrow. A negative value inverts the
/// arrow so it points in negative x-direction.
/// - `y` defines the total length of the y-arrow. A negative value inverts the
/// arrow so it points in negative y-direction.
/// - `arrow_head_size` defines the size of the equilateral triangle
/// representing the arrow head. See [`ArrowHeadSize`].
/// - `style` defines the style of the individual grid lines.
///
/// The image below was drawn using `examples/grid_and_coordinate_system.rs`.
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Coordinate system][example_cs]")]
#[cfg_attr(
    feature = "doc-images",
    embed_doc_image::embed_doc_image("example_cs", "docs/img/example_cs.svg")
)]
#[cfg_attr(
    not(feature = "doc-images"),
    doc = "**Doc images not enabled**. Compile docs with `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
pub fn coordinate_system(
    origin: [f64; 2],
    x: f64,
    y: f64,
    arrow_head_size: ArrowHeadSize,
    style: &Style,
    context: &cairo::Context,
) -> Result<(), cairo::Error> {
    let arrow_height = arrow_head_size.height();
    let side_length = arrow_head_size.side_length();

    // arrow x-axis
    let h = if x > 0.0 { arrow_height } else { -arrow_height };
    context.move_to(origin[0], origin[1]);
    context.line_to(origin[0] + x - h, origin[1]);
    context.line_to(origin[0] + x - h, origin[1] + 0.5 * side_length);
    context.line_to(origin[0] + x, origin[1]);
    context.line_to(origin[0] + x - h, origin[1] - 0.5 * side_length);
    context.line_to(origin[0] + x - h, origin[1]);
    context.close_path();
    let fc = &style.background_color;
    context.set_source_rgba(fc.r.into(), fc.g.into(), fc.b.into(), fc.a.into());
    context.fill_preserve()?;
    stroke_line(style, context)?;

    // arrow y-axis
    let h = if y > 0.0 { arrow_height } else { -arrow_height };
    context.move_to(origin[0], origin[1]);
    context.line_to(origin[0], origin[1] + y - h);
    context.line_to(origin[0] + 0.5 * side_length, origin[1] + y - h);
    context.line_to(origin[0], origin[1] + y);
    context.line_to(origin[0] - 0.5 * side_length, origin[1] + y - h);
    context.line_to(origin[0], origin[1] + y - h);
    context.close_path();
    let fc = &style.background_color;
    context.set_source_rgba(fc.r.into(), fc.g.into(), fc.b.into(), fc.a.into());
    context.fill_preserve()?;
    stroke_line(style, context)?;

    return Ok(());
}

fn stroke_line(style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
    // Set the style
    match &style.line_style {
        LineStyle::None => {}
        LineStyle::Dotted => {
            context.set_dash([1.0, 3.0].as_slice(), 0.0);
        }
        LineStyle::Dashed { pattern, offset } => {
            context.set_dash(pattern.as_slice(), *offset);
        }
        LineStyle::Solid => {
            context.set_dash([].as_slice(), 0.0);
        }
    };

    if let LineStyle::None = style.line_style {
        // Do nothing
    } else {
        let lc = &style.line_color;
        context.set_line_width(style.line_width);
        context.set_source_rgba(lc.r.into(), lc.g.into(), lc.b.into(), lc.a.into());
        context.set_line_cap(style.line_cap);

        // Reset the transformation to use the original line width,
        // then stroke, then use the transformation again.
        context.save()?;
        context.identity_matrix();
        context.stroke()?;
        context.restore()?;
    }

    return Ok(());
}

impl Polysegment {
    /**
    Draws the [`Polysegment`] onto the [`cairo::Context`] with the given [`Style`].

    See the [module level documentation](crate::draw::geometry) for more.
     */
    pub fn draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
        return self.draw_stroking::<false, false>(style, context);
    }

    fn draw_stroking<const CLOSE: bool, const FILL: bool>(
        &self,
        style: &Style,
        context: &cairo::Context,
    ) -> Result<(), cairo::Error> {
        self.draw_without_stroking::<CLOSE, FILL>(&style, context)?;
        if let LineStyle::None = style.line_style {
            // Do nothing
        } else {
            let lc = &style.line_color;
            context.set_line_width(style.line_width);
            context.set_source_rgba(lc.r.into(), lc.g.into(), lc.b.into(), lc.a.into());
            context.set_line_cap(style.line_cap);
            context.set_line_join(style.line_join);

            // Reset the transformation to use the original line width,
            // then stroke, then use the transformation again.
            context.save()?;
            context.identity_matrix();
            context.stroke()?;
            context.restore()?;
        }

        if let Some(txt) = &style.text {
            let bb = if txt.anchor == crate::draw::Anchor::Centroid {
                let c = self.centroid();
                BoundingBox::new(c[0], c[0], c[1], c[1])
            } else {
                BoundingBox::from(self)
            };
            txt.draw_with_bounding_box::<true>(context, &bb)?;
        }
        return Ok(());
    }

    fn draw_without_stroking<const CLOSE: bool, const FILL: bool>(
        &self,
        style: &Style,
        context: &cairo::Context,
    ) -> Result<(), cairo::Error> {
        // Initialize the Cairo path
        if let Some(first_segment) = self.front() {
            match &style.line_style {
                LineStyle::None => {}
                LineStyle::Dotted => {
                    context.set_dash([1.0, 3.0].as_slice(), 0.0);
                }
                LineStyle::Dashed { pattern, offset } => {
                    context.set_dash(pattern.as_slice(), *offset);
                }
                LineStyle::Solid => {
                    context.set_dash([].as_slice(), 0.0);
                }
            };

            let vertex = first_segment.start();

            context.move_to(vertex[0], vertex[1]);

            for segment in self.segments() {
                match segment {
                    Segment::LineSegment(ls) => context.line_to(ls.stop()[0], ls.stop()[1]),
                    Segment::ArcSegment(arc) => {
                        let radius = arc.radius();
                        let center = arc.center();
                        let start_angle = arc.start_angle();
                        let stop_angle = arc.stop_angle();

                        // Counter-clockwise arc
                        if arc.is_positive() {
                            context.arc(center[0], center[1], radius, start_angle, stop_angle);
                        } else {
                            context.arc_negative(
                                center[0],
                                center[1],
                                radius,
                                start_angle,
                                stop_angle,
                            );
                        }
                    }
                }
            }
            if CLOSE {
                context.close_path();
                if FILL {
                    let fc = &style.background_color;
                    context.set_source_rgba(fc.r.into(), fc.g.into(), fc.b.into(), fc.a.into());
                    context.fill_preserve()?;
                }
            }
        }
        return Ok(());
    }
}

impl Contour {
    /**
    Draws the [`Contour`] onto the [`cairo::Context`] with the given [`Style`].

    See the [module level documentation](crate::draw::geometry) for more.
     */
    pub fn draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
        return self
            .polysegment()
            .draw_stroking::<true, true>(style, context);
    }
}

impl Shape {
    /**
    Draws the [`Shape`] onto the [`cairo::Context`] with the given [`Style`].

    See the [module level documentation](crate::draw::geometry) for more.
     */
    pub fn draw(&self, style: &Style, context: &cairo::Context) -> Result<(), cairo::Error> {
        let draw_contour = if let LineStyle::None = style.line_style {
            false
        } else {
            true
        };
        let fill_contour = style.background_color.a != 0.0;

        // Skip the entire evaluation in case no border and no background color has been
        // given
        if draw_contour || fill_contour {
            self.contour()
                .polysegment()
                .draw_without_stroking::<true, false>(style, context)?;
            // Fill path while substracting inner paths
            for hole in self.holes() {
                context.new_sub_path();
                hole.polysegment()
                    .draw_without_stroking::<true, false>(style, context)?;
            }

            // Substract the holes regardless of the direction of the polysegment
            // (clockwise or counter-clockwise)
            if fill_contour {
                context.set_fill_rule(cairo::FillRule::EvenOdd);
                let fc = &style.background_color;
                context.set_source_rgba(fc.r.into(), fc.g.into(), fc.b.into(), fc.a.into());
                context.fill_preserve()?;
            }

            if draw_contour {
                let lc = &style.line_color;
                context.set_line_width(style.line_width);
                context.set_source_rgba(lc.r.into(), lc.g.into(), lc.b.into(), lc.a.into());
                context.set_line_cap(style.line_cap);
                context.set_line_join(style.line_join);

                // Reset the transformation to use the original line width,
                // then stroke, then use the transformation again.
                context.save()?;
                context.identity_matrix();
                context.stroke()?;
                context.restore()?;
            }
        }

        if let Some(txt) = &style.text {
            let bb = if txt.anchor == crate::draw::Anchor::Centroid {
                let c = self.centroid();
                BoundingBox::new(c[0], c[0], c[1], c[1])
            } else {
                BoundingBox::from(self)
            };
            txt.draw_with_bounding_box::<true>(context, &bb)?;
        }
        return Ok(());
    }
}

/**
A representation of a color in RGB and A(lpha) values.

When used to draw an object, all values are clamped to [0, 1] by
[cairo](https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-set-source-rgba).

For the color components, 0 means no component and 1 means maximum component.
If e.g. applied to the RGB8 color space, 0 corresponds to 0 and 1 corresponds
to 255.

The alpha value defines the translucency of the color. 0 means that the color
is invisible, while 1 means the color is opaque.
 */
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
    /// Red component.
    pub r: f32,
    /// Green component.
    pub g: f32,
    /// Blue component.
    pub b: f32,
    /// Alpha value of the color.
    pub a: f32,
}

impl Color {
    /**
    Creates a new instance of [`Color`] from its components.
     */
    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
        return Self { r, g, b, a };
    }

    /**
    Creates a new instance of [`Color`] from RGB8 values (integers from 0 to 255).
     */
    pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self {
            r: r as f32 / 255.0,
            g: g as f32 / 255.0,
            b: b as f32 / 255.0,
            a: a as f32 / 255.0,
        }
    }
}

/**
This struct defines the visual appearance of a geometric object - i.e. a
[`Polysegment`], a [`Contour`] or a [`Shape`]. These properties are derived
from the CSS standard, see links in field descriptions.
*/
#[derive(Debug, Clone)]
pub struct Style {
    /// Color of all lines of the geometric object. Corresponds to
    /// "border-color" as defined in <https://www.w3.org/TR/css-backgrounds-3/#border-color>.
    pub line_color: Color,
    /// Fill color for a closed surface, e.g. as they appear in a [`Contour`] or
    /// a [`Shape`]. For segments, this property is meaningless.
    /// Corresponds to "background-color" as defined in
    /// <https://www.w3.org/TR/css-backgrounds-3/#background-color>.
    pub background_color: Color,
    /// Width of all lines of the geometric object in . Corresponds to
    /// "border-width" as defined in <https://www.w3.org/TR/css-backgrounds-3/#the-border-width>.
    pub line_width: f64,
    /// Style of all lines of the geometric object. See docstring of
    /// [`LineStyle`] for an example. Corresponds to "border-style" as defined
    /// in <https://www.w3.org/TR/css-backgrounds-3/#the-border-style>.
    pub line_style: LineStyle,
    /// How the end points of lines are rendered. See
    /// <https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-line-cap-t>.
    pub line_cap: cairo::LineCap,
    /// How the junction points of individual [`Segment`]s are rendered. See
    /// <https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-line-cap-t>.
    pub line_join: cairo::LineJoin,
    /// Specifies a [`Text`] which is displayed next to the geometric object.
    /// The location is defined by [`Text::anchor`],
    /// [`Text::fixed_anchor_offset`] and [`Text::scaled_anchor_offset`].
    /// See the docstring of [`Text`]. The [`Text`] is boxed to minimize the
    /// size of [`Style`] in the common case that no text is used in the
    /// visual representation of a geometric type.
    pub text: Option<Box<Text>>,
}

impl Style {
    /**
    Creates a new instance of [`Style`] from its components.
     */
    pub fn new(
        line_color: Color,
        background_color: Color,
        line_width: f64,
        line_style: LineStyle,
        line_cap: cairo::LineCap,
        line_join: cairo::LineJoin,
        text: Option<Box<Text>>,
    ) -> Self {
        return Style {
            line_color,
            background_color,
            line_width,
            line_style,
            line_cap,
            line_join,
            text,
        };
    }
}

impl Default for Style {
    fn default() -> Self {
        let black = Color::new(0.0, 0.0, 0.0, 1.0);
        let white = Color::new(1.0, 1.0, 1.0, 1.0);
        return Style {
            line_color: black,
            background_color: white,
            line_width: 0.5,
            line_style: LineStyle::Solid,
            line_cap: cairo::LineCap::Round,
            line_join: cairo::LineJoin::Miter,
            text: None,
        };
    }
}

/**
Definition of the line style.
*/
#[doc = ""]
#[cfg_attr(feature = "doc-images", doc = "![Line styles][line_styles]")]
#[cfg_attr(
    feature = "doc-images",
    embed_doc_image::embed_doc_image("line_styles", "docs/img/line_styles.svg")
)]
#[cfg_attr(
    not(feature = "doc-images"),
    doc = "**Doc images not enabled**. Compile docs with
    `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
/**
```rust
use cairo_viewport::{SideLength, Viewport};
use planar_geo::prelude::*;
use cairo;

fn create_fn(cr: &cairo::Context) -> Result<(), cairo::Error> {
    let e = DEFAULT_EPSILON;
    let m = DEFAULT_MAX_ULPS;

    // Set the background to white
    cr.set_source_rgb(1.0, 1.0, 1.0);
    cr.paint()?;

    let mut txt = Text {
        text: "".into(),
        anchor: Anchor::Bottom,
        fixed_anchor_offset: [0.0, -5.0],
        scaled_anchor_offset: [0.0, 0.0],
        color: Color::new(0.0, 0.0, 0.0, 1.0),
        font_size: 16.0,
        angle: 0.0,
    };

    let mut style = Style::default();
    style.line_width = 2.0;
    style.line_cap = cairo::LineCap::Butt;

    let segment = LineSegment::new([0.1, 0.5], [0.7, 0.5], e, m).unwrap();
    txt.text = "Solid".into();
    style.line_style = LineStyle::Solid;
    style.text = Some(Box::new(txt.clone()));
    segment.draw(&style, cr)?;

    let segment = LineSegment::new([0.8, 0.5], [1.4, 0.5], e, m).unwrap();
    txt.text = "Dotted".into();
    style.line_style = LineStyle::Dotted;
    style.text = Some(Box::new(txt.clone()));
    segment.draw(&style, cr)?;

    let segment = LineSegment::new([1.5, 0.5], [2.1, 0.5], e, m).unwrap();
    txt.text = "Dashed".into();
    style.line_style = LineStyle::default_dashed();
    style.text = Some(Box::new(txt.clone()));
    segment.draw(&style, cr)?;

    let segment = LineSegment::new([2.2, 0.5], [2.8, 0.5], e, m).unwrap();
    txt.text = "None".into();
    style.line_style = LineStyle::None;
    style.text = Some(Box::new(txt.clone()));
    segment.draw(&style, cr)?;

    return Ok(());
}

let view = Viewport::from_bounding_box(
    &BoundingBox::new(0.0, 2.9, 0.35, 0.55),
    SideLength::Long(600),
);

// Comment this in to create the image
// view.write_to_file("docs/line_styles.svg", create_fn).unwrap();
#
# assert!(
#     view.compare_or_create(std::path::Path::new("tests/img/line_styles.png"), create_fn, 0.98)
#         .is_ok()
# );
```
 */
#[derive(Debug, Clone)]
pub enum LineStyle {
    /// No line is drawn
    None,
    /// Draws a dotted border. Equal to [`LineStyle::Dashed`] with a pattern of
    /// `(1.0, 3.0)` and an offset of 0.
    Dotted,
    /// Draws a dashed border with the defined pattern. See
    /// <https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-set-dash>.
    Dashed {
        /// Each value of the vector provides the length of alternate "on" and
        /// "off" portions of the dash pattern stroke. If any value is negative
        /// or if all values are 0, the underlying [cairo] library will return
        /// an error.
        pattern: Vec<f64>,
        /// An "off" portion offset which specifies an offset into the `pattern`
        /// at which the stroke begins.
        offset: f64,
    },
    /// Draws a solid line.
    Solid,
}

impl LineStyle {
    /**
    Returns [`LineStyle::Dashed{pattern: vec![4.0, 4.0], offset: 0.0}`](`LineStyle::Dashed`).
    This is a default dash pattern which is symmetric (non-solid and solid
    parts are equal), see [`LineStyle`] docstring.
     */
    pub fn default_dashed() -> Self {
        return LineStyle::Dashed {
            pattern: vec![4.0, 4.0],
            offset: 0.0,
        };
    }
}

/**
A displayable string. Besides the string itself, it also has a couple of
properties which define the placement and depiction of the string on the canvas.

Two properties which warrant further explanation are `fixed_anchor_offset` and
`scaled_anchor_offset`. The two images below show the same two
[`Contour`](crate::prelude::Contour) objects, but the
[`Viewport`](cairo_viewport::Viewport) of the second image has been scaled by
2. On the left side, `scaled_anchor_offset` is set to `(0.3, 0.3)` and on the
right side, `fixed_anchor_offset` is set to `(10, 10)`. Comparing the two images
show that the offset of the text from the top left corner is scaled with the
[`Viewport`](cairo_viewport::Viewport) for the left
[`Contour`](crate::prelude::Contour) , but not for the right
[`Contour`](crate::prelude::Contour). The font size is also not scaled.

If a [`Text`] is used as part of a [`Style`] for a geometric object (such as a
[`Shape`] or [`LineSegment`]), the offsets are applied to the [`Anchor`] of the
object (for example, in the images they are applied to the top left corner of
the contour). If a [`Text`] is drawn on its own, the offset is applied to the
origin of the [`cairo::Context`] coordinates. See docstring of [`Anchor`] for
an example.
*/
#[doc = ""]
#[cfg_attr(
    feature = "doc-images",
    doc = "![Unscaled][anchor_offset_scale_1] ![Scaled][anchor_offset_scale_2]"
)]
#[cfg_attr(
    feature = "doc-images",
    embed_doc_image::embed_doc_image(
        "anchor_offset_scale_1",
        "docs/img/anchor_offset_scale_1.svg"
    )
)]
#[cfg_attr(
    feature = "doc-images",
    embed_doc_image::embed_doc_image(
        "anchor_offset_scale_2",
        "docs/img/anchor_offset_scale_2.svg"
    )
)]
#[cfg_attr(
    not(feature = "doc-images"),
    doc = "**Doc images not enabled**. Compile docs with
    `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
/**

These images were created with the code below:

```rust
use cairo_viewport::{SideLength, Viewport};
use planar_geo::prelude::*;

fn draw_fn() -> Box<dyn FnOnce(&cairo::Context) -> Result<(), cairo::Error>> {
    let e = DEFAULT_EPSILON;
    let m = DEFAULT_MAX_ULPS;

    let mut style = Style::default();
    style.line_color = Color::new(1.0, 0.5, 0.5, 1.0);
    style.line_width = 2.0;

    let mut contour = Contour::new(Polysegment::from_points(&[[0.1, 0.1], [0.9, 0.1], [0.9, 0.9], [0.1, 0.9]]));

    let drawing_fn = move |cr: &cairo::Context| {
        // Set the background to white
        cr.set_source_rgb(1.0, 1.0, 1.0);
        cr.paint()?;

        // Left box
        let txt = Text {
            text: "Scaled offset".into(),
            anchor: Anchor::TopLeft,
            fixed_anchor_offset: [0.0, 0.0],
            scaled_anchor_offset: [0.3, 0.3], // Values in contour scale
            color: Color::new(0.0, 0.0, 0.0, 1.0),
            font_size: 16.0,
            angle: 0.0,
        };
        style.text = Some(Box::new(txt));
        contour.draw(&style, cr)?;

        // Right box
        let txt = Text {
            text: "Fixed offset".into(),
            anchor: Anchor::TopLeft,
            fixed_anchor_offset: [10.0, 10.0], // Values in image scale (pixels)
            scaled_anchor_offset: [0.0, 0.0],
            color: Color::new(0.0, 0.0, 0.0, 1.0),
            font_size: 16.0,
            angle: 0.0,
        };
        style.line_color = Color::new(0.5, 0.8, 0.5, 1.0);
        style.text = Some(Box::new(txt));
        contour.translate([1.0, 0.0]);
        contour.draw(&style, cr)?;

        return Ok(());
    };
    return Box::new(drawing_fn);
}

// Prepare the view for the first image
let mut view =
    Viewport::from_bounding_box(&BoundingBox::new(0.0, 3.0, 0.0, 1.0), SideLength::Long(600));

// Comment this in to create the first image
view.write_to_file("docs/img/anchor_offset_scale_1.svg", draw_fn()).unwrap();
#
# assert!(
#     view.compare_or_create(
#         std::path::Path::new("tests/img/anchor_offset_scale_1.png"),
#         draw_fn(),
#         0.98
#     )
#     .is_ok()
# );

// Zoom into the image
view.scale *= 2.0;

// Comment this in to create the second image
view.write_to_file("docs/img/anchor_offset_scale_2.svg", draw_fn()).unwrap();
#
# assert!(
#    view.compare_or_create(
#         std::path::Path::new("tests/img/anchor_offset_scale_2.png"),
#         draw_fn(),
#         0.98
#     )
#    .is_ok()
# );
```
 */
#[derive(Debug, Clone)]
pub struct Text {
    /// A String containing the displayed text.
    pub text: String,
    /// Specifies the text position relative to a reference bounding box /
    /// point. See the docstring of [`Anchor`] for more.
    pub anchor: Anchor,
    /// Defines an offset from the reference bounding box / origin which
    /// is not scaled with `context.matrix().xx()`. See [`Text`] docstring.
    pub fixed_anchor_offset: [f64; 2],
    /// Defines an offset from the reference bounding box / origin which
    /// is scaled with `context.matrix().xx()`. See [`Text`] docstring.
    pub scaled_anchor_offset: [f64; 2],
    /// Defines the text color.
    pub color: Color,
    /// Defines the text font size in points (pt). If set to a negative value,
    /// the text is not shown. A pt is roughly 0.3528 mm on a screen. The
    /// font size is not scaled with the canvas, see example images in the
    /// [`Text`] docstring.
    pub font_size: f64,
    /// Rotational angle of the text in rad.
    pub angle: f64,
}

impl Text {
    /**
    Creates a new instance of [`Text`] from its components.
     */
    pub fn new(
        text: String,
        anchor: Anchor,
        fixed_anchor_offset: [f64; 2],
        scaled_anchor_offset: [f64; 2],
        color: Color,
        font_size: f64,
        angle: f64,
    ) -> Self {
        return Text {
            text,
            anchor,
            fixed_anchor_offset,
            scaled_anchor_offset,
            color,
            font_size,
            angle,
        };
    }

    /**
    Draw the [`Text`] onto the given [`cairo::Context`].

    The placement of the text is defined by the following fields:
    [`Text::anchor`], [`Text::fixed_anchor_offset`] and
    [`Text::scaled_anchor_offset`]. See the docstrings of [`Text`] and
    [`Anchor`] for details and examples.
     */
    pub fn draw(&self, context: &cairo::Context) -> Result<(), cairo::Error> {
        // This bounding box is a point. Together with INSIDE_BB = false, this
        // results in the text being placed around the point as defined by
        // the anchor.
        let bb = BoundingBox::new(0.0, 0.0, 0.0, 0.0);
        return self.draw_with_bounding_box::<false>(context, &bb);
    }

    /**
    This function performs the actual text drawing. It can be used either to
    draw a text "standalone" (INSIDE_BB = false) or within a bounding box of a
    geometric object (INSIDE_BB = true). In the latter mode, it is used
    throughout the drawing functions of different objects.
     */
    pub(crate) fn draw_with_bounding_box<const INSIDE_BB: bool>(
        &self,
        context: &cairo::Context,
        bounding_box: &BoundingBox,
    ) -> Result<(), cairo::Error> {
        let bb = bounding_box;
        let xmean = (bb.xmin() + bb.xmax()) / 2.0;
        let ymean = (bb.ymin() + bb.ymax()) / 2.0;
        context.set_source_rgba(
            self.color.r.into(),
            self.color.g.into(),
            self.color.b.into(),
            self.color.a.into(),
        );
        let font_size = self.font_size / context.matrix().xx(); // Is equivalent to context.matrix().yy
        context.set_font_size(font_size);

        let extents = context.text_extents(&self.text)?;
        let x: f64;
        let y: f64;

        let anchor = if INSIDE_BB {
            self.anchor
        } else {
            self.anchor.opposite()
        };

        match anchor {
            Anchor::Center | Anchor::Centroid => {
                x = xmean - (extents.width() * 0.5 + extents.x_bearing());
                y = ymean - (extents.height() * 0.5 + extents.y_bearing());
            }
            Anchor::Top => {
                x = xmean - (extents.width() * 0.5 + extents.x_bearing());
                y = bb.ymin() - extents.y_bearing();
            }
            Anchor::TopRight => {
                x = bb.xmax() - (extents.width() + extents.x_bearing());
                y = bb.ymin() - extents.y_bearing();
            }
            Anchor::Right => {
                x = bb.xmax() - (extents.width() + extents.x_bearing());
                y = ymean - (extents.height() * 0.5 + extents.y_bearing());
            }
            Anchor::BottomRight => {
                x = bb.xmax() - (extents.width() + extents.x_bearing());
                y = bb.ymax();
            }
            Anchor::Bottom => {
                x = xmean - (extents.width() * 0.5 + extents.x_bearing());
                y = bb.ymax();
            }
            Anchor::BottomLeft => {
                x = bb.xmin();
                y = bb.ymax();
            }
            Anchor::Left => {
                x = bb.xmin();
                y = ymean - (extents.height() * 0.5 + extents.y_bearing());
            }
            Anchor::TopLeft => {
                x = bb.xmin();
                y = bb.ymin() - extents.y_bearing();
            }
        }

        // Offset to anchor position y/y. Since the user coordinates are already
        // transformed, the fixed offset is transformed back into screen coordinates
        let offset_x =
            self.fixed_anchor_offset[0] / context.matrix().xx() + self.scaled_anchor_offset[0];
        let offset_y =
            self.fixed_anchor_offset[1] / context.matrix().yy() + self.scaled_anchor_offset[1];

        // Short-cut if the text is not rotated
        if self.angle == 0.0 {
            context.new_path();
            context.move_to(x + offset_x, y + offset_y);
            context.show_text(&self.text)?;
        } else {
            /*
            The text needs to be rotated around its center. To rotate a 2d entity (e.g. a point P)
            around a rotation center C, the entity first needs to be translated into a new
            coordinate system where the rotation center equals the origin.
            Then, the points of the entity are rotated by an rotational matrix R and then
            the entity is translated back into its original coordinate system (P').

            P' = R * (P - C) + C

            Since the "rotate" command of Cairo rotates any subsequent commands around the upper left
            corner, the center of the text box needs to be "counter-rotated" before the
            text can be shown.
            */
            // Get the context origin (distance to the upper left corner)
            let x0 = context.matrix().x0() / context.matrix().xx();
            let y0 = context.matrix().y0() / context.matrix().yy();

            // Lower left corner of the text box relative to the upper left context corner
            // (in Cairo coordinates, e.g. with the y-axis pointing down)
            let text_box_ll = [x + offset_x + x0, y + offset_y + y0];

            // Center of the text box
            let center_text_box = [
                text_box_ll[0] + 0.5 * extents.width(),
                text_box_ll[1] - 0.5 * extents.height(),
            ];

            let delta_ll_to_center = [-0.5 * extents.width(), 0.5 * extents.height()];
            let center_text_box_rot = Rotation2::new(-self.angle) * center_text_box; // Counter rotation

            context.save()?;
            context.translate(-x0, -y0); // Reset the Cairo context origin
            context.rotate(self.angle); // Rotate the text

            /*
            center_text_box_rot is rotated by Cairo => compensated by counter rotation,
            lower left corner of the text box appears unchanged in user coordinates.
            Adding delta_ll_to_center => Center of the text box appears unchanged in user coordinates.
            */
            context.move_to(
                center_text_box_rot[0] + delta_ll_to_center[0],
                center_text_box_rot[1] + delta_ll_to_center[1],
            );
            context.show_text(&self.text)?;
            context.restore()?;
        }
        return Ok(());
    }
}

/**
An [`Anchor`] describes the position of a [`Text`] relative to a reference point.

When a [`Text`] object is used as part of a [`Style`] struct to define the
visualization of a geometric object, the [`Anchor`] determines how the text is
placed relative to the bounding box of the geometric object. However, a [`Text`]
can also be shown "standalone" using [`Text::draw`]. In that case, the
[`Anchor`] defines the text placement relative to a point defined by
[`Text::fixed_anchor_offset`] and [`Text::scaled_anchor_offset`]. See docstring
of [`Text`].

The image below shows both cases: On the left side, the red bounding box of an
geometric object and the associated text placement options are shown. On the
right side, the [`Text`] has been drawn "standalone" (the text placement point
is drawn as a red cross).
*/
#[doc = ""]
#[cfg_attr(
    feature = "doc-images",
    doc = "![Text placement options][text_placement]"
)]
#[cfg_attr(
    feature = "doc-images",
    embed_doc_image::embed_doc_image("text_placement", "docs/img/text_placement.svg")
)]
#[cfg_attr(
    not(feature = "doc-images"),
    doc = "**Doc images not enabled**. Compile docs with
    `cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
/**

This image was created with the following code:
```
use cairo_viewport::{SideLength, Viewport};
use planar_geo::prelude::*;

let e = DEFAULT_EPSILON;
let m = DEFAULT_MAX_ULPS;

let mut style = Style::default();
style.line_color = Color::new(1.0, 0.5, 0.5, 1.0);
style.line_width = 2.0;
style.background_color = Color::new(0.0, 0.0, 0.0, 0.0);
let mut txt = Text {
    text: "Placeholder".into(),
    anchor: Anchor::Center,
    fixed_anchor_offset: [0.0, 0.0],
    scaled_anchor_offset: [0.0, 0.0],
    color: Color::new(0.0, 0.0, 0.0, 1.0),
    font_size: 16.0,
    angle: 0.0,
};

// This contour is equivalent to its bounding box, since it is just a rectangle.
let contour = Contour::new(Polysegment::from_points(&[[0.1, 0.1], [1.9, 0.1], [1.9, 0.9], [0.1, 0.9]]));

// A cross on the right of the contour which shows placement of standalone texts
let cc = [2.5, 0.5];
let hori = LineSegment::new([cc[0] - 0.2, cc[1]], [cc[0] + 0.2, cc[1]], e, m)
    .expect("points not identical");
let vert = LineSegment::new([cc[0], cc[1] - 0.2], [cc[0], cc[1] + 0.2], e, m)
    .expect("points not identical");

let view =
    Viewport::from_bounding_box(&BoundingBox::new(0.0, 3.0, 0.0, 1.0), SideLength::Long(600));

// This function can be used to draw the image
let draw_fn = |cr: &cairo::Context| {
    // Set the background to white
    cr.set_source_rgb(1.0, 1.0, 1.0);
    cr.paint()?;

    // Draw the contour first
    contour.draw(&style, cr)?;

    // Draw the cross
    hori.draw(&style, cr)?;
    vert.draw(&style, cr)?;

    // Then draw the contour again with an invisible line color to demonstrate the text placement
    style.line_color = Color::new(0.0, 0.0, 0.0, 0.0);

    let anchors_str = &[
        "Center",
        "TopLeft",
        "Top",
        "TopRight",
        "Right",
        "BottomRight",
        "Bottom",
        "BottomLeft",
        "Left",
    ];
    let anchors = &[
        Anchor::Center,
        Anchor::TopLeft,
        Anchor::Top,
        Anchor::TopRight,
        Anchor::Right,
        Anchor::BottomRight,
        Anchor::Bottom,
        Anchor::BottomLeft,
        Anchor::Left,
    ];
    for (anchor_str, anchor) in anchors_str.iter().zip(anchors.iter()) {
        txt.anchor = *anchor;
        txt.text = anchor_str.to_string();
        style.text = Some(Box::new(txt.clone()));
        contour.draw(&style, cr)?;
    }

    // Show text placement without an associated geometric type
    txt.scaled_anchor_offset = cc;
    let anchors_str = &["TopLeft", "TopRight", "BottomRight", "BottomLeft"];
    let anchors = &[
        Anchor::TopLeft,
        Anchor::TopRight,
        Anchor::BottomRight,
        Anchor::BottomLeft,
    ];
    for (anchor_str, anchor) in anchors_str.iter().zip(anchors.iter()) {
        txt.anchor = *anchor;
        txt.text = anchor_str.to_string();
        txt.draw(cr)?;
    }

    return Ok(());
};

// Comment this in to actually create the shown image
// view.write_to_file("docs/img/text_placement.svg", draw_fn).expect("image could not be created");
# assert!(
#     view.compare_or_create(
#         std::path::Path::new("tests/img/text_placement.png"),
#         draw_fn,
#         0.95
#     )
#     .is_ok()
# );
```
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Anchor {
    /// If used to style a geometric type, the text is placed at its centroid
    /// (center of mass). If used to draw a [`Text`] object directly, this
    /// variant behaves identical to [`Anchor::Center`].
    Centroid,
    /// Center of text extents is placed at the bounding box center / at the
    /// reference point.
    Center,
    /// Top left corner of text extents is placed in the top left corner of the
    /// bounding box / reference point.
    TopLeft,
    /// Top middle of the text extents is placed in the middle of the top edge
    /// of the bounding box / at the top of the reference point.
    Top,
    /// Top right corner of text extents is placed in the top right corner of
    /// the bounding box / reference point.
    TopRight,
    /// Right middle of the text extents is placed in the middle of the right
    /// edge of the bounding box / right to the reference point.
    Right,
    /// Bottom right corner of text extents is placed in the bottom right corner
    /// of the bounding box / reference point.
    BottomRight,
    /// Top middle of the text extents is placed in the middle of the bottom
    /// edge of the bounding box / at the bottom of the reference point.
    Bottom,
    /// Bottom left corner of text extents is placed in the bottom left corner
    /// of the bounding box / reference point.
    BottomLeft,
    /// Left middle of the text extents is placed in the middle of the left edge
    /// of the bounding box / left to the reference point.
    Left,
}

impl Anchor {
    /**
    Returns the "opposite" [`Anchor`]. See the examples below.

    ```
    use planar_geo::draw::Anchor;

    // Centroid and center are not changed
    assert_eq!(Anchor::Centroid.opposite(), Anchor::Centroid);
    assert_eq!(Anchor::Center.opposite(), Anchor::Center);

    // All directional anchors are inverted
    assert_eq!(Anchor::TopLeft.opposite(), Anchor::BottomRight);
    assert_eq!(Anchor::Top.opposite(), Anchor::Bottom);
    assert_eq!(Anchor::TopRight.opposite(), Anchor::BottomLeft);
    assert_eq!(Anchor::Right.opposite(), Anchor::Left);
    assert_eq!(Anchor::BottomRight.opposite(), Anchor::TopLeft);
    assert_eq!(Anchor::Bottom.opposite(), Anchor::Top);
    assert_eq!(Anchor::BottomLeft.opposite(), Anchor::TopRight);
    assert_eq!(Anchor::Left.opposite(), Anchor::Right);
    ```
     */
    pub fn opposite(&self) -> Self {
        match self {
            Anchor::Centroid => Anchor::Centroid,
            Anchor::Center => Anchor::Center,
            Anchor::TopLeft => Anchor::BottomRight,
            Anchor::Top => Anchor::Bottom,
            Anchor::TopRight => Anchor::BottomLeft,
            Anchor::Right => Anchor::Left,
            Anchor::BottomRight => Anchor::TopLeft,
            Anchor::Bottom => Anchor::Top,
            Anchor::BottomLeft => Anchor::TopRight,
            Anchor::Left => Anchor::Right,
        }
    }
}