Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
873
third-party/vendor/tiny-skia-path/src/dash.rs
vendored
Normal file
873
third-party/vendor/tiny-skia-path/src/dash.rs
vendored
Normal file
|
|
@ -0,0 +1,873 @@
|
|||
// Copyright 2014 Google Inc.
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This module is a mix of SkDashPath, SkDashPathEffect, SkContourMeasure and SkPathMeasure.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use arrayref::array_ref;
|
||||
|
||||
use crate::{Path, Point};
|
||||
|
||||
use crate::floating_point::{FiniteF32, NonZeroPositiveF32, NormalizedF32, NormalizedF32Exclusive};
|
||||
use crate::path::{PathSegment, PathSegmentsIter, PathVerb};
|
||||
use crate::path_builder::PathBuilder;
|
||||
use crate::path_geometry;
|
||||
use crate::scalar::Scalar;
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
use crate::NoStdFloat;
|
||||
|
||||
/// A stroke dashing properties.
|
||||
///
|
||||
/// Contains an array of pairs, where the first number indicates an "on" interval
|
||||
/// and the second one indicates an "off" interval;
|
||||
/// a dash offset value and internal properties.
|
||||
///
|
||||
/// # Guarantees
|
||||
///
|
||||
/// - The dash array always have an even number of values.
|
||||
/// - All dash array values are finite and >= 0.
|
||||
/// - There is at least two dash array values.
|
||||
/// - The sum of all dash array values is positive and finite.
|
||||
/// - Dash offset is finite.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct StrokeDash {
|
||||
array: Vec<f32>,
|
||||
offset: f32,
|
||||
interval_len: NonZeroPositiveF32,
|
||||
first_len: f32, // TODO: PositiveF32
|
||||
first_index: usize,
|
||||
}
|
||||
|
||||
impl StrokeDash {
|
||||
/// Creates a new stroke dashing object.
|
||||
pub fn new(dash_array: Vec<f32>, dash_offset: f32) -> Option<Self> {
|
||||
let dash_offset = FiniteF32::new(dash_offset)?;
|
||||
|
||||
if dash_array.len() < 2 || dash_array.len() % 2 != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if dash_array.iter().any(|n| *n < 0.0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let interval_len: f32 = dash_array.iter().sum();
|
||||
let interval_len = NonZeroPositiveF32::new(interval_len)?;
|
||||
|
||||
let dash_offset = adjust_dash_offset(dash_offset.get(), interval_len.get());
|
||||
debug_assert!(dash_offset >= 0.0);
|
||||
debug_assert!(dash_offset < interval_len.get());
|
||||
|
||||
let (first_len, first_index) = find_first_interval(&dash_array, dash_offset);
|
||||
debug_assert!(first_len >= 0.0);
|
||||
debug_assert!(first_index < dash_array.len());
|
||||
|
||||
Some(StrokeDash {
|
||||
array: dash_array,
|
||||
offset: dash_offset,
|
||||
interval_len,
|
||||
first_len,
|
||||
first_index,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
assert_eq!(StrokeDash::new(vec![], 0.0), None);
|
||||
assert_eq!(StrokeDash::new(vec![1.0], 0.0), None);
|
||||
assert_eq!(StrokeDash::new(vec![1.0, 2.0, 3.0], 0.0), None);
|
||||
assert_eq!(StrokeDash::new(vec![1.0, -2.0], 0.0), None);
|
||||
assert_eq!(StrokeDash::new(vec![0.0, 0.0], 0.0), None);
|
||||
assert_eq!(StrokeDash::new(vec![1.0, -1.0], 0.0), None);
|
||||
assert_eq!(StrokeDash::new(vec![1.0, 1.0], f32::INFINITY), None);
|
||||
assert_eq!(StrokeDash::new(vec![1.0, f32::INFINITY], 0.0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bug_26() {
|
||||
let mut pb = PathBuilder::new();
|
||||
pb.move_to(665.54, 287.3);
|
||||
pb.line_to(675.67, 273.04);
|
||||
pb.line_to(675.52, 271.32);
|
||||
pb.line_to(674.79, 269.61);
|
||||
pb.line_to(674.05, 268.04);
|
||||
pb.line_to(672.88, 266.47);
|
||||
pb.line_to(671.27, 264.9);
|
||||
let path = pb.finish().unwrap();
|
||||
|
||||
let stroke_dash = StrokeDash::new(vec![6.0, 4.5], 0.0).unwrap();
|
||||
|
||||
assert!(path.dash(&stroke_dash, 1.0).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust phase to be between 0 and len, "flipping" phase if negative.
|
||||
// e.g., if len is 100, then phase of -20 (or -120) is equivalent to 80.
|
||||
fn adjust_dash_offset(mut offset: f32, len: f32) -> f32 {
|
||||
if offset < 0.0 {
|
||||
offset = -offset;
|
||||
if offset > len {
|
||||
offset %= len;
|
||||
}
|
||||
|
||||
offset = len - offset;
|
||||
|
||||
// Due to finite precision, it's possible that phase == len,
|
||||
// even after the subtract (if len >>> phase), so fix that here.
|
||||
debug_assert!(offset <= len);
|
||||
if offset == len {
|
||||
offset = 0.0;
|
||||
}
|
||||
|
||||
offset
|
||||
} else if offset >= len {
|
||||
offset % len
|
||||
} else {
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
fn find_first_interval(dash_array: &[f32], mut dash_offset: f32) -> (f32, usize) {
|
||||
for (i, gap) in dash_array.iter().copied().enumerate() {
|
||||
if dash_offset > gap || (dash_offset == gap && gap != 0.0) {
|
||||
dash_offset -= gap;
|
||||
} else {
|
||||
return (gap - dash_offset, i);
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, phase "appears" to be larger than our length. This
|
||||
// shouldn't happen with perfect precision, but we can accumulate errors
|
||||
// during the initial length computation (rounding can make our sum be too
|
||||
// big or too small. In that event, we just have to eat the error here.
|
||||
(dash_array[0], 0)
|
||||
}
|
||||
|
||||
impl Path {
|
||||
/// Converts the current path into a dashed one.
|
||||
///
|
||||
/// `resolution_scale` can be obtained via
|
||||
/// [`compute_resolution_scale`](crate::PathStroker::compute_resolution_scale).
|
||||
pub fn dash(&self, dash: &StrokeDash, resolution_scale: f32) -> Option<Path> {
|
||||
dash_impl(self, dash, resolution_scale)
|
||||
}
|
||||
}
|
||||
|
||||
fn dash_impl(src: &Path, dash: &StrokeDash, res_scale: f32) -> Option<Path> {
|
||||
// We do not support the `cull_path` branch here.
|
||||
// Skia has a lot of code for cases when a path contains only a single zero-length line
|
||||
// or when a path is a rect. Not sure why.
|
||||
// We simply ignoring it for the sake of simplicity.
|
||||
|
||||
// We also doesn't support the `SpecialLineRec` case.
|
||||
// I have no idea what the point in it.
|
||||
|
||||
fn is_even(x: usize) -> bool {
|
||||
x % 2 == 0
|
||||
}
|
||||
|
||||
let mut pb = PathBuilder::new();
|
||||
let mut dash_count = 0.0;
|
||||
for contour in ContourMeasureIter::new(src, res_scale) {
|
||||
let mut skip_first_segment = contour.is_closed;
|
||||
let mut added_segment = false;
|
||||
let length = contour.length;
|
||||
let mut index = dash.first_index;
|
||||
|
||||
// Since the path length / dash length ratio may be arbitrarily large, we can exert
|
||||
// significant memory pressure while attempting to build the filtered path. To avoid this,
|
||||
// we simply give up dashing beyond a certain threshold.
|
||||
//
|
||||
// The original bug report (http://crbug.com/165432) is based on a path yielding more than
|
||||
// 90 million dash segments and crashing the memory allocator. A limit of 1 million
|
||||
// segments seems reasonable: at 2 verbs per segment * 9 bytes per verb, this caps the
|
||||
// maximum dash memory overhead at roughly 17MB per path.
|
||||
const MAX_DASH_COUNT: usize = 1000000;
|
||||
dash_count += length * (dash.array.len() >> 1) as f32 / dash.interval_len.get();
|
||||
if dash_count > MAX_DASH_COUNT as f32 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Using double precision to avoid looping indefinitely due to single precision rounding
|
||||
// (for extreme path_length/dash_length ratios). See test_infinite_dash() unittest.
|
||||
let mut distance = 0.0;
|
||||
let mut d_len = dash.first_len;
|
||||
|
||||
while distance < length {
|
||||
debug_assert!(d_len >= 0.0);
|
||||
added_segment = false;
|
||||
if is_even(index) && !skip_first_segment {
|
||||
added_segment = true;
|
||||
contour.push_segment(distance as f32, (distance + d_len) as f32, true, &mut pb);
|
||||
}
|
||||
|
||||
distance += d_len;
|
||||
|
||||
// clear this so we only respect it the first time around
|
||||
skip_first_segment = false;
|
||||
|
||||
// wrap around our intervals array if necessary
|
||||
index += 1;
|
||||
debug_assert!(index <= dash.array.len());
|
||||
if index == dash.array.len() {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
// fetch our next d_len
|
||||
d_len = dash.array[index];
|
||||
}
|
||||
|
||||
// extend if we ended on a segment and we need to join up with the (skipped) initial segment
|
||||
if contour.is_closed && is_even(dash.first_index) && dash.first_len >= 0.0 {
|
||||
contour.push_segment(0.0, dash.first_len, !added_segment, &mut pb);
|
||||
}
|
||||
}
|
||||
|
||||
pb.finish()
|
||||
}
|
||||
|
||||
const MAX_T_VALUE: u32 = 0x3FFFFFFF;
|
||||
|
||||
struct ContourMeasureIter<'a> {
|
||||
iter: PathSegmentsIter<'a>,
|
||||
tolerance: f32,
|
||||
}
|
||||
|
||||
impl<'a> ContourMeasureIter<'a> {
|
||||
fn new(path: &'a Path, res_scale: f32) -> Self {
|
||||
// can't use tangents, since we need [0..1..................2] to be seen
|
||||
// as definitely not a line (it is when drawn, but not parametrically)
|
||||
// so we compare midpoints
|
||||
const CHEAP_DIST_LIMIT: f32 = 0.5; // just made this value up
|
||||
|
||||
ContourMeasureIter {
|
||||
iter: path.segments(),
|
||||
tolerance: CHEAP_DIST_LIMIT * res_scale.invert(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ContourMeasureIter<'_> {
|
||||
type Item = ContourMeasure;
|
||||
|
||||
// If it encounters a zero-length contour, it is skipped.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Note:
|
||||
// as we accumulate distance, we have to check that the result of +=
|
||||
// actually made it larger, since a very small delta might be > 0, but
|
||||
// still have no effect on distance (if distance >>> delta).
|
||||
//
|
||||
// We do this check below, and in compute_quad_segs and compute_cubic_segs
|
||||
|
||||
let mut contour = ContourMeasure::default();
|
||||
|
||||
let mut point_index = 0;
|
||||
let mut distance = 0.0;
|
||||
let mut have_seen_close = false;
|
||||
let mut prev_p = Point::zero();
|
||||
while let Some(seg) = self.iter.next() {
|
||||
match seg {
|
||||
PathSegment::MoveTo(p0) => {
|
||||
contour.points.push(p0);
|
||||
prev_p = p0;
|
||||
}
|
||||
PathSegment::LineTo(p0) => {
|
||||
let prev_d = distance;
|
||||
distance = contour.compute_line_seg(prev_p, p0, distance, point_index);
|
||||
|
||||
if distance > prev_d {
|
||||
contour.points.push(p0);
|
||||
point_index += 1;
|
||||
}
|
||||
|
||||
prev_p = p0;
|
||||
}
|
||||
PathSegment::QuadTo(p0, p1) => {
|
||||
let prev_d = distance;
|
||||
distance = contour.compute_quad_segs(
|
||||
prev_p,
|
||||
p0,
|
||||
p1,
|
||||
distance,
|
||||
0,
|
||||
MAX_T_VALUE,
|
||||
point_index,
|
||||
self.tolerance,
|
||||
);
|
||||
|
||||
if distance > prev_d {
|
||||
contour.points.push(p0);
|
||||
contour.points.push(p1);
|
||||
point_index += 2;
|
||||
}
|
||||
|
||||
prev_p = p1;
|
||||
}
|
||||
PathSegment::CubicTo(p0, p1, p2) => {
|
||||
let prev_d = distance;
|
||||
distance = contour.compute_cubic_segs(
|
||||
prev_p,
|
||||
p0,
|
||||
p1,
|
||||
p2,
|
||||
distance,
|
||||
0,
|
||||
MAX_T_VALUE,
|
||||
point_index,
|
||||
self.tolerance,
|
||||
);
|
||||
|
||||
if distance > prev_d {
|
||||
contour.points.push(p0);
|
||||
contour.points.push(p1);
|
||||
contour.points.push(p2);
|
||||
point_index += 3;
|
||||
}
|
||||
|
||||
prev_p = p2;
|
||||
}
|
||||
PathSegment::Close => {
|
||||
have_seen_close = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: to contour iter?
|
||||
if self.iter.next_verb() == Some(PathVerb::Move) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !distance.is_finite() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if have_seen_close {
|
||||
let prev_d = distance;
|
||||
let first_pt = contour.points[0];
|
||||
distance = contour.compute_line_seg(
|
||||
contour.points[point_index],
|
||||
first_pt,
|
||||
distance,
|
||||
point_index,
|
||||
);
|
||||
|
||||
if distance > prev_d {
|
||||
contour.points.push(first_pt);
|
||||
}
|
||||
}
|
||||
|
||||
contour.length = distance;
|
||||
contour.is_closed = have_seen_close;
|
||||
|
||||
if contour.points.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(contour)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
enum SegmentType {
|
||||
Line,
|
||||
Quad,
|
||||
Cubic,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct Segment {
|
||||
distance: f32, // total distance up to this point
|
||||
point_index: usize, // index into the ContourMeasure::points array
|
||||
t_value: u32,
|
||||
kind: SegmentType,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
fn scalar_t(&self) -> f32 {
|
||||
debug_assert!(self.t_value <= MAX_T_VALUE);
|
||||
// 1/kMaxTValue can't be represented as a float, but it's close and the limits work fine.
|
||||
const MAX_T_RECIPROCAL: f32 = 1.0 / MAX_T_VALUE as f32;
|
||||
self.t_value as f32 * MAX_T_RECIPROCAL
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct ContourMeasure {
|
||||
segments: Vec<Segment>,
|
||||
points: Vec<Point>,
|
||||
length: f32,
|
||||
is_closed: bool,
|
||||
}
|
||||
|
||||
impl ContourMeasure {
|
||||
fn push_segment(
|
||||
&self,
|
||||
mut start_d: f32,
|
||||
mut stop_d: f32,
|
||||
start_with_move_to: bool,
|
||||
pb: &mut PathBuilder,
|
||||
) -> Option<()> {
|
||||
if start_d < 0.0 {
|
||||
start_d = 0.0;
|
||||
}
|
||||
|
||||
if stop_d > self.length {
|
||||
stop_d = self.length;
|
||||
}
|
||||
|
||||
if !(start_d <= stop_d) {
|
||||
// catch NaN values as well
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.segments.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (seg_index, mut start_t) = self.distance_to_segment(start_d)?;
|
||||
let mut seg = self.segments[seg_index];
|
||||
|
||||
let (stop_seg_index, stop_t) = self.distance_to_segment(stop_d)?;
|
||||
let stop_seg = self.segments[stop_seg_index];
|
||||
|
||||
debug_assert!(stop_seg_index <= stop_seg_index);
|
||||
let mut p = Point::zero();
|
||||
if start_with_move_to {
|
||||
compute_pos_tan(
|
||||
&self.points[seg.point_index..],
|
||||
seg.kind,
|
||||
start_t,
|
||||
Some(&mut p),
|
||||
None,
|
||||
);
|
||||
pb.move_to(p.x, p.y);
|
||||
}
|
||||
|
||||
if seg.point_index == stop_seg.point_index {
|
||||
segment_to(
|
||||
&self.points[seg.point_index..],
|
||||
seg.kind,
|
||||
start_t,
|
||||
stop_t,
|
||||
pb,
|
||||
);
|
||||
} else {
|
||||
let mut new_seg_index = seg_index;
|
||||
loop {
|
||||
segment_to(
|
||||
&self.points[seg.point_index..],
|
||||
seg.kind,
|
||||
start_t,
|
||||
NormalizedF32::ONE,
|
||||
pb,
|
||||
);
|
||||
|
||||
let old_point_index = seg.point_index;
|
||||
loop {
|
||||
new_seg_index += 1;
|
||||
if self.segments[new_seg_index].point_index != old_point_index {
|
||||
break;
|
||||
}
|
||||
}
|
||||
seg = self.segments[new_seg_index];
|
||||
|
||||
start_t = NormalizedF32::ZERO;
|
||||
|
||||
if seg.point_index >= stop_seg.point_index {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
segment_to(
|
||||
&self.points[seg.point_index..],
|
||||
seg.kind,
|
||||
NormalizedF32::ZERO,
|
||||
stop_t,
|
||||
pb,
|
||||
);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn distance_to_segment(&self, distance: f32) -> Option<(usize, NormalizedF32)> {
|
||||
debug_assert!(distance >= 0.0 && distance <= self.length);
|
||||
|
||||
let mut index = find_segment(&self.segments, distance);
|
||||
// don't care if we hit an exact match or not, so we xor index if it is negative
|
||||
index ^= index >> 31;
|
||||
let index = index as usize;
|
||||
let seg = self.segments[index];
|
||||
|
||||
// now interpolate t-values with the prev segment (if possible)
|
||||
let mut start_t = 0.0;
|
||||
let mut start_d = 0.0;
|
||||
// check if the prev segment is legal, and references the same set of points
|
||||
if index > 0 {
|
||||
start_d = self.segments[index - 1].distance;
|
||||
if self.segments[index - 1].point_index == seg.point_index {
|
||||
debug_assert!(self.segments[index - 1].kind == seg.kind);
|
||||
start_t = self.segments[index - 1].scalar_t();
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(seg.scalar_t() > start_t);
|
||||
debug_assert!(distance >= start_d);
|
||||
debug_assert!(seg.distance > start_d);
|
||||
|
||||
let t =
|
||||
start_t + (seg.scalar_t() - start_t) * (distance - start_d) / (seg.distance - start_d);
|
||||
let t = NormalizedF32::new(t)?;
|
||||
Some((index, t))
|
||||
}
|
||||
|
||||
fn compute_line_seg(
|
||||
&mut self,
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
mut distance: f32,
|
||||
point_index: usize,
|
||||
) -> f32 {
|
||||
let d = p0.distance(p1);
|
||||
debug_assert!(d >= 0.0);
|
||||
let prev_d = distance;
|
||||
distance += d;
|
||||
if distance > prev_d {
|
||||
debug_assert!(point_index < self.points.len());
|
||||
self.segments.push(Segment {
|
||||
distance,
|
||||
point_index,
|
||||
t_value: MAX_T_VALUE,
|
||||
kind: SegmentType::Line,
|
||||
});
|
||||
}
|
||||
|
||||
distance
|
||||
}
|
||||
|
||||
fn compute_quad_segs(
|
||||
&mut self,
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
mut distance: f32,
|
||||
min_t: u32,
|
||||
max_t: u32,
|
||||
point_index: usize,
|
||||
tolerance: f32,
|
||||
) -> f32 {
|
||||
if t_span_big_enough(max_t - min_t) != 0 && quad_too_curvy(p0, p1, p2, tolerance) {
|
||||
let mut tmp = [Point::zero(); 5];
|
||||
let half_t = (min_t + max_t) >> 1;
|
||||
|
||||
path_geometry::chop_quad_at(&[p0, p1, p2], NormalizedF32Exclusive::HALF, &mut tmp);
|
||||
distance = self.compute_quad_segs(
|
||||
tmp[0],
|
||||
tmp[1],
|
||||
tmp[2],
|
||||
distance,
|
||||
min_t,
|
||||
half_t,
|
||||
point_index,
|
||||
tolerance,
|
||||
);
|
||||
distance = self.compute_quad_segs(
|
||||
tmp[2],
|
||||
tmp[3],
|
||||
tmp[4],
|
||||
distance,
|
||||
half_t,
|
||||
max_t,
|
||||
point_index,
|
||||
tolerance,
|
||||
);
|
||||
} else {
|
||||
let d = p0.distance(p2);
|
||||
let prev_d = distance;
|
||||
distance += d;
|
||||
if distance > prev_d {
|
||||
debug_assert!(point_index < self.points.len());
|
||||
self.segments.push(Segment {
|
||||
distance,
|
||||
point_index,
|
||||
t_value: max_t,
|
||||
kind: SegmentType::Quad,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
distance
|
||||
}
|
||||
|
||||
fn compute_cubic_segs(
|
||||
&mut self,
|
||||
p0: Point,
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
p3: Point,
|
||||
mut distance: f32,
|
||||
min_t: u32,
|
||||
max_t: u32,
|
||||
point_index: usize,
|
||||
tolerance: f32,
|
||||
) -> f32 {
|
||||
if t_span_big_enough(max_t - min_t) != 0 && cubic_too_curvy(p0, p1, p2, p3, tolerance) {
|
||||
let mut tmp = [Point::zero(); 7];
|
||||
let half_t = (min_t + max_t) >> 1;
|
||||
|
||||
path_geometry::chop_cubic_at2(
|
||||
&[p0, p1, p2, p3],
|
||||
NormalizedF32Exclusive::HALF,
|
||||
&mut tmp,
|
||||
);
|
||||
distance = self.compute_cubic_segs(
|
||||
tmp[0],
|
||||
tmp[1],
|
||||
tmp[2],
|
||||
tmp[3],
|
||||
distance,
|
||||
min_t,
|
||||
half_t,
|
||||
point_index,
|
||||
tolerance,
|
||||
);
|
||||
distance = self.compute_cubic_segs(
|
||||
tmp[3],
|
||||
tmp[4],
|
||||
tmp[5],
|
||||
tmp[6],
|
||||
distance,
|
||||
half_t,
|
||||
max_t,
|
||||
point_index,
|
||||
tolerance,
|
||||
);
|
||||
} else {
|
||||
let d = p0.distance(p3);
|
||||
let prev_d = distance;
|
||||
distance += d;
|
||||
if distance > prev_d {
|
||||
debug_assert!(point_index < self.points.len());
|
||||
self.segments.push(Segment {
|
||||
distance,
|
||||
point_index,
|
||||
t_value: max_t,
|
||||
kind: SegmentType::Cubic,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
distance
|
||||
}
|
||||
}
|
||||
|
||||
fn find_segment(base: &[Segment], key: f32) -> i32 {
|
||||
let mut lo = 0u32;
|
||||
let mut hi = (base.len() - 1) as u32;
|
||||
|
||||
while lo < hi {
|
||||
let mid = (hi + lo) >> 1;
|
||||
if base[mid as usize].distance < key {
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
|
||||
if base[hi as usize].distance < key {
|
||||
hi += 1;
|
||||
hi = !hi;
|
||||
} else if key < base[hi as usize].distance {
|
||||
hi = !hi;
|
||||
}
|
||||
|
||||
hi as i32
|
||||
}
|
||||
|
||||
fn compute_pos_tan(
|
||||
points: &[Point],
|
||||
seg_kind: SegmentType,
|
||||
t: NormalizedF32,
|
||||
pos: Option<&mut Point>,
|
||||
tangent: Option<&mut Point>,
|
||||
) {
|
||||
match seg_kind {
|
||||
SegmentType::Line => {
|
||||
if let Some(pos) = pos {
|
||||
*pos = Point::from_xy(
|
||||
interp(points[0].x, points[1].x, t),
|
||||
interp(points[0].y, points[1].y, t),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(tangent) = tangent {
|
||||
tangent.set_normalize(points[1].x - points[0].x, points[1].y - points[0].y);
|
||||
}
|
||||
}
|
||||
SegmentType::Quad => {
|
||||
let src = array_ref![points, 0, 3];
|
||||
if let Some(pos) = pos {
|
||||
*pos = path_geometry::eval_quad_at(src, t);
|
||||
}
|
||||
|
||||
if let Some(tangent) = tangent {
|
||||
*tangent = path_geometry::eval_quad_tangent_at(src, t);
|
||||
tangent.normalize();
|
||||
}
|
||||
}
|
||||
SegmentType::Cubic => {
|
||||
let src = array_ref![points, 0, 4];
|
||||
if let Some(pos) = pos {
|
||||
*pos = path_geometry::eval_cubic_pos_at(src, t);
|
||||
}
|
||||
|
||||
if let Some(tangent) = tangent {
|
||||
*tangent = path_geometry::eval_cubic_tangent_at(src, t);
|
||||
tangent.normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn segment_to(
|
||||
points: &[Point],
|
||||
seg_kind: SegmentType,
|
||||
start_t: NormalizedF32,
|
||||
stop_t: NormalizedF32,
|
||||
pb: &mut PathBuilder,
|
||||
) {
|
||||
debug_assert!(start_t <= stop_t);
|
||||
|
||||
if start_t == stop_t {
|
||||
if let Some(pt) = pb.last_point() {
|
||||
// If the dash as a zero-length on segment, add a corresponding zero-length line.
|
||||
// The stroke code will add end caps to zero length lines as appropriate.
|
||||
pb.line_to(pt.x, pt.y);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
match seg_kind {
|
||||
SegmentType::Line => {
|
||||
if stop_t == NormalizedF32::ONE {
|
||||
pb.line_to(points[1].x, points[1].y);
|
||||
} else {
|
||||
pb.line_to(
|
||||
interp(points[0].x, points[1].x, stop_t),
|
||||
interp(points[0].y, points[1].y, stop_t),
|
||||
);
|
||||
}
|
||||
}
|
||||
SegmentType::Quad => {
|
||||
let mut tmp0 = [Point::zero(); 5];
|
||||
let mut tmp1 = [Point::zero(); 5];
|
||||
if start_t == NormalizedF32::ZERO {
|
||||
if stop_t == NormalizedF32::ONE {
|
||||
pb.quad_to_pt(points[1], points[2]);
|
||||
} else {
|
||||
let stop_t = NormalizedF32Exclusive::new_bounded(stop_t.get());
|
||||
path_geometry::chop_quad_at(points, stop_t, &mut tmp0);
|
||||
pb.quad_to_pt(tmp0[1], tmp0[2]);
|
||||
}
|
||||
} else {
|
||||
let start_tt = NormalizedF32Exclusive::new_bounded(start_t.get());
|
||||
path_geometry::chop_quad_at(points, start_tt, &mut tmp0);
|
||||
if stop_t == NormalizedF32::ONE {
|
||||
pb.quad_to_pt(tmp0[3], tmp0[4]);
|
||||
} else {
|
||||
let new_t = (stop_t.get() - start_t.get()) / (1.0 - start_t.get());
|
||||
let new_t = NormalizedF32Exclusive::new_bounded(new_t);
|
||||
path_geometry::chop_quad_at(&tmp0[2..], new_t, &mut tmp1);
|
||||
pb.quad_to_pt(tmp1[1], tmp1[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
SegmentType::Cubic => {
|
||||
let mut tmp0 = [Point::zero(); 7];
|
||||
let mut tmp1 = [Point::zero(); 7];
|
||||
if start_t == NormalizedF32::ZERO {
|
||||
if stop_t == NormalizedF32::ONE {
|
||||
pb.cubic_to_pt(points[1], points[2], points[3]);
|
||||
} else {
|
||||
let stop_t = NormalizedF32Exclusive::new_bounded(stop_t.get());
|
||||
path_geometry::chop_cubic_at2(array_ref![points, 0, 4], stop_t, &mut tmp0);
|
||||
pb.cubic_to_pt(tmp0[1], tmp0[2], tmp0[3]);
|
||||
}
|
||||
} else {
|
||||
let start_tt = NormalizedF32Exclusive::new_bounded(start_t.get());
|
||||
path_geometry::chop_cubic_at2(array_ref![points, 0, 4], start_tt, &mut tmp0);
|
||||
if stop_t == NormalizedF32::ONE {
|
||||
pb.cubic_to_pt(tmp0[4], tmp0[5], tmp0[6]);
|
||||
} else {
|
||||
let new_t = (stop_t.get() - start_t.get()) / (1.0 - start_t.get());
|
||||
let new_t = NormalizedF32Exclusive::new_bounded(new_t);
|
||||
path_geometry::chop_cubic_at2(array_ref![tmp0, 3, 4], new_t, &mut tmp1);
|
||||
pb.cubic_to_pt(tmp1[1], tmp1[2], tmp1[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn t_span_big_enough(t_span: u32) -> u32 {
|
||||
debug_assert!(t_span <= MAX_T_VALUE);
|
||||
t_span >> 10
|
||||
}
|
||||
|
||||
fn quad_too_curvy(p0: Point, p1: Point, p2: Point, tolerance: f32) -> bool {
|
||||
// diff = (a/4 + b/2 + c/4) - (a/2 + c/2)
|
||||
// diff = -a/4 + b/2 - c/4
|
||||
let dx = (p1.x).half() - (p0.x + p2.x).half().half();
|
||||
let dy = (p1.y).half() - (p0.y + p2.y).half().half();
|
||||
|
||||
let dist = dx.abs().max(dy.abs());
|
||||
dist > tolerance
|
||||
}
|
||||
|
||||
fn cubic_too_curvy(p0: Point, p1: Point, p2: Point, p3: Point, tolerance: f32) -> bool {
|
||||
let n0 = cheap_dist_exceeds_limit(
|
||||
p1,
|
||||
interp_safe(p0.x, p3.x, 1.0 / 3.0),
|
||||
interp_safe(p0.y, p3.y, 1.0 / 3.0),
|
||||
tolerance,
|
||||
);
|
||||
|
||||
let n1 = cheap_dist_exceeds_limit(
|
||||
p2,
|
||||
interp_safe(p0.x, p3.x, 2.0 / 3.0),
|
||||
interp_safe(p0.y, p3.y, 2.0 / 3.0),
|
||||
tolerance,
|
||||
);
|
||||
|
||||
n0 || n1
|
||||
}
|
||||
|
||||
fn cheap_dist_exceeds_limit(pt: Point, x: f32, y: f32, tolerance: f32) -> bool {
|
||||
let dist = (x - pt.x).abs().max((y - pt.y).abs());
|
||||
// just made up the 1/2
|
||||
dist > tolerance
|
||||
}
|
||||
|
||||
/// Linearly interpolate between A and B, based on t.
|
||||
///
|
||||
/// If t is 0, return A. If t is 1, return B else interpolate.
|
||||
fn interp(a: f32, b: f32, t: NormalizedF32) -> f32 {
|
||||
a + (b - a) * t.get()
|
||||
}
|
||||
|
||||
fn interp_safe(a: f32, b: f32, t: f32) -> f32 {
|
||||
debug_assert!(t >= 0.0 && t <= 1.0);
|
||||
a + (b - a) * t
|
||||
}
|
||||
109
third-party/vendor/tiny-skia-path/src/f32x2_t.rs
vendored
Normal file
109
third-party/vendor/tiny-skia-path/src/f32x2_t.rs
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
use crate::NoStdFloat;
|
||||
|
||||
// Right now, there are no visible benefits of using SIMD for f32x2. So we don't.
|
||||
/// A pair of f32 numbers.
|
||||
///
|
||||
/// Mainly for internal use. Do not rely on it!
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Copy, Clone, Default, PartialEq, Debug)]
|
||||
pub struct f32x2(pub [f32; 2]);
|
||||
|
||||
impl f32x2 {
|
||||
/// Creates a new pair.
|
||||
pub fn new(a: f32, b: f32) -> f32x2 {
|
||||
f32x2([a, b])
|
||||
}
|
||||
|
||||
/// Creates a new pair from a single value.
|
||||
pub fn splat(x: f32) -> f32x2 {
|
||||
f32x2([x, x])
|
||||
}
|
||||
|
||||
/// Returns an absolute value.
|
||||
pub fn abs(self) -> f32x2 {
|
||||
f32x2([self.x().abs(), self.y().abs()])
|
||||
}
|
||||
|
||||
/// Returns a minimum value.
|
||||
pub fn min(self, other: f32x2) -> f32x2 {
|
||||
f32x2([pmin(self.x(), other.x()), pmin(self.y(), other.y())])
|
||||
}
|
||||
|
||||
/// Returns a maximum value.
|
||||
pub fn max(self, other: f32x2) -> f32x2 {
|
||||
f32x2([pmax(self.x(), other.x()), pmax(self.y(), other.y())])
|
||||
}
|
||||
|
||||
/// Returns a maximum of both values.
|
||||
pub fn max_component(self) -> f32 {
|
||||
pmax(self.x(), self.y())
|
||||
}
|
||||
|
||||
/// Returns the first value.
|
||||
pub fn x(&self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
/// Returns the second value.
|
||||
pub fn y(&self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add<f32x2> for f32x2 {
|
||||
type Output = f32x2;
|
||||
|
||||
fn add(self, other: f32x2) -> f32x2 {
|
||||
f32x2([self.x() + other.x(), self.y() + other.y()])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Sub<f32x2> for f32x2 {
|
||||
type Output = f32x2;
|
||||
|
||||
fn sub(self, other: f32x2) -> f32x2 {
|
||||
f32x2([self.x() - other.x(), self.y() - other.y()])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul<f32x2> for f32x2 {
|
||||
type Output = f32x2;
|
||||
|
||||
fn mul(self, other: f32x2) -> f32x2 {
|
||||
f32x2([self.x() * other.x(), self.y() * other.y()])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Div<f32x2> for f32x2 {
|
||||
type Output = f32x2;
|
||||
|
||||
fn div(self, other: f32x2) -> f32x2 {
|
||||
f32x2([self.x() / other.x(), self.y() / other.y()])
|
||||
}
|
||||
}
|
||||
|
||||
// A faster and more forgiving f32 min/max implementation.
|
||||
//
|
||||
// Unlike std one, we do not care about NaN.
|
||||
|
||||
fn pmax(a: f32, b: f32) -> f32 {
|
||||
if a < b {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
fn pmin(a: f32, b: f32) -> f32 {
|
||||
if b < a {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
80
third-party/vendor/tiny-skia-path/src/f32x4_t.rs
vendored
Normal file
80
third-party/vendor/tiny-skia-path/src/f32x4_t.rs
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Right now, there are no visible benefits of using SIMD for f32x4. So we don't.
|
||||
#[derive(Default, Clone, Copy, PartialEq, Debug)]
|
||||
#[repr(C, align(16))]
|
||||
pub struct f32x4(pub [f32; 4]);
|
||||
|
||||
impl f32x4 {
|
||||
pub fn max(self, rhs: Self) -> Self {
|
||||
Self([
|
||||
self.0[0].max(rhs.0[0]),
|
||||
self.0[1].max(rhs.0[1]),
|
||||
self.0[2].max(rhs.0[2]),
|
||||
self.0[3].max(rhs.0[3]),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn min(self, rhs: Self) -> Self {
|
||||
Self([
|
||||
self.0[0].min(rhs.0[0]),
|
||||
self.0[1].min(rhs.0[1]),
|
||||
self.0[2].min(rhs.0[2]),
|
||||
self.0[3].min(rhs.0[3]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add for f32x4 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self([
|
||||
self.0[0] + rhs.0[0],
|
||||
self.0[1] + rhs.0[1],
|
||||
self.0[2] + rhs.0[2],
|
||||
self.0[3] + rhs.0[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::AddAssign for f32x4 {
|
||||
fn add_assign(&mut self, rhs: f32x4) {
|
||||
*self = *self + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Sub for f32x4 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self([
|
||||
self.0[0] - rhs.0[0],
|
||||
self.0[1] - rhs.0[1],
|
||||
self.0[2] - rhs.0[2],
|
||||
self.0[3] - rhs.0[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul for f32x4 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Self([
|
||||
self.0[0] * rhs.0[0],
|
||||
self.0[1] * rhs.0[1],
|
||||
self.0[2] * rhs.0[2],
|
||||
self.0[3] * rhs.0[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::MulAssign for f32x4 {
|
||||
fn mul_assign(&mut self, rhs: f32x4) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
||||
148
third-party/vendor/tiny-skia-path/src/floating_point.rs
vendored
Normal file
148
third-party/vendor/tiny-skia-path/src/floating_point.rs
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::scalar::Scalar;
|
||||
|
||||
pub use strict_num::{FiniteF32, NonZeroPositiveF32, NormalizedF32};
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
use crate::NoStdFloat;
|
||||
|
||||
pub(crate) const FLOAT_PI: f32 = 3.14159265;
|
||||
|
||||
const MAX_I32_FITS_IN_F32: f32 = 2147483520.0;
|
||||
const MIN_I32_FITS_IN_F32: f32 = -MAX_I32_FITS_IN_F32;
|
||||
|
||||
// TODO: is there an std alternative?
|
||||
/// Custom float to integer conversion routines.
|
||||
pub trait SaturateCast<T>: Sized {
|
||||
/// Return the closest integer for the given float.
|
||||
fn saturate_from(n: T) -> Self;
|
||||
}
|
||||
|
||||
impl SaturateCast<f32> for i32 {
|
||||
/// Return the closest integer for the given float.
|
||||
///
|
||||
/// Returns MAX_I32_FITS_IN_F32 for NaN.
|
||||
fn saturate_from(mut x: f32) -> Self {
|
||||
x = if x < MAX_I32_FITS_IN_F32 {
|
||||
x
|
||||
} else {
|
||||
MAX_I32_FITS_IN_F32
|
||||
};
|
||||
x = if x > MIN_I32_FITS_IN_F32 {
|
||||
x
|
||||
} else {
|
||||
MIN_I32_FITS_IN_F32
|
||||
};
|
||||
x as i32
|
||||
}
|
||||
}
|
||||
|
||||
impl SaturateCast<f64> for i32 {
|
||||
/// Return the closest integer for the given double.
|
||||
///
|
||||
/// Returns i32::MAX for NaN.
|
||||
fn saturate_from(mut x: f64) -> Self {
|
||||
x = if x < i32::MAX as f64 {
|
||||
x
|
||||
} else {
|
||||
i32::MAX as f64
|
||||
};
|
||||
x = if x > i32::MIN as f64 {
|
||||
x
|
||||
} else {
|
||||
i32::MIN as f64
|
||||
};
|
||||
x as i32
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom float to integer rounding routines.
|
||||
#[allow(missing_docs)]
|
||||
pub trait SaturateRound<T>: SaturateCast<T> {
|
||||
fn saturate_floor(n: T) -> Self;
|
||||
fn saturate_ceil(n: T) -> Self;
|
||||
fn saturate_round(n: T) -> Self;
|
||||
}
|
||||
|
||||
impl SaturateRound<f32> for i32 {
|
||||
fn saturate_floor(x: f32) -> Self {
|
||||
Self::saturate_from(x.floor())
|
||||
}
|
||||
|
||||
fn saturate_ceil(x: f32) -> Self {
|
||||
Self::saturate_from(x.ceil())
|
||||
}
|
||||
|
||||
fn saturate_round(x: f32) -> Self {
|
||||
Self::saturate_from(x.floor() + 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the float as a 2s compliment int. Just to be used to compare floats
|
||||
/// to each other or against positive float-bit-constants (like 0). This does
|
||||
/// not return the int equivalent of the float, just something cheaper for
|
||||
/// compares-only.
|
||||
pub(crate) fn f32_as_2s_compliment(x: f32) -> i32 {
|
||||
sign_bit_to_2s_compliment(bytemuck::cast(x))
|
||||
}
|
||||
|
||||
/// Convert a sign-bit int (i.e. float interpreted as int) into a 2s compliement
|
||||
/// int. This also converts -0 (0x80000000) to 0. Doing this to a float allows
|
||||
/// it to be compared using normal C operators (<, <=, etc.)
|
||||
fn sign_bit_to_2s_compliment(mut x: i32) -> i32 {
|
||||
if x < 0 {
|
||||
x &= 0x7FFFFFFF;
|
||||
x = -x;
|
||||
}
|
||||
|
||||
x
|
||||
}
|
||||
|
||||
/// An immutable `f32` that is larger than 0 but less then 1.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct NormalizedF32Exclusive(FiniteF32);
|
||||
|
||||
impl NormalizedF32Exclusive {
|
||||
/// Just a random, valid number.
|
||||
pub const ANY: Self = Self::HALF;
|
||||
|
||||
/// A predefined 0.5 value.
|
||||
pub const HALF: Self = NormalizedF32Exclusive(unsafe { FiniteF32::new_unchecked(0.5) });
|
||||
|
||||
/// Creates a `NormalizedF32Exclusive`.
|
||||
pub fn new(n: f32) -> Option<Self> {
|
||||
if n > 0.0 && n < 1.0 {
|
||||
// `n` is guarantee to be finite after the bounds check.
|
||||
FiniteF32::new(n).map(NormalizedF32Exclusive)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `NormalizedF32Exclusive` clamping the given value.
|
||||
///
|
||||
/// Returns zero in case of NaN or infinity.
|
||||
pub fn new_bounded(n: f32) -> Self {
|
||||
let n = n.bound(core::f32::EPSILON, 1.0 - core::f32::EPSILON);
|
||||
// `n` is guarantee to be finite after clamping.
|
||||
debug_assert!(n.is_finite());
|
||||
NormalizedF32Exclusive(unsafe { FiniteF32::new_unchecked(n) })
|
||||
}
|
||||
|
||||
/// Returns the value as a primitive type.
|
||||
pub fn get(self) -> f32 {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
/// Returns the value as a `FiniteF32`.
|
||||
pub fn to_normalized(self) -> NormalizedF32 {
|
||||
// NormalizedF32 is (0,1), while NormalizedF32 is [0,1], so it will always fit.
|
||||
unsafe { NormalizedF32::new_unchecked(self.0.get()) }
|
||||
}
|
||||
}
|
||||
389
third-party/vendor/tiny-skia-path/src/lib.rs
vendored
Normal file
389
third-party/vendor/tiny-skia-path/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! A [tiny-skia](https://github.com/RazrFalcon/tiny-skia) Bezier path implementation.
|
||||
//!
|
||||
//! Provides a memory-efficient Bezier path container, path builder, path stroker and path dasher.
|
||||
//!
|
||||
//! Also provides some basic geometry types, but they will be moved to an external crate eventually.
|
||||
//!
|
||||
//! Note that all types use single precision floats (`f32`), just like [Skia](https://skia.org/).
|
||||
|
||||
#![no_std]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(missing_copy_implementations)]
|
||||
#![warn(missing_debug_implementations)]
|
||||
#![allow(clippy::approx_constant)]
|
||||
#![allow(clippy::collapsible_if)]
|
||||
#![allow(clippy::eq_op)]
|
||||
#![allow(clippy::excessive_precision)]
|
||||
#![allow(clippy::identity_op)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
#![allow(clippy::neg_cmp_op_on_partial_ord)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
#![allow(clippy::wrong_self_convention)]
|
||||
|
||||
#[cfg(not(any(feature = "std", feature = "no-std-float")))]
|
||||
compile_error!("You have to activate either the `std` or the `no-std-float` feature.");
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod dash;
|
||||
mod f32x2_t;
|
||||
mod f32x4_t;
|
||||
mod floating_point;
|
||||
mod path;
|
||||
mod path_builder;
|
||||
pub mod path_geometry;
|
||||
mod rect;
|
||||
mod scalar;
|
||||
mod stroker;
|
||||
mod transform;
|
||||
|
||||
pub use dash::StrokeDash;
|
||||
pub use f32x2_t::f32x2;
|
||||
pub use floating_point::*;
|
||||
pub use path::*;
|
||||
pub use path_builder::*;
|
||||
pub use rect::*;
|
||||
pub use scalar::*;
|
||||
pub use stroker::*;
|
||||
pub use transform::*;
|
||||
|
||||
/// An integer length that is guarantee to be > 0
|
||||
type LengthU32 = core::num::NonZeroU32;
|
||||
|
||||
/// A point.
|
||||
///
|
||||
/// Doesn't guarantee to be finite.
|
||||
#[allow(missing_docs)]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, PartialEq, Default, Debug)]
|
||||
pub struct Point {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
impl From<(f32, f32)> for Point {
|
||||
#[inline]
|
||||
fn from(v: (f32, f32)) -> Self {
|
||||
Point { x: v.0, y: v.1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Point {
|
||||
/// Creates a new `Point`.
|
||||
pub fn from_xy(x: f32, y: f32) -> Self {
|
||||
Point { x, y }
|
||||
}
|
||||
|
||||
/// Creates a new `Point` from `f32x2`.
|
||||
pub fn from_f32x2(r: f32x2) -> Self {
|
||||
Point::from_xy(r.x(), r.y())
|
||||
}
|
||||
|
||||
/// Converts a `Point` into a `f32x2`.
|
||||
pub fn to_f32x2(&self) -> f32x2 {
|
||||
f32x2::new(self.x, self.y)
|
||||
}
|
||||
|
||||
/// Creates a point at 0x0 position.
|
||||
pub fn zero() -> Self {
|
||||
Point { x: 0.0, y: 0.0 }
|
||||
}
|
||||
|
||||
/// Returns true if x and y are both zero.
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.x == 0.0 && self.y == 0.0
|
||||
}
|
||||
|
||||
/// Returns true if both x and y are measurable values.
|
||||
///
|
||||
/// Both values are other than infinities and NaN.
|
||||
pub fn is_finite(&self) -> bool {
|
||||
(self.x * self.y).is_finite()
|
||||
}
|
||||
|
||||
/// Checks that two `Point`s are almost equal.
|
||||
pub(crate) fn almost_equal(&self, other: Point) -> bool {
|
||||
!(*self - other).can_normalize()
|
||||
}
|
||||
|
||||
/// Checks that two `Point`s are almost equal using the specified tolerance.
|
||||
pub(crate) fn equals_within_tolerance(&self, other: Point, tolerance: f32) -> bool {
|
||||
(self.x - other.x).is_nearly_zero_within_tolerance(tolerance)
|
||||
&& (self.y - other.y).is_nearly_zero_within_tolerance(tolerance)
|
||||
}
|
||||
|
||||
/// Scales (fX, fY) so that length() returns one, while preserving ratio of fX to fY,
|
||||
/// if possible.
|
||||
///
|
||||
/// If prior length is nearly zero, sets vector to (0, 0) and returns
|
||||
/// false; otherwise returns true.
|
||||
pub fn normalize(&mut self) -> bool {
|
||||
self.set_length_from(self.x, self.y, 1.0)
|
||||
}
|
||||
|
||||
/// Sets vector to (x, y) scaled so length() returns one, and so that (x, y)
|
||||
/// is proportional to (x, y).
|
||||
///
|
||||
/// If (x, y) length is nearly zero, sets vector to (0, 0) and returns false;
|
||||
/// otherwise returns true.
|
||||
pub fn set_normalize(&mut self, x: f32, y: f32) -> bool {
|
||||
self.set_length_from(x, y, 1.0)
|
||||
}
|
||||
|
||||
pub(crate) fn can_normalize(&self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite() && (self.x != 0.0 || self.y != 0.0)
|
||||
}
|
||||
|
||||
/// Returns the Euclidean distance from origin.
|
||||
pub fn length(&self) -> f32 {
|
||||
let mag2 = self.x * self.x + self.y * self.y;
|
||||
if mag2.is_finite() {
|
||||
mag2.sqrt()
|
||||
} else {
|
||||
let xx = f64::from(self.x);
|
||||
let yy = f64::from(self.y);
|
||||
(xx * xx + yy * yy).sqrt() as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales vector so that distanceToOrigin() returns length, if possible.
|
||||
///
|
||||
/// If former length is nearly zero, sets vector to (0, 0) and return false;
|
||||
/// otherwise returns true.
|
||||
pub fn set_length(&mut self, length: f32) -> bool {
|
||||
self.set_length_from(self.x, self.y, length)
|
||||
}
|
||||
|
||||
/// Sets vector to (x, y) scaled to length, if possible.
|
||||
///
|
||||
/// If former length is nearly zero, sets vector to (0, 0) and return false;
|
||||
/// otherwise returns true.
|
||||
pub fn set_length_from(&mut self, x: f32, y: f32, length: f32) -> bool {
|
||||
set_point_length(self, x, y, length, &mut None)
|
||||
}
|
||||
|
||||
/// Returns the Euclidean distance from origin.
|
||||
pub fn distance(&self, other: Point) -> f32 {
|
||||
(*self - other).length()
|
||||
}
|
||||
|
||||
/// Returns the dot product of two points.
|
||||
pub fn dot(&self, other: Point) -> f32 {
|
||||
self.x * other.x + self.y * other.y
|
||||
}
|
||||
|
||||
/// Returns the cross product of vector and vec.
|
||||
///
|
||||
/// Vector and vec form three-dimensional vectors with z-axis value equal to zero.
|
||||
/// The cross product is a three-dimensional vector with x-axis and y-axis values
|
||||
/// equal to zero. The cross product z-axis component is returned.
|
||||
pub fn cross(&self, other: Point) -> f32 {
|
||||
self.x * other.y - self.y * other.x
|
||||
}
|
||||
|
||||
pub(crate) fn distance_to_sqd(&self, pt: Point) -> f32 {
|
||||
let dx = self.x - pt.x;
|
||||
let dy = self.y - pt.y;
|
||||
dx * dx + dy * dy
|
||||
}
|
||||
|
||||
pub(crate) fn length_sqd(&self) -> f32 {
|
||||
self.dot(*self)
|
||||
}
|
||||
|
||||
/// Scales Point in-place by scale.
|
||||
pub fn scale(&mut self, scale: f32) {
|
||||
self.x *= scale;
|
||||
self.y *= scale;
|
||||
}
|
||||
|
||||
pub(crate) fn scaled(&self, scale: f32) -> Self {
|
||||
Point::from_xy(self.x * scale, self.y * scale)
|
||||
}
|
||||
|
||||
pub(crate) fn swap_coords(&mut self) {
|
||||
core::mem::swap(&mut self.x, &mut self.y);
|
||||
}
|
||||
|
||||
pub(crate) fn rotate_cw(&mut self) {
|
||||
self.swap_coords();
|
||||
self.x = -self.x;
|
||||
}
|
||||
|
||||
pub(crate) fn rotate_ccw(&mut self) {
|
||||
self.swap_coords();
|
||||
self.y = -self.y;
|
||||
}
|
||||
}
|
||||
|
||||
// We have to worry about 2 tricky conditions:
|
||||
// 1. underflow of mag2 (compared against nearlyzero^2)
|
||||
// 2. overflow of mag2 (compared w/ isfinite)
|
||||
//
|
||||
// If we underflow, we return false. If we overflow, we compute again using
|
||||
// doubles, which is much slower (3x in a desktop test) but will not overflow.
|
||||
fn set_point_length(
|
||||
pt: &mut Point,
|
||||
mut x: f32,
|
||||
mut y: f32,
|
||||
length: f32,
|
||||
orig_length: &mut Option<f32>,
|
||||
) -> bool {
|
||||
// our mag2 step overflowed to infinity, so use doubles instead.
|
||||
// much slower, but needed when x or y are very large, other wise we
|
||||
// divide by inf. and return (0,0) vector.
|
||||
let xx = x as f64;
|
||||
let yy = y as f64;
|
||||
let dmag = (xx * xx + yy * yy).sqrt();
|
||||
let dscale = length as f64 / dmag;
|
||||
x *= dscale as f32;
|
||||
y *= dscale as f32;
|
||||
|
||||
// check if we're not finite, or we're zero-length
|
||||
if !x.is_finite() || !y.is_finite() || (x == 0.0 && y == 0.0) {
|
||||
*pt = Point::zero();
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut mag = 0.0;
|
||||
if orig_length.is_some() {
|
||||
mag = dmag as f32;
|
||||
}
|
||||
|
||||
*pt = Point::from_xy(x, y);
|
||||
|
||||
if orig_length.is_some() {
|
||||
*orig_length = Some(mag);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
impl core::ops::Neg for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Point {
|
||||
x: -self.x,
|
||||
y: -self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn add(self, other: Point) -> Self::Output {
|
||||
Point::from_xy(self.x + other.x, self.y + other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::AddAssign for Point {
|
||||
fn add_assign(&mut self, other: Point) {
|
||||
self.x += other.x;
|
||||
self.y += other.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Sub for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn sub(self, other: Point) -> Self::Output {
|
||||
Point::from_xy(self.x - other.x, self.y - other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::SubAssign for Point {
|
||||
fn sub_assign(&mut self, other: Point) {
|
||||
self.x -= other.x;
|
||||
self.y -= other.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Mul for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn mul(self, other: Point) -> Self::Output {
|
||||
Point::from_xy(self.x * other.x, self.y * other.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::MulAssign for Point {
|
||||
fn mul_assign(&mut self, other: Point) {
|
||||
self.x *= other.x;
|
||||
self.y *= other.y;
|
||||
}
|
||||
}
|
||||
|
||||
/// An integer size.
|
||||
///
|
||||
/// # Guarantees
|
||||
///
|
||||
/// - Width and height are positive and non-zero.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub struct IntSize {
|
||||
width: LengthU32,
|
||||
height: LengthU32,
|
||||
}
|
||||
|
||||
impl IntSize {
|
||||
/// Creates a new `IntSize` from width and height.
|
||||
pub fn from_wh(width: u32, height: u32) -> Option<Self> {
|
||||
Some(IntSize {
|
||||
width: LengthU32::new(width)?,
|
||||
height: LengthU32::new(height)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns width.
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width.get()
|
||||
}
|
||||
|
||||
/// Returns height.
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height.get()
|
||||
}
|
||||
|
||||
/// Converts the current size into a `IntRect` at a provided position.
|
||||
pub fn to_int_rect(&self, x: i32, y: i32) -> IntRect {
|
||||
IntRect::from_xywh(x, y, self.width.get(), self.height.get()).unwrap()
|
||||
}
|
||||
|
||||
/// Converts the current size into a `IntRect` at a provided position.
|
||||
pub fn to_screen_int_rect(&self, x: u32, y: u32) -> ScreenIntRect {
|
||||
ScreenIntRect::from_xywh_safe(x, y, self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn int_size_tests() {
|
||||
assert_eq!(IntSize::from_wh(0, 0), None);
|
||||
assert_eq!(IntSize::from_wh(1, 0), None);
|
||||
assert_eq!(IntSize::from_wh(0, 1), None);
|
||||
|
||||
let size = IntSize::from_wh(3, 4).unwrap();
|
||||
assert_eq!(
|
||||
size.to_int_rect(1, 2),
|
||||
IntRect::from_xywh(1, 2, 3, 4).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
size.to_screen_int_rect(1, 2),
|
||||
ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
288
third-party/vendor/tiny-skia-path/src/path.rs
vendored
Normal file
288
third-party/vendor/tiny-skia-path/src/path.rs
vendored
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::path_builder::PathBuilder;
|
||||
use crate::transform::Transform;
|
||||
use crate::{Point, Rect};
|
||||
|
||||
/// A path verb.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
pub enum PathVerb {
|
||||
Move,
|
||||
Line,
|
||||
Quad,
|
||||
Cubic,
|
||||
Close,
|
||||
}
|
||||
|
||||
/// A Bezier path.
|
||||
///
|
||||
/// Can be created via [`PathBuilder`].
|
||||
/// Where [`PathBuilder`] can be created from the [`Path`] using [`clear`] to reuse the allocation.
|
||||
///
|
||||
/// Path is immutable and uses compact storage, where segment types and numbers are stored
|
||||
/// separately. Use can access path segments via [`Path::verbs`] and [`Path::points`],
|
||||
/// or via [`Path::segments`]
|
||||
///
|
||||
/// # Guarantees
|
||||
///
|
||||
/// - Has a valid, precomputed bounds.
|
||||
/// - All points are finite.
|
||||
/// - Has at least two segments.
|
||||
/// - Each contour starts with a MoveTo.
|
||||
/// - No duplicated Move.
|
||||
/// - No duplicated Close.
|
||||
/// - Zero-length contours are allowed.
|
||||
///
|
||||
/// [`PathBuilder`]: struct.PathBuilder.html
|
||||
/// [`clear`]: struct.Path.html#method.clear
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Path {
|
||||
pub(crate) verbs: Vec<PathVerb>,
|
||||
pub(crate) points: Vec<Point>,
|
||||
pub(crate) bounds: Rect,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
/// Returns the number of segments in the path.
|
||||
pub fn len(&self) -> usize {
|
||||
self.verbs.len()
|
||||
}
|
||||
|
||||
/// Checks if path is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the bounds of the path's points.
|
||||
///
|
||||
/// The value is already calculated.
|
||||
pub fn bounds(&self) -> Rect {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
/// Returns an internal vector of verbs.
|
||||
pub fn verbs(&self) -> &[PathVerb] {
|
||||
&self.verbs
|
||||
}
|
||||
|
||||
/// Returns an internal vector of points.
|
||||
pub fn points(&self) -> &[Point] {
|
||||
&self.points
|
||||
}
|
||||
|
||||
/// Returns a transformed in-place path.
|
||||
///
|
||||
/// Some points may become NaN/inf therefore this method can fail.
|
||||
pub fn transform(mut self, ts: Transform) -> Option<Self> {
|
||||
if ts.is_identity() {
|
||||
return Some(self);
|
||||
}
|
||||
|
||||
ts.map_points(&mut self.points);
|
||||
|
||||
// Update bounds.
|
||||
self.bounds = Rect::from_points(&self.points)?;
|
||||
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over path's segments.
|
||||
pub fn segments(&self) -> PathSegmentsIter {
|
||||
PathSegmentsIter {
|
||||
path: self,
|
||||
verb_index: 0,
|
||||
points_index: 0,
|
||||
is_auto_close: false,
|
||||
last_move_to: Point::zero(),
|
||||
last_point: Point::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the path and returns a `PathBuilder` that will reuse an allocated memory.
|
||||
pub fn clear(mut self) -> PathBuilder {
|
||||
self.verbs.clear();
|
||||
self.points.clear();
|
||||
|
||||
PathBuilder {
|
||||
verbs: self.verbs,
|
||||
points: self.points,
|
||||
last_move_to_index: 0,
|
||||
move_to_required: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Path {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
use core::fmt::Write;
|
||||
|
||||
let mut s = alloc::string::String::new();
|
||||
for segment in self.segments() {
|
||||
match segment {
|
||||
PathSegment::MoveTo(p) => s.write_fmt(format_args!("M {} {} ", p.x, p.y))?,
|
||||
PathSegment::LineTo(p) => s.write_fmt(format_args!("L {} {} ", p.x, p.y))?,
|
||||
PathSegment::QuadTo(p0, p1) => {
|
||||
s.write_fmt(format_args!("Q {} {} {} {} ", p0.x, p0.y, p1.x, p1.y))?
|
||||
}
|
||||
PathSegment::CubicTo(p0, p1, p2) => s.write_fmt(format_args!(
|
||||
"C {} {} {} {} {} {} ",
|
||||
p0.x, p0.y, p1.x, p1.y, p2.x, p2.y
|
||||
))?,
|
||||
PathSegment::Close => s.write_fmt(format_args!("Z "))?,
|
||||
}
|
||||
}
|
||||
|
||||
s.pop(); // ' '
|
||||
|
||||
f.debug_struct("Path")
|
||||
.field("segments", &s)
|
||||
.field("bounds", &self.bounds)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A path segment.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum PathSegment {
|
||||
MoveTo(Point),
|
||||
LineTo(Point),
|
||||
QuadTo(Point, Point),
|
||||
CubicTo(Point, Point, Point),
|
||||
Close,
|
||||
}
|
||||
|
||||
/// A path segments iterator.
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone)]
|
||||
pub struct PathSegmentsIter<'a> {
|
||||
path: &'a Path,
|
||||
verb_index: usize,
|
||||
points_index: usize,
|
||||
|
||||
is_auto_close: bool,
|
||||
last_move_to: Point,
|
||||
last_point: Point,
|
||||
}
|
||||
|
||||
impl<'a> PathSegmentsIter<'a> {
|
||||
/// Sets the auto closing mode. Off by default.
|
||||
///
|
||||
/// When enabled, emits an additional `PathSegment::Line` from the current position
|
||||
/// to the previous `PathSegment::Move`. And only then emits `PathSegment::Close`.
|
||||
pub fn set_auto_close(&mut self, flag: bool) {
|
||||
self.is_auto_close = flag;
|
||||
}
|
||||
|
||||
pub(crate) fn auto_close(&mut self) -> PathSegment {
|
||||
if self.is_auto_close && self.last_point != self.last_move_to {
|
||||
self.verb_index -= 1;
|
||||
PathSegment::LineTo(self.last_move_to)
|
||||
} else {
|
||||
PathSegment::Close
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_valid_tangent(&self) -> bool {
|
||||
let mut iter = self.clone();
|
||||
while let Some(segment) = iter.next() {
|
||||
match segment {
|
||||
PathSegment::MoveTo(_) => {
|
||||
return false;
|
||||
}
|
||||
PathSegment::LineTo(p) => {
|
||||
if iter.last_point == p {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
PathSegment::QuadTo(p1, p2) => {
|
||||
if iter.last_point == p1 && iter.last_point == p2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
PathSegment::CubicTo(p1, p2, p3) => {
|
||||
if iter.last_point == p1 && iter.last_point == p2 && iter.last_point == p3 {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
PathSegment::Close => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the current verb.
|
||||
pub fn curr_verb(&self) -> PathVerb {
|
||||
self.path.verbs[self.verb_index - 1]
|
||||
}
|
||||
|
||||
/// Returns the next verb.
|
||||
pub fn next_verb(&self) -> Option<PathVerb> {
|
||||
self.path.verbs.get(self.verb_index).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PathSegmentsIter<'a> {
|
||||
type Item = PathSegment;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.verb_index < self.path.verbs.len() {
|
||||
let verb = self.path.verbs[self.verb_index];
|
||||
self.verb_index += 1;
|
||||
|
||||
match verb {
|
||||
PathVerb::Move => {
|
||||
self.points_index += 1;
|
||||
self.last_move_to = self.path.points[self.points_index - 1];
|
||||
self.last_point = self.last_move_to;
|
||||
Some(PathSegment::MoveTo(self.last_move_to))
|
||||
}
|
||||
PathVerb::Line => {
|
||||
self.points_index += 1;
|
||||
self.last_point = self.path.points[self.points_index - 1];
|
||||
Some(PathSegment::LineTo(self.last_point))
|
||||
}
|
||||
PathVerb::Quad => {
|
||||
self.points_index += 2;
|
||||
self.last_point = self.path.points[self.points_index - 1];
|
||||
Some(PathSegment::QuadTo(
|
||||
self.path.points[self.points_index - 2],
|
||||
self.last_point,
|
||||
))
|
||||
}
|
||||
PathVerb::Cubic => {
|
||||
self.points_index += 3;
|
||||
self.last_point = self.path.points[self.points_index - 1];
|
||||
Some(PathSegment::CubicTo(
|
||||
self.path.points[self.points_index - 3],
|
||||
self.path.points[self.points_index - 2],
|
||||
self.last_point,
|
||||
))
|
||||
}
|
||||
PathVerb::Close => {
|
||||
let seg = self.auto_close();
|
||||
self.last_point = self.last_move_to;
|
||||
Some(seg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
426
third-party/vendor/tiny-skia-path/src/path_builder.rs
vendored
Normal file
426
third-party/vendor/tiny-skia-path/src/path_builder.rs
vendored
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// NOTE: this is not SkPathBuilder, but rather a reimplementation of SkPath.
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::{Path, Point, Rect};
|
||||
|
||||
use crate::path::PathVerb;
|
||||
use crate::path_geometry;
|
||||
use crate::scalar::{Scalar, SCALAR_ROOT_2_OVER_2};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub(crate) enum PathDirection {
|
||||
/// Clockwise direction for adding closed contours.
|
||||
CW,
|
||||
/// Counter-clockwise direction for adding closed contours.
|
||||
CCW,
|
||||
}
|
||||
|
||||
/// A path builder.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct PathBuilder {
|
||||
pub(crate) verbs: Vec<PathVerb>,
|
||||
pub(crate) points: Vec<Point>,
|
||||
pub(crate) last_move_to_index: usize,
|
||||
pub(crate) move_to_required: bool,
|
||||
}
|
||||
|
||||
impl PathBuilder {
|
||||
/// Creates a new builder.
|
||||
pub fn new() -> Self {
|
||||
PathBuilder {
|
||||
verbs: Vec::new(),
|
||||
points: Vec::new(),
|
||||
last_move_to_index: 0,
|
||||
move_to_required: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new builder with a specified capacity.
|
||||
///
|
||||
/// Number of points depends on a verb type:
|
||||
///
|
||||
/// - Move - 1
|
||||
/// - Line - 1
|
||||
/// - Quad - 2
|
||||
/// - Cubic - 3
|
||||
/// - Close - 0
|
||||
pub fn with_capacity(verbs_capacity: usize, points_capacity: usize) -> Self {
|
||||
PathBuilder {
|
||||
verbs: Vec::with_capacity(verbs_capacity),
|
||||
points: Vec::with_capacity(points_capacity),
|
||||
last_move_to_index: 0,
|
||||
move_to_required: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Path` from `Rect`.
|
||||
///
|
||||
/// Never fails since `Rect` is always valid.
|
||||
///
|
||||
/// Segments are created clockwise: TopLeft -> TopRight -> BottomRight -> BottomLeft
|
||||
///
|
||||
/// The contour is closed.
|
||||
pub fn from_rect(rect: Rect) -> Path {
|
||||
let verbs = vec![
|
||||
PathVerb::Move,
|
||||
PathVerb::Line,
|
||||
PathVerb::Line,
|
||||
PathVerb::Line,
|
||||
PathVerb::Close,
|
||||
];
|
||||
|
||||
let points = vec![
|
||||
Point::from_xy(rect.left(), rect.top()),
|
||||
Point::from_xy(rect.right(), rect.top()),
|
||||
Point::from_xy(rect.right(), rect.bottom()),
|
||||
Point::from_xy(rect.left(), rect.bottom()),
|
||||
];
|
||||
|
||||
Path {
|
||||
bounds: rect,
|
||||
verbs,
|
||||
points,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Path` from a circle.
|
||||
///
|
||||
/// See [`PathBuilder::push_circle`] for details.
|
||||
pub fn from_circle(cx: f32, cy: f32, radius: f32) -> Option<Path> {
|
||||
let mut b = PathBuilder::new();
|
||||
b.push_circle(cx, cy, radius);
|
||||
b.finish()
|
||||
}
|
||||
|
||||
/// Creates a new `Path` from an oval.
|
||||
///
|
||||
/// See [`PathBuilder::push_oval`] for details.
|
||||
pub fn from_oval(oval: Rect) -> Option<Path> {
|
||||
let mut b = PathBuilder::new();
|
||||
b.push_oval(oval);
|
||||
b.finish()
|
||||
}
|
||||
|
||||
pub(crate) fn reserve(&mut self, additional_verbs: usize, additional_points: usize) {
|
||||
self.verbs.reserve(additional_verbs);
|
||||
self.points.reserve(additional_points);
|
||||
}
|
||||
|
||||
/// Returns the current number of segments in the builder.
|
||||
pub fn len(&self) -> usize {
|
||||
self.verbs.len()
|
||||
}
|
||||
|
||||
/// Checks if the builder has any segments added.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.verbs.is_empty()
|
||||
}
|
||||
|
||||
/// Adds beginning of a contour.
|
||||
///
|
||||
/// Multiple continuous MoveTo segments are not allowed.
|
||||
/// If the previous segment was also MoveTo, it will be overwritten with the current one.
|
||||
pub fn move_to(&mut self, x: f32, y: f32) {
|
||||
if let Some(PathVerb::Move) = self.verbs.last() {
|
||||
let last_idx = self.points.len() - 1;
|
||||
self.points[last_idx] = Point::from_xy(x, y);
|
||||
} else {
|
||||
self.last_move_to_index = self.points.len();
|
||||
self.move_to_required = false;
|
||||
|
||||
self.verbs.push(PathVerb::Move);
|
||||
self.points.push(Point::from_xy(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_move_to_if_needed(&mut self) {
|
||||
if self.move_to_required {
|
||||
match self.points.get(self.last_move_to_index).cloned() {
|
||||
Some(p) => self.move_to(p.x, p.y),
|
||||
None => self.move_to(0.0, 0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a line from the last point.
|
||||
///
|
||||
/// - If `Path` is empty - adds Move(0, 0) first.
|
||||
/// - If `Path` ends with Close - adds Move(last_x, last_y) first.
|
||||
pub fn line_to(&mut self, x: f32, y: f32) {
|
||||
self.inject_move_to_if_needed();
|
||||
|
||||
self.verbs.push(PathVerb::Line);
|
||||
self.points.push(Point::from_xy(x, y));
|
||||
}
|
||||
|
||||
/// Adds a quad curve from the last point to `x`, `y`.
|
||||
///
|
||||
/// - If `Path` is empty - adds Move(0, 0) first.
|
||||
/// - If `Path` ends with Close - adds Move(last_x, last_y) first.
|
||||
pub fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
|
||||
self.inject_move_to_if_needed();
|
||||
|
||||
self.verbs.push(PathVerb::Quad);
|
||||
self.points.push(Point::from_xy(x1, y1));
|
||||
self.points.push(Point::from_xy(x, y));
|
||||
}
|
||||
|
||||
pub(crate) fn quad_to_pt(&mut self, p1: Point, p: Point) {
|
||||
self.quad_to(p1.x, p1.y, p.x, p.y);
|
||||
}
|
||||
|
||||
// We do not support conic segments, but Skia still relies on them from time to time.
|
||||
// This method will simply convert the input data into quad segments.
|
||||
pub(crate) fn conic_to(&mut self, x1: f32, y1: f32, x: f32, y: f32, weight: f32) {
|
||||
// check for <= 0 or NaN with this test
|
||||
if !(weight > 0.0) {
|
||||
self.line_to(x, y);
|
||||
} else if !weight.is_finite() {
|
||||
self.line_to(x1, y1);
|
||||
self.line_to(x, y);
|
||||
} else if weight == 1.0 {
|
||||
self.quad_to(x1, y1, x, y);
|
||||
} else {
|
||||
self.inject_move_to_if_needed();
|
||||
|
||||
let last = self.last_point().unwrap();
|
||||
let quadder = path_geometry::AutoConicToQuads::compute(
|
||||
last,
|
||||
Point::from_xy(x1, y1),
|
||||
Point::from_xy(x, y),
|
||||
weight,
|
||||
);
|
||||
if let Some(quadder) = quadder {
|
||||
// Points are ordered as: 0 - 1 2 - 3 4 - 5 6 - ..
|
||||
// `count` is a number of pairs +1
|
||||
let mut offset = 1;
|
||||
for _ in 0..quadder.len {
|
||||
let pt1 = quadder.points[offset + 0];
|
||||
let pt2 = quadder.points[offset + 1];
|
||||
self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
|
||||
offset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn conic_points_to(&mut self, pt1: Point, pt2: Point, weight: f32) {
|
||||
self.conic_to(pt1.x, pt1.y, pt2.x, pt2.y, weight);
|
||||
}
|
||||
|
||||
/// Adds a cubic curve from the last point to `x`, `y`.
|
||||
///
|
||||
/// - If `Path` is empty - adds Move(0, 0) first.
|
||||
/// - If `Path` ends with Close - adds Move(last_x, last_y) first.
|
||||
pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
|
||||
self.inject_move_to_if_needed();
|
||||
|
||||
self.verbs.push(PathVerb::Cubic);
|
||||
self.points.push(Point::from_xy(x1, y1));
|
||||
self.points.push(Point::from_xy(x2, y2));
|
||||
self.points.push(Point::from_xy(x, y));
|
||||
}
|
||||
|
||||
pub(crate) fn cubic_to_pt(&mut self, p1: Point, p2: Point, p: Point) {
|
||||
self.cubic_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y);
|
||||
}
|
||||
|
||||
/// Closes the current contour.
|
||||
///
|
||||
/// A closed contour connects the first and the last Point
|
||||
/// with a line, forming a continuous loop.
|
||||
///
|
||||
/// Does nothing when `Path` is empty or already closed.
|
||||
///
|
||||
/// Open and closed contour will be filled the same way.
|
||||
/// Stroking an open contour will add LineCap at contour's start and end.
|
||||
/// Stroking an closed contour will add LineJoin at contour's start and end.
|
||||
pub fn close(&mut self) {
|
||||
// don't add a close if it's the first verb or a repeat
|
||||
if !self.verbs.is_empty() {
|
||||
if self.verbs.last().cloned() != Some(PathVerb::Close) {
|
||||
self.verbs.push(PathVerb::Close);
|
||||
}
|
||||
}
|
||||
|
||||
self.move_to_required = true;
|
||||
}
|
||||
|
||||
/// Returns the last point if any.
|
||||
pub fn last_point(&self) -> Option<Point> {
|
||||
self.points.last().cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn set_last_point(&mut self, pt: Point) {
|
||||
match self.points.last_mut() {
|
||||
Some(last) => *last = pt,
|
||||
None => self.move_to(pt.x, pt.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_zero_length_since_point(&self, start_pt_index: usize) -> bool {
|
||||
let count = self.points.len() - start_pt_index;
|
||||
if count < 2 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let first = self.points[start_pt_index];
|
||||
for i in 1..count {
|
||||
if first != self.points[start_pt_index + i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Adds a rectangle contour.
|
||||
///
|
||||
/// The contour is closed and has a clock-wise direction.
|
||||
///
|
||||
/// Does nothing when:
|
||||
/// - any value is not finite or really large
|
||||
pub fn push_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
|
||||
if let Some(rect) = Rect::from_xywh(x, y, w, h) {
|
||||
self.move_to(rect.left(), rect.top());
|
||||
self.line_to(rect.right(), rect.top());
|
||||
self.line_to(rect.right(), rect.bottom());
|
||||
self.line_to(rect.left(), rect.bottom());
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an oval contour bounded by the provided rectangle.
|
||||
///
|
||||
/// The contour is closed and has a clock-wise direction.
|
||||
pub fn push_oval(&mut self, oval: Rect) {
|
||||
let cx = oval.left().half() + oval.right().half();
|
||||
let cy = oval.top().half() + oval.bottom().half();
|
||||
|
||||
let oval_points = [
|
||||
Point::from_xy(cx, oval.bottom()),
|
||||
Point::from_xy(oval.left(), cy),
|
||||
Point::from_xy(cx, oval.top()),
|
||||
Point::from_xy(oval.right(), cy),
|
||||
];
|
||||
|
||||
let rect_points = [
|
||||
Point::from_xy(oval.right(), oval.bottom()),
|
||||
Point::from_xy(oval.left(), oval.bottom()),
|
||||
Point::from_xy(oval.left(), oval.top()),
|
||||
Point::from_xy(oval.right(), oval.top()),
|
||||
];
|
||||
|
||||
let weight = SCALAR_ROOT_2_OVER_2;
|
||||
self.move_to(oval_points[3].x, oval_points[3].y);
|
||||
for (p1, p2) in rect_points.iter().zip(oval_points.iter()) {
|
||||
self.conic_points_to(*p1, *p2, weight);
|
||||
}
|
||||
self.close();
|
||||
}
|
||||
|
||||
/// Adds a circle contour.
|
||||
///
|
||||
/// The contour is closed and has a clock-wise direction.
|
||||
///
|
||||
/// Does nothing when:
|
||||
/// - `radius` <= 0
|
||||
/// - any value is not finite or really large
|
||||
pub fn push_circle(&mut self, x: f32, y: f32, r: f32) {
|
||||
if let Some(r) = Rect::from_xywh(x - r, y - r, r + r, r + r) {
|
||||
self.push_oval(r);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push_path(&mut self, other: &PathBuilder) {
|
||||
if other.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.last_move_to_index != 0 {
|
||||
self.last_move_to_index = self.points.len() + other.last_move_to_index;
|
||||
}
|
||||
|
||||
self.verbs.extend_from_slice(&other.verbs);
|
||||
self.points.extend_from_slice(&other.points);
|
||||
}
|
||||
|
||||
/// Appends, in a reverse order, the first contour of path ignoring path's last point.
|
||||
pub(crate) fn reverse_path_to(&mut self, other: &PathBuilder) {
|
||||
if other.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
debug_assert_eq!(other.verbs[0], PathVerb::Move);
|
||||
|
||||
let mut points_offset = other.points.len() - 1;
|
||||
for verb in other.verbs.iter().rev() {
|
||||
match verb {
|
||||
PathVerb::Move => {
|
||||
// if the path has multiple contours, stop after reversing the last
|
||||
break;
|
||||
}
|
||||
PathVerb::Line => {
|
||||
// We're moving one point back manually, to prevent points_offset overflow.
|
||||
let pt = other.points[points_offset - 1];
|
||||
points_offset -= 1;
|
||||
self.line_to(pt.x, pt.y);
|
||||
}
|
||||
PathVerb::Quad => {
|
||||
let pt1 = other.points[points_offset - 1];
|
||||
let pt2 = other.points[points_offset - 2];
|
||||
points_offset -= 2;
|
||||
self.quad_to(pt1.x, pt1.y, pt2.x, pt2.y);
|
||||
}
|
||||
PathVerb::Cubic => {
|
||||
let pt1 = other.points[points_offset - 1];
|
||||
let pt2 = other.points[points_offset - 2];
|
||||
let pt3 = other.points[points_offset - 3];
|
||||
points_offset -= 3;
|
||||
self.cubic_to(pt1.x, pt1.y, pt2.x, pt2.y, pt3.x, pt3.y);
|
||||
}
|
||||
PathVerb::Close => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the builder.
|
||||
///
|
||||
/// Memory is not deallocated.
|
||||
pub fn clear(&mut self) {
|
||||
self.verbs.clear();
|
||||
self.points.clear();
|
||||
self.last_move_to_index = 0;
|
||||
self.move_to_required = true;
|
||||
}
|
||||
|
||||
/// Finishes the builder and returns a `Path`.
|
||||
///
|
||||
/// Returns `None` when `Path` is empty or has invalid bounds.
|
||||
pub fn finish(self) -> Option<Path> {
|
||||
if self.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Just a move to? Bail.
|
||||
if self.verbs.len() == 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bounds = Rect::from_points(&self.points)?;
|
||||
|
||||
Some(Path {
|
||||
bounds,
|
||||
verbs: self.verbs,
|
||||
points: self.points,
|
||||
})
|
||||
}
|
||||
}
|
||||
901
third-party/vendor/tiny-skia-path/src/path_geometry.rs
vendored
Normal file
901
third-party/vendor/tiny-skia-path/src/path_geometry.rs
vendored
Normal file
|
|
@ -0,0 +1,901 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! A collection of functions to work with Bezier paths.
|
||||
//!
|
||||
//! Mainly for internal use. Do not rely on it!
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::{Point, Transform};
|
||||
|
||||
use crate::f32x2_t::f32x2;
|
||||
use crate::floating_point::FLOAT_PI;
|
||||
use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2};
|
||||
|
||||
use crate::floating_point::{NormalizedF32, NormalizedF32Exclusive};
|
||||
use crate::path_builder::PathDirection;
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
use crate::NoStdFloat;
|
||||
|
||||
// use for : eval(t) == A * t^2 + B * t + C
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct QuadCoeff {
|
||||
pub a: f32x2,
|
||||
pub b: f32x2,
|
||||
pub c: f32x2,
|
||||
}
|
||||
|
||||
impl QuadCoeff {
|
||||
pub fn from_points(points: &[Point; 3]) -> Self {
|
||||
let c = points[0].to_f32x2();
|
||||
let p1 = points[1].to_f32x2();
|
||||
let p2 = points[2].to_f32x2();
|
||||
let b = times_2(p1 - c);
|
||||
let a = p2 - times_2(p1) + c;
|
||||
|
||||
QuadCoeff { a, b, c }
|
||||
}
|
||||
|
||||
pub fn eval(&self, t: f32x2) -> f32x2 {
|
||||
(self.a * t + self.b) * t + self.c
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct CubicCoeff {
|
||||
pub a: f32x2,
|
||||
pub b: f32x2,
|
||||
pub c: f32x2,
|
||||
pub d: f32x2,
|
||||
}
|
||||
|
||||
impl CubicCoeff {
|
||||
pub fn from_points(points: &[Point; 4]) -> Self {
|
||||
let p0 = points[0].to_f32x2();
|
||||
let p1 = points[1].to_f32x2();
|
||||
let p2 = points[2].to_f32x2();
|
||||
let p3 = points[3].to_f32x2();
|
||||
let three = f32x2::splat(3.0);
|
||||
|
||||
CubicCoeff {
|
||||
a: p3 + three * (p1 - p2) - p0,
|
||||
b: three * (p2 - times_2(p1) + p0),
|
||||
c: three * (p1 - p0),
|
||||
d: p0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval(&self, t: f32x2) -> f32x2 {
|
||||
((self.a * t + self.b) * t + self.c) * t + self.d
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: to a custom type?
|
||||
pub fn new_t_values() -> [NormalizedF32Exclusive; 3] {
|
||||
[
|
||||
NormalizedF32Exclusive::ANY,
|
||||
NormalizedF32Exclusive::ANY,
|
||||
NormalizedF32Exclusive::ANY,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn chop_quad_at(src: &[Point], t: NormalizedF32Exclusive, dst: &mut [Point; 5]) {
|
||||
let p0 = src[0].to_f32x2();
|
||||
let p1 = src[1].to_f32x2();
|
||||
let p2 = src[2].to_f32x2();
|
||||
let tt = f32x2::splat(t.get());
|
||||
|
||||
let p01 = interp(p0, p1, tt);
|
||||
let p12 = interp(p1, p2, tt);
|
||||
|
||||
dst[0] = Point::from_f32x2(p0);
|
||||
dst[1] = Point::from_f32x2(p01);
|
||||
dst[2] = Point::from_f32x2(interp(p01, p12, tt));
|
||||
dst[3] = Point::from_f32x2(p12);
|
||||
dst[4] = Point::from_f32x2(p2);
|
||||
}
|
||||
|
||||
// From Numerical Recipes in C.
|
||||
//
|
||||
// Q = -1/2 (B + sign(B) sqrt[B*B - 4*A*C])
|
||||
// x1 = Q / A
|
||||
// x2 = C / Q
|
||||
pub fn find_unit_quad_roots(
|
||||
a: f32,
|
||||
b: f32,
|
||||
c: f32,
|
||||
roots: &mut [NormalizedF32Exclusive; 3],
|
||||
) -> usize {
|
||||
if a == 0.0 {
|
||||
if let Some(r) = valid_unit_divide(-c, b) {
|
||||
roots[0] = r;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// use doubles so we don't overflow temporarily trying to compute R
|
||||
let mut dr = f64::from(b) * f64::from(b) - 4.0 * f64::from(a) * f64::from(c);
|
||||
if dr < 0.0 {
|
||||
return 0;
|
||||
}
|
||||
dr = dr.sqrt();
|
||||
let r = dr as f32;
|
||||
if !r.is_finite() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let q = if b < 0.0 {
|
||||
-(b - r) / 2.0
|
||||
} else {
|
||||
-(b + r) / 2.0
|
||||
};
|
||||
|
||||
let mut roots_offset = 0;
|
||||
if let Some(r) = valid_unit_divide(q, a) {
|
||||
roots[roots_offset] = r;
|
||||
roots_offset += 1;
|
||||
}
|
||||
|
||||
if let Some(r) = valid_unit_divide(c, q) {
|
||||
roots[roots_offset] = r;
|
||||
roots_offset += 1;
|
||||
}
|
||||
|
||||
if roots_offset == 2 {
|
||||
if roots[0].get() > roots[1].get() {
|
||||
roots.swap(0, 1);
|
||||
} else if roots[0] == roots[1] {
|
||||
// nearly-equal?
|
||||
roots_offset -= 1; // skip the double root
|
||||
}
|
||||
}
|
||||
|
||||
roots_offset
|
||||
}
|
||||
|
||||
pub fn chop_cubic_at2(src: &[Point; 4], t: NormalizedF32Exclusive, dst: &mut [Point]) {
|
||||
let p0 = src[0].to_f32x2();
|
||||
let p1 = src[1].to_f32x2();
|
||||
let p2 = src[2].to_f32x2();
|
||||
let p3 = src[3].to_f32x2();
|
||||
let tt = f32x2::splat(t.get());
|
||||
|
||||
let ab = interp(p0, p1, tt);
|
||||
let bc = interp(p1, p2, tt);
|
||||
let cd = interp(p2, p3, tt);
|
||||
let abc = interp(ab, bc, tt);
|
||||
let bcd = interp(bc, cd, tt);
|
||||
let abcd = interp(abc, bcd, tt);
|
||||
|
||||
dst[0] = Point::from_f32x2(p0);
|
||||
dst[1] = Point::from_f32x2(ab);
|
||||
dst[2] = Point::from_f32x2(abc);
|
||||
dst[3] = Point::from_f32x2(abcd);
|
||||
dst[4] = Point::from_f32x2(bcd);
|
||||
dst[5] = Point::from_f32x2(cd);
|
||||
dst[6] = Point::from_f32x2(p3);
|
||||
}
|
||||
|
||||
pub fn valid_unit_divide(mut numer: f32, mut denom: f32) -> Option<NormalizedF32Exclusive> {
|
||||
if numer < 0.0 {
|
||||
numer = -numer;
|
||||
denom = -denom;
|
||||
}
|
||||
|
||||
if denom == 0.0 || numer == 0.0 || numer >= denom {
|
||||
return None;
|
||||
}
|
||||
|
||||
let r = numer / denom;
|
||||
NormalizedF32Exclusive::new(r)
|
||||
}
|
||||
|
||||
fn interp(v0: f32x2, v1: f32x2, t: f32x2) -> f32x2 {
|
||||
v0 + (v1 - v0) * t
|
||||
}
|
||||
|
||||
fn times_2(value: f32x2) -> f32x2 {
|
||||
value + value
|
||||
}
|
||||
|
||||
// F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2
|
||||
// F'(t) = 2 (b - a) + 2 (a - 2b + c) t
|
||||
// F''(t) = 2 (a - 2b + c)
|
||||
//
|
||||
// A = 2 (b - a)
|
||||
// B = 2 (a - 2b + c)
|
||||
//
|
||||
// Maximum curvature for a quadratic means solving
|
||||
// Fx' Fx'' + Fy' Fy'' = 0
|
||||
//
|
||||
// t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2)
|
||||
pub(crate) fn find_quad_max_curvature(src: &[Point; 3]) -> NormalizedF32 {
|
||||
let ax = src[1].x - src[0].x;
|
||||
let ay = src[1].y - src[0].y;
|
||||
let bx = src[0].x - src[1].x - src[1].x + src[2].x;
|
||||
let by = src[0].y - src[1].y - src[1].y + src[2].y;
|
||||
|
||||
let mut numer = -(ax * bx + ay * by);
|
||||
let mut denom = bx * bx + by * by;
|
||||
if denom < 0.0 {
|
||||
numer = -numer;
|
||||
denom = -denom;
|
||||
}
|
||||
|
||||
if numer <= 0.0 {
|
||||
return NormalizedF32::ZERO;
|
||||
}
|
||||
|
||||
if numer >= denom {
|
||||
// Also catches denom=0
|
||||
return NormalizedF32::ONE;
|
||||
}
|
||||
|
||||
let t = numer / denom;
|
||||
NormalizedF32::new(t).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn eval_quad_at(src: &[Point; 3], t: NormalizedF32) -> Point {
|
||||
Point::from_f32x2(QuadCoeff::from_points(src).eval(f32x2::splat(t.get())))
|
||||
}
|
||||
|
||||
pub(crate) fn eval_quad_tangent_at(src: &[Point; 3], tol: NormalizedF32) -> Point {
|
||||
// The derivative equation is 2(b - a +(a - 2b +c)t). This returns a
|
||||
// zero tangent vector when t is 0 or 1, and the control point is equal
|
||||
// to the end point. In this case, use the quad end points to compute the tangent.
|
||||
if (tol == NormalizedF32::ZERO && src[0] == src[1])
|
||||
|| (tol == NormalizedF32::ONE && src[1] == src[2])
|
||||
{
|
||||
return src[2] - src[0];
|
||||
}
|
||||
|
||||
let p0 = src[0].to_f32x2();
|
||||
let p1 = src[1].to_f32x2();
|
||||
let p2 = src[2].to_f32x2();
|
||||
|
||||
let b = p1 - p0;
|
||||
let a = p2 - p1 - b;
|
||||
let t = a * f32x2::splat(tol.get()) + b;
|
||||
|
||||
Point::from_f32x2(t + t)
|
||||
}
|
||||
|
||||
// Looking for F' dot F'' == 0
|
||||
//
|
||||
// A = b - a
|
||||
// B = c - 2b + a
|
||||
// C = d - 3c + 3b - a
|
||||
//
|
||||
// F' = 3Ct^2 + 6Bt + 3A
|
||||
// F'' = 6Ct + 6B
|
||||
//
|
||||
// F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
|
||||
pub fn find_cubic_max_curvature<'a>(
|
||||
src: &[Point; 4],
|
||||
t_values: &'a mut [NormalizedF32; 3],
|
||||
) -> &'a [NormalizedF32] {
|
||||
let mut coeff_x = formulate_f1_dot_f2(&[src[0].x, src[1].x, src[2].x, src[3].x]);
|
||||
let coeff_y = formulate_f1_dot_f2(&[src[0].y, src[1].y, src[2].y, src[3].y]);
|
||||
|
||||
for i in 0..4 {
|
||||
coeff_x[i] += coeff_y[i];
|
||||
}
|
||||
|
||||
let len = solve_cubic_poly(&coeff_x, t_values);
|
||||
&t_values[0..len]
|
||||
}
|
||||
|
||||
// Looking for F' dot F'' == 0
|
||||
//
|
||||
// A = b - a
|
||||
// B = c - 2b + a
|
||||
// C = d - 3c + 3b - a
|
||||
//
|
||||
// F' = 3Ct^2 + 6Bt + 3A
|
||||
// F'' = 6Ct + 6B
|
||||
//
|
||||
// F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB
|
||||
fn formulate_f1_dot_f2(src: &[f32; 4]) -> [f32; 4] {
|
||||
let a = src[1] - src[0];
|
||||
let b = src[2] - 2.0 * src[1] + src[0];
|
||||
let c = src[3] + 3.0 * (src[1] - src[2]) - src[0];
|
||||
|
||||
[c * c, 3.0 * b * c, 2.0 * b * b + c * a, a * b]
|
||||
}
|
||||
|
||||
/// Solve coeff(t) == 0, returning the number of roots that lie withing 0 < t < 1.
|
||||
/// coeff[0]t^3 + coeff[1]t^2 + coeff[2]t + coeff[3]
|
||||
///
|
||||
/// Eliminates repeated roots (so that all t_values are distinct, and are always
|
||||
/// in increasing order.
|
||||
fn solve_cubic_poly(coeff: &[f32; 4], t_values: &mut [NormalizedF32; 3]) -> usize {
|
||||
if coeff[0].is_nearly_zero() {
|
||||
// we're just a quadratic
|
||||
let mut tmp_t = new_t_values();
|
||||
let count = find_unit_quad_roots(coeff[1], coeff[2], coeff[3], &mut tmp_t);
|
||||
for i in 0..count {
|
||||
t_values[i] = tmp_t[i].to_normalized();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
debug_assert!(coeff[0] != 0.0);
|
||||
|
||||
let inva = coeff[0].invert();
|
||||
let a = coeff[1] * inva;
|
||||
let b = coeff[2] * inva;
|
||||
let c = coeff[3] * inva;
|
||||
|
||||
let q = (a * a - b * 3.0) / 9.0;
|
||||
let r = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0;
|
||||
|
||||
let q3 = q * q * q;
|
||||
let r2_minus_q3 = r * r - q3;
|
||||
let adiv3 = a / 3.0;
|
||||
|
||||
if r2_minus_q3 < 0.0 {
|
||||
// we have 3 real roots
|
||||
// the divide/root can, due to finite precisions, be slightly outside of -1...1
|
||||
let theta = (r / q3.sqrt()).bound(-1.0, 1.0).acos();
|
||||
let neg2_root_q = -2.0 * q.sqrt();
|
||||
|
||||
t_values[0] = NormalizedF32::new_clamped(neg2_root_q * (theta / 3.0).cos() - adiv3);
|
||||
t_values[1] = NormalizedF32::new_clamped(
|
||||
neg2_root_q * ((theta + 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
|
||||
);
|
||||
t_values[2] = NormalizedF32::new_clamped(
|
||||
neg2_root_q * ((theta - 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
|
||||
);
|
||||
|
||||
// now sort the roots
|
||||
sort_array3(t_values);
|
||||
collapse_duplicates3(t_values)
|
||||
} else {
|
||||
// we have 1 real root
|
||||
let mut a = r.abs() + r2_minus_q3.sqrt();
|
||||
a = scalar_cube_root(a);
|
||||
if r > 0.0 {
|
||||
a = -a;
|
||||
}
|
||||
|
||||
if a != 0.0 {
|
||||
a += q / a;
|
||||
}
|
||||
|
||||
t_values[0] = NormalizedF32::new_clamped(a - adiv3);
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_array3(array: &mut [NormalizedF32; 3]) {
|
||||
if array[0] > array[1] {
|
||||
array.swap(0, 1);
|
||||
}
|
||||
|
||||
if array[1] > array[2] {
|
||||
array.swap(1, 2);
|
||||
}
|
||||
|
||||
if array[0] > array[1] {
|
||||
array.swap(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn collapse_duplicates3(array: &mut [NormalizedF32; 3]) -> usize {
|
||||
let mut len = 3;
|
||||
|
||||
if array[1] == array[2] {
|
||||
len = 2;
|
||||
}
|
||||
|
||||
if array[0] == array[1] {
|
||||
len = 1;
|
||||
}
|
||||
|
||||
len
|
||||
}
|
||||
|
||||
fn scalar_cube_root(x: f32) -> f32 {
|
||||
x.powf(0.3333333)
|
||||
}
|
||||
|
||||
// This is SkEvalCubicAt split into three functions.
|
||||
pub(crate) fn eval_cubic_pos_at(src: &[Point; 4], t: NormalizedF32) -> Point {
|
||||
Point::from_f32x2(CubicCoeff::from_points(src).eval(f32x2::splat(t.get())))
|
||||
}
|
||||
|
||||
// This is SkEvalCubicAt split into three functions.
|
||||
pub(crate) fn eval_cubic_tangent_at(src: &[Point; 4], t: NormalizedF32) -> Point {
|
||||
// The derivative equation returns a zero tangent vector when t is 0 or 1, and the
|
||||
// adjacent control point is equal to the end point. In this case, use the
|
||||
// next control point or the end points to compute the tangent.
|
||||
if (t.get() == 0.0 && src[0] == src[1]) || (t.get() == 1.0 && src[2] == src[3]) {
|
||||
let mut tangent = if t.get() == 0.0 {
|
||||
src[2] - src[0]
|
||||
} else {
|
||||
src[3] - src[1]
|
||||
};
|
||||
|
||||
if tangent.x == 0.0 && tangent.y == 0.0 {
|
||||
tangent = src[3] - src[0];
|
||||
}
|
||||
|
||||
tangent
|
||||
} else {
|
||||
eval_cubic_derivative(src, t)
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_cubic_derivative(src: &[Point; 4], t: NormalizedF32) -> Point {
|
||||
let p0 = src[0].to_f32x2();
|
||||
let p1 = src[1].to_f32x2();
|
||||
let p2 = src[2].to_f32x2();
|
||||
let p3 = src[3].to_f32x2();
|
||||
|
||||
let coeff = QuadCoeff {
|
||||
a: p3 + f32x2::splat(3.0) * (p1 - p2) - p0,
|
||||
b: times_2(p2 - times_2(p1) + p0),
|
||||
c: p1 - p0,
|
||||
};
|
||||
|
||||
Point::from_f32x2(coeff.eval(f32x2::splat(t.get())))
|
||||
}
|
||||
|
||||
// http://www.faculty.idc.ac.il/arik/quality/appendixA.html
|
||||
//
|
||||
// Inflection means that curvature is zero.
|
||||
// Curvature is [F' x F''] / [F'^3]
|
||||
// So we solve F'x X F''y - F'y X F''y == 0
|
||||
// After some canceling of the cubic term, we get
|
||||
// A = b - a
|
||||
// B = c - 2b + a
|
||||
// C = d - 3c + 3b - a
|
||||
// (BxCy - ByCx)t^2 + (AxCy - AyCx)t + AxBy - AyBx == 0
|
||||
pub(crate) fn find_cubic_inflections<'a>(
|
||||
src: &[Point; 4],
|
||||
t_values: &'a mut [NormalizedF32Exclusive; 3],
|
||||
) -> &'a [NormalizedF32Exclusive] {
|
||||
let ax = src[1].x - src[0].x;
|
||||
let ay = src[1].y - src[0].y;
|
||||
let bx = src[2].x - 2.0 * src[1].x + src[0].x;
|
||||
let by = src[2].y - 2.0 * src[1].y + src[0].y;
|
||||
let cx = src[3].x + 3.0 * (src[1].x - src[2].x) - src[0].x;
|
||||
let cy = src[3].y + 3.0 * (src[1].y - src[2].y) - src[0].y;
|
||||
|
||||
let len = find_unit_quad_roots(
|
||||
bx * cy - by * cx,
|
||||
ax * cy - ay * cx,
|
||||
ax * by - ay * bx,
|
||||
t_values,
|
||||
);
|
||||
|
||||
&t_values[0..len]
|
||||
}
|
||||
|
||||
// Return location (in t) of cubic cusp, if there is one.
|
||||
// Note that classify cubic code does not reliably return all cusp'd cubics, so
|
||||
// it is not called here.
|
||||
pub(crate) fn find_cubic_cusp(src: &[Point; 4]) -> Option<NormalizedF32Exclusive> {
|
||||
// When the adjacent control point matches the end point, it behaves as if
|
||||
// the cubic has a cusp: there's a point of max curvature where the derivative
|
||||
// goes to zero. Ideally, this would be where t is zero or one, but math
|
||||
// error makes not so. It is not uncommon to create cubics this way; skip them.
|
||||
if src[0] == src[1] {
|
||||
return None;
|
||||
}
|
||||
|
||||
if src[2] == src[3] {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Cubics only have a cusp if the line segments formed by the control and end points cross.
|
||||
// Detect crossing if line ends are on opposite sides of plane formed by the other line.
|
||||
if on_same_side(src, 0, 2) || on_same_side(src, 2, 0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Cubics may have multiple points of maximum curvature, although at most only
|
||||
// one is a cusp.
|
||||
let mut t_values = [NormalizedF32::ZERO; 3];
|
||||
let max_curvature = find_cubic_max_curvature(src, &mut t_values);
|
||||
for test_t in max_curvature {
|
||||
if 0.0 >= test_t.get() || test_t.get() >= 1.0 {
|
||||
// no need to consider max curvature on the end
|
||||
continue;
|
||||
}
|
||||
|
||||
// A cusp is at the max curvature, and also has a derivative close to zero.
|
||||
// Choose the 'close to zero' meaning by comparing the derivative length
|
||||
// with the overall cubic size.
|
||||
let d_pt = eval_cubic_derivative(src, *test_t);
|
||||
let d_pt_magnitude = d_pt.length_sqd();
|
||||
let precision = calc_cubic_precision(src);
|
||||
if d_pt_magnitude < precision {
|
||||
// All three max curvature t values may be close to the cusp;
|
||||
// return the first one.
|
||||
return Some(NormalizedF32Exclusive::new_bounded(test_t.get()));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// Returns true if both points src[testIndex], src[testIndex+1] are in the same half plane defined
|
||||
// by the line segment src[lineIndex], src[lineIndex+1].
|
||||
fn on_same_side(src: &[Point; 4], test_index: usize, line_index: usize) -> bool {
|
||||
let origin = src[line_index];
|
||||
let line = src[line_index + 1] - origin;
|
||||
let mut crosses = [0.0, 0.0];
|
||||
for index in 0..2 {
|
||||
let test_line = src[test_index + index] - origin;
|
||||
crosses[index] = line.cross(test_line);
|
||||
}
|
||||
|
||||
crosses[0] * crosses[1] >= 0.0
|
||||
}
|
||||
|
||||
// Returns a constant proportional to the dimensions of the cubic.
|
||||
// Constant found through experimentation -- maybe there's a better way....
|
||||
fn calc_cubic_precision(src: &[Point; 4]) -> f32 {
|
||||
(src[1].distance_to_sqd(src[0])
|
||||
+ src[2].distance_to_sqd(src[1])
|
||||
+ src[3].distance_to_sqd(src[2]))
|
||||
* 1e-8
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub(crate) struct Conic {
|
||||
pub points: [Point; 3],
|
||||
pub weight: f32,
|
||||
}
|
||||
|
||||
impl Conic {
|
||||
pub fn new(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Self {
|
||||
Conic {
|
||||
points: [pt0, pt1, pt2],
|
||||
weight,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_points(points: &[Point], weight: f32) -> Self {
|
||||
Conic {
|
||||
points: [points[0], points[1], points[2]],
|
||||
weight,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_quad_pow2(&self, tolerance: f32) -> Option<u8> {
|
||||
if tolerance < 0.0 || !tolerance.is_finite() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !self.points[0].is_finite() || !self.points[1].is_finite() || !self.points[2].is_finite()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Limit the number of suggested quads to approximate a conic
|
||||
const MAX_CONIC_TO_QUAD_POW2: usize = 4;
|
||||
|
||||
// "High order approximation of conic sections by quadratic splines"
|
||||
// by Michael Floater, 1993
|
||||
let a = self.weight - 1.0;
|
||||
let k = a / (4.0 * (2.0 + a));
|
||||
let x = k * (self.points[0].x - 2.0 * self.points[1].x + self.points[2].x);
|
||||
let y = k * (self.points[0].y - 2.0 * self.points[1].y + self.points[2].y);
|
||||
|
||||
let mut error = (x * x + y * y).sqrt();
|
||||
let mut pow2 = 0;
|
||||
for _ in 0..MAX_CONIC_TO_QUAD_POW2 {
|
||||
if error <= tolerance {
|
||||
break;
|
||||
}
|
||||
|
||||
error *= 0.25;
|
||||
pow2 += 1;
|
||||
}
|
||||
|
||||
// Unlike Skia, we always expect `pow2` to be at least 1.
|
||||
// Otherwise it produces ugly results.
|
||||
Some(pow2.max(1))
|
||||
}
|
||||
|
||||
// Chop this conic into N quads, stored continuously in pts[], where
|
||||
// N = 1 << pow2. The amount of storage needed is (1 + 2 * N)
|
||||
pub fn chop_into_quads_pow2(&self, pow2: u8, points: &mut [Point]) -> u8 {
|
||||
debug_assert!(pow2 < 5);
|
||||
|
||||
points[0] = self.points[0];
|
||||
subdivide(self, &mut points[1..], pow2);
|
||||
|
||||
let quad_count = 1 << pow2;
|
||||
let pt_count = 2 * quad_count + 1;
|
||||
if points.iter().take(pt_count).any(|n| !n.is_finite()) {
|
||||
// if we generated a non-finite, pin ourselves to the middle of the hull,
|
||||
// as our first and last are already on the first/last pts of the hull.
|
||||
for p in points.iter_mut().take(pt_count - 1).skip(1) {
|
||||
*p = self.points[1];
|
||||
}
|
||||
}
|
||||
|
||||
1 << pow2
|
||||
}
|
||||
|
||||
fn chop(&self) -> (Conic, Conic) {
|
||||
let scale = f32x2::splat((1.0 + self.weight).invert());
|
||||
let new_w = subdivide_weight_value(self.weight);
|
||||
|
||||
let p0 = self.points[0].to_f32x2();
|
||||
let p1 = self.points[1].to_f32x2();
|
||||
let p2 = self.points[2].to_f32x2();
|
||||
let ww = f32x2::splat(self.weight);
|
||||
|
||||
let wp1 = ww * p1;
|
||||
let m = (p0 + times_2(wp1) + p2) * scale * f32x2::splat(0.5);
|
||||
let mut m_pt = Point::from_f32x2(m);
|
||||
if !m_pt.is_finite() {
|
||||
let w_d = self.weight as f64;
|
||||
let w_2 = w_d * 2.0;
|
||||
let scale_half = 1.0 / (1.0 + w_d) * 0.5;
|
||||
m_pt.x = ((self.points[0].x as f64
|
||||
+ w_2 * self.points[1].x as f64
|
||||
+ self.points[2].x as f64)
|
||||
* scale_half) as f32;
|
||||
|
||||
m_pt.y = ((self.points[0].y as f64
|
||||
+ w_2 * self.points[1].y as f64
|
||||
+ self.points[2].y as f64)
|
||||
* scale_half) as f32;
|
||||
}
|
||||
|
||||
(
|
||||
Conic {
|
||||
points: [self.points[0], Point::from_f32x2((p0 + wp1) * scale), m_pt],
|
||||
weight: new_w,
|
||||
},
|
||||
Conic {
|
||||
points: [m_pt, Point::from_f32x2((wp1 + p2) * scale), self.points[2]],
|
||||
weight: new_w,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_unit_arc(
|
||||
u_start: Point,
|
||||
u_stop: Point,
|
||||
dir: PathDirection,
|
||||
user_transform: Transform,
|
||||
dst: &mut [Conic; 5],
|
||||
) -> Option<&[Conic]> {
|
||||
// rotate by x,y so that u_start is (1.0)
|
||||
let x = u_start.dot(u_stop);
|
||||
let mut y = u_start.cross(u_stop);
|
||||
|
||||
let abs_y = y.abs();
|
||||
|
||||
// check for (effectively) coincident vectors
|
||||
// this can happen if our angle is nearly 0 or nearly 180 (y == 0)
|
||||
// ... we use the dot-prod to distinguish between 0 and 180 (x > 0)
|
||||
if abs_y <= SCALAR_NEARLY_ZERO
|
||||
&& x > 0.0
|
||||
&& ((y >= 0.0 && dir == PathDirection::CW) || (y <= 0.0 && dir == PathDirection::CCW))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if dir == PathDirection::CCW {
|
||||
y = -y;
|
||||
}
|
||||
|
||||
// We decide to use 1-conic per quadrant of a circle. What quadrant does [xy] lie in?
|
||||
// 0 == [0 .. 90)
|
||||
// 1 == [90 ..180)
|
||||
// 2 == [180..270)
|
||||
// 3 == [270..360)
|
||||
//
|
||||
let mut quadrant = 0;
|
||||
if y == 0.0 {
|
||||
quadrant = 2; // 180
|
||||
debug_assert!((x + 1.0) <= SCALAR_NEARLY_ZERO);
|
||||
} else if x == 0.0 {
|
||||
debug_assert!(abs_y - 1.0 <= SCALAR_NEARLY_ZERO);
|
||||
quadrant = if y > 0.0 { 1 } else { 3 }; // 90 / 270
|
||||
} else {
|
||||
if y < 0.0 {
|
||||
quadrant += 2;
|
||||
}
|
||||
|
||||
if (x < 0.0) != (y < 0.0) {
|
||||
quadrant += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let quadrant_points = [
|
||||
Point::from_xy(1.0, 0.0),
|
||||
Point::from_xy(1.0, 1.0),
|
||||
Point::from_xy(0.0, 1.0),
|
||||
Point::from_xy(-1.0, 1.0),
|
||||
Point::from_xy(-1.0, 0.0),
|
||||
Point::from_xy(-1.0, -1.0),
|
||||
Point::from_xy(0.0, -1.0),
|
||||
Point::from_xy(1.0, -1.0),
|
||||
];
|
||||
|
||||
const QUADRANT_WEIGHT: f32 = SCALAR_ROOT_2_OVER_2;
|
||||
|
||||
let mut conic_count = quadrant;
|
||||
for i in 0..conic_count {
|
||||
dst[i] = Conic::from_points(&quadrant_points[i * 2..], QUADRANT_WEIGHT);
|
||||
}
|
||||
|
||||
// Now compute any remaing (sub-90-degree) arc for the last conic
|
||||
let final_pt = Point::from_xy(x, y);
|
||||
let last_q = quadrant_points[quadrant * 2]; // will already be a unit-vector
|
||||
let dot = last_q.dot(final_pt);
|
||||
debug_assert!(0.0 <= dot && dot <= 1.0 + SCALAR_NEARLY_ZERO);
|
||||
|
||||
if dot < 1.0 {
|
||||
let mut off_curve = Point::from_xy(last_q.x + x, last_q.y + y);
|
||||
// compute the bisector vector, and then rescale to be the off-curve point.
|
||||
// we compute its length from cos(theta/2) = length / 1, using half-angle identity we get
|
||||
// length = sqrt(2 / (1 + cos(theta)). We already have cos() when to computed the dot.
|
||||
// This is nice, since our computed weight is cos(theta/2) as well!
|
||||
let cos_theta_over_2 = ((1.0 + dot) / 2.0).sqrt();
|
||||
off_curve.set_length(cos_theta_over_2.invert());
|
||||
if !last_q.almost_equal(off_curve) {
|
||||
dst[conic_count] = Conic::new(last_q, off_curve, final_pt, cos_theta_over_2);
|
||||
conic_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// now handle counter-clockwise and the initial unitStart rotation
|
||||
let mut transform = Transform::from_sin_cos(u_start.y, u_start.x);
|
||||
if dir == PathDirection::CCW {
|
||||
transform = transform.pre_scale(1.0, -1.0);
|
||||
}
|
||||
|
||||
transform = transform.post_concat(user_transform);
|
||||
|
||||
for conic in dst.iter_mut().take(conic_count) {
|
||||
transform.map_points(&mut conic.points);
|
||||
}
|
||||
|
||||
if conic_count == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(&dst[0..conic_count])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subdivide_weight_value(w: f32) -> f32 {
|
||||
(0.5 + w * 0.5).sqrt()
|
||||
}
|
||||
|
||||
fn subdivide<'a>(src: &Conic, mut points: &'a mut [Point], mut level: u8) -> &'a mut [Point] {
|
||||
if level == 0 {
|
||||
points[0] = src.points[1];
|
||||
points[1] = src.points[2];
|
||||
&mut points[2..]
|
||||
} else {
|
||||
let mut dst = src.chop();
|
||||
|
||||
let start_y = src.points[0].y;
|
||||
let end_y = src.points[2].y;
|
||||
if between(start_y, src.points[1].y, end_y) {
|
||||
// If the input is monotonic and the output is not, the scan converter hangs.
|
||||
// Ensure that the chopped conics maintain their y-order.
|
||||
let mid_y = dst.0.points[2].y;
|
||||
if !between(start_y, mid_y, end_y) {
|
||||
// If the computed midpoint is outside the ends, move it to the closer one.
|
||||
let closer_y = if (mid_y - start_y).abs() < (mid_y - end_y).abs() {
|
||||
start_y
|
||||
} else {
|
||||
end_y
|
||||
};
|
||||
dst.0.points[2].y = closer_y;
|
||||
dst.1.points[0].y = closer_y;
|
||||
}
|
||||
|
||||
if !between(start_y, dst.0.points[1].y, dst.0.points[2].y) {
|
||||
// If the 1st control is not between the start and end, put it at the start.
|
||||
// This also reduces the quad to a line.
|
||||
dst.0.points[1].y = start_y;
|
||||
}
|
||||
|
||||
if !between(dst.1.points[0].y, dst.1.points[1].y, end_y) {
|
||||
// If the 2nd control is not between the start and end, put it at the end.
|
||||
// This also reduces the quad to a line.
|
||||
dst.1.points[1].y = end_y;
|
||||
}
|
||||
|
||||
// Verify that all five points are in order.
|
||||
debug_assert!(between(start_y, dst.0.points[1].y, dst.0.points[2].y));
|
||||
debug_assert!(between(
|
||||
dst.0.points[1].y,
|
||||
dst.0.points[2].y,
|
||||
dst.1.points[1].y
|
||||
));
|
||||
debug_assert!(between(dst.0.points[2].y, dst.1.points[1].y, end_y));
|
||||
}
|
||||
|
||||
level -= 1;
|
||||
points = subdivide(&dst.0, points, level);
|
||||
subdivide(&dst.1, points, level)
|
||||
}
|
||||
}
|
||||
|
||||
// This was originally developed and tested for pathops: see SkOpTypes.h
|
||||
// returns true if (a <= b <= c) || (a >= b >= c)
|
||||
fn between(a: f32, b: f32, c: f32) -> bool {
|
||||
(a - b) * (c - b) <= 0.0
|
||||
}
|
||||
|
||||
pub(crate) struct AutoConicToQuads {
|
||||
pub points: [Point; 64],
|
||||
pub len: u8, // the number of quads
|
||||
}
|
||||
|
||||
impl AutoConicToQuads {
|
||||
pub fn compute(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Option<Self> {
|
||||
let conic = Conic::new(pt0, pt1, pt2, weight);
|
||||
let pow2 = conic.compute_quad_pow2(0.25)?;
|
||||
let mut points = [Point::zero(); 64];
|
||||
let len = conic.chop_into_quads_pow2(pow2, &mut points);
|
||||
Some(AutoConicToQuads { points, len })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn eval_cubic_at_1() {
|
||||
let src = [
|
||||
Point::from_xy(30.0, 40.0),
|
||||
Point::from_xy(30.0, 40.0),
|
||||
Point::from_xy(171.0, 45.0),
|
||||
Point::from_xy(180.0, 155.0),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
eval_cubic_pos_at(&src, NormalizedF32::ZERO),
|
||||
Point::from_xy(30.0, 40.0)
|
||||
);
|
||||
assert_eq!(
|
||||
eval_cubic_tangent_at(&src, NormalizedF32::ZERO),
|
||||
Point::from_xy(141.0, 5.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_cubic_max_curvature_1() {
|
||||
let src = [
|
||||
Point::from_xy(20.0, 160.0),
|
||||
Point::from_xy(20.0001, 160.0),
|
||||
Point::from_xy(160.0, 20.0),
|
||||
Point::from_xy(160.0001, 20.0),
|
||||
];
|
||||
|
||||
let mut t_values = [NormalizedF32::ZERO; 3];
|
||||
let t_values = find_cubic_max_curvature(&src, &mut t_values);
|
||||
|
||||
assert_eq!(
|
||||
&t_values,
|
||||
&[
|
||||
NormalizedF32::ZERO,
|
||||
NormalizedF32::new_clamped(0.5),
|
||||
NormalizedF32::ONE,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
657
third-party/vendor/tiny-skia-path/src/rect.rs
vendored
Normal file
657
third-party/vendor/tiny-skia-path/src/rect.rs
vendored
Normal file
|
|
@ -0,0 +1,657 @@
|
|||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use crate::{FiniteF32, IntSize, LengthU32, Point, SaturateRound};
|
||||
|
||||
/// An integer rectangle.
|
||||
///
|
||||
/// # Guarantees
|
||||
///
|
||||
/// - Width and height are in 1..=i32::MAX range.
|
||||
/// - x+width and y+height does not overflow.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub struct IntRect {
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: LengthU32,
|
||||
height: LengthU32,
|
||||
}
|
||||
|
||||
impl IntRect {
|
||||
/// Creates a new `IntRect`.
|
||||
pub fn from_xywh(x: i32, y: i32, width: u32, height: u32) -> Option<Self> {
|
||||
x.checked_add(i32::try_from(width).ok()?)?;
|
||||
y.checked_add(i32::try_from(height).ok()?)?;
|
||||
|
||||
Some(IntRect {
|
||||
x,
|
||||
y,
|
||||
width: LengthU32::new(width)?,
|
||||
height: LengthU32::new(height)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new `IntRect`.
|
||||
pub fn from_ltrb(left: i32, top: i32, right: i32, bottom: i32) -> Option<Self> {
|
||||
let width = u32::try_from(right.checked_sub(left)?).ok()?;
|
||||
let height = u32::try_from(bottom.checked_sub(top)?).ok()?;
|
||||
IntRect::from_xywh(left, top, width, height)
|
||||
}
|
||||
|
||||
/// Returns rect's X position.
|
||||
pub fn x(&self) -> i32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Returns rect's Y position.
|
||||
pub fn y(&self) -> i32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Returns rect's width.
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width.get()
|
||||
}
|
||||
|
||||
/// Returns rect's height.
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height.get()
|
||||
}
|
||||
|
||||
/// Returns rect's left edge.
|
||||
pub fn left(&self) -> i32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Returns rect's top edge.
|
||||
pub fn top(&self) -> i32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Returns rect's right edge.
|
||||
pub fn right(&self) -> i32 {
|
||||
// No overflow is guaranteed by constructors.
|
||||
self.x + self.width.get() as i32
|
||||
}
|
||||
|
||||
/// Returns rect's bottom edge.
|
||||
pub fn bottom(&self) -> i32 {
|
||||
// No overflow is guaranteed by constructors.
|
||||
self.y + self.height.get() as i32
|
||||
}
|
||||
|
||||
/// Returns rect's size.
|
||||
pub fn size(&self) -> IntSize {
|
||||
IntSize {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the rect is completely includes `other` Rect.
|
||||
pub fn contains(&self, other: &Self) -> bool {
|
||||
self.x <= other.x
|
||||
&& self.y <= other.y
|
||||
&& self.right() >= other.right()
|
||||
&& self.bottom() >= other.bottom()
|
||||
}
|
||||
|
||||
/// Returns an intersection of two rectangles.
|
||||
///
|
||||
/// Returns `None` otherwise.
|
||||
pub fn intersect(&self, other: &Self) -> Option<Self> {
|
||||
let left = self.x.max(other.x);
|
||||
let top = self.y.max(other.y);
|
||||
|
||||
let right = self.right().min(other.right());
|
||||
let bottom = self.bottom().min(other.bottom());
|
||||
|
||||
let w = u32::try_from(right.checked_sub(left)?).ok()?;
|
||||
let h = u32::try_from(bottom.checked_sub(top)?).ok()?;
|
||||
|
||||
IntRect::from_xywh(left, top, w, h)
|
||||
}
|
||||
|
||||
/// Insets the rectangle.
|
||||
pub fn inset(&self, dx: i32, dy: i32) -> Option<Self> {
|
||||
IntRect::from_ltrb(
|
||||
self.left() + dx,
|
||||
self.top() + dy,
|
||||
self.right() - dx,
|
||||
self.bottom() - dy,
|
||||
)
|
||||
}
|
||||
|
||||
/// Outsets the rectangle.
|
||||
pub fn make_outset(&self, dx: i32, dy: i32) -> Option<Self> {
|
||||
IntRect::from_ltrb(
|
||||
self.left().saturating_sub(dx),
|
||||
self.top().saturating_sub(dy),
|
||||
self.right().saturating_add(dx),
|
||||
self.bottom().saturating_add(dy),
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts into `Rect`.
|
||||
pub fn to_rect(&self) -> Rect {
|
||||
// Can't fail, because `IntRect` is always valid.
|
||||
Rect::from_ltrb(
|
||||
self.x as f32,
|
||||
self.y as f32,
|
||||
self.x as f32 + self.width.get() as f32,
|
||||
self.y as f32 + self.height.get() as f32,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Converts into `ScreenIntRect`.
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// - x >= 0
|
||||
/// - y >= 0
|
||||
pub fn to_screen_int_rect(&self) -> Option<ScreenIntRect> {
|
||||
let x = u32::try_from(self.x).ok()?;
|
||||
let y = u32::try_from(self.y).ok()?;
|
||||
Some(ScreenIntRect::from_xywh_safe(x, y, self.width, self.height))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod int_rect_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tests() {
|
||||
assert_eq!(IntRect::from_xywh(0, 0, 0, 0), None);
|
||||
assert_eq!(IntRect::from_xywh(0, 0, 1, 0), None);
|
||||
assert_eq!(IntRect::from_xywh(0, 0, 0, 1), None);
|
||||
|
||||
assert_eq!(
|
||||
IntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
|
||||
None
|
||||
);
|
||||
assert_eq!(IntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
|
||||
assert_eq!(IntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
|
||||
|
||||
assert_eq!(IntRect::from_xywh(core::i32::MAX, 0, 1, 1), None);
|
||||
assert_eq!(IntRect::from_xywh(0, core::i32::MAX, 1, 1), None);
|
||||
|
||||
let r = IntRect::from_xywh(1, 2, 3, 4).unwrap();
|
||||
assert_eq!(
|
||||
r.to_screen_int_rect().unwrap(),
|
||||
ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap()
|
||||
);
|
||||
|
||||
let r = IntRect::from_xywh(-1, -1, 3, 4).unwrap();
|
||||
assert_eq!(r.to_screen_int_rect(), None);
|
||||
|
||||
{
|
||||
// No intersection.
|
||||
let r1 = IntRect::from_xywh(1, 2, 3, 4).unwrap();
|
||||
let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
|
||||
assert_eq!(r1.intersect(&r2), None);
|
||||
}
|
||||
|
||||
{
|
||||
// Second inside the first one.
|
||||
let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
|
||||
let r2 = IntRect::from_xywh(11, 12, 13, 14).unwrap();
|
||||
assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 13, 14));
|
||||
}
|
||||
|
||||
{
|
||||
// Partial overlap.
|
||||
let r1 = IntRect::from_xywh(1, 2, 30, 40).unwrap();
|
||||
let r2 = IntRect::from_xywh(11, 12, 50, 60).unwrap();
|
||||
assert_eq!(r1.intersect(&r2), IntRect::from_xywh(11, 12, 20, 30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A screen `IntRect`.
|
||||
///
|
||||
/// # Guarantees
|
||||
///
|
||||
/// - X and Y are in 0..=i32::MAX range.
|
||||
/// - Width and height are in 1..=i32::MAX range.
|
||||
/// - x+width and y+height does not overflow.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub struct ScreenIntRect {
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: LengthU32,
|
||||
height: LengthU32,
|
||||
}
|
||||
|
||||
impl ScreenIntRect {
|
||||
/// Creates a new `ScreenIntRect`.
|
||||
pub fn from_xywh(x: u32, y: u32, width: u32, height: u32) -> Option<Self> {
|
||||
i32::try_from(x).ok()?;
|
||||
i32::try_from(y).ok()?;
|
||||
i32::try_from(width).ok()?;
|
||||
i32::try_from(height).ok()?;
|
||||
|
||||
x.checked_add(width)?;
|
||||
y.checked_add(height)?;
|
||||
|
||||
let width = LengthU32::new(width)?;
|
||||
let height = LengthU32::new(height)?;
|
||||
|
||||
Some(ScreenIntRect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new `ScreenIntRect`.
|
||||
pub const fn from_xywh_safe(x: u32, y: u32, width: LengthU32, height: LengthU32) -> Self {
|
||||
ScreenIntRect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns rect's X position.
|
||||
pub fn x(&self) -> u32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Returns rect's Y position.
|
||||
pub fn y(&self) -> u32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Returns rect's width.
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width.get()
|
||||
}
|
||||
|
||||
/// Returns rect's height.
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height.get()
|
||||
}
|
||||
|
||||
/// Returns rect's width.
|
||||
pub fn width_safe(&self) -> LengthU32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
/// Returns rect's left edge.
|
||||
pub fn left(&self) -> u32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
/// Returns rect's top edge.
|
||||
pub fn top(&self) -> u32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
/// Returns rect's right edge.
|
||||
///
|
||||
/// The right edge is at least 1.
|
||||
pub fn right(&self) -> u32 {
|
||||
// No overflow is guaranteed by constructors.
|
||||
self.x + self.width.get()
|
||||
}
|
||||
|
||||
/// Returns rect's bottom edge.
|
||||
///
|
||||
/// The bottom edge is at least 1.
|
||||
pub fn bottom(&self) -> u32 {
|
||||
// No overflow is guaranteed by constructors.
|
||||
self.y + self.height.get()
|
||||
}
|
||||
|
||||
/// Returns rect's size.
|
||||
pub fn size(&self) -> IntSize {
|
||||
IntSize {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the rect is completely includes `other` Rect.
|
||||
pub fn contains(&self, other: &Self) -> bool {
|
||||
self.x <= other.x
|
||||
&& self.y <= other.y
|
||||
&& self.right() >= other.right()
|
||||
&& self.bottom() >= other.bottom()
|
||||
}
|
||||
|
||||
/// Converts into a `IntRect`.
|
||||
pub fn to_int_rect(&self) -> IntRect {
|
||||
// Everything is already checked by constructors.
|
||||
IntRect::from_xywh(
|
||||
self.x as i32,
|
||||
self.y as i32,
|
||||
self.width.get(),
|
||||
self.height.get(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Converts into a `Rect`.
|
||||
pub fn to_rect(&self) -> Rect {
|
||||
// Can't fail, because `ScreenIntRect` is always valid.
|
||||
// And u32 always fits into f32.
|
||||
Rect::from_ltrb(
|
||||
self.x as f32,
|
||||
self.y as f32,
|
||||
self.x as f32 + self.width.get() as f32,
|
||||
self.y as f32 + self.height.get() as f32,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod screen_int_rect_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tests() {
|
||||
assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 0), None);
|
||||
assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, 0), None);
|
||||
assert_eq!(ScreenIntRect::from_xywh(0, 0, 0, 1), None);
|
||||
|
||||
assert_eq!(
|
||||
ScreenIntRect::from_xywh(0, 0, core::u32::MAX, core::u32::MAX),
|
||||
None
|
||||
);
|
||||
assert_eq!(ScreenIntRect::from_xywh(0, 0, 1, core::u32::MAX), None);
|
||||
assert_eq!(ScreenIntRect::from_xywh(0, 0, core::u32::MAX, 1), None);
|
||||
|
||||
assert_eq!(ScreenIntRect::from_xywh(core::u32::MAX, 0, 1, 1), None);
|
||||
assert_eq!(ScreenIntRect::from_xywh(0, core::u32::MAX, 1, 1), None);
|
||||
|
||||
assert_eq!(
|
||||
ScreenIntRect::from_xywh(
|
||||
core::u32::MAX,
|
||||
core::u32::MAX,
|
||||
core::u32::MAX,
|
||||
core::u32::MAX
|
||||
),
|
||||
None
|
||||
);
|
||||
|
||||
let r = ScreenIntRect::from_xywh(1, 2, 3, 4).unwrap();
|
||||
assert_eq!(r.x(), 1);
|
||||
assert_eq!(r.y(), 2);
|
||||
assert_eq!(r.width(), 3);
|
||||
assert_eq!(r.height(), 4);
|
||||
assert_eq!(r.right(), 4);
|
||||
assert_eq!(r.bottom(), 6);
|
||||
}
|
||||
}
|
||||
|
||||
/// A rectangle defined by left, top, right and bottom edges.
|
||||
///
|
||||
/// Can have zero width and/or height. But not a negative one.
|
||||
///
|
||||
/// # Guarantees
|
||||
///
|
||||
/// - All values are finite.
|
||||
/// - Left edge is <= right.
|
||||
/// - Top edge is <= bottom.
|
||||
/// - Width and height are <= f32::MAX.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Rect {
|
||||
left: FiniteF32,
|
||||
top: FiniteF32,
|
||||
right: FiniteF32,
|
||||
bottom: FiniteF32,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Rect {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Rect")
|
||||
.field("left", &self.left.get())
|
||||
.field("top", &self.top.get())
|
||||
.field("right", &self.right.get())
|
||||
.field("bottom", &self.bottom.get())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Creates new `Rect`.
|
||||
pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option<Self> {
|
||||
let left = FiniteF32::new(left)?;
|
||||
let top = FiniteF32::new(top)?;
|
||||
let right = FiniteF32::new(right)?;
|
||||
let bottom = FiniteF32::new(bottom)?;
|
||||
|
||||
if left.get() <= right.get() && top.get() <= bottom.get() {
|
||||
// Width and height must not overflow.
|
||||
checked_f32_sub(right.get(), left.get())?;
|
||||
checked_f32_sub(bottom.get(), top.get())?;
|
||||
|
||||
Some(Rect {
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new `Rect`.
|
||||
pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
|
||||
Rect::from_ltrb(x, y, w + x, h + y)
|
||||
}
|
||||
|
||||
/// Returns the left edge.
|
||||
pub fn left(&self) -> f32 {
|
||||
self.left.get()
|
||||
}
|
||||
|
||||
/// Returns the top edge.
|
||||
pub fn top(&self) -> f32 {
|
||||
self.top.get()
|
||||
}
|
||||
|
||||
/// Returns the right edge.
|
||||
pub fn right(&self) -> f32 {
|
||||
self.right.get()
|
||||
}
|
||||
|
||||
/// Returns the bottom edge.
|
||||
pub fn bottom(&self) -> f32 {
|
||||
self.bottom.get()
|
||||
}
|
||||
|
||||
/// Returns rect's X position.
|
||||
pub fn x(&self) -> f32 {
|
||||
self.left.get()
|
||||
}
|
||||
|
||||
/// Returns rect's Y position.
|
||||
pub fn y(&self) -> f32 {
|
||||
self.top.get()
|
||||
}
|
||||
|
||||
/// Returns rect's width.
|
||||
#[inline]
|
||||
pub fn width(&self) -> f32 {
|
||||
self.right.get() - self.left.get()
|
||||
}
|
||||
|
||||
/// Returns rect's height.
|
||||
#[inline]
|
||||
pub fn height(&self) -> f32 {
|
||||
self.bottom.get() - self.top.get()
|
||||
}
|
||||
|
||||
/// Converts into an `IntRect` by adding 0.5 and discarding the fractional portion.
|
||||
///
|
||||
/// Width and height are guarantee to be >= 1.
|
||||
pub fn round(&self) -> Option<IntRect> {
|
||||
IntRect::from_xywh(
|
||||
i32::saturate_round(self.x()),
|
||||
i32::saturate_round(self.y()),
|
||||
core::cmp::max(1, i32::saturate_round(self.width()) as u32),
|
||||
core::cmp::max(1, i32::saturate_round(self.height()) as u32),
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts into an `IntRect` rounding outwards.
|
||||
///
|
||||
/// Width and height are guarantee to be >= 1.
|
||||
pub fn round_out(&self) -> Option<IntRect> {
|
||||
IntRect::from_xywh(
|
||||
i32::saturate_floor(self.x()),
|
||||
i32::saturate_floor(self.y()),
|
||||
core::cmp::max(1, i32::saturate_ceil(self.width()) as u32),
|
||||
core::cmp::max(1, i32::saturate_ceil(self.height()) as u32),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an intersection of two rectangles.
|
||||
///
|
||||
/// Returns `None` otherwise.
|
||||
pub fn intersect(&self, other: &Self) -> Option<Self> {
|
||||
let left = self.x().max(other.x());
|
||||
let top = self.y().max(other.y());
|
||||
|
||||
let right = self.right().min(other.right());
|
||||
let bottom = self.bottom().min(other.bottom());
|
||||
|
||||
Rect::from_ltrb(left, top, right, bottom)
|
||||
}
|
||||
|
||||
/// Creates a Rect from Point array.
|
||||
///
|
||||
/// Returns None if count is zero or if Point array contains an infinity or NaN.
|
||||
pub fn from_points(points: &[Point]) -> Option<Self> {
|
||||
use crate::f32x4_t::f32x4;
|
||||
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
let mut min;
|
||||
let mut max;
|
||||
if points.len() & 1 != 0 {
|
||||
let pt = points[0];
|
||||
min = f32x4([pt.x, pt.y, pt.x, pt.y]);
|
||||
max = min;
|
||||
offset += 1;
|
||||
} else {
|
||||
let pt0 = points[0];
|
||||
let pt1 = points[1];
|
||||
min = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
|
||||
max = min;
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
let mut accum = f32x4::default();
|
||||
while offset != points.len() {
|
||||
let pt0 = points[offset + 0];
|
||||
let pt1 = points[offset + 1];
|
||||
let xy = f32x4([pt0.x, pt0.y, pt1.x, pt1.y]);
|
||||
|
||||
accum *= xy;
|
||||
min = min.min(xy);
|
||||
max = max.max(xy);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
let all_finite = accum * f32x4::default() == f32x4::default();
|
||||
let min: [f32; 4] = min.0;
|
||||
let max: [f32; 4] = max.0;
|
||||
if all_finite {
|
||||
Rect::from_ltrb(
|
||||
min[0].min(min[2]),
|
||||
min[1].min(min[3]),
|
||||
max[0].max(max[2]),
|
||||
max[1].max(max[3]),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Insets the rectangle by the specified offset.
|
||||
pub fn inset(&mut self, dx: f32, dy: f32) -> Option<Self> {
|
||||
Rect::from_ltrb(
|
||||
self.left() + dx,
|
||||
self.top() + dy,
|
||||
self.right() - dx,
|
||||
self.bottom() - dy,
|
||||
)
|
||||
}
|
||||
|
||||
/// Outsets the rectangle by the specified offset.
|
||||
pub fn outset(&mut self, dx: f32, dy: f32) -> Option<Self> {
|
||||
self.inset(-dx, -dy)
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_f32_sub(a: f32, b: f32) -> Option<f32> {
|
||||
debug_assert!(a.is_finite());
|
||||
debug_assert!(b.is_finite());
|
||||
|
||||
let n = a as f64 - b as f64;
|
||||
// Not sure if this is perfectly correct.
|
||||
if n > core::f32::MIN as f64 && n < core::f32::MAX as f64 {
|
||||
Some(n as f32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod rect_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tests() {
|
||||
assert_eq!(Rect::from_ltrb(10.0, 10.0, 5.0, 10.0), None);
|
||||
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 5.0), None);
|
||||
assert_eq!(Rect::from_ltrb(core::f32::NAN, 10.0, 10.0, 10.0), None);
|
||||
assert_eq!(Rect::from_ltrb(10.0, core::f32::NAN, 10.0, 10.0), None);
|
||||
assert_eq!(Rect::from_ltrb(10.0, 10.0, core::f32::NAN, 10.0), None);
|
||||
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::NAN), None);
|
||||
assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, core::f32::INFINITY), None);
|
||||
|
||||
let rect = Rect::from_ltrb(10.0, 20.0, 30.0, 40.0).unwrap();
|
||||
assert_eq!(rect.left(), 10.0);
|
||||
assert_eq!(rect.top(), 20.0);
|
||||
assert_eq!(rect.right(), 30.0);
|
||||
assert_eq!(rect.bottom(), 40.0);
|
||||
assert_eq!(rect.width(), 20.0);
|
||||
assert_eq!(rect.height(), 20.0);
|
||||
|
||||
let rect = Rect::from_ltrb(-30.0, 20.0, -10.0, 40.0).unwrap();
|
||||
assert_eq!(rect.width(), 20.0);
|
||||
assert_eq!(rect.height(), 20.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_overflow() {
|
||||
// minimum value that cause overflow
|
||||
// because i32::MAX has no exact conversion to f32
|
||||
let x = 128.0;
|
||||
// maximum width
|
||||
let width = i32::MAX as f32;
|
||||
|
||||
let rect = Rect::from_xywh(x, 0.0, width, 1.0).unwrap();
|
||||
assert_eq!(rect.round(), None);
|
||||
assert_eq!(rect.round_out(), None);
|
||||
}
|
||||
}
|
||||
168
third-party/vendor/tiny-skia-path/src/scalar.rs
vendored
Normal file
168
third-party/vendor/tiny-skia-path/src/scalar.rs
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::floating_point::f32_as_2s_compliment;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub const SCALAR_MAX: f32 = 3.402823466e+38;
|
||||
#[allow(missing_docs)]
|
||||
pub const SCALAR_NEARLY_ZERO: f32 = 1.0 / (1 << 12) as f32;
|
||||
#[allow(missing_docs)]
|
||||
pub const SCALAR_ROOT_2_OVER_2: f32 = 0.707106781;
|
||||
|
||||
/// Float number extension methods.
|
||||
///
|
||||
/// Mainly for internal use. Do not rely on it!
|
||||
#[allow(missing_docs)]
|
||||
pub trait Scalar {
|
||||
fn half(self) -> Self;
|
||||
fn ave(self, other: Self) -> Self;
|
||||
fn sqr(self) -> Self;
|
||||
fn invert(self) -> Self;
|
||||
fn bound(self, min: Self, max: Self) -> Self;
|
||||
fn is_nearly_equal(self, other: Self) -> bool;
|
||||
fn is_nearly_zero(self) -> bool;
|
||||
fn is_nearly_zero_within_tolerance(self, tolerance: Self) -> bool;
|
||||
fn almost_dequal_ulps(self, other: Self) -> bool;
|
||||
}
|
||||
|
||||
impl Scalar for f32 {
|
||||
fn half(self) -> f32 {
|
||||
self * 0.5
|
||||
}
|
||||
|
||||
fn ave(self, other: Self) -> f32 {
|
||||
(self + other) * 0.5
|
||||
}
|
||||
|
||||
fn sqr(self) -> f32 {
|
||||
self * self
|
||||
}
|
||||
|
||||
fn invert(self) -> f32 {
|
||||
1.0 / self
|
||||
}
|
||||
|
||||
// Works just like SkTPin, returning `max` for NaN/inf
|
||||
/// A non-panicking clamp.
|
||||
fn bound(self, min: Self, max: Self) -> Self {
|
||||
max.min(self).max(min)
|
||||
}
|
||||
|
||||
fn is_nearly_equal(self, other: Self) -> bool {
|
||||
(self - other).abs() <= SCALAR_NEARLY_ZERO
|
||||
}
|
||||
|
||||
fn is_nearly_zero(self) -> bool {
|
||||
self.is_nearly_zero_within_tolerance(SCALAR_NEARLY_ZERO)
|
||||
}
|
||||
|
||||
fn is_nearly_zero_within_tolerance(self, tolerance: Self) -> bool {
|
||||
debug_assert!(tolerance >= 0.0);
|
||||
self.abs() <= tolerance
|
||||
}
|
||||
|
||||
// From SkPathOpsTypes.
|
||||
fn almost_dequal_ulps(self, other: Self) -> bool {
|
||||
const ULPS_EPSILON: i32 = 16;
|
||||
let a_bits = f32_as_2s_compliment(self);
|
||||
let b_bits = f32_as_2s_compliment(other);
|
||||
// Find the difference in ULPs.
|
||||
a_bits < b_bits + ULPS_EPSILON && b_bits < a_bits + ULPS_EPSILON
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
pub trait NoStdFloat {
|
||||
fn trunc(self) -> Self;
|
||||
fn sqrt(self) -> Self;
|
||||
fn abs(self) -> Self;
|
||||
fn sin(self) -> Self;
|
||||
fn cos(self) -> Self;
|
||||
fn ceil(self) -> Self;
|
||||
fn floor(self) -> Self;
|
||||
fn powf(self, y: Self) -> Self;
|
||||
fn acos(self) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
impl NoStdFloat for f32 {
|
||||
fn trunc(self) -> Self {
|
||||
libm::truncf(self)
|
||||
}
|
||||
fn sqrt(self) -> Self {
|
||||
libm::sqrtf(self)
|
||||
}
|
||||
fn abs(self) -> Self {
|
||||
libm::fabsf(self)
|
||||
}
|
||||
fn sin(self) -> Self {
|
||||
libm::sinf(self)
|
||||
}
|
||||
fn cos(self) -> Self {
|
||||
libm::cosf(self)
|
||||
}
|
||||
fn ceil(self) -> Self {
|
||||
libm::ceilf(self)
|
||||
}
|
||||
fn floor(self) -> Self {
|
||||
libm::floorf(self)
|
||||
}
|
||||
fn powf(self, y: Self) -> Self {
|
||||
libm::powf(self, y)
|
||||
}
|
||||
fn acos(self) -> Self {
|
||||
libm::acosf(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
impl NoStdFloat for f64 {
|
||||
fn trunc(self) -> Self {
|
||||
libm::trunc(self)
|
||||
}
|
||||
fn sqrt(self) -> Self {
|
||||
libm::sqrt(self)
|
||||
}
|
||||
fn abs(self) -> Self {
|
||||
libm::fabs(self)
|
||||
}
|
||||
fn sin(self) -> Self {
|
||||
libm::sin(self)
|
||||
}
|
||||
fn cos(self) -> Self {
|
||||
libm::cos(self)
|
||||
}
|
||||
fn ceil(self) -> Self {
|
||||
libm::ceil(self)
|
||||
}
|
||||
fn floor(self) -> Self {
|
||||
libm::floor(self)
|
||||
}
|
||||
fn powf(self, y: Self) -> Self {
|
||||
libm::pow(self, y)
|
||||
}
|
||||
fn acos(self) -> Self {
|
||||
libm::acos(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bound() {
|
||||
assert_eq!(core::f32::NAN.bound(0.0, 1.0), 1.0);
|
||||
assert_eq!(core::f32::INFINITY.bound(0.0, 1.0), 1.0);
|
||||
assert_eq!(core::f32::NEG_INFINITY.bound(0.0, 1.0), 0.0);
|
||||
assert_eq!(core::f32::EPSILON.bound(0.0, 1.0), core::f32::EPSILON);
|
||||
assert_eq!(0.5.bound(0.0, 1.0), 0.5);
|
||||
assert_eq!((-1.0).bound(0.0, 1.0), 0.0);
|
||||
assert_eq!(2.0.bound(0.0, 1.0), 1.0);
|
||||
}
|
||||
}
|
||||
2082
third-party/vendor/tiny-skia-path/src/stroker.rs
vendored
Normal file
2082
third-party/vendor/tiny-skia-path/src/stroker.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
435
third-party/vendor/tiny-skia-path/src/transform.rs
vendored
Normal file
435
third-party/vendor/tiny-skia-path/src/transform.rs
vendored
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
// Copyright 2006 The Android Open Source Project
|
||||
// Copyright 2020 Yevhenii Reizner
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::Point;
|
||||
|
||||
use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO};
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
|
||||
use crate::NoStdFloat;
|
||||
|
||||
/// An affine transformation matrix.
|
||||
///
|
||||
/// Unlike other types, doesn't guarantee to be valid. This is Skia quirk.
|
||||
/// Meaning Transform(0, 0, 0, 0, 0, 0) is ok, while it's technically not.
|
||||
/// Non-finite values are also not an error.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub struct Transform {
|
||||
pub sx: f32,
|
||||
pub kx: f32,
|
||||
pub ky: f32,
|
||||
pub sy: f32,
|
||||
pub tx: f32,
|
||||
pub ty: f32,
|
||||
}
|
||||
|
||||
impl Default for Transform {
|
||||
fn default() -> Self {
|
||||
Transform {
|
||||
sx: 1.0,
|
||||
kx: 0.0,
|
||||
ky: 0.0,
|
||||
sy: 1.0,
|
||||
tx: 0.0,
|
||||
ty: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
/// Creates an identity transform.
|
||||
pub fn identity() -> Self {
|
||||
Transform::default()
|
||||
}
|
||||
|
||||
/// Creates a new `Transform`.
|
||||
///
|
||||
/// We are using column-major-column-vector matrix notation, therefore it's ky-kx, not kx-ky.
|
||||
pub fn from_row(sx: f32, ky: f32, kx: f32, sy: f32, tx: f32, ty: f32) -> Self {
|
||||
Transform {
|
||||
sx,
|
||||
ky,
|
||||
kx,
|
||||
sy,
|
||||
tx,
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new translating `Transform`.
|
||||
pub fn from_translate(tx: f32, ty: f32) -> Self {
|
||||
Transform::from_row(1.0, 0.0, 0.0, 1.0, tx, ty)
|
||||
}
|
||||
|
||||
/// Creates a new scaling `Transform`.
|
||||
pub fn from_scale(sx: f32, sy: f32) -> Self {
|
||||
Transform::from_row(sx, 0.0, 0.0, sy, 0.0, 0.0)
|
||||
}
|
||||
|
||||
/// Creates a new skewing `Transform`.
|
||||
pub fn from_skew(kx: f32, ky: f32) -> Self {
|
||||
Transform::from_row(1.0, ky, kx, 1.0, 0.0, 0.0)
|
||||
}
|
||||
|
||||
/// Creates a new rotating `Transform`.
|
||||
pub fn from_rotate(angle: f32) -> Self {
|
||||
let v = angle.to_radians();
|
||||
let a = v.cos();
|
||||
let b = v.sin();
|
||||
let c = -b;
|
||||
let d = a;
|
||||
Transform::from_row(a, b, c, d, 0.0, 0.0)
|
||||
}
|
||||
|
||||
/// Creates a new rotating `Transform` at the specified position.
|
||||
pub fn from_rotate_at(angle: f32, tx: f32, ty: f32) -> Self {
|
||||
let mut ts = Self::default();
|
||||
ts = ts.pre_translate(tx, ty);
|
||||
ts = ts.pre_concat(Transform::from_rotate(angle));
|
||||
ts = ts.pre_translate(-tx, -ty);
|
||||
ts
|
||||
}
|
||||
|
||||
/// Checks that transform is finite.
|
||||
pub fn is_finite(&self) -> bool {
|
||||
self.sx.is_finite()
|
||||
&& self.ky.is_finite()
|
||||
&& self.kx.is_finite()
|
||||
&& self.sy.is_finite()
|
||||
&& self.tx.is_finite()
|
||||
&& self.ty.is_finite()
|
||||
}
|
||||
|
||||
/// Checks that transform is identity.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn is_identity(&self) -> bool {
|
||||
*self == Transform::default()
|
||||
}
|
||||
|
||||
/// Checks that transform is scale-only.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn is_scale(&self) -> bool {
|
||||
self.has_scale() && !self.has_skew() && !self.has_translate()
|
||||
}
|
||||
|
||||
/// Checks that transform is skew-only.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn is_skew(&self) -> bool {
|
||||
!self.has_scale() && self.has_skew() && !self.has_translate()
|
||||
}
|
||||
|
||||
/// Checks that transform is translate-only.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn is_translate(&self) -> bool {
|
||||
!self.has_scale() && !self.has_skew() && self.has_translate()
|
||||
}
|
||||
|
||||
/// Checks that transform contains only scale and translate.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn is_scale_translate(&self) -> bool {
|
||||
(self.has_scale() || self.has_translate()) && !self.has_skew()
|
||||
}
|
||||
|
||||
/// Checks that transform contains a scale part.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn has_scale(&self) -> bool {
|
||||
self.sx != 1.0 || self.sy != 1.0
|
||||
}
|
||||
|
||||
/// Checks that transform contains a skew part.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn has_skew(&self) -> bool {
|
||||
self.kx != 0.0 || self.ky != 0.0
|
||||
}
|
||||
|
||||
/// Checks that transform contains a translate part.
|
||||
///
|
||||
/// The transform type is detected on creation, so this method is essentially free.
|
||||
pub fn has_translate(&self) -> bool {
|
||||
self.tx != 0.0 || self.ty != 0.0
|
||||
}
|
||||
|
||||
/// Pre-scales the current transform.
|
||||
#[must_use]
|
||||
pub fn pre_scale(&self, sx: f32, sy: f32) -> Self {
|
||||
self.pre_concat(Transform::from_scale(sx, sy))
|
||||
}
|
||||
|
||||
/// Post-scales the current transform.
|
||||
#[must_use]
|
||||
pub fn post_scale(&self, sx: f32, sy: f32) -> Self {
|
||||
self.post_concat(Transform::from_scale(sx, sy))
|
||||
}
|
||||
|
||||
/// Pre-translates the current transform.
|
||||
#[must_use]
|
||||
pub fn pre_translate(&self, tx: f32, ty: f32) -> Self {
|
||||
self.pre_concat(Transform::from_translate(tx, ty))
|
||||
}
|
||||
|
||||
/// Post-translates the current transform.
|
||||
#[must_use]
|
||||
pub fn post_translate(&self, tx: f32, ty: f32) -> Self {
|
||||
self.post_concat(Transform::from_translate(tx, ty))
|
||||
}
|
||||
|
||||
/// Pre-concats the current transform.
|
||||
#[must_use]
|
||||
pub fn pre_concat(&self, other: Self) -> Self {
|
||||
concat(*self, other)
|
||||
}
|
||||
|
||||
/// Post-concats the current transform.
|
||||
#[must_use]
|
||||
pub fn post_concat(&self, other: Self) -> Self {
|
||||
concat(other, *self)
|
||||
}
|
||||
|
||||
pub(crate) fn from_sin_cos(sin: f32, cos: f32) -> Self {
|
||||
Transform::from_row(cos, sin, -sin, cos, 0.0, 0.0)
|
||||
}
|
||||
|
||||
/// Transforms a slice of points using the current transform.
|
||||
pub fn map_points(&self, points: &mut [Point]) {
|
||||
if points.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: simd
|
||||
|
||||
if self.is_identity() {
|
||||
// Do nothing.
|
||||
} else if self.is_translate() {
|
||||
for p in points {
|
||||
p.x += self.tx;
|
||||
p.y += self.ty;
|
||||
}
|
||||
} else if self.is_scale_translate() {
|
||||
for p in points {
|
||||
p.x = p.x * self.sx + self.tx;
|
||||
p.y = p.y * self.sy + self.ty;
|
||||
}
|
||||
} else {
|
||||
for p in points {
|
||||
let x = p.x * self.sx + p.y * self.kx + self.tx;
|
||||
let y = p.x * self.ky + p.y * self.sy + self.ty;
|
||||
p.x = x;
|
||||
p.y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an inverted transform.
|
||||
pub fn invert(&self) -> Option<Self> {
|
||||
// Allow the trivial case to be inlined.
|
||||
if self.is_identity() {
|
||||
return Some(*self);
|
||||
}
|
||||
|
||||
invert(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn invert(ts: &Transform) -> Option<Transform> {
|
||||
debug_assert!(!ts.is_identity());
|
||||
|
||||
if ts.is_scale_translate() {
|
||||
if ts.has_scale() {
|
||||
let inv_x = ts.sx.invert();
|
||||
let inv_y = ts.sy.invert();
|
||||
Some(Transform::from_row(
|
||||
inv_x,
|
||||
0.0,
|
||||
0.0,
|
||||
inv_y,
|
||||
-ts.tx * inv_x,
|
||||
-ts.ty * inv_y,
|
||||
))
|
||||
} else {
|
||||
// translate only
|
||||
Some(Transform::from_translate(-ts.tx, -ts.ty))
|
||||
}
|
||||
} else {
|
||||
let inv_det = inv_determinant(ts)?;
|
||||
let inv_ts = compute_inv(ts, inv_det);
|
||||
|
||||
if inv_ts.is_finite() {
|
||||
Some(inv_ts)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inv_determinant(ts: &Transform) -> Option<f64> {
|
||||
let det = dcross(ts.sx as f64, ts.sy as f64, ts.kx as f64, ts.ky as f64);
|
||||
|
||||
// Since the determinant is on the order of the cube of the matrix members,
|
||||
// compare to the cube of the default nearly-zero constant (although an
|
||||
// estimate of the condition number would be better if it wasn't so expensive).
|
||||
let tolerance = SCALAR_NEARLY_ZERO * SCALAR_NEARLY_ZERO * SCALAR_NEARLY_ZERO;
|
||||
if (det as f32).is_nearly_zero_within_tolerance(tolerance) {
|
||||
None
|
||||
} else {
|
||||
Some(1.0 / det)
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_inv(ts: &Transform, inv_det: f64) -> Transform {
|
||||
Transform::from_row(
|
||||
(ts.sy as f64 * inv_det) as f32,
|
||||
(-ts.ky as f64 * inv_det) as f32,
|
||||
(-ts.kx as f64 * inv_det) as f32,
|
||||
(ts.sx as f64 * inv_det) as f32,
|
||||
dcross_dscale(ts.kx, ts.ty, ts.sy, ts.tx, inv_det),
|
||||
dcross_dscale(ts.ky, ts.tx, ts.sx, ts.ty, inv_det),
|
||||
)
|
||||
}
|
||||
|
||||
fn dcross(a: f64, b: f64, c: f64, d: f64) -> f64 {
|
||||
a * b - c * d
|
||||
}
|
||||
|
||||
fn dcross_dscale(a: f32, b: f32, c: f32, d: f32, scale: f64) -> f32 {
|
||||
(dcross(a as f64, b as f64, c as f64, d as f64) * scale) as f32
|
||||
}
|
||||
|
||||
fn concat(a: Transform, b: Transform) -> Transform {
|
||||
if a.is_identity() {
|
||||
b
|
||||
} else if b.is_identity() {
|
||||
a
|
||||
} else if !a.has_skew() && !b.has_skew() {
|
||||
// just scale and translate
|
||||
Transform::from_row(
|
||||
a.sx * b.sx,
|
||||
0.0,
|
||||
0.0,
|
||||
a.sy * b.sy,
|
||||
a.sx * b.tx + a.tx,
|
||||
a.sy * b.ty + a.ty,
|
||||
)
|
||||
} else {
|
||||
Transform::from_row(
|
||||
mul_add_mul(a.sx, b.sx, a.kx, b.ky),
|
||||
mul_add_mul(a.ky, b.sx, a.sy, b.ky),
|
||||
mul_add_mul(a.sx, b.kx, a.kx, b.sy),
|
||||
mul_add_mul(a.ky, b.kx, a.sy, b.sy),
|
||||
mul_add_mul(a.sx, b.tx, a.kx, b.ty) + a.tx,
|
||||
mul_add_mul(a.ky, b.tx, a.sy, b.ty) + a.ty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn mul_add_mul(a: f32, b: f32, c: f32, d: f32) -> f32 {
|
||||
(f64::from(a) * f64::from(b) + f64::from(c) * f64::from(d)) as f32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn transform() {
|
||||
assert_eq!(
|
||||
Transform::identity(),
|
||||
Transform::from_row(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Transform::from_scale(1.0, 2.0),
|
||||
Transform::from_row(1.0, 0.0, 0.0, 2.0, 0.0, 0.0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Transform::from_skew(2.0, 3.0),
|
||||
Transform::from_row(1.0, 3.0, 2.0, 1.0, 0.0, 0.0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Transform::from_translate(5.0, 6.0),
|
||||
Transform::from_row(1.0, 0.0, 0.0, 1.0, 5.0, 6.0)
|
||||
);
|
||||
|
||||
let ts = Transform::identity();
|
||||
assert_eq!(ts.is_identity(), true);
|
||||
assert_eq!(ts.is_scale(), false);
|
||||
assert_eq!(ts.is_skew(), false);
|
||||
assert_eq!(ts.is_translate(), false);
|
||||
assert_eq!(ts.is_scale_translate(), false);
|
||||
assert_eq!(ts.has_scale(), false);
|
||||
assert_eq!(ts.has_skew(), false);
|
||||
assert_eq!(ts.has_translate(), false);
|
||||
|
||||
let ts = Transform::from_scale(2.0, 3.0);
|
||||
assert_eq!(ts.is_identity(), false);
|
||||
assert_eq!(ts.is_scale(), true);
|
||||
assert_eq!(ts.is_skew(), false);
|
||||
assert_eq!(ts.is_translate(), false);
|
||||
assert_eq!(ts.is_scale_translate(), true);
|
||||
assert_eq!(ts.has_scale(), true);
|
||||
assert_eq!(ts.has_skew(), false);
|
||||
assert_eq!(ts.has_translate(), false);
|
||||
|
||||
let ts = Transform::from_skew(2.0, 3.0);
|
||||
assert_eq!(ts.is_identity(), false);
|
||||
assert_eq!(ts.is_scale(), false);
|
||||
assert_eq!(ts.is_skew(), true);
|
||||
assert_eq!(ts.is_translate(), false);
|
||||
assert_eq!(ts.is_scale_translate(), false);
|
||||
assert_eq!(ts.has_scale(), false);
|
||||
assert_eq!(ts.has_skew(), true);
|
||||
assert_eq!(ts.has_translate(), false);
|
||||
|
||||
let ts = Transform::from_translate(2.0, 3.0);
|
||||
assert_eq!(ts.is_identity(), false);
|
||||
assert_eq!(ts.is_scale(), false);
|
||||
assert_eq!(ts.is_skew(), false);
|
||||
assert_eq!(ts.is_translate(), true);
|
||||
assert_eq!(ts.is_scale_translate(), true);
|
||||
assert_eq!(ts.has_scale(), false);
|
||||
assert_eq!(ts.has_skew(), false);
|
||||
assert_eq!(ts.has_translate(), true);
|
||||
|
||||
let ts = Transform::from_row(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
|
||||
assert_eq!(ts.is_identity(), false);
|
||||
assert_eq!(ts.is_scale(), false);
|
||||
assert_eq!(ts.is_skew(), false);
|
||||
assert_eq!(ts.is_translate(), false);
|
||||
assert_eq!(ts.is_scale_translate(), false);
|
||||
assert_eq!(ts.has_scale(), true);
|
||||
assert_eq!(ts.has_skew(), true);
|
||||
assert_eq!(ts.has_translate(), true);
|
||||
|
||||
let ts = Transform::from_scale(1.0, 1.0);
|
||||
assert_eq!(ts.has_scale(), false);
|
||||
|
||||
let ts = Transform::from_skew(0.0, 0.0);
|
||||
assert_eq!(ts.has_skew(), false);
|
||||
|
||||
let ts = Transform::from_translate(0.0, 0.0);
|
||||
assert_eq!(ts.has_translate(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat() {
|
||||
let mut ts = Transform::from_row(1.2, 3.4, -5.6, -7.8, 1.2, 3.4);
|
||||
ts = ts.pre_scale(2.0, -4.0);
|
||||
assert_eq!(ts, Transform::from_row(2.4, 6.8, 22.4, 31.2, 1.2, 3.4));
|
||||
|
||||
let mut ts = Transform::from_row(1.2, 3.4, -5.6, -7.8, 1.2, 3.4);
|
||||
ts = ts.post_scale(2.0, -4.0);
|
||||
assert_eq!(ts, Transform::from_row(2.4, -13.6, -11.2, 31.2, 2.4, -13.6));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue