Commit 78683370 authored by RazrFalcon's avatar RazrFalcon

Added marker support.

parent 07b51916
......@@ -8,17 +8,18 @@ This changelog also contains important changes in dependencies.
## [Unreleased]
### Added
- (resvg) Partial `baseline-shift` support.
- (resvg) `letter-spacing` support.
- Added marker support.
- Partial `baseline-shift` support.
- `letter-spacing` support.
- (qt-backend) `word-spacing` support.
Does not work on the cairo backend.
- (resvg) Keep invisible shapes during *export by ID*.
Required for a proper bbox resolving.
### Fixed
- (usvg) `offset` attribute resolving inside the `stop` element.
- (usvg) Ungrouping of groups with non-inheritable attributes.
- (usvg) `rotate` attribute resolving.
- (usvg) Paths without stroke and fill will no longer be removed.
Required for a proper bbox resolving.
- (svgdom) `stroke-miterlimit` attribute parsing.
- (svgdom) `length` and `number` attribute types parsing.
- (svgdom) `offset` attribute parsing.
......
......@@ -547,19 +547,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "svgdom"
version = "0.15.0"
source = "git+https://github.com/RazrFalcon/svgdom?rev=f53af72#f53af72283fbc6fdfc3ff00a3238f3e4b3e053c1"
source = "git+https://github.com/RazrFalcon/svgdom?rev=6ff5579#6ff557934d84be85f25223c626057df8ebb9004c"
dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"roxmltree 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"simplecss 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=3034692)",
"svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=63fc6da)",
]
[[package]]
name = "svgtypes"
version = "0.3.0"
source = "git+https://github.com/RazrFalcon/svgtypes?rev=3034692#3034692f4ef2dc31f6da62d23c3d363029aeeaca"
source = "git+https://github.com/RazrFalcon/svgtypes?rev=63fc6da#63fc6da5b1cb226b056344db546510ef8b923923"
dependencies = [
"float-cmp 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.23 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -605,7 +605,7 @@ dependencies = [
"lyon_geom 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pretty_assertions 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rctree 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=f53af72)",
"svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=6ff5579)",
"unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
......@@ -699,8 +699,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum simplecss 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "135685097a85a64067df36e28a243e94a94f76d829087ce0be34eeb014260c0e"
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
"checksum slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f9776d6b986f77b35c6cf846c11ad986ff128fe0b2b63a3628e3755e8d3102d"
"checksum svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=f53af72)" = "<none>"
"checksum svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=3034692)" = "<none>"
"checksum svgdom 0.15.0 (git+https://github.com/RazrFalcon/svgdom?rev=6ff5579)" = "<none>"
"checksum svgtypes 0.3.0 (git+https://github.com/RazrFalcon/svgtypes?rev=63fc6da)" = "<none>"
"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
"checksum time 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "847da467bf0db05882a9e2375934a8a55cffdc9db0d128af1518200260ba1f6c"
"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1"
......
......@@ -63,7 +63,7 @@ of the SVG Tiny 1.2 subset. In simple terms - it correctly renders only primitiv
*resvg* is aiming to support only the [static](http://www.w3.org/TR/SVG11/feature#SVG-static)
SVG subset. E.g. no `a`, `script`, `view`, `cursor` elements, no events and no animations.
Also, `marker`, `textPath` and
Also, `textPath` and
[embedded fonts](https://www.w3.org/TR/SVG11/feature#Font) are not yet implemented.
A full list can be found [here](docs/unsupported.md).
......
......@@ -49,6 +49,8 @@ List of features required from the 2D graphics library to implement a backend fo
- Stretch
- Variant: *normal*, *small cap*
- Size
- Letter spacing
- Word spacing
- Font metrics:
- Text bounding box
- Ascent/baseline
......
......@@ -26,7 +26,6 @@
- `altGlyphItem`
- `glyphRef`
- `color-profile`
- `marker`
- `textPath`
- `use` with a reference to an external SVG
......@@ -49,10 +48,6 @@
with `BackgroundImage`, `BackgroundAlpha`, `FillPaint`, `StrokePaint`
- `image-rendering`
- `kerning`
- `lighting-color`
- `marker-start`
- `marker-mid`
- `marker-end`
- `shape-rendering`
- `text-rendering`
- `unicode-bidi`
......
......@@ -46,7 +46,7 @@ pub fn apply(
match *node.borrow() {
usvg::NodeKind::Path(ref p) => {
path::draw(&node.tree(), p, opt, &clip_cr);
path::draw(&node.tree(), p, opt, layers, &clip_cr);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&node.tree(), text, opt, &clip_cr);
......@@ -103,7 +103,7 @@ fn clip_group(
clip_cr.paint();
clip_cr.set_matrix(cr.get_matrix());
draw_group_child(&node, opt, &clip_cr);
draw_group_child(&node, opt, layers, &clip_cr);
apply(clip_node, cp, opt, bbox, layers, &clip_cr);
......@@ -120,6 +120,7 @@ fn clip_group(
fn draw_group_child(
node: &usvg::Node,
opt: &Options,
layers: &mut CairoLayers,
cr: &cairo::Context,
) {
if let Some(child) = node.first_child() {
......@@ -127,7 +128,7 @@ fn draw_group_child(
match *child.borrow() {
usvg::NodeKind::Path(ref path_node) => {
path::draw(&child.tree(), path_node, opt, cr);
path::draw(&child.tree(), path_node, opt, layers, cr);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&child.tree(), text, opt, cr);
......
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// external
use cairo;
// self
use super::prelude::*;
use backend_utils::marker::*;
pub fn apply(
tree: &usvg::Tree,
path: &usvg::Path,
opt: &Options,
layers: &mut CairoLayers,
cr: &cairo::Context,
) {
let mut draw_marker = |id: &Option<String>, kind: MarkerKind| {
if let Some(ref id) = id {
if let Some(node) = tree.defs_by_id(id) {
if let usvg::NodeKind::Marker(ref marker) = *node.borrow() {
_apply(path, marker, &node, kind, opt, layers, cr);
}
}
}
};
draw_marker(&path.marker.start, MarkerKind::Start);
draw_marker(&path.marker.mid, MarkerKind::Middle);
draw_marker(&path.marker.end, MarkerKind::End);
}
fn _apply(
path: &usvg::Path,
marker: &usvg::Marker,
marker_node: &usvg::Node,
marker_kind: MarkerKind,
opt: &Options,
layers: &mut CairoLayers,
cr: &cairo::Context,
) {
let stroke_scale = try_opt!(stroke_scale(marker, path), ());
let r = marker.rect;
debug_assert!(r.is_valid());
let draw_marker = |x: f64, y: f64, idx: usize| {
let old_ts = cr.get_matrix();
cr.translate(x, y);
let angle = match marker.orientation {
usvg::MarkerOrientation::Auto => calc_vertex_angle(&path.segments, idx),
usvg::MarkerOrientation::Angle(angle) => angle,
};
if !angle.is_fuzzy_zero() {
let ts = usvg::Transform::new_rotate(angle);
cr.transform(ts.to_native());
}
if let Some(vbox) = marker.view_box {
let size = Size::new(r.width * stroke_scale, r.height * stroke_scale);
let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, size);
cr.transform(ts.to_native());
cr.translate(vbox.rect.x, vbox.rect.y);
} else {
cr.scale(stroke_scale, stroke_scale);
}
cr.translate(-r.x, -r.y);
match marker.overflow {
usvg::Overflow::Hidden | usvg::Overflow::Scroll => {
if let Some(vbox) = marker.view_box {
cr.rectangle(vbox.rect.x, vbox.rect.y, vbox.rect.width, vbox.rect.height);
} else {
cr.rectangle(0.0, 0.0, r.width, r.height);
}
cr.clip();
}
_ => {}
}
super::render_group(marker_node, opt, layers, cr);
cr.set_matrix(old_ts);
cr.reset_clip();
};
draw_markers(&path.segments, marker_kind, draw_marker);
}
......@@ -37,6 +37,7 @@ mod fill;
mod filter;
mod gradient;
mod image;
mod marker;
mod mask;
mod path;
mod pattern;
......@@ -264,7 +265,7 @@ fn render_node(
Some(render_group(node, opt, layers, cr))
}
usvg::NodeKind::Path(ref path) => {
Some(path::draw(&node.tree(), path, opt, cr))
Some(path::draw(&node.tree(), path, opt, layers, cr))
}
usvg::NodeKind::Text(ref text) => {
Some(text::draw(&node.tree(), text, opt, cr))
......
......@@ -10,6 +10,7 @@ use super::prelude::*;
use super::{
fill,
stroke,
marker,
};
......@@ -17,6 +18,7 @@ pub fn draw(
tree: &usvg::Tree,
path: &usvg::Path,
opt: &Options,
layers: &mut CairoLayers,
cr: &cairo::Context,
) -> Rect {
let mut is_square_cap = false;
......@@ -42,6 +44,8 @@ pub fn draw(
cr.fill();
}
marker::apply(tree, path, opt, layers, cr);
bbox
}
......
......@@ -59,7 +59,7 @@ pub fn apply(
cr.set_line_join(linejoin);
match stroke.dasharray {
Some(ref list) => cr.set_dash(list, stroke.dashoffset),
Some(ref list) => cr.set_dash(list, stroke.dashoffset as f64),
None => cr.set_dash(&[], 0.0),
}
......
......@@ -41,7 +41,7 @@ pub fn apply(
match *node.borrow() {
usvg::NodeKind::Path(ref path_node) => {
path::draw(&node.tree(), path_node, opt, &mut clip_p);
path::draw(&node.tree(), path_node, opt, layers, &mut clip_p);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&node.tree(), text, opt, &mut clip_p);
......@@ -90,7 +90,7 @@ fn clip_group(
let mut clip_p = qt::Painter::new(&mut clip_img);
clip_p.set_transform(&p.get_transform());
draw_group_child(&node, opt, &mut clip_p);
draw_group_child(&node, opt, layers, &mut clip_p);
apply(clip_node, cp, opt, bbox, layers, &mut clip_p);
clip_p.end();
......@@ -106,6 +106,7 @@ fn clip_group(
fn draw_group_child(
node: &usvg::Node,
opt: &Options,
layers: &mut QtLayers,
p: &mut qt::Painter,
) {
if let Some(child) = node.first_child() {
......@@ -113,7 +114,7 @@ fn draw_group_child(
match *child.borrow() {
usvg::NodeKind::Path(ref path_node) => {
path::draw(&child.tree(), path_node, opt, p);
path::draw(&child.tree(), path_node, opt, layers, p);
}
usvg::NodeKind::Text(ref text) => {
text::draw(&child.tree(), text, opt, p);
......
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// external
use qt;
// self
use super::prelude::*;
use backend_utils::marker::*;
pub fn apply(
tree: &usvg::Tree,
path: &usvg::Path,
opt: &Options,
layers: &mut QtLayers,
p: &mut qt::Painter,
) {
let mut draw_marker = |id: &Option<String>, kind: MarkerKind| {
if let Some(ref id) = id {
if let Some(node) = tree.defs_by_id(id) {
if let usvg::NodeKind::Marker(ref marker) = *node.borrow() {
_apply(path, marker, &node, kind, opt, layers, p);
}
}
}
};
draw_marker(&path.marker.start, MarkerKind::Start);
draw_marker(&path.marker.mid, MarkerKind::Middle);
draw_marker(&path.marker.end, MarkerKind::End);
}
fn _apply(
path: &usvg::Path,
marker: &usvg::Marker,
marker_node: &usvg::Node,
marker_kind: MarkerKind,
opt: &Options,
layers: &mut QtLayers,
p: &mut qt::Painter,
) {
let stroke_scale = try_opt!(stroke_scale(marker, path), ());
let r = marker.rect;
debug_assert!(r.is_valid());
let draw_marker = |x: f64, y: f64, idx: usize| {
let old_ts = p.get_transform();
p.translate(x, y);
let angle = match marker.orientation {
usvg::MarkerOrientation::Auto => calc_vertex_angle(&path.segments, idx),
usvg::MarkerOrientation::Angle(angle) => angle,
};
if !angle.is_fuzzy_zero() {
let ts = usvg::Transform::new_rotate(angle);
p.apply_transform(&ts.to_native());
}
if let Some(vbox) = marker.view_box {
let size = Size::new(r.width * stroke_scale, r.height * stroke_scale);
let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, size);
p.apply_transform(&ts.to_native());
p.translate(vbox.rect.x, vbox.rect.y);
} else {
p.scale(stroke_scale, stroke_scale);
}
p.translate(-r.x, -r.y);
match marker.overflow {
usvg::Overflow::Hidden | usvg::Overflow::Scroll => {
if let Some(vbox) = marker.view_box {
p.set_clip_rect(vbox.rect.x, vbox.rect.y, vbox.rect.width, vbox.rect.height);
} else {
p.set_clip_rect(0.0, 0.0, r.width, r.height);
}
}
_ => {}
}
super::render_group(marker_node, opt, layers, p);
p.set_transform(&old_ts);
p.reset_clip_path();
};
draw_markers(&path.segments, marker_kind, draw_marker);
}
......@@ -33,6 +33,7 @@ mod fill;
mod filter;
mod gradient;
mod image;
mod marker;
mod mask;
mod path;
mod pattern;
......@@ -221,7 +222,7 @@ fn render_node(
Some(render_group(node, opt, layers, p))
}
usvg::NodeKind::Path(ref path) => {
Some(path::draw(&node.tree(), path, opt, p))
Some(path::draw(&node.tree(), path, opt, layers, p))
}
usvg::NodeKind::Text(ref text) => {
Some(text::draw(&node.tree(), text, opt, p))
......
......@@ -10,6 +10,7 @@ use super::prelude::*;
use super::{
fill,
stroke,
marker,
};
......@@ -17,6 +18,7 @@ pub fn draw(
tree: &usvg::Tree,
path: &usvg::Path,
opt: &Options,
layers: &mut QtLayers,
p: &mut qt::Painter,
) -> Rect {
let mut p_path = qt::PainterPath::new();
......@@ -40,6 +42,8 @@ pub fn draw(
p.draw_path(&p_path);
marker::apply(tree, path, opt, layers, p);
bbox
}
......
......@@ -23,6 +23,7 @@ pub fn apply(
pattern.rect
};
// TODO: wrong
let global_ts = usvg::Transform::from_native(&global_ts);
let (sx, sy) = global_ts.get_scale();
// Only integer scaling is allowed.
......
......@@ -71,7 +71,7 @@ pub fn apply(
pen.set_width(stroke.width.value());
if let Some(ref list) = stroke.dasharray {
pen.set_dash_offset(stroke.dashoffset);
pen.set_dash_offset(stroke.dashoffset as f64);
pen.set_dash_array(list);
}
......
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// external
use usvg::PathSegment as Segment;
// self
use super::prelude::*;
pub enum MarkerKind {
Start,
Middle,
End,
}
pub fn stroke_scale(marker: &usvg::Marker, path: &usvg::Path) -> Option<f64> {
match marker.units {
usvg::MarkerUnits::StrokeWidth => {
match path.marker.stroke {
Some(sw) => Some(sw.value()),
None => None,
}
}
usvg::MarkerUnits::UserSpaceOnUse => Some(1.0),
}
}
pub fn draw_markers<P>(segments: &[Segment], kind: MarkerKind, mut draw_marker: P)
where P: FnMut(f64, f64, usize)
{
match kind {
MarkerKind::Start => {
if let Some(Segment::MoveTo { x, y }) = segments.first() {
draw_marker(*x, *y, 0);
}
}
MarkerKind::Middle => {
let total = segments.len() - 1;
let mut i = 1;
while i < total {
let (x, y) = match segments[i] {
Segment::MoveTo { x, y } => (x, y),
Segment::LineTo { x, y } => (x, y),
Segment::CurveTo { x, y, .. } => (x, y),
_ => {
i += 1;
continue
}
};
draw_marker(x, y, i);
i += 1;
}
}
MarkerKind::End => {
let idx = segments.len() - 1;
match segments.last() {
Some(Segment::LineTo { x, y }) => {
draw_marker(*x, *y, idx);
}
Some(Segment::CurveTo { x, y, .. }) => {
draw_marker(*x, *y, idx);
}
Some(Segment::ClosePath) => {
let (x, y) = get_subpath_start(segments, idx);
draw_marker(x, y, idx);
}
_ => {}
}
}
}
}
pub fn calc_vertex_angle(segments: &[Segment], idx: usize) -> f64 {
if idx == 0 {
// First segment.
debug_assert!(segments.len() > 1);
let seg1 = segments[0];
let seg2 = segments[1];
match (seg1, seg2) {
(Segment::MoveTo { x: mx, y: my }, Segment::LineTo { x, y }) => {
calc_line_angle(mx, my, x, y)
}
(Segment::MoveTo { x: mx, y: my }, Segment::CurveTo { x1, y1, x, y, .. }) => {
if mx.fuzzy_eq(&x1) && my.fuzzy_eq(&y1) {
calc_line_angle(mx, my, x, y)
} else {
calc_line_angle(mx, my, x1, y1)
}
}
_ => 0.0,
}
} else if idx == segments.len() - 1 {
// Last segment.
let seg1 = segments[idx - 1];
let seg2 = segments[idx];
match (seg1, seg2) {
(_, Segment::MoveTo { .. }) => 0.0, // unreachable
(_, Segment::LineTo { x, y }) => {
let (px, py) = get_prev_vertex(segments, idx);
calc_line_angle(px, py, x, y)
}
(_, Segment::CurveTo { x2, y2, x, y, .. }) => {
if x2.fuzzy_eq(&x) && y2.fuzzy_eq(&y) {
let (px, py) = get_prev_vertex(segments, idx);
calc_line_angle(px, py, x, y)
} else {
calc_line_angle(x2, y2, x, y)
}
}
(Segment::LineTo { x, y }, Segment::ClosePath) => {
let (nx, ny) = get_subpath_start(segments, idx);
calc_line_angle(x, y, nx, ny)
}
(Segment::CurveTo { x2, y2, x, y, .. }, Segment::ClosePath) => {
let (px, py) = get_prev_vertex(segments, idx);
let (nx, ny) = get_subpath_start(segments, idx);
calc_curves_angle(
px, py, x2, y2,
x, y,
nx, ny, nx, ny,
)
}
(_, Segment::ClosePath) => 0.0,
}
} else {
// Middle segments.
let seg1 = segments[idx];
let seg2 = segments[idx + 1];
// Not sure if there is a better way.
match (seg1, seg2) {
(Segment::MoveTo { x: mx, y: my }, Segment::LineTo { x, y }) => {
calc_line_angle(mx, my, x, y)
}
(Segment::MoveTo { x: mx, y: my }, Segment::CurveTo { x1, y1, .. }) => {
calc_line_angle(mx, my, x1, y1)
}
(Segment::LineTo { x: x1, y: y1 }, Segment::LineTo { x: x2, y: y2 }) => {
let (px, py) = get_prev_vertex(segments, idx);
calc_angle(px, py, x1, y1,
x1, y1, x2, y2)
}
(Segment::CurveTo { x2: c1_x2, y2: c1_y2, x, y, .. },
Segment::CurveTo { x1: c2_x1, y1: c2_y1, x: nx, y: ny, .. }) => {
let (px, py) = get_prev_vertex(segments, idx);
calc_curves_angle(
px, py, c1_x2, c1_y2,
x, y,
c2_x1, c2_y1, nx, ny,
)
}
(Segment::LineTo { x, y },
Segment::CurveTo { x1, y1, x: nx, y: ny, .. }) => {
let (px, py) = get_prev_vertex(segments, idx);
calc_curves_angle(
px, py, px, py,
x, y,
x1, y1, nx, ny,
)
}
(Segment::CurveTo { x2, y2, x, y, .. },
Segment::LineTo { x: nx, y: ny }) => {
let (px, py) = get_prev_vertex(segments, idx);
calc_curves_angle(
px, py, x2, y2,
x, y,
nx, ny, nx, ny,
)
}
(Segment::LineTo { x, y }, Segment::MoveTo { .. }) => {
let (px, py) = get_prev_vertex(segments, idx);
calc_line_angle(px, py, x, y)
}
(Segment::CurveTo { x2, y2, x, y, .. }, Segment::MoveTo { .. }) => {
if x.fuzzy_eq(&x2) && y.fuzzy_eq(&y2) {
let (px, py) = get_prev_vertex(segments, idx);
calc_line_angle(px, py, x, y)
} else {
calc_line_angle(x2, y2, x, y)
}
}
(Segment::LineTo { x, y }, Segment::ClosePath) => {
let (px, py) = get_prev_vertex(segments, idx);
let (nx, ny) = get_subpath_start(segments, idx);
calc_angle(px, py, x, y,
x, y, nx, ny)
}
(_, Segment::ClosePath) => {
let (px, py) = get_prev_vertex(segments, idx);
let (nx, ny) = get_subpath_start(segments, idx);
calc_line_angle(px, py, nx, ny)
}
(_, Segment::MoveTo { .. }) |
(Segment::ClosePath, _) => {
0.0
}
}
}
}
fn calc_line_angle(
x1: f64, y1: f64,
x2: f64, y2: f64,
) -> f64 {
calc_angle(x1, y1, x2, y2, x1, y1, x2, y2)
}
fn calc_curves_angle(
px: f64, py: f64, // previous vertex
cx1: f64, cy1: f64, // previous control point
x: f64, y: f64, // current vertex
cx2: f64, cy2: f64, // next control point
nx: f64, ny: f64, // next vertex
) -> f64 {
if cx1.fuzzy_eq(&x) && cy1.fuzzy_eq(&y) {
calc_angle(px, py, x, y, x, y, cx2, cy2)
} else if x.fuzzy_eq(&cx2) && y.fuzzy_eq(&cy2) {
calc_angle(cx1, cy1, x, y, x, y, nx, ny)
} else {
calc_angle(cx1, cy1, x, y, x, y, cx2, cy2)
}
}
fn calc_angle(
x1: f64, y1: f64,
x2: f64, y2: f64,
x3: f64, y3: f64,
x4: f64, y4: f64,