typst_layout/
shapes.rs

1use std::f64::consts::SQRT_2;
2
3use kurbo::{CubicBez, ParamCurveExtrema};
4use typst_library::diag::{bail, SourceResult};
5use typst_library::engine::Engine;
6use typst_library::foundations::{Content, Packed, Resolve, Smart, StyleChain};
7use typst_library::introspection::Locator;
8use typst_library::layout::{
9    Abs, Axes, Corner, Corners, Frame, FrameItem, Length, Point, Ratio, Region, Rel,
10    Sides, Size,
11};
12use typst_library::visualize::{
13    CircleElem, CloseMode, Curve, CurveComponent, CurveElem, EllipseElem, FillRule,
14    FixedStroke, Geometry, LineElem, Paint, PathElem, PathVertex, PolygonElem, RectElem,
15    Shape, SquareElem, Stroke,
16};
17use typst_syntax::Span;
18use typst_utils::{Get, Numeric};
19
20/// Layout the line.
21#[typst_macros::time(span = elem.span())]
22pub fn layout_line(
23    elem: &Packed<LineElem>,
24    _: &mut Engine,
25    _: Locator,
26    styles: StyleChain,
27    region: Region,
28) -> SourceResult<Frame> {
29    let resolve = |axes: Axes<Rel<Abs>>| axes.zip_map(region.size, Rel::relative_to);
30    let start = resolve(elem.start(styles));
31    let delta = elem.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
32        let length = elem.length(styles);
33        let angle = elem.angle(styles);
34        let x = angle.cos() * length;
35        let y = angle.sin() * length;
36        resolve(Axes::new(x, y))
37    });
38
39    let stroke = elem.stroke(styles).unwrap_or_default();
40    let size = start.max(start + delta).max(Size::zero());
41
42    if !size.is_finite() {
43        bail!(elem.span(), "cannot create line with infinite length");
44    }
45
46    let mut frame = Frame::soft(size);
47    let shape = Geometry::Line(delta.to_point()).stroked(stroke);
48    frame.push(start.to_point(), FrameItem::Shape(shape, elem.span()));
49    Ok(frame)
50}
51
52/// Layout the path.
53#[typst_macros::time(span = elem.span())]
54pub fn layout_path(
55    elem: &Packed<PathElem>,
56    _: &mut Engine,
57    _: Locator,
58    styles: StyleChain,
59    region: Region,
60) -> SourceResult<Frame> {
61    let resolve = |axes: Axes<Rel<Length>>| {
62        axes.resolve(styles).zip_map(region.size, Rel::relative_to).to_point()
63    };
64
65    let vertices = &elem.vertices;
66    let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
67
68    let mut size = Size::zero();
69    if points.is_empty() {
70        return Ok(Frame::soft(size));
71    }
72
73    // Only create a path if there are more than zero points.
74    // Construct a closed path given all points.
75    let mut curve = Curve::new();
76    curve.move_(points[0]);
77
78    let mut add_cubic = |from_point: Point,
79                         to_point: Point,
80                         from: PathVertex,
81                         to: PathVertex| {
82        let from_control_point = resolve(from.control_point_from()) + from_point;
83        let to_control_point = resolve(to.control_point_to()) + to_point;
84        curve.cubic(from_control_point, to_control_point, to_point);
85
86        let p0 = kurbo::Point::new(from_point.x.to_raw(), from_point.y.to_raw());
87        let p1 = kurbo::Point::new(
88            from_control_point.x.to_raw(),
89            from_control_point.y.to_raw(),
90        );
91        let p2 =
92            kurbo::Point::new(to_control_point.x.to_raw(), to_control_point.y.to_raw());
93        let p3 = kurbo::Point::new(to_point.x.to_raw(), to_point.y.to_raw());
94        let extrema = kurbo::CubicBez::new(p0, p1, p2, p3).bounding_box();
95        size.x.set_max(Abs::raw(extrema.x1));
96        size.y.set_max(Abs::raw(extrema.y1));
97    };
98
99    for (vertex_window, point_window) in vertices.windows(2).zip(points.windows(2)) {
100        let from = vertex_window[0];
101        let to = vertex_window[1];
102        let from_point = point_window[0];
103        let to_point = point_window[1];
104
105        add_cubic(from_point, to_point, from, to);
106    }
107
108    if elem.closed(styles) {
109        let from = *vertices.last().unwrap(); // We checked that we have at least one element.
110        let to = vertices[0];
111        let from_point = *points.last().unwrap();
112        let to_point = points[0];
113
114        add_cubic(from_point, to_point, from, to);
115        curve.close();
116    }
117
118    if !size.is_finite() {
119        bail!(elem.span(), "cannot create path with infinite length");
120    }
121
122    // Prepare fill and stroke.
123    let fill = elem.fill(styles);
124    let fill_rule = elem.fill_rule(styles);
125    let stroke = match elem.stroke(styles) {
126        Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
127        Smart::Auto => None,
128        Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
129    };
130
131    let mut frame = Frame::soft(size);
132    let shape = Shape {
133        geometry: Geometry::Curve(curve),
134        stroke,
135        fill,
136        fill_rule,
137    };
138    frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
139    Ok(frame)
140}
141
142/// Layout the curve.
143#[typst_macros::time(span = elem.span())]
144pub fn layout_curve(
145    elem: &Packed<CurveElem>,
146    _: &mut Engine,
147    _: Locator,
148    styles: StyleChain,
149    region: Region,
150) -> SourceResult<Frame> {
151    let mut builder = CurveBuilder::new(region, styles);
152
153    for item in &elem.components {
154        match item {
155            CurveComponent::Move(element) => {
156                let relative = element.relative(styles);
157                let point = builder.resolve_point(element.start, relative);
158                builder.move_(point);
159            }
160
161            CurveComponent::Line(element) => {
162                let relative = element.relative(styles);
163                let point = builder.resolve_point(element.end, relative);
164                builder.line(point);
165            }
166
167            CurveComponent::Quad(element) => {
168                let relative = element.relative(styles);
169                let end = builder.resolve_point(element.end, relative);
170                let control = match element.control {
171                    Smart::Auto => {
172                        control_c2q(builder.last_point, builder.last_control_from)
173                    }
174                    Smart::Custom(Some(p)) => builder.resolve_point(p, relative),
175                    Smart::Custom(None) => end,
176                };
177                builder.quad(control, end);
178            }
179
180            CurveComponent::Cubic(element) => {
181                let relative = element.relative(styles);
182                let end = builder.resolve_point(element.end, relative);
183                let c1 = match element.control_start {
184                    Some(Smart::Custom(p)) => builder.resolve_point(p, relative),
185                    Some(Smart::Auto) => builder.last_control_from,
186                    None => builder.last_point,
187                };
188                let c2 = match element.control_end {
189                    Some(p) => builder.resolve_point(p, relative),
190                    None => end,
191                };
192                builder.cubic(c1, c2, end);
193            }
194
195            CurveComponent::Close(element) => {
196                builder.close(element.mode(styles));
197            }
198        }
199    }
200
201    let (curve, size) = builder.finish();
202    if curve.is_empty() {
203        return Ok(Frame::soft(size));
204    }
205
206    if !size.is_finite() {
207        bail!(elem.span(), "cannot create curve with infinite size");
208    }
209
210    // Prepare fill and stroke.
211    let fill = elem.fill(styles);
212    let fill_rule = elem.fill_rule(styles);
213    let stroke = match elem.stroke(styles) {
214        Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
215        Smart::Auto => None,
216        Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
217    };
218
219    let mut frame = Frame::soft(size);
220    let shape = Shape {
221        geometry: Geometry::Curve(curve),
222        stroke,
223        fill,
224        fill_rule,
225    };
226    frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
227    Ok(frame)
228}
229
230/// Builds a `Curve` from a [`CurveElem`]'s parts.
231struct CurveBuilder<'a> {
232    /// The output curve.
233    curve: Curve,
234    /// The curve's bounds.
235    size: Size,
236    /// The region relative to which points are resolved.
237    region: Region,
238    /// The styles for the curve.
239    styles: StyleChain<'a>,
240    /// The next start point.
241    start_point: Point,
242    /// Mirror of the first cubic start control point (for closing).
243    start_control_into: Point,
244    /// The point we previously ended on.
245    last_point: Point,
246    /// Mirror of the last cubic control point (for auto control points).
247    last_control_from: Point,
248    /// Whether a component has been start. This does not mean that something
249    /// has been added to `self.curve` yet.
250    is_started: bool,
251    /// Whether anything was added to `self.curve` for the current component.
252    is_empty: bool,
253}
254
255impl<'a> CurveBuilder<'a> {
256    /// Create a new curve builder.
257    fn new(region: Region, styles: StyleChain<'a>) -> Self {
258        Self {
259            curve: Curve::new(),
260            size: Size::zero(),
261            region,
262            styles,
263            start_point: Point::zero(),
264            start_control_into: Point::zero(),
265            last_point: Point::zero(),
266            last_control_from: Point::zero(),
267            is_started: false,
268            is_empty: true,
269        }
270    }
271
272    /// Finish building, returning the curve and its bounding size.
273    fn finish(self) -> (Curve, Size) {
274        (self.curve, self.size)
275    }
276
277    /// Move to a point, starting a new segment.
278    fn move_(&mut self, point: Point) {
279        // Delay calling `curve.move` in case there is another move element
280        // before any actual drawing.
281        self.expand_bounds(point);
282        self.start_point = point;
283        self.start_control_into = point;
284        self.last_point = point;
285        self.last_control_from = point;
286        self.is_started = true;
287    }
288
289    /// Add a line segment.
290    fn line(&mut self, point: Point) {
291        if self.is_empty {
292            self.start_component();
293            self.start_control_into = self.start_point;
294        }
295        self.curve.line(point);
296        self.expand_bounds(point);
297        self.last_point = point;
298        self.last_control_from = point;
299    }
300
301    /// Add a quadratic curve segment.
302    fn quad(&mut self, control: Point, end: Point) {
303        let c1 = control_q2c(self.last_point, control);
304        let c2 = control_q2c(end, control);
305        self.cubic(c1, c2, end);
306    }
307
308    /// Add a cubic curve segment.
309    fn cubic(&mut self, c1: Point, c2: Point, end: Point) {
310        if self.is_empty {
311            self.start_component();
312            self.start_control_into = mirror_c(self.start_point, c1);
313        }
314        self.curve.cubic(c1, c2, end);
315
316        let p0 = point_to_kurbo(self.last_point);
317        let p1 = point_to_kurbo(c1);
318        let p2 = point_to_kurbo(c2);
319        let p3 = point_to_kurbo(end);
320        let extrema = CubicBez::new(p0, p1, p2, p3).bounding_box();
321        self.size.x.set_max(Abs::raw(extrema.x1));
322        self.size.y.set_max(Abs::raw(extrema.y1));
323
324        self.last_point = end;
325        self.last_control_from = mirror_c(end, c2);
326    }
327
328    /// Close the curve if it was opened.
329    fn close(&mut self, mode: CloseMode) {
330        if self.is_started && !self.is_empty {
331            if mode == CloseMode::Smooth {
332                self.cubic(
333                    self.last_control_from,
334                    self.start_control_into,
335                    self.start_point,
336                );
337            }
338            self.curve.close();
339            self.last_point = self.start_point;
340            self.last_control_from = self.start_point;
341        }
342        self.is_started = false;
343        self.is_empty = true;
344    }
345
346    /// Push the initial move component.
347    fn start_component(&mut self) {
348        self.curve.move_(self.start_point);
349        self.is_empty = false;
350        self.is_started = true;
351    }
352
353    /// Expand the curve's bounding box.
354    fn expand_bounds(&mut self, point: Point) {
355        self.size.x.set_max(point.x);
356        self.size.y.set_max(point.y);
357    }
358
359    /// Resolve the point relative to the region.
360    fn resolve_point(&self, point: Axes<Rel>, relative: bool) -> Point {
361        let mut p = point
362            .resolve(self.styles)
363            .zip_map(self.region.size, Rel::relative_to)
364            .to_point();
365        if relative {
366            p += self.last_point;
367        }
368        p
369    }
370}
371
372/// Convert a cubic control point into a quadratic one.
373fn control_c2q(p: Point, c: Point) -> Point {
374    1.5 * c - 0.5 * p
375}
376
377/// Convert a quadratic control point into a cubic one.
378fn control_q2c(p: Point, c: Point) -> Point {
379    (p + 2.0 * c) / 3.0
380}
381
382/// Mirror a control point.
383fn mirror_c(p: Point, c: Point) -> Point {
384    2.0 * p - c
385}
386
387/// Convert a point to a `kurbo::Point`.
388fn point_to_kurbo(point: Point) -> kurbo::Point {
389    kurbo::Point::new(point.x.to_raw(), point.y.to_raw())
390}
391
392/// Layout the polygon.
393#[typst_macros::time(span = elem.span())]
394pub fn layout_polygon(
395    elem: &Packed<PolygonElem>,
396    _: &mut Engine,
397    _: Locator,
398    styles: StyleChain,
399    region: Region,
400) -> SourceResult<Frame> {
401    let points: Vec<Point> = elem
402        .vertices
403        .iter()
404        .map(|c| c.resolve(styles).zip_map(region.size, Rel::relative_to).to_point())
405        .collect();
406
407    let size = points.iter().fold(Point::zero(), |max, c| c.max(max)).to_size();
408    if !size.is_finite() {
409        bail!(elem.span(), "cannot create polygon with infinite size");
410    }
411
412    let mut frame = Frame::hard(size);
413
414    // Only create a curve if there are more than zero points.
415    if points.is_empty() {
416        return Ok(frame);
417    }
418
419    // Prepare fill and stroke.
420    let fill = elem.fill(styles);
421    let fill_rule = elem.fill_rule(styles);
422    let stroke = match elem.stroke(styles) {
423        Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
424        Smart::Auto => None,
425        Smart::Custom(stroke) => stroke.map(Stroke::unwrap_or_default),
426    };
427
428    // Construct a closed curve given all points.
429    let mut curve = Curve::new();
430    curve.move_(points[0]);
431    for &point in &points[1..] {
432        curve.line(point);
433    }
434    curve.close();
435
436    let shape = Shape {
437        geometry: Geometry::Curve(curve),
438        stroke,
439        fill,
440        fill_rule,
441    };
442    frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
443    Ok(frame)
444}
445
446/// Lay out the rectangle.
447#[typst_macros::time(span = elem.span())]
448pub fn layout_rect(
449    elem: &Packed<RectElem>,
450    engine: &mut Engine,
451    locator: Locator,
452    styles: StyleChain,
453    region: Region,
454) -> SourceResult<Frame> {
455    layout_shape(
456        engine,
457        locator,
458        styles,
459        region,
460        ShapeKind::Rect,
461        elem.body(styles),
462        elem.fill(styles),
463        elem.stroke(styles),
464        elem.inset(styles),
465        elem.outset(styles),
466        elem.radius(styles),
467        elem.span(),
468    )
469}
470
471/// Lay out the square.
472#[typst_macros::time(span = elem.span())]
473pub fn layout_square(
474    elem: &Packed<SquareElem>,
475    engine: &mut Engine,
476    locator: Locator,
477    styles: StyleChain,
478    region: Region,
479) -> SourceResult<Frame> {
480    layout_shape(
481        engine,
482        locator,
483        styles,
484        region,
485        ShapeKind::Square,
486        elem.body(styles),
487        elem.fill(styles),
488        elem.stroke(styles),
489        elem.inset(styles),
490        elem.outset(styles),
491        elem.radius(styles),
492        elem.span(),
493    )
494}
495
496/// Lay out the ellipse.
497#[typst_macros::time(span = elem.span())]
498pub fn layout_ellipse(
499    elem: &Packed<EllipseElem>,
500    engine: &mut Engine,
501    locator: Locator,
502    styles: StyleChain,
503    region: Region,
504) -> SourceResult<Frame> {
505    layout_shape(
506        engine,
507        locator,
508        styles,
509        region,
510        ShapeKind::Ellipse,
511        elem.body(styles),
512        elem.fill(styles),
513        elem.stroke(styles).map(|s| Sides::splat(Some(s))),
514        elem.inset(styles),
515        elem.outset(styles),
516        Corners::splat(None),
517        elem.span(),
518    )
519}
520
521/// Lay out the circle.
522#[typst_macros::time(span = elem.span())]
523pub fn layout_circle(
524    elem: &Packed<CircleElem>,
525    engine: &mut Engine,
526    locator: Locator,
527    styles: StyleChain,
528    region: Region,
529) -> SourceResult<Frame> {
530    layout_shape(
531        engine,
532        locator,
533        styles,
534        region,
535        ShapeKind::Circle,
536        elem.body(styles),
537        elem.fill(styles),
538        elem.stroke(styles).map(|s| Sides::splat(Some(s))),
539        elem.inset(styles),
540        elem.outset(styles),
541        Corners::splat(None),
542        elem.span(),
543    )
544}
545
546/// A category of shape.
547#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
548enum ShapeKind {
549    /// A rectangle with equal side lengths.
550    Square,
551    /// A quadrilateral with four right angles.
552    Rect,
553    /// An ellipse with coinciding foci.
554    Circle,
555    /// A curve around two focal points.
556    Ellipse,
557}
558
559impl ShapeKind {
560    /// Whether this shape kind is curvy.
561    fn is_round(self) -> bool {
562        matches!(self, Self::Circle | Self::Ellipse)
563    }
564
565    /// Whether this shape kind has equal side length.
566    fn is_quadratic(self) -> bool {
567        matches!(self, Self::Square | Self::Circle)
568    }
569}
570
571/// Layout a shape.
572#[allow(clippy::too_many_arguments)]
573fn layout_shape(
574    engine: &mut Engine,
575    locator: Locator,
576    styles: StyleChain,
577    region: Region,
578    kind: ShapeKind,
579    body: &Option<Content>,
580    fill: Option<Paint>,
581    stroke: Smart<Sides<Option<Option<Stroke<Abs>>>>>,
582    inset: Sides<Option<Rel<Abs>>>,
583    outset: Sides<Option<Rel<Abs>>>,
584    radius: Corners<Option<Rel<Abs>>>,
585    span: Span,
586) -> SourceResult<Frame> {
587    let mut frame;
588    if let Some(child) = body {
589        let mut inset = inset.unwrap_or_default();
590        if kind.is_round() {
591            // Apply extra inset to round shapes.
592            inset = inset.map(|v| v + Ratio::new(0.5 - SQRT_2 / 4.0));
593        }
594        let has_inset = !inset.is_zero();
595
596        // Take the inset, if any, into account.
597        let mut pod = region;
598        if has_inset {
599            pod.size = crate::pad::shrink(region.size, &inset);
600        }
601
602        // If the shape is quadratic, we first measure it to determine its size
603        // and then layout with full expansion to force the aspect ratio and
604        // make sure it's really quadratic.
605        if kind.is_quadratic() {
606            let length = match quadratic_size(pod) {
607                Some(length) => length,
608                None => {
609                    // Take as much as the child wants, but without overflowing.
610                    crate::layout_frame(engine, child, locator.relayout(), styles, pod)?
611                        .size()
612                        .max_by_side()
613                        .min(pod.size.min_by_side())
614                }
615            };
616
617            pod = Region::new(Size::splat(length), Axes::splat(true));
618        }
619
620        // Layout the child.
621        frame = crate::layout_frame(engine, child, locator, styles, pod)?;
622
623        // Apply the inset.
624        if has_inset {
625            crate::pad::grow(&mut frame, &inset);
626        }
627    } else {
628        // The default size that a shape takes on if it has no child and no
629        // forced sizes.
630        let default = Size::new(Abs::pt(45.0), Abs::pt(30.0)).min(region.size);
631
632        let size = if kind.is_quadratic() {
633            Size::splat(match quadratic_size(region) {
634                Some(length) => length,
635                None => default.min_by_side(),
636            })
637        } else {
638            // For each dimension, pick the region size if forced, otherwise
639            // use the default size (or the region size if the default
640            // is too large for the region).
641            region.expand.select(region.size, default)
642        };
643
644        frame = Frame::soft(size);
645    }
646
647    // Prepare stroke.
648    let stroke = match stroke {
649        Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())),
650        Smart::Auto => Sides::splat(None),
651        Smart::Custom(strokes) => {
652            strokes.unwrap_or_default().map(|s| s.map(Stroke::unwrap_or_default))
653        }
654    };
655
656    // Add fill and/or stroke.
657    if fill.is_some() || stroke.iter().any(Option::is_some) {
658        if kind.is_round() {
659            let outset = outset.unwrap_or_default().relative_to(frame.size());
660            let size = frame.size() + outset.sum_by_axis();
661            let pos = Point::new(-outset.left, -outset.top);
662            let shape = Shape {
663                geometry: Geometry::Curve(Curve::ellipse(size)),
664                fill,
665                stroke: stroke.left,
666                fill_rule: FillRule::default(),
667            };
668            frame.prepend(pos, FrameItem::Shape(shape, span));
669        } else {
670            fill_and_stroke(
671                &mut frame,
672                fill,
673                &stroke,
674                &outset.unwrap_or_default(),
675                &radius.unwrap_or_default(),
676                span,
677            );
678        }
679    }
680
681    Ok(frame)
682}
683
684/// Determines the forced size of a quadratic shape based on the region, if any.
685///
686/// The size is forced if at least one axis is expanded because `expand` is
687/// `true` for axes whose size was manually specified by the user.
688fn quadratic_size(region: Region) -> Option<Abs> {
689    if region.expand.x && region.expand.y {
690        // If both `width` and `height` are specified, we choose the
691        // smaller one.
692        Some(region.size.x.min(region.size.y))
693    } else if region.expand.x {
694        Some(region.size.x)
695    } else if region.expand.y {
696        Some(region.size.y)
697    } else {
698        None
699    }
700}
701
702/// Creates a new rectangle as a curve.
703pub fn clip_rect(
704    size: Size,
705    radius: &Corners<Rel<Abs>>,
706    stroke: &Sides<Option<FixedStroke>>,
707    outset: &Sides<Rel<Abs>>,
708) -> Curve {
709    let outset = outset.relative_to(size);
710    let size = size + outset.sum_by_axis();
711
712    let stroke_widths = stroke
713        .as_ref()
714        .map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0));
715
716    let max_radius = (size.x.min(size.y)) / 2.0
717        + stroke_widths.iter().cloned().min().unwrap_or(Abs::zero());
718
719    let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius));
720    let corners = corners_control_points(size, &radius, stroke, &stroke_widths);
721
722    let mut curve = Curve::new();
723    if corners.top_left.arc_inner() {
724        curve.arc_move(
725            corners.top_left.start_inner(),
726            corners.top_left.center_inner(),
727            corners.top_left.end_inner(),
728        );
729    } else {
730        curve.move_(corners.top_left.center_inner());
731    }
732    for corner in [&corners.top_right, &corners.bottom_right, &corners.bottom_left] {
733        if corner.arc_inner() {
734            curve.arc_line(
735                corner.start_inner(),
736                corner.center_inner(),
737                corner.end_inner(),
738            )
739        } else {
740            curve.line(corner.center_inner());
741        }
742    }
743    curve.close();
744    curve.translate(Point::new(-outset.left, -outset.top));
745    curve
746}
747
748/// Add a fill and stroke with optional radius and outset to the frame.
749pub fn fill_and_stroke(
750    frame: &mut Frame,
751    fill: Option<Paint>,
752    stroke: &Sides<Option<FixedStroke>>,
753    outset: &Sides<Rel<Abs>>,
754    radius: &Corners<Rel<Abs>>,
755    span: Span,
756) {
757    let outset = outset.relative_to(frame.size());
758    let size = frame.size() + outset.sum_by_axis();
759    let pos = Point::new(-outset.left, -outset.top);
760    frame.prepend_multiple(
761        styled_rect(size, radius, fill, stroke)
762            .into_iter()
763            .map(|x| (pos, FrameItem::Shape(x, span))),
764    );
765}
766
767/// Create a styled rectangle with shapes.
768/// - use rect primitive for simple rectangles
769/// - stroke sides if possible
770/// - use fill for sides for best looks
771pub fn styled_rect(
772    size: Size,
773    radius: &Corners<Rel<Abs>>,
774    fill: Option<Paint>,
775    stroke: &Sides<Option<FixedStroke>>,
776) -> Vec<Shape> {
777    if stroke.is_uniform() && radius.iter().cloned().all(Rel::is_zero) {
778        simple_rect(size, fill, stroke.top.clone())
779    } else {
780        segmented_rect(size, radius, fill, stroke)
781    }
782}
783
784/// Use rect primitive for the rectangle
785fn simple_rect(
786    size: Size,
787    fill: Option<Paint>,
788    stroke: Option<FixedStroke>,
789) -> Vec<Shape> {
790    vec![Shape {
791        geometry: Geometry::Rect(size),
792        fill,
793        stroke,
794        fill_rule: FillRule::default(),
795    }]
796}
797
798fn corners_control_points(
799    size: Size,
800    radius: &Corners<Abs>,
801    strokes: &Sides<Option<FixedStroke>>,
802    stroke_widths: &Sides<Abs>,
803) -> Corners<ControlPoints> {
804    Corners {
805        top_left: Corner::TopLeft,
806        top_right: Corner::TopRight,
807        bottom_right: Corner::BottomRight,
808        bottom_left: Corner::BottomLeft,
809    }
810    .map(|corner| ControlPoints {
811        radius: radius.get(corner),
812        stroke_before: stroke_widths.get(corner.side_ccw()),
813        stroke_after: stroke_widths.get(corner.side_cw()),
814        corner,
815        size,
816        same: match (
817            strokes.get_ref(corner.side_ccw()),
818            strokes.get_ref(corner.side_cw()),
819        ) {
820            (Some(a), Some(b)) => a.paint == b.paint && a.dash == b.dash,
821            (None, None) => true,
822            _ => false,
823        },
824    })
825}
826
827/// Use stroke and fill for the rectangle
828fn segmented_rect(
829    size: Size,
830    radius: &Corners<Rel<Abs>>,
831    fill: Option<Paint>,
832    strokes: &Sides<Option<FixedStroke>>,
833) -> Vec<Shape> {
834    let mut res = vec![];
835    let stroke_widths = strokes
836        .as_ref()
837        .map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0));
838
839    let max_radius = (size.x.min(size.y)) / 2.0
840        + stroke_widths.iter().cloned().min().unwrap_or(Abs::zero());
841
842    let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius));
843    let corners = corners_control_points(size, &radius, strokes, &stroke_widths);
844
845    // insert stroked sides below filled sides
846    let mut stroke_insert = 0;
847
848    // fill shape with inner curve
849    if let Some(fill) = fill {
850        let mut curve = Curve::new();
851        let c = corners.get_ref(Corner::TopLeft);
852        if c.arc() {
853            curve.arc_move(c.start(), c.center(), c.end());
854        } else {
855            curve.move_(c.center());
856        };
857
858        for corner in [Corner::TopRight, Corner::BottomRight, Corner::BottomLeft] {
859            let c = corners.get_ref(corner);
860            if c.arc() {
861                curve.arc_line(c.start(), c.center(), c.end());
862            } else {
863                curve.line(c.center());
864            }
865        }
866        curve.close();
867        res.push(Shape {
868            geometry: Geometry::Curve(curve),
869            fill: Some(fill),
870            fill_rule: FillRule::default(),
871            stroke: None,
872        });
873        stroke_insert += 1;
874    }
875
876    let current = corners.iter().find(|c| !c.same).map(|c| c.corner);
877    if let Some(mut current) = current {
878        // multiple segments
879        // start at a corner with a change between sides and iterate clockwise all other corners
880        let mut last = current;
881        for _ in 0..4 {
882            current = current.next_cw();
883            if corners.get_ref(current).same {
884                continue;
885            }
886            // create segment
887            let start = last;
888            let end = current;
889            last = current;
890            let Some(stroke) = strokes.get_ref(start.side_cw()) else { continue };
891            let (shape, ontop) = segment(start, end, &corners, stroke);
892            if ontop {
893                res.push(shape);
894            } else {
895                res.insert(stroke_insert, shape);
896                stroke_insert += 1;
897            }
898        }
899    } else if let Some(stroke) = &strokes.top {
900        // single segment
901        let (shape, _) = segment(Corner::TopLeft, Corner::TopLeft, &corners, stroke);
902        res.push(shape);
903    }
904    res
905}
906
907fn curve_segment(
908    start: Corner,
909    end: Corner,
910    corners: &Corners<ControlPoints>,
911    curve: &mut Curve,
912) {
913    // create start corner
914    let c = corners.get_ref(start);
915    if start == end || !c.arc() {
916        curve.move_(c.end());
917    } else {
918        curve.arc_move(c.mid(), c.center(), c.end());
919    }
920
921    // create corners between start and end
922    let mut current = start.next_cw();
923    while current != end {
924        let c = corners.get_ref(current);
925        if c.arc() {
926            curve.arc_line(c.start(), c.center(), c.end());
927        } else {
928            curve.line(c.end());
929        }
930        current = current.next_cw();
931    }
932
933    // create end corner
934    let c = corners.get_ref(end);
935    if !c.arc() {
936        curve.line(c.start());
937    } else if start == end {
938        curve.arc_line(c.start(), c.center(), c.end());
939    } else {
940        curve.arc_line(c.start(), c.center(), c.mid());
941    }
942}
943
944/// Returns the shape for the segment and whether the shape should be drawn on top.
945fn segment(
946    start: Corner,
947    end: Corner,
948    corners: &Corners<ControlPoints>,
949    stroke: &FixedStroke,
950) -> (Shape, bool) {
951    fn fill_corner(corner: &ControlPoints) -> bool {
952        corner.stroke_before != corner.stroke_after
953            || corner.radius() < corner.stroke_before
954    }
955
956    fn fill_corners(
957        start: Corner,
958        end: Corner,
959        corners: &Corners<ControlPoints>,
960    ) -> bool {
961        if fill_corner(corners.get_ref(start)) {
962            return true;
963        }
964        if fill_corner(corners.get_ref(end)) {
965            return true;
966        }
967        let mut current = start.next_cw();
968        while current != end {
969            if fill_corner(corners.get_ref(current)) {
970                return true;
971            }
972            current = current.next_cw();
973        }
974        false
975    }
976
977    let solid = stroke.dash.as_ref().map(|dash| dash.array.is_empty()).unwrap_or(true);
978
979    let use_fill = solid && fill_corners(start, end, corners);
980    let shape = if use_fill {
981        fill_segment(start, end, corners, stroke)
982    } else {
983        stroke_segment(start, end, corners, stroke.clone())
984    };
985
986    (shape, use_fill)
987}
988
989/// Stroke the sides from `start` to `end` clockwise.
990fn stroke_segment(
991    start: Corner,
992    end: Corner,
993    corners: &Corners<ControlPoints>,
994    stroke: FixedStroke,
995) -> Shape {
996    // Create start corner.
997    let mut curve = Curve::new();
998    curve_segment(start, end, corners, &mut curve);
999
1000    Shape {
1001        geometry: Geometry::Curve(curve),
1002        stroke: Some(stroke),
1003        fill: None,
1004        fill_rule: FillRule::default(),
1005    }
1006}
1007
1008/// Fill the sides from `start` to `end` clockwise.
1009fn fill_segment(
1010    start: Corner,
1011    end: Corner,
1012    corners: &Corners<ControlPoints>,
1013    stroke: &FixedStroke,
1014) -> Shape {
1015    let mut curve = Curve::new();
1016
1017    // create the start corner
1018    // begin on the inside and finish on the outside
1019    // no corner if start and end are equal
1020    // half corner if different
1021    if start == end {
1022        let c = corners.get_ref(start);
1023        curve.move_(c.end_inner());
1024        curve.line(c.end_outer());
1025    } else {
1026        let c = corners.get_ref(start);
1027
1028        if c.arc_inner() {
1029            curve.arc_move(c.end_inner(), c.center_inner(), c.mid_inner());
1030        } else {
1031            curve.move_(c.end_inner());
1032        }
1033
1034        if c.arc_outer() {
1035            curve.arc_line(c.mid_outer(), c.center_outer(), c.end_outer());
1036        } else {
1037            curve.line(c.outer());
1038            curve.line(c.end_outer());
1039        }
1040    }
1041
1042    // create the clockwise outside curve for the corners between start and end
1043    let mut current = start.next_cw();
1044    while current != end {
1045        let c = corners.get_ref(current);
1046        if c.arc_outer() {
1047            curve.arc_line(c.start_outer(), c.center_outer(), c.end_outer());
1048        } else {
1049            curve.line(c.outer());
1050        }
1051        current = current.next_cw();
1052    }
1053
1054    // create the end corner
1055    // begin on the outside and finish on the inside
1056    // full corner if start and end are equal
1057    // half corner if different
1058    if start == end {
1059        let c = corners.get_ref(end);
1060        if c.arc_outer() {
1061            curve.arc_line(c.start_outer(), c.center_outer(), c.end_outer());
1062        } else {
1063            curve.line(c.outer());
1064            curve.line(c.end_outer());
1065        }
1066        if c.arc_inner() {
1067            curve.arc_line(c.end_inner(), c.center_inner(), c.start_inner());
1068        } else {
1069            curve.line(c.center_inner());
1070        }
1071    } else {
1072        let c = corners.get_ref(end);
1073        if c.arc_outer() {
1074            curve.arc_line(c.start_outer(), c.center_outer(), c.mid_outer());
1075        } else {
1076            curve.line(c.outer());
1077        }
1078        if c.arc_inner() {
1079            curve.arc_line(c.mid_inner(), c.center_inner(), c.start_inner());
1080        } else {
1081            curve.line(c.center_inner());
1082        }
1083    }
1084
1085    // create the counterclockwise inside curve for the corners between start and end
1086    let mut current = end.next_ccw();
1087    while current != start {
1088        let c = corners.get_ref(current);
1089        if c.arc_inner() {
1090            curve.arc_line(c.end_inner(), c.center_inner(), c.start_inner());
1091        } else {
1092            curve.line(c.center_inner());
1093        }
1094        current = current.next_ccw();
1095    }
1096
1097    curve.close();
1098
1099    Shape {
1100        geometry: Geometry::Curve(curve),
1101        stroke: None,
1102        fill: Some(stroke.paint.clone()),
1103        fill_rule: FillRule::default(),
1104    }
1105}
1106
1107/// Helper to calculate different control points for the corners.
1108/// Clockwise orientation from start to end.
1109/// ```text
1110/// O-------------------EO  ---   - Z: Zero/Origin ({x: 0, y: 0} for top left corner)
1111/// |\   ___----'''     |    |    - O: Outer: intersection between the straight outer lines
1112/// | \ /               |    |    - S_: start
1113/// |  MO               |    |    - M_: midpoint
1114/// | /Z\  __-----------E    |    - E_: end
1115/// |/   \M             |    ro   - r_: radius
1116/// |    /\             |    |    - middle of the stroke
1117/// |   /  \            |    |      - arc from S through M to E with center C and radius r
1118/// |  |    MI--EI-------    |    - outer curve
1119/// |  |  /  \               |      - arc from SO through MO to EO with center CO and radius ro
1120/// SO | |    \         CO  ---   - inner curve
1121/// |  | |     \                    - arc from SI through MI to EI with center CI and radius ri
1122/// |--S-SI-----CI      C
1123///      |--ri--|
1124///    |-------r--------|
1125/// ```
1126struct ControlPoints {
1127    radius: Abs,
1128    stroke_after: Abs,
1129    stroke_before: Abs,
1130    corner: Corner,
1131    size: Size,
1132    same: bool,
1133}
1134
1135impl ControlPoints {
1136    /// Move and rotate the point from top-left to the required corner.
1137    fn rotate(&self, point: Point) -> Point {
1138        match self.corner {
1139            Corner::TopLeft => point,
1140            Corner::TopRight => Point { x: self.size.x - point.y, y: point.x },
1141            Corner::BottomRight => {
1142                Point { x: self.size.x - point.x, y: self.size.y - point.y }
1143            }
1144            Corner::BottomLeft => Point { x: point.y, y: self.size.y - point.x },
1145        }
1146    }
1147
1148    /// Outside intersection of the sides.
1149    pub fn outer(&self) -> Point {
1150        self.rotate(Point { x: -self.stroke_before, y: -self.stroke_after })
1151    }
1152
1153    /// Center for the outer arc.
1154    pub fn center_outer(&self) -> Point {
1155        let r = self.radius_outer();
1156        self.rotate(Point {
1157            x: r - self.stroke_before,
1158            y: r - self.stroke_after,
1159        })
1160    }
1161
1162    /// Center for the middle arc.
1163    pub fn center(&self) -> Point {
1164        let r = self.radius();
1165        self.rotate(Point { x: r, y: r })
1166    }
1167
1168    /// Center for the inner arc.
1169    pub fn center_inner(&self) -> Point {
1170        let r = self.radius_inner();
1171
1172        self.rotate(Point {
1173            x: self.stroke_before + r,
1174            y: self.stroke_after + r,
1175        })
1176    }
1177
1178    /// Radius of the outer arc.
1179    pub fn radius_outer(&self) -> Abs {
1180        self.radius
1181    }
1182
1183    /// Radius of the middle arc.
1184    pub fn radius(&self) -> Abs {
1185        (self.radius - self.stroke_before.min(self.stroke_after)).max(Abs::zero())
1186    }
1187
1188    /// Radius of the inner arc.
1189    pub fn radius_inner(&self) -> Abs {
1190        (self.radius - 2.0 * self.stroke_before.max(self.stroke_after)).max(Abs::zero())
1191    }
1192
1193    /// Middle of the corner on the outside of the stroke.
1194    pub fn mid_outer(&self) -> Point {
1195        let c_i = self.center_inner();
1196        let c_o = self.center_outer();
1197        let o = self.outer();
1198        let r = self.radius_outer();
1199
1200        // https://math.stackexchange.com/a/311956
1201        // intersection between the line from inner center to outside and the outer arc
1202        let a = (o.x - c_i.x).to_raw().powi(2) + (o.y - c_i.y).to_raw().powi(2);
1203        let b = 2.0 * (o.x - c_i.x).to_raw() * (c_i.x - c_o.x).to_raw()
1204            + 2.0 * (o.y - c_i.y).to_raw() * (c_i.y - c_o.y).to_raw();
1205        let c = (c_i.x - c_o.x).to_raw().powi(2) + (c_i.y - c_o.y).to_raw().powi(2)
1206            - r.to_raw().powi(2);
1207        let t = (-b + (b * b - 4.0 * a * c).max(0.0).sqrt()) / (2.0 * a);
1208        c_i + t * (o - c_i)
1209    }
1210
1211    /// Middle of the corner in the middle of the stroke.
1212    pub fn mid(&self) -> Point {
1213        let center = self.center_outer();
1214        let outer = self.outer();
1215        let diff = outer - center;
1216        center + diff / diff.hypot().to_raw() * self.radius().to_raw()
1217    }
1218
1219    /// Middle of the corner on the inside of the stroke.
1220    pub fn mid_inner(&self) -> Point {
1221        let center = self.center_inner();
1222        let outer = self.outer();
1223        let diff = outer - center;
1224        center + diff / diff.hypot().to_raw() * self.radius_inner().to_raw()
1225    }
1226
1227    /// If an outer arc is required.
1228    pub fn arc_outer(&self) -> bool {
1229        self.radius_outer() > Abs::zero()
1230    }
1231
1232    pub fn arc(&self) -> bool {
1233        self.radius() > Abs::zero()
1234    }
1235
1236    /// If an inner arc is required.
1237    pub fn arc_inner(&self) -> bool {
1238        self.radius_inner() > Abs::zero()
1239    }
1240
1241    /// Start of the corner on the outside of the stroke.
1242    pub fn start_outer(&self) -> Point {
1243        self.rotate(Point {
1244            x: -self.stroke_before,
1245            y: self.radius_outer() - self.stroke_after,
1246        })
1247    }
1248
1249    /// Start of the corner in the center of the stroke.
1250    pub fn start(&self) -> Point {
1251        self.rotate(Point::with_y(self.radius()))
1252    }
1253
1254    /// Start of the corner on the inside of the stroke.
1255    pub fn start_inner(&self) -> Point {
1256        self.rotate(Point {
1257            x: self.stroke_before,
1258            y: self.stroke_after + self.radius_inner(),
1259        })
1260    }
1261
1262    /// End of the corner on the outside of the stroke.
1263    pub fn end_outer(&self) -> Point {
1264        self.rotate(Point {
1265            x: self.radius_outer() - self.stroke_before,
1266            y: -self.stroke_after,
1267        })
1268    }
1269
1270    /// End of the corner in the center of the stroke.
1271    pub fn end(&self) -> Point {
1272        self.rotate(Point::with_x(self.radius()))
1273    }
1274
1275    /// End of the corner on the inside of the stroke.
1276    pub fn end_inner(&self) -> Point {
1277        self.rotate(Point {
1278            x: self.stroke_before + self.radius_inner(),
1279            y: self.stroke_after,
1280        })
1281    }
1282}
1283
1284/// Helper to draw arcs with Bézier curves.
1285trait CurveExt {
1286    fn arc(&mut self, start: Point, center: Point, end: Point);
1287    fn arc_move(&mut self, start: Point, center: Point, end: Point);
1288    fn arc_line(&mut self, start: Point, center: Point, end: Point);
1289}
1290
1291impl CurveExt for Curve {
1292    fn arc(&mut self, start: Point, center: Point, end: Point) {
1293        let arc = bezier_arc_control(start, center, end);
1294        self.cubic(arc[0], arc[1], end);
1295    }
1296
1297    fn arc_move(&mut self, start: Point, center: Point, end: Point) {
1298        self.move_(start);
1299        self.arc(start, center, end);
1300    }
1301
1302    fn arc_line(&mut self, start: Point, center: Point, end: Point) {
1303        self.line(start);
1304        self.arc(start, center, end);
1305    }
1306}
1307
1308/// Get the control points for a Bézier curve that approximates a circular arc for
1309/// a start point, an end point and a center of the circle whose arc connects
1310/// the two.
1311fn bezier_arc_control(start: Point, center: Point, end: Point) -> [Point; 2] {
1312    // https://stackoverflow.com/a/44829356/1567835
1313    let a = start - center;
1314    let b = end - center;
1315
1316    let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw();
1317    let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw();
1318    let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2)
1319        / (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw());
1320
1321    let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x);
1322    let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x);
1323
1324    [control_1, control_2]
1325}