Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/linebender/kurbo into shape…
Browse files Browse the repository at this point in the history
…-accuracy
  • Loading branch information
george-steel committed Aug 29, 2023
2 parents e9848e0 + 3d05ef6 commit d2f7f12
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 68 deletions.
92 changes: 36 additions & 56 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,24 @@ name: CI
on:
push:
branches:
- master
- main
pull_request:

jobs:
rustfmt:
runs-on: ubuntu-latest
name: cargo fmt
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
profile: minimal
toolchain: "stable"
components: rustfmt
override: true

- name: install rustfmt
run: rustup component add rustfmt

- name: cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run: cargo fmt --all -- --check

test-stable:
runs-on: ${{ matrix.os }}
Expand All @@ -37,29 +29,21 @@ jobs:
os: [macOS-latest, windows-2019, ubuntu-latest]
name: cargo clippy+test
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
toolchain: "stable"
components: clippy
profile: minimal
override: true

- run: rustup target add thumbv7m-none-eabi

- name: cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features --all-targets -- -D warnings
run: cargo clippy --all-features --all-targets -- -D warnings

- name: cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
run: cargo test --all-features

- name: Build with no default features
# Use no-std target to ensure we don't link to std.
Expand All @@ -76,29 +60,35 @@ jobs:

name: cargo clippy+test (wasm32)
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
toolchain: "stable"
target: wasm32-unknown-unknown
components: clippy
profile: minimal
override: true

- name: cargo clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-features --all-targets --target wasm32-unknown-unknown -- -D warnings
run: cargo clippy --all-features --all-targets --target wasm32-unknown-unknown -- -D warnings

# TODO: Find a way to make tests work. Until then the tests are merely compiled.
- name: cargo test compile
uses: actions-rs/cargo@v1
run: cargo test --all-features --no-run --target wasm32-unknown-unknown

test-msrv:
runs-on: ubuntu-latest
name: cargo test msrv
steps:
- uses: actions/checkout@v3

- name: install msrv toolchain
uses: dtolnay/rust-toolchain@master
with:
command: test
args: --all-features --no-run --target wasm32-unknown-unknown
toolchain: "1.65"

- name: cargo test
run: cargo test --all-features

test-nightly:
runs-on: ${{ matrix.os }}
Expand All @@ -107,20 +97,15 @@ jobs:
os: [macOS-latest, windows-2019, ubuntu-latest]
name: cargo test nightly
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: install nightly toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly
profile: minimal
override: true
toolchain: "nightly"

- name: cargo test
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
run: cargo test --all-features

check-docs:
name: Docs
Expand All @@ -129,17 +114,12 @@ jobs:
matrix:
os: [macOS-latest, windows-2019, ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
profile: minimal
override: true
toolchain: "stable"

- name: cargo doc
uses: actions-rs/cargo@v1
with:
command: doc
args: --all-features --document-private-items
run: cargo doc --all-features --document-private-items
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
name = "kurbo"
version = "0.9.5"
authors = ["Raph Levien <[email protected]>"]
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65" # When updating this, also update the README.md and CI.
keywords = ["graphics", "curve", "curves", "bezier", "geometry"]
repository = "https://github.com/linebender/kurbo"
description = "A 2D curves library"
Expand Down
85 changes: 84 additions & 1 deletion src/affine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,50 @@ impl Affine {
Affine([1.0, skew_y, skew_x, 1.0, 0.0, 0.0])
}

/// Create an affine transform that represents reflection about the line `point + direction * t, t in (-infty, infty)`
///
/// # Examples
///
/// ```
/// # use kurbo::{Point, Vec2, Affine};
/// # fn assert_near(p0: Point, p1: Point) {
/// # assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
/// # }
/// let point = Point::new(1., 0.);
/// let vec = Vec2::new(1., 1.);
/// let map = Affine::reflect(point, vec);
/// assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
/// assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
/// assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
/// ```
#[inline]
#[must_use]
pub fn reflect(point: impl Into<Point>, direction: impl Into<Vec2>) -> Self {
let point = point.into();
let direction = direction.into();

let n = Vec2 {
x: direction.y,
y: -direction.x,
}
.normalize();

// Compute Householder reflection matrix
let x2 = n.x * n.x;
let xy = n.x * n.y;
let y2 = n.y * n.y;
// Here we also add in the post translation, because it doesn't require any further calc.
let aff = Affine::new([
1. - 2. * x2,
-2. * xy,
-2. * xy,
1. - 2. * y2,
point.x,
point.y,
]);
aff.pre_translate(-point.to_vec2())
}

/// A rotation by `th` followed by `self`.
///
/// Equivalent to `self * Affine::rotate(th)`
Expand Down Expand Up @@ -432,13 +476,19 @@ impl From<mint::ColumnMatrix2x3<f64>> for Affine {

#[cfg(test)]
mod tests {
use crate::{Affine, Point};
use crate::{Affine, Point, Vec2};
use std::f64::consts::PI;

fn assert_near(p0: Point, p1: Point) {
assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
}

fn affine_assert_near(a0: Affine, a1: Affine) {
for i in 0..6 {
assert!((a0.0[i] - a1.0[i]).abs() < 1e-9, "{a0:?} != {a1:?}");
}
}

#[test]
fn affine_basic() {
let p = Point::new(3.0, 4.0);
Expand Down Expand Up @@ -480,4 +530,37 @@ mod tests {
assert_near(a_inv * (a * py), py);
assert_near(a_inv * (a * pxy), pxy);
}

#[test]
fn reflection() {
affine_assert_near(
Affine::reflect(Point::ZERO, (1., 0.)),
Affine::new([1., 0., 0., -1., 0., 0.]),
);
affine_assert_near(
Affine::reflect(Point::ZERO, (0., 1.)),
Affine::new([-1., 0., 0., 1., 0., 0.]),
);
// y = x
affine_assert_near(
Affine::reflect(Point::ZERO, (1., 1.)),
Affine::new([0., 1., 1., 0., 0., 0.]),
);

// no translate
let point = Point::new(0., 0.);
let vec = Vec2::new(1., 1.);
let map = Affine::reflect(point, vec);
assert_near(map * Point::new(0., 0.), Point::new(0., 0.));
assert_near(map * Point::new(1., 1.), Point::new(1., 1.));
assert_near(map * Point::new(1., 2.), Point::new(2., 1.));

// with translate
let point = Point::new(1., 0.);
let vec = Vec2::new(1., 1.);
let map = Affine::reflect(point, vec);
assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
}
}
2 changes: 1 addition & 1 deletion src/bezpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,7 @@ impl Shape for PathSeg {

/// The area under the curve.
///
/// We could just return 0, but this seems more useful.
/// We could just return `0`, but this seems more useful.
fn area(&self, _tolerance: f64) -> f64 {
self.signed_area()
}
Expand Down
2 changes: 1 addition & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ fn solve_quartic_inner(a: f64, b: f64, c: f64, d: f64, rescale: bool) -> Option<

/// Factor a quartic into two quadratics.
///
/// Attempt to factor a quartic equation into two quadratic equations. Returns None either if there
/// Attempt to factor a quartic equation into two quadratic equations. Returns `None` either if there
/// is overflow (in which case rescaling might succeed) or the factorization would result in
/// complex coefficients.
///
Expand Down
2 changes: 1 addition & 1 deletion src/cubicbez.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl CubicBez {
/// Approximate a cubic with a single quadratic
///
/// Returns a quadratic approximating the given cubic that maintains
/// endpoint tangents if that is within tolerance, or None otherwise.
/// endpoint tangents if that is within tolerance, or `None` otherwise.
fn try_approx_quadratic(&self, accuracy: f64) -> Option<QuadBez> {
if let Some(q1) = Line::new(self.p0, self.p1).crossing_point(Line::new(self.p2, self.p3)) {
let c1 = self.p0.lerp(q1, 2.0 / 3.0);
Expand Down
6 changes: 3 additions & 3 deletions src/fit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub trait ParamCurveFit {
/// to derive the moments needed for curve fitting.
///
/// A default implementation is proved which does quadrature integration
/// with Green's theorem, in terms of samples evaulated with
/// with Green's theorem, in terms of samples evaluated with
/// [`sample_pt_deriv`].
///
/// [`sample_pt_deriv`]: ParamCurveFit::sample_pt_deriv
Expand Down Expand Up @@ -281,10 +281,10 @@ impl CurveDist {

/// Evaluate distance to a cubic approximation.
///
/// If distance exceeds stated accuracy, can return None. Note that
/// If distance exceeds stated accuracy, can return `None`. Note that
/// `acc2` is the square of the target.
///
/// Returns the squre of the error, which is intended to be a good
/// Returns the square of the error, which is intended to be a good
/// approximation of the Fréchet distance.
fn eval_ray(&self, c: CubicBez, acc2: f64) -> Option<f64> {
let mut max_err2 = 0.0;
Expand Down
2 changes: 1 addition & 1 deletion src/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl CubicOffset {
/// Create a new curve from Bézier segment and offset.
///
/// This method should only be used if the Bézier is smooth. Use
/// [`new_regularized`]instead to deal with a wider range of inputs.
/// [`new_regularized`] instead to deal with a wider range of inputs.
///
/// [`new_regularized`]: Self::new_regularized
pub fn new(c: CubicBez, d: f64) -> Self {
Expand Down
4 changes: 2 additions & 2 deletions src/rounded_rect_radii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ impl RoundedRectRadii {
)
}

/// Returns true if all radius values are finite.
/// Returns `true` if all radius values are finite.
pub fn is_finite(&self) -> bool {
self.top_left.is_finite()
&& self.top_right.is_finite()
&& self.bottom_right.is_finite()
&& self.bottom_left.is_finite()
}

/// Returns true if any corner radius value is NaN.
/// Returns `true` if any corner radius value is NaN.
pub fn is_nan(&self) -> bool {
self.top_left.is_nan()
|| self.top_right.is_nan()
Expand Down
2 changes: 1 addition & 1 deletion src/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct SvgArc {
}

impl BezPath {
/// Create a BezPath with segments corresponding to the sequence of
/// Create a `BezPath` with segments corresponding to the sequence of
/// `PathSeg`s
pub fn from_path_segments(segments: impl Iterator<Item = PathSeg>) -> BezPath {
let mut path_elements = Vec::new();
Expand Down

0 comments on commit d2f7f12

Please sign in to comment.