use std::f64::consts::SQRT_2;
use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain,
};
use crate::introspection::Locator;
use crate::layout::{
layout_frame, Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point,
Ratio, Region, Rel, Sides, Size, Sizing,
};
use crate::syntax::Span;
use crate::utils::Get;
use crate::visualize::{FixedStroke, Paint, Path, Stroke};
#[elem(title = "Rectangle", Show)]
pub struct RectElem {
pub width: Smart<Rel<Length>>,
pub height: Sizing,
pub fill: Option<Paint>,
#[resolve]
#[fold]
pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
#[resolve]
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[resolve]
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
#[borrowed]
pub body: Option<Content>,
}
impl Show for Packed<RectElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, region| {
layout_shape(
engine,
locator,
styles,
region,
ShapeKind::Rect,
elem.body(styles),
elem.fill(styles),
elem.stroke(styles),
elem.inset(styles),
elem.outset(styles),
elem.radius(styles),
elem.span(),
)
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack()
.spanned(self.span()))
}
}
#[elem(Show)]
pub struct SquareElem {
#[external]
pub size: Smart<Length>,
#[parse(
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
match size {
None => args.named("width")?,
size => size,
}
)]
pub width: Smart<Rel<Length>>,
#[parse(match size {
None => args.named("height")?,
size => size.map(Into::into),
})]
pub height: Sizing,
pub fill: Option<Paint>,
#[resolve]
#[fold]
pub stroke: Smart<Sides<Option<Option<Stroke>>>>,
#[resolve]
#[fold]
pub radius: Corners<Option<Rel<Length>>>,
#[resolve]
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
#[borrowed]
pub body: Option<Content>,
}
impl Show for Packed<SquareElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, regions| {
layout_shape(
engine,
locator,
styles,
regions,
ShapeKind::Square,
elem.body(styles),
elem.fill(styles),
elem.stroke(styles),
elem.inset(styles),
elem.outset(styles),
elem.radius(styles),
elem.span(),
)
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack()
.spanned(self.span()))
}
}
#[elem(Show)]
pub struct EllipseElem {
pub width: Smart<Rel<Length>>,
pub height: Sizing,
pub fill: Option<Paint>,
#[resolve]
#[fold]
pub stroke: Smart<Option<Stroke>>,
#[resolve]
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
#[borrowed]
pub body: Option<Content>,
}
impl Show for Packed<EllipseElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, regions| {
layout_shape(
engine,
locator,
styles,
regions,
ShapeKind::Ellipse,
elem.body(styles),
elem.fill(styles),
elem.stroke(styles).map(|s| Sides::splat(Some(s))),
elem.inset(styles),
elem.outset(styles),
Corners::splat(None),
elem.span(),
)
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack()
.spanned(self.span()))
}
}
#[elem(Show)]
pub struct CircleElem {
#[external]
pub radius: Length,
#[parse(
let size = args
.named::<Smart<Length>>("radius")?
.map(|s| s.map(|r| 2.0 * Rel::from(r)));
match size {
None => args.named("width")?,
size => size,
}
)]
pub width: Smart<Rel<Length>>,
#[parse(match size {
None => args.named("height")?,
size => size.map(Into::into),
})]
pub height: Sizing,
pub fill: Option<Paint>,
#[resolve]
#[fold]
#[default(Smart::Auto)]
pub stroke: Smart<Option<Stroke>>,
#[resolve]
#[fold]
#[default(Sides::splat(Some(Abs::pt(5.0).into())))]
pub inset: Sides<Option<Rel<Length>>>,
#[resolve]
#[fold]
pub outset: Sides<Option<Rel<Length>>>,
#[positional]
#[borrowed]
pub body: Option<Content>,
}
impl Show for Packed<CircleElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
Ok(BlockElem::single_layouter(
self.clone(),
|elem, engine, locator, styles, regions| {
layout_shape(
engine,
locator,
styles,
regions,
ShapeKind::Circle,
elem.body(styles),
elem.fill(styles),
elem.stroke(styles).map(|s| Sides::splat(Some(s))),
elem.inset(styles),
elem.outset(styles),
Corners::splat(None),
elem.span(),
)
},
)
.with_width(self.width(styles))
.with_height(self.height(styles))
.pack()
.spanned(self.span()))
}
}
#[typst_macros::time(span = span)]
#[allow(clippy::too_many_arguments)]
fn layout_shape(
engine: &mut Engine,
locator: Locator,
styles: StyleChain,
region: Region,
kind: ShapeKind,
body: &Option<Content>,
fill: Option<Paint>,
stroke: Smart<Sides<Option<Option<Stroke<Abs>>>>>,
inset: Sides<Option<Rel<Abs>>>,
outset: Sides<Option<Rel<Abs>>>,
radius: Corners<Option<Rel<Abs>>>,
span: Span,
) -> SourceResult<Frame> {
let mut frame;
if let Some(child) = body {
let mut inset = inset.unwrap_or_default();
if kind.is_round() {
inset = inset.map(|v| v + Ratio::new(0.5 - SQRT_2 / 4.0));
}
let has_inset = !inset.is_zero();
let mut pod = region;
if has_inset {
pod.size = crate::layout::shrink(region.size, &inset);
}
frame = layout_frame(engine, child, locator.relayout(), styles, pod)?;
if kind.is_quadratic() {
let length = frame.size().max_by_side().min(pod.size.min_by_side());
let quad_pod = Region::new(Size::splat(length), Axes::splat(true));
frame = layout_frame(engine, child, locator, styles, quad_pod)?;
}
if has_inset {
crate::layout::grow(&mut frame, &inset);
}
} else {
let default = Size::new(Abs::pt(45.0), Abs::pt(30.0));
let mut size = region.expand.select(region.size, default.min(region.size));
if kind.is_quadratic() {
size = Size::splat(size.min_by_side());
}
frame = Frame::soft(size);
}
let stroke = match stroke {
Smart::Auto if fill.is_none() => Sides::splat(Some(FixedStroke::default())),
Smart::Auto => Sides::splat(None),
Smart::Custom(strokes) => {
strokes.unwrap_or_default().map(|s| s.map(Stroke::unwrap_or_default))
}
};
if fill.is_some() || stroke.iter().any(Option::is_some) {
if kind.is_round() {
let outset = outset.unwrap_or_default().relative_to(frame.size());
let size = frame.size() + outset.sum_by_axis();
let pos = Point::new(-outset.left, -outset.top);
let shape = ellipse(size, fill, stroke.left);
frame.prepend(pos, FrameItem::Shape(shape, span));
} else {
frame.fill_and_stroke(
fill,
&stroke,
&outset.unwrap_or_default(),
&radius.unwrap_or_default(),
span,
);
}
}
Ok(frame)
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ShapeKind {
Square,
Rect,
Circle,
Ellipse,
}
impl ShapeKind {
fn is_round(self) -> bool {
matches!(self, Self::Circle | Self::Ellipse)
}
fn is_quadratic(self) -> bool {
matches!(self, Self::Square | Self::Circle)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Shape {
pub geometry: Geometry,
pub fill: Option<Paint>,
pub fill_rule: FillRule,
pub stroke: Option<FixedStroke>,
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum FillRule {
#[default]
NonZero,
EvenOdd,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Geometry {
Line(Point),
Rect(Size),
Path(Path),
}
impl Geometry {
pub fn filled(self, fill: Paint) -> Shape {
Shape {
geometry: self,
fill: Some(fill),
fill_rule: FillRule::default(),
stroke: None,
}
}
pub fn stroked(self, stroke: FixedStroke) -> Shape {
Shape {
geometry: self,
fill: None,
fill_rule: FillRule::default(),
stroke: Some(stroke),
}
}
pub fn bbox_size(&self) -> Size {
match self {
Self::Line(line) => Size::new(line.x, line.y),
Self::Rect(s) => *s,
Self::Path(p) => p.bbox_size(),
}
}
}
pub(crate) fn ellipse(
size: Size,
fill: Option<Paint>,
stroke: Option<FixedStroke>,
) -> Shape {
let z = Abs::zero();
let rx = size.x / 2.0;
let ry = size.y / 2.0;
let m = 0.551784;
let mx = m * rx;
let my = m * ry;
let point = |x, y| Point::new(x + rx, y + ry);
let mut path = Path::new();
path.move_to(point(-rx, z));
path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
Shape {
geometry: Geometry::Path(path),
stroke,
fill,
fill_rule: FillRule::default(),
}
}
pub(crate) fn clip_rect(
size: Size,
radius: &Corners<Rel<Abs>>,
stroke: &Sides<Option<FixedStroke>>,
) -> Path {
let stroke_widths = stroke
.as_ref()
.map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0));
let max_radius = (size.x.min(size.y)) / 2.0
+ stroke_widths.iter().cloned().min().unwrap_or(Abs::zero());
let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius));
let corners = corners_control_points(size, &radius, stroke, &stroke_widths);
let mut path = Path::new();
if corners.top_left.arc_inner() {
path.arc_move(
corners.top_left.start_inner(),
corners.top_left.center_inner(),
corners.top_left.end_inner(),
);
} else {
path.move_to(corners.top_left.center_inner());
}
for corner in [&corners.top_right, &corners.bottom_right, &corners.bottom_left] {
if corner.arc_inner() {
path.arc_line(corner.start_inner(), corner.center_inner(), corner.end_inner())
} else {
path.line_to(corner.center_inner());
}
}
path.close_path();
path
}
pub(crate) fn styled_rect(
size: Size,
radius: &Corners<Rel<Abs>>,
fill: Option<Paint>,
stroke: &Sides<Option<FixedStroke>>,
) -> Vec<Shape> {
if stroke.is_uniform() && radius.iter().cloned().all(Rel::is_zero) {
simple_rect(size, fill, stroke.top.clone())
} else {
segmented_rect(size, radius, fill, stroke)
}
}
fn simple_rect(
size: Size,
fill: Option<Paint>,
stroke: Option<FixedStroke>,
) -> Vec<Shape> {
vec![Shape {
geometry: Geometry::Rect(size),
fill,
stroke,
fill_rule: FillRule::default(),
}]
}
fn corners_control_points(
size: Size,
radius: &Corners<Abs>,
strokes: &Sides<Option<FixedStroke>>,
stroke_widths: &Sides<Abs>,
) -> Corners<ControlPoints> {
Corners {
top_left: Corner::TopLeft,
top_right: Corner::TopRight,
bottom_right: Corner::BottomRight,
bottom_left: Corner::BottomLeft,
}
.map(|corner| ControlPoints {
radius: radius.get(corner),
stroke_before: stroke_widths.get(corner.side_ccw()),
stroke_after: stroke_widths.get(corner.side_cw()),
corner,
size,
same: match (
strokes.get_ref(corner.side_ccw()),
strokes.get_ref(corner.side_cw()),
) {
(Some(a), Some(b)) => a.paint == b.paint && a.dash == b.dash,
(None, None) => true,
_ => false,
},
})
}
fn segmented_rect(
size: Size,
radius: &Corners<Rel<Abs>>,
fill: Option<Paint>,
strokes: &Sides<Option<FixedStroke>>,
) -> Vec<Shape> {
let mut res = vec![];
let stroke_widths = strokes
.as_ref()
.map(|s| s.as_ref().map_or(Abs::zero(), |s| s.thickness / 2.0));
let max_radius = (size.x.min(size.y)) / 2.0
+ stroke_widths.iter().cloned().min().unwrap_or(Abs::zero());
let radius = radius.map(|side| side.relative_to(max_radius * 2.0).min(max_radius));
let corners = corners_control_points(size, &radius, strokes, &stroke_widths);
let mut stroke_insert = 0;
if let Some(fill) = fill {
let mut path = Path::new();
let c = corners.get_ref(Corner::TopLeft);
if c.arc() {
path.arc_move(c.start(), c.center(), c.end());
} else {
path.move_to(c.center());
};
for corner in [Corner::TopRight, Corner::BottomRight, Corner::BottomLeft] {
let c = corners.get_ref(corner);
if c.arc() {
path.arc_line(c.start(), c.center(), c.end());
} else {
path.line_to(c.center());
}
}
path.close_path();
res.push(Shape {
geometry: Geometry::Path(path),
fill: Some(fill),
fill_rule: FillRule::default(),
stroke: None,
});
stroke_insert += 1;
}
let current = corners.iter().find(|c| !c.same).map(|c| c.corner);
if let Some(mut current) = current {
let mut last = current;
for _ in 0..4 {
current = current.next_cw();
if corners.get_ref(current).same {
continue;
}
let start = last;
let end = current;
last = current;
let Some(stroke) = strokes.get_ref(start.side_cw()) else { continue };
let (shape, ontop) = segment(start, end, &corners, stroke);
if ontop {
res.push(shape);
} else {
res.insert(stroke_insert, shape);
stroke_insert += 1;
}
}
} else if let Some(stroke) = &strokes.top {
let (shape, _) = segment(Corner::TopLeft, Corner::TopLeft, &corners, stroke);
res.push(shape);
}
res
}
fn path_segment(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
path: &mut Path,
) {
let c = corners.get_ref(start);
if start == end || !c.arc() {
path.move_to(c.end());
} else {
path.arc_move(c.mid(), c.center(), c.end());
}
let mut current = start.next_cw();
while current != end {
let c = corners.get_ref(current);
if c.arc() {
path.arc_line(c.start(), c.center(), c.end());
} else {
path.line_to(c.end());
}
current = current.next_cw();
}
let c = corners.get_ref(end);
if !c.arc() {
path.line_to(c.start());
} else if start == end {
path.arc_line(c.start(), c.center(), c.end());
} else {
path.arc_line(c.start(), c.center(), c.mid());
}
}
fn segment(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
stroke: &FixedStroke,
) -> (Shape, bool) {
fn fill_corner(corner: &ControlPoints) -> bool {
corner.stroke_before != corner.stroke_after
|| corner.radius() < corner.stroke_before
}
fn fill_corners(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
) -> bool {
if fill_corner(corners.get_ref(start)) {
return true;
}
if fill_corner(corners.get_ref(end)) {
return true;
}
let mut current = start.next_cw();
while current != end {
if fill_corner(corners.get_ref(current)) {
return true;
}
current = current.next_cw();
}
false
}
let solid = stroke
.dash
.as_ref()
.map(|pattern| pattern.array.is_empty())
.unwrap_or(true);
let use_fill = solid && fill_corners(start, end, corners);
let shape = if use_fill {
fill_segment(start, end, corners, stroke)
} else {
stroke_segment(start, end, corners, stroke.clone())
};
(shape, use_fill)
}
fn stroke_segment(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
stroke: FixedStroke,
) -> Shape {
let mut path = Path::new();
path_segment(start, end, corners, &mut path);
Shape {
geometry: Geometry::Path(path),
stroke: Some(stroke),
fill: None,
fill_rule: FillRule::default(),
}
}
fn fill_segment(
start: Corner,
end: Corner,
corners: &Corners<ControlPoints>,
stroke: &FixedStroke,
) -> Shape {
let mut path = Path::new();
if start == end {
let c = corners.get_ref(start);
path.move_to(c.end_inner());
path.line_to(c.end_outer());
} else {
let c = corners.get_ref(start);
if c.arc_inner() {
path.arc_move(c.end_inner(), c.center_inner(), c.mid_inner());
} else {
path.move_to(c.end_inner());
}
if c.arc_outer() {
path.arc_line(c.mid_outer(), c.center_outer(), c.end_outer());
} else {
path.line_to(c.outer());
path.line_to(c.end_outer());
}
}
let mut current = start.next_cw();
while current != end {
let c = corners.get_ref(current);
if c.arc_outer() {
path.arc_line(c.start_outer(), c.center_outer(), c.end_outer());
} else {
path.line_to(c.outer());
}
current = current.next_cw();
}
if start == end {
let c = corners.get_ref(end);
if c.arc_outer() {
path.arc_line(c.start_outer(), c.center_outer(), c.end_outer());
} else {
path.line_to(c.outer());
path.line_to(c.end_outer());
}
if c.arc_inner() {
path.arc_line(c.end_inner(), c.center_inner(), c.start_inner());
} else {
path.line_to(c.center_inner());
}
} else {
let c = corners.get_ref(end);
if c.arc_outer() {
path.arc_line(c.start_outer(), c.center_outer(), c.mid_outer());
} else {
path.line_to(c.outer());
}
if c.arc_inner() {
path.arc_line(c.mid_inner(), c.center_inner(), c.start_inner());
} else {
path.line_to(c.center_inner());
}
}
let mut current = end.next_ccw();
while current != start {
let c = corners.get_ref(current);
if c.arc_inner() {
path.arc_line(c.end_inner(), c.center_inner(), c.start_inner());
} else {
path.line_to(c.center_inner());
}
current = current.next_ccw();
}
path.close_path();
Shape {
geometry: Geometry::Path(path),
stroke: None,
fill: Some(stroke.paint.clone()),
fill_rule: FillRule::default(),
}
}
struct ControlPoints {
radius: Abs,
stroke_after: Abs,
stroke_before: Abs,
corner: Corner,
size: Size,
same: bool,
}
impl ControlPoints {
fn rotate(&self, point: Point) -> Point {
match self.corner {
Corner::TopLeft => point,
Corner::TopRight => Point { x: self.size.x - point.y, y: point.x },
Corner::BottomRight => {
Point { x: self.size.x - point.x, y: self.size.y - point.y }
}
Corner::BottomLeft => Point { x: point.y, y: self.size.y - point.x },
}
}
pub fn outer(&self) -> Point {
self.rotate(Point { x: -self.stroke_before, y: -self.stroke_after })
}
pub fn center_outer(&self) -> Point {
let r = self.radius_outer();
self.rotate(Point {
x: r - self.stroke_before,
y: r - self.stroke_after,
})
}
pub fn center(&self) -> Point {
let r = self.radius();
self.rotate(Point { x: r, y: r })
}
pub fn center_inner(&self) -> Point {
let r = self.radius_inner();
self.rotate(Point {
x: self.stroke_before + r,
y: self.stroke_after + r,
})
}
pub fn radius_outer(&self) -> Abs {
self.radius
}
pub fn radius(&self) -> Abs {
(self.radius - self.stroke_before.min(self.stroke_after)).max(Abs::zero())
}
pub fn radius_inner(&self) -> Abs {
(self.radius - 2.0 * self.stroke_before.max(self.stroke_after)).max(Abs::zero())
}
pub fn mid_outer(&self) -> Point {
let c_i = self.center_inner();
let c_o = self.center_outer();
let o = self.outer();
let r = self.radius_outer();
let a = (o.x - c_i.x).to_raw().powi(2) + (o.y - c_i.y).to_raw().powi(2);
let b = 2.0 * (o.x - c_i.x).to_raw() * (c_i.x - c_o.x).to_raw()
+ 2.0 * (o.y - c_i.y).to_raw() * (c_i.y - c_o.y).to_raw();
let c = (c_i.x - c_o.x).to_raw().powi(2) + (c_i.y - c_o.y).to_raw().powi(2)
- r.to_raw().powi(2);
let t = (-b + (b * b - 4.0 * a * c).max(0.0).sqrt()) / (2.0 * a);
c_i + t * (o - c_i)
}
pub fn mid(&self) -> Point {
let center = self.center_outer();
let outer = self.outer();
let diff = outer - center;
center + diff / diff.hypot().to_raw() * self.radius().to_raw()
}
pub fn mid_inner(&self) -> Point {
let center = self.center_inner();
let outer = self.outer();
let diff = outer - center;
center + diff / diff.hypot().to_raw() * self.radius_inner().to_raw()
}
pub fn arc_outer(&self) -> bool {
self.radius_outer() > Abs::zero()
}
pub fn arc(&self) -> bool {
self.radius() > Abs::zero()
}
pub fn arc_inner(&self) -> bool {
self.radius_inner() > Abs::zero()
}
pub fn start_outer(&self) -> Point {
self.rotate(Point {
x: -self.stroke_before,
y: self.radius_outer() - self.stroke_after,
})
}
pub fn start(&self) -> Point {
self.rotate(Point::with_y(self.radius()))
}
pub fn start_inner(&self) -> Point {
self.rotate(Point {
x: self.stroke_before,
y: self.stroke_after + self.radius_inner(),
})
}
pub fn end_outer(&self) -> Point {
self.rotate(Point {
x: self.radius_outer() - self.stroke_before,
y: -self.stroke_after,
})
}
pub fn end(&self) -> Point {
self.rotate(Point::with_x(self.radius()))
}
pub fn end_inner(&self) -> Point {
self.rotate(Point {
x: self.stroke_before + self.radius_inner(),
y: self.stroke_after,
})
}
}
trait PathExt {
fn arc(&mut self, start: Point, center: Point, end: Point);
fn arc_move(&mut self, start: Point, center: Point, end: Point);
fn arc_line(&mut self, start: Point, center: Point, end: Point);
}
impl PathExt for Path {
fn arc(&mut self, start: Point, center: Point, end: Point) {
let arc = bezier_arc_control(start, center, end);
self.cubic_to(arc[0], arc[1], end);
}
fn arc_move(&mut self, start: Point, center: Point, end: Point) {
self.move_to(start);
self.arc(start, center, end);
}
fn arc_line(&mut self, start: Point, center: Point, end: Point) {
self.line_to(start);
self.arc(start, center, end);
}
}
fn bezier_arc_control(start: Point, center: Point, end: Point) -> [Point; 2] {
let a = start - center;
let b = end - center;
let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw();
let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw();
let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2)
/ (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw());
let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x);
let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x);
[control_1, control_2]
}