tiny-skia-path-0.11.3/.cargo_vcs_info.json0000644000000001420000000000100137550ustar { "git": { "sha1": "47412517525b3e6a542917bd91c84c3aea9b727c" }, "path_in_vcs": "path" }tiny-skia-path-0.11.3/Cargo.toml0000644000000022510000000000100117560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "tiny-skia-path" version = "0.11.3" authors = ["Yevhenii Reizner "] description = "A tiny-skia Bezier path implementation" documentation = "https://docs.rs/tiny-skia-path/" readme = "README.md" keywords = [ "graphics", "bezier", "path", "dash", "stroke", ] categories = ["graphics"] license = "BSD-3-Clause" repository = "https://github.com/RazrFalcon/tiny-skia/tree/master/path" [dependencies.arrayref] version = "0.3.6" [dependencies.bytemuck] version = "1.4" [dependencies.libm] version = "0.2.1" optional = true [dependencies.strict-num] version = "0.1" default-features = false [features] default = ["std"] no-std-float = ["libm"] std = [] tiny-skia-path-0.11.3/Cargo.toml.orig000064400000000000000000000015101046102023000154340ustar 00000000000000[package] name = "tiny-skia-path" version = "0.11.3" authors = ["Yevhenii Reizner "] edition = "2018" description = "A tiny-skia Bezier path implementation" documentation = "https://docs.rs/tiny-skia-path/" readme = "README.md" repository = "https://github.com/RazrFalcon/tiny-skia/tree/master/path" license = "BSD-3-Clause" keywords = ["graphics", "bezier", "path", "dash", "stroke"] categories = ["graphics"] workspace = ".." [dependencies] arrayref = "0.3.6" bytemuck = "1.4" libm = { version = "0.2.1", optional = true } # float support on no_std strict-num = { version = "0.1", default-features = false } [features] default = ["std"] # Enables the use of the standard library. Deactivate this and activate the no-std-float # feature to compile for targets that don't have std. std = [] no-std-float = ["libm"] tiny-skia-path-0.11.3/LICENSE000064400000000000000000000030341046102023000135550ustar 00000000000000Copyright (c) 2011 Google Inc. All rights reserved. Copyright (c) 2020 Yevhenii Reizner All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. tiny-skia-path-0.11.3/README.md000064400000000000000000000014101046102023000140230ustar 00000000000000# tiny-skia-path ![Build Status](https://github.com/RazrFalcon/tiny-skia/workflows/Rust/badge.svg) [![Crates.io](https://img.shields.io/crates/v/tiny-skia-path.svg)](https://crates.io/crates/tiny-skia-path) [![Documentation](https://docs.rs/tiny-skia-path/badge.svg)](https://docs.rs/tiny-skia-path) 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/). MSRV: stable ## License The same as used by [Skia](https://skia.org/): [New BSD License](./LICENSE) tiny-skia-path-0.11.3/src/dash.rs000064400000000000000000000656651046102023000146460ustar 00000000000000// 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, 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, dash_offset: f32) -> Option { 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). /// /// Returns `None` when more than 1_000_000 dashes had to be produced /// or when the final path has an invalid bounding box. pub fn dash(&self, dash: &StrokeDash, resolution_scale: f32) -> Option { dash_impl(self, dash, resolution_scale) } } fn dash_impl(src: &Path, dash: &StrokeDash, res_scale: f32) -> Option { // 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, distance + d_len, 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 { // 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, points: Vec, 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, ) { 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; } if self.segments.is_empty() { return; } let (seg_index, mut start_t) = match self.distance_to_segment(start_d) { Some(v) => v, None => return, }; let mut seg = self.segments[seg_index]; let (stop_seg_index, stop_t) = match self.distance_to_segment(stop_d) { Some(v) => v, None => return, }; 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, ); } } 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 } tiny-skia-path-0.11.3/src/f32x2_t.rs000064400000000000000000000046231046102023000151010ustar 00000000000000// 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 for f32x2 { type Output = f32x2; fn add(self, other: f32x2) -> f32x2 { f32x2([self.x() + other.x(), self.y() + other.y()]) } } impl core::ops::Sub for f32x2 { type Output = f32x2; fn sub(self, other: f32x2) -> f32x2 { f32x2([self.x() - other.x(), self.y() - other.y()]) } } impl core::ops::Mul for f32x2 { type Output = f32x2; fn mul(self, other: f32x2) -> f32x2 { f32x2([self.x() * other.x(), self.y() * other.y()]) } } impl core::ops::Div 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 } } tiny-skia-path-0.11.3/src/f32x4_t.rs000064400000000000000000000034721046102023000151040ustar 00000000000000// 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; } } tiny-skia-path-0.11.3/src/floating_point.rs000064400000000000000000000103211046102023000167160ustar 00000000000000// 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: Sized { /// Return the closest integer for the given float. fn saturate_from(n: T) -> Self; } impl SaturateCast 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 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: SaturateCast { fn saturate_floor(n: T) -> Self; fn saturate_ceil(n: T) -> Self; fn saturate_round(n: T) -> Self; } impl SaturateRound 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 { 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(f32::EPSILON, 1.0 - 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()) } } } tiny-skia-path-0.11.3/src/lib.rs000064400000000000000000000214601046102023000144560ustar 00000000000000// 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 size; 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 size::*; 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, ) -> 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; } } tiny-skia-path-0.11.3/src/path.rs000064400000000000000000000264341046102023000146520ustar 00000000000000// 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}; #[cfg(all(not(feature = "std"), feature = "no-std-float"))] use crate::NoStdFloat; /// 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, pub(crate) points: Vec, pub(crate) bounds: Rect, } impl Path { /// Returns the number of segments in the path. pub fn len(&self) -> usize { self.verbs.len() } /// Return if the path is empty. pub fn is_empty(&self) -> bool { self.verbs.is_empty() } /// Returns the bounds of the path's points. /// /// The value is already calculated. pub fn bounds(&self) -> Rect { self.bounds } /// Calculates path's tight bounds. /// /// This operation can be expensive. pub fn compute_tight_bounds(&self) -> Option { // big enough to hold worst-case curve type (cubic) extremas + 1 let mut extremas = [Point::zero(); 5]; let mut min = self.points[0]; let mut max = self.points[0]; let mut iter = self.segments(); while let Some(segment) = iter.next() { let mut count = 0; match segment { PathSegment::MoveTo(p) => { extremas[0] = p; count = 1; } PathSegment::LineTo(p) => { extremas[0] = p; count = 1; } PathSegment::QuadTo(p0, p1) => { count = compute_quad_extremas(iter.last_point, p0, p1, &mut extremas); } PathSegment::CubicTo(p0, p1, p2) => { count = compute_cubic_extremas(iter.last_point, p0, p1, p2, &mut extremas); } PathSegment::Close => {} } for tmp in &extremas[0..count] { min.x = min.x.min(tmp.x); min.y = min.y.min(tmp.y); max.x = max.x.max(tmp.x); max.y = max.y.max(tmp.y); } } Rect::from_ltrb(min.x, min.y, max.x, max.y) } /// 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 { 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() } } fn compute_quad_extremas(p0: Point, p1: Point, p2: Point, extremas: &mut [Point; 5]) -> usize { use crate::path_geometry; let src = [p0, p1, p2]; let mut extrema_idx = 0; if let Some(t) = path_geometry::find_quad_extrema(p0.x, p1.x, p2.x) { extremas[extrema_idx] = path_geometry::eval_quad_at(&src, t.to_normalized()); extrema_idx += 1; } if let Some(t) = path_geometry::find_quad_extrema(p0.y, p1.y, p2.y) { extremas[extrema_idx] = path_geometry::eval_quad_at(&src, t.to_normalized()); extrema_idx += 1; } extremas[extrema_idx] = p2; extrema_idx + 1 } fn compute_cubic_extremas( p0: Point, p1: Point, p2: Point, p3: Point, extremas: &mut [Point; 5], ) -> usize { use crate::path_geometry; let mut ts0 = path_geometry::new_t_values(); let mut ts1 = path_geometry::new_t_values(); let n0 = path_geometry::find_cubic_extrema(p0.x, p1.x, p2.x, p3.x, &mut ts0); let n1 = path_geometry::find_cubic_extrema(p0.y, p1.y, p2.y, p3.y, &mut ts1); let total_len = n0 + n1; debug_assert!(total_len <= 4); let src = [p0, p1, p2, p3]; let mut extrema_idx = 0; for t in &ts0[0..n0] { extremas[extrema_idx] = path_geometry::eval_cubic_pos_at(&src, t.to_normalized()); extrema_idx += 1; } for t in &ts1[0..n1] { extremas[extrema_idx] = path_geometry::eval_cubic_pos_at(&src, t.to_normalized()); extrema_idx += 1; } extremas[total_len] = p3; total_len + 1 } /// 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 { self.path.verbs.get(self.verb_index).cloned() } } impl<'a> Iterator for PathSegmentsIter<'a> { type Item = PathSegment; fn next(&mut self) -> Option { 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 } } } tiny-skia-path-0.11.3/src/path_builder.rs000064400000000000000000000326601046102023000163560ustar 00000000000000// 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, pub(crate) points: Vec, 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 { 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 { 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 { 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. pub fn push_rect(&mut self, rect: Rect) { 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); } } /// Adds a path. pub fn push_path(&mut self, other: &Path) { self.last_move_to_index = self.points.len(); self.verbs.extend_from_slice(&other.verbs); self.points.extend_from_slice(&other.points); } pub(crate) fn push_path_builder(&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 { 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, }) } } tiny-skia-path-0.11.3/src/path_geometry.rs000064400000000000000000000664631046102023000165730ustar 00000000000000// 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; 3] } 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); } // Quad'(t) = At + B, where // A = 2(a - 2b + c) // B = 2(b - a) // Solve for t, only if it fits between 0 < t < 1 pub(crate) fn find_quad_extrema(a: f32, b: f32, c: f32) -> Option { // At + B == 0 // t = -B / A valid_unit_divide(a - b, a - b - b + c) } pub fn valid_unit_divide(mut numer: f32, mut denom: f32) -> Option { 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()))) } // Cubic'(t) = At^2 + Bt + C, where // A = 3(-a + 3(b - c) + d) // B = 6(a - 2b + c) // C = 3(b - a) // Solve for t, keeping only those that fit between 0 < t < 1 pub(crate) fn find_cubic_extrema( a: f32, b: f32, c: f32, d: f32, t_values: &mut [NormalizedF32Exclusive; 3], ) -> usize { // we divide A,B,C by 3 to simplify let aa = d - a + 3.0 * (b - c); let bb = 2.0 * (a - b - b + c); let cc = b - a; find_unit_quad_roots(aa, bb, cc, t_values) } // 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 { // 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 { 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 { 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, ] ); } } tiny-skia-path-0.11.3/src/rect.rs000064400000000000000000000437561046102023000146610ustar 00000000000000// 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, PathBuilder, Point, SaturateRound, Size, Transform}; #[cfg(all(not(feature = "std"), feature = "no-std-float"))] use crate::NoStdFloat; /// 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 { 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 { 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::from_wh_safe(self.width, 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 { 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 { 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 { IntRect::from_ltrb( self.left().saturating_sub(dx), self.top().saturating_sub(dy), self.right().saturating_add(dx), self.bottom().saturating_add(dy), ) } /// Translates the rect by the specified offset. pub fn translate(&self, tx: i32, ty: i32) -> Option { IntRect::from_xywh(self.x() + tx, self.y() + ty, self.width(), self.height()) } /// Translates the rect to the specified position. pub fn translate_to(&self, x: i32, y: i32) -> Option { IntRect::from_xywh(x, y, self.width(), self.height()) } /// 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() } } #[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, u32::MAX, u32::MAX), None); assert_eq!(IntRect::from_xywh(0, 0, 1, u32::MAX), None); assert_eq!(IntRect::from_xywh(0, 0, u32::MAX, 1), None); assert_eq!(IntRect::from_xywh(i32::MAX, 0, 1, 1), None); assert_eq!(IntRect::from_xywh(0, i32::MAX, 1, 1), 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 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 { 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 { 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::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::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 { 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 { 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(&self, dx: f32, dy: f32) -> Option { 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(&self, dx: f32, dy: f32) -> Option { self.inset(-dx, -dy) } /// Transforms the rect using the provided `Transform`. /// /// This method is expensive. pub fn transform(&self, ts: Transform) -> Option { if !ts.is_identity() { // TODO: remove allocation let mut path = PathBuilder::from_rect(*self); path = path.transform(ts)?; Some(path.bounds()) } else { Some(*self) } } /// Applies a bounding box transform. pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self { let x = self.x() * bbox.width() + bbox.x(); let y = self.y() * bbox.height() + bbox.y(); let w = self.width() * bbox.width(); let h = self.height() * bbox.height(); Self::from_xywh(x, y, w, h).unwrap() } /// Converts into [`NonZeroRect`]. pub fn to_non_zero_rect(&self) -> Option { NonZeroRect::from_xywh(self.x(), self.y(), self.width(), self.height()) } } fn checked_f32_sub(a: f32, b: f32) -> Option { 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 > f32::MIN as f64 && n < 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(f32::NAN, 10.0, 10.0, 10.0), None); assert_eq!(Rect::from_ltrb(10.0, f32::NAN, 10.0, 10.0), None); assert_eq!(Rect::from_ltrb(10.0, 10.0, f32::NAN, 10.0), None); assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, f32::NAN), None); assert_eq!(Rect::from_ltrb(10.0, 10.0, 10.0, 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); } } /// A rectangle defined by left, top, right and bottom edges. /// /// Similar to [`Rect`], but width and height guarantee to be non-zero and positive. /// /// # Guarantees /// /// - All values are finite. /// - Left edge is < right. /// - Top edge is < bottom. /// - Width and height are <= f32::MAX. /// - Width and height are > 0.0 #[allow(missing_docs)] #[derive(Copy, Clone, PartialEq)] pub struct NonZeroRect { left: FiniteF32, top: FiniteF32, right: FiniteF32, bottom: FiniteF32, } impl core::fmt::Debug for NonZeroRect { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("NonZeroRect") .field("left", &self.left.get()) .field("top", &self.top.get()) .field("right", &self.right.get()) .field("bottom", &self.bottom.get()) .finish() } } impl NonZeroRect { /// Creates new `NonZeroRect`. pub fn from_ltrb(left: f32, top: f32, right: f32, bottom: f32) -> Option { 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(Self { left, top, right, bottom, }) } else { None } } /// Creates new `NonZeroRect`. pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) -> Option { Self::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. pub fn width(&self) -> f32 { self.right.get() - self.left.get() } /// Returns rect's height. pub fn height(&self) -> f32 { self.bottom.get() - self.top.get() } /// Returns rect's size. pub fn size(&self) -> Size { Size::from_wh(self.width(), self.height()).unwrap() } /// Translates the rect to the specified position. pub fn translate_to(&self, x: f32, y: f32) -> Option { Self::from_xywh(x, y, self.width(), self.height()) } /// Transforms the rect using the provided `Transform`. /// /// This method is expensive. pub fn transform(&self, ts: Transform) -> Option { if !ts.is_identity() { // TODO: remove allocation let mut path = PathBuilder::from_rect(self.to_rect()); path = path.transform(ts)?; path.bounds().to_non_zero_rect() } else { Some(*self) } } /// Applies a bounding box transform. pub fn bbox_transform(&self, bbox: NonZeroRect) -> Self { let x = self.x() * bbox.width() + bbox.x(); let y = self.y() * bbox.height() + bbox.y(); let w = self.width() * bbox.width(); let h = self.height() * bbox.height(); Self::from_xywh(x, y, w, h).unwrap() } /// Converts into [`Rect`]. pub fn to_rect(&self) -> Rect { Rect::from_xywh(self.x(), self.y(), self.width(), self.height()).unwrap() } /// Converts into [`IntRect`]. pub fn to_int_rect(&self) -> IntRect { IntRect::from_xywh( self.x().floor() as i32, self.y().floor() as i32, core::cmp::max(1, self.width().ceil() as u32), core::cmp::max(1, self.height().ceil() as u32), ) .unwrap() } } tiny-skia-path-0.11.3/src/scalar.rs000064400000000000000000000104221046102023000151510ustar 00000000000000// 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 round(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 round(self) -> Self { libm::roundf(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 round(self) -> Self { libm::round(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!(f32::NAN.bound(0.0, 1.0), 1.0); assert_eq!(f32::INFINITY.bound(0.0, 1.0), 1.0); assert_eq!(f32::NEG_INFINITY.bound(0.0, 1.0), 0.0); assert_eq!(f32::EPSILON.bound(0.0, 1.0), 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); } } tiny-skia-path-0.11.3/src/size.rs000064400000000000000000000127071046102023000146660ustar 00000000000000// 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 strict_num::NonZeroPositiveF32; use crate::{IntRect, LengthU32, NonZeroRect, Rect}; #[cfg(all(not(feature = "std"), feature = "no-std-float"))] use crate::NoStdFloat; /// 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 { Some(IntSize { width: LengthU32::new(width)?, height: LengthU32::new(height)?, }) } pub(crate) fn from_wh_safe(width: LengthU32, height: LengthU32) -> Self { IntSize { width, height } } /// Returns width. pub fn width(&self) -> u32 { self.width.get() } /// Returns height. pub fn height(&self) -> u32 { self.height.get() } /// Returns width and height as a tuple. pub fn dimensions(&self) -> (u32, u32) { (self.width(), self.height()) } /// Scales current size by the specified factor. #[inline] pub fn scale_by(&self, factor: f32) -> Option { Self::from_wh( (self.width() as f32 * factor).round() as u32, (self.height() as f32 * factor).round() as u32, ) } /// Scales current size to the specified size. #[inline] pub fn scale_to(&self, to: Self) -> Self { size_scale(*self, to, false) } /// Scales current size to the specified width. #[inline] pub fn scale_to_width(&self, new_width: u32) -> Option { let new_height = (new_width as f32 * self.height() as f32 / self.width() as f32).ceil(); Self::from_wh(new_width, new_height as u32) } /// Scales current size to the specified height. #[inline] pub fn scale_to_height(&self, new_height: u32) -> Option { let new_width = (new_height as f32 * self.width() as f32 / self.height() as f32).ceil(); Self::from_wh(new_width as u32, new_height) } /// Converts into [`Size`]. pub fn to_size(&self) -> Size { Size::from_wh(self.width() as f32, self.height() as f32).unwrap() } /// Converts into [`IntRect`] at the provided position. pub fn to_int_rect(&self, x: i32, y: i32) -> IntRect { IntRect::from_xywh(x, y, self.width(), self.height()).unwrap() } } fn size_scale(s1: IntSize, s2: IntSize, expand: bool) -> IntSize { let rw = (s2.height() as f32 * s1.width() as f32 / s1.height() as f32).ceil() as u32; let with_h = if expand { rw <= s2.width() } else { rw >= s2.width() }; if !with_h { IntSize::from_wh(rw, s2.height()).unwrap() } else { let h = (s2.width() as f32 * s1.height() as f32 / s1.width() as f32).ceil() as u32; IntSize::from_wh(s2.width(), h).unwrap() } } #[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() ); } } /// A size. /// /// # Guarantees /// /// - Width and height are positive, non-zero and finite. #[derive(Copy, Clone, PartialEq, Debug)] pub struct Size { width: NonZeroPositiveF32, height: NonZeroPositiveF32, } impl Size { /// Creates a new `Size` from width and height. pub fn from_wh(width: f32, height: f32) -> Option { Some(Size { width: NonZeroPositiveF32::new(width)?, height: NonZeroPositiveF32::new(height)?, }) } /// Returns width. pub fn width(&self) -> f32 { self.width.get() } /// Returns height. pub fn height(&self) -> f32 { self.height.get() } /// Scales current size to specified size. pub fn scale_to(&self, to: Self) -> Self { size_scale_f64(*self, to, false) } /// Expands current size to specified size. pub fn expand_to(&self, to: Self) -> Self { size_scale_f64(*self, to, true) } /// Converts into [`IntSize`]. pub fn to_int_size(&self) -> IntSize { IntSize::from_wh( core::cmp::max(1, self.width().round() as u32), core::cmp::max(1, self.height().round() as u32), ) .unwrap() } /// Converts the current size to `Rect` at provided position. pub fn to_rect(&self, x: f32, y: f32) -> Option { Rect::from_xywh(x, y, self.width.get(), self.height.get()) } /// Converts the current size to `NonZeroRect` at provided position. pub fn to_non_zero_rect(&self, x: f32, y: f32) -> NonZeroRect { NonZeroRect::from_xywh(x, y, self.width.get(), self.height.get()).unwrap() } } fn size_scale_f64(s1: Size, s2: Size, expand: bool) -> Size { let rw = s2.height.get() * s1.width.get() / s1.height.get(); let with_h = if expand { rw <= s2.width.get() } else { rw >= s2.width.get() }; if !with_h { Size::from_wh(rw, s2.height.get()).unwrap() } else { let h = s2.width.get() * s1.height.get() / s1.width.get(); Size::from_wh(s2.width.get(), h).unwrap() } } tiny-skia-path-0.11.3/src/stroker.rs000064400000000000000000002101571046102023000154040ustar 00000000000000// Copyright 2008 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. // Based on SkStroke.cpp use crate::{Path, Point, Transform}; use crate::dash::StrokeDash; use crate::floating_point::{NonZeroPositiveF32, NormalizedF32, NormalizedF32Exclusive}; use crate::path::{PathSegment, PathSegmentsIter}; use crate::path_builder::{PathBuilder, PathDirection}; use crate::path_geometry; use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2}; #[cfg(all(not(feature = "std"), feature = "no-std-float"))] use crate::NoStdFloat; struct SwappableBuilders<'a> { inner: &'a mut PathBuilder, outer: &'a mut PathBuilder, } impl<'a> SwappableBuilders<'a> { fn swap(&mut self) { // Skia swaps pointers to inner and outer builders during joining, // but not builders itself. So a simple `core::mem::swap` will produce invalid results. // And if we try to use use `core::mem::swap` on references, like below, // borrow checker will be unhappy. // That's why we need this wrapper. Maybe there is a better solution. core::mem::swap(&mut self.inner, &mut self.outer); } } /// Stroke properties. #[derive(Clone, PartialEq, Debug)] pub struct Stroke { /// A stroke thickness. /// /// Must be >= 0. /// /// When set to 0, a hairline stroking will be used. /// /// Default: 1.0 pub width: f32, /// The limit at which a sharp corner is drawn beveled. /// /// Default: 4.0 pub miter_limit: f32, /// A stroke line cap. /// /// Default: Butt pub line_cap: LineCap, /// A stroke line join. /// /// Default: Miter pub line_join: LineJoin, /// A stroke dashing properties. /// /// Default: None pub dash: Option, } impl Default for Stroke { fn default() -> Self { Stroke { width: 1.0, miter_limit: 4.0, line_cap: LineCap::default(), line_join: LineJoin::default(), dash: None, } } } /// Draws at the beginning and end of an open path contour. #[derive(Copy, Clone, PartialEq, Debug)] pub enum LineCap { /// No stroke extension. Butt, /// Adds circle. Round, /// Adds square. Square, } impl Default for LineCap { fn default() -> Self { LineCap::Butt } } /// Specifies how corners are drawn when a shape is stroked. /// /// Join affects the four corners of a stroked rectangle, and the connected segments in a /// stroked path. /// /// Choose miter join to draw sharp corners. Choose round join to draw a circle with a /// radius equal to the stroke width on top of the corner. Choose bevel join to minimally /// connect the thick strokes. /// /// The fill path constructed to describe the stroked path respects the join setting but may /// not contain the actual join. For instance, a fill path constructed with round joins does /// not necessarily include circles at each connected segment. #[derive(Copy, Clone, PartialEq, Debug)] pub enum LineJoin { /// Extends to miter limit, then switches to bevel. Miter, /// Extends to miter limit, then clips the corner. MiterClip, /// Adds circle. Round, /// Connects outside edges. Bevel, } impl Default for LineJoin { fn default() -> Self { LineJoin::Miter } } const QUAD_RECURSIVE_LIMIT: usize = 3; // quads with extreme widths (e.g. (0,1) (1,6) (0,3) width=5e7) recurse to point of failure // largest seen for normal cubics: 5, 26 // largest seen for normal quads: 11 const RECURSIVE_LIMITS: [i32; 4] = [5 * 3, 26 * 3, 11 * 3, 11 * 3]; // 3x limits seen in practice type CapProc = fn( pivot: Point, normal: Point, stop: Point, other_path: Option<&PathBuilder>, path: &mut PathBuilder, ); type JoinProc = fn( before_unit_normal: Point, pivot: Point, after_unit_normal: Point, radius: f32, inv_miter_limit: f32, prev_is_line: bool, curr_is_line: bool, builders: SwappableBuilders, ); #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] enum ReductionType { Point, // all curve points are practically identical Line, // the control point is on the line between the ends Quad, // the control point is outside the line between the ends Degenerate, // the control point is on the line but outside the ends Degenerate2, // two control points are on the line but outside ends (cubic) Degenerate3, // three areas of max curvature found (for cubic) } #[derive(Copy, Clone, PartialEq, Debug)] enum StrokeType { Outer = 1, // use sign-opposite values later to flip perpendicular axis Inner = -1, } #[derive(Copy, Clone, PartialEq, Debug)] enum ResultType { Split, // the caller should split the quad stroke in two Degenerate, // the caller should add a line Quad, // the caller should (continue to try to) add a quad stroke } #[derive(Copy, Clone, PartialEq, Debug)] enum IntersectRayType { CtrlPt, ResultType, } impl Path { /// Returns a stoked path. /// /// `resolution_scale` can be obtained via /// [`compute_resolution_scale`](PathStroker::compute_resolution_scale). /// /// If you plan stroking multiple paths, you can try using [`PathStroker`] /// which will preserve temporary allocations required during stroking. /// This might improve performance a bit. pub fn stroke(&self, stroke: &Stroke, resolution_scale: f32) -> Option { PathStroker::new().stroke(self, stroke, resolution_scale) } } /// A path stroker. #[allow(missing_debug_implementations)] #[derive(Clone)] pub struct PathStroker { radius: f32, inv_miter_limit: f32, res_scale: f32, inv_res_scale: f32, inv_res_scale_squared: f32, first_normal: Point, prev_normal: Point, first_unit_normal: Point, prev_unit_normal: Point, // on original path first_pt: Point, prev_pt: Point, first_outer_pt: Point, first_outer_pt_index_in_contour: usize, segment_count: i32, prev_is_line: bool, capper: CapProc, joiner: JoinProc, // outer is our working answer, inner is temp inner: PathBuilder, outer: PathBuilder, cusper: PathBuilder, stroke_type: StrokeType, recursion_depth: i32, // track stack depth to abort if numerics run amok found_tangents: bool, // do less work until tangents meet (cubic) join_completed: bool, // previous join was not degenerate } impl Default for PathStroker { fn default() -> Self { PathStroker::new() } } impl PathStroker { /// Creates a new PathStroker. pub fn new() -> Self { PathStroker { radius: 0.0, inv_miter_limit: 0.0, res_scale: 1.0, inv_res_scale: 1.0, inv_res_scale_squared: 1.0, first_normal: Point::zero(), prev_normal: Point::zero(), first_unit_normal: Point::zero(), prev_unit_normal: Point::zero(), first_pt: Point::zero(), prev_pt: Point::zero(), first_outer_pt: Point::zero(), first_outer_pt_index_in_contour: 0, segment_count: -1, prev_is_line: false, capper: butt_capper, joiner: miter_joiner, inner: PathBuilder::new(), outer: PathBuilder::new(), cusper: PathBuilder::new(), stroke_type: StrokeType::Outer, recursion_depth: 0, found_tangents: false, join_completed: false, } } /// Computes a resolution scale. /// /// Resolution scale is the "intended" resolution for the output. Default is 1.0. /// /// Larger values (res > 1) indicate that the result should be more precise, since it will /// be zoomed up, and small errors will be magnified. /// /// Smaller values (0 < res < 1) indicate that the result can be less precise, since it will /// be zoomed down, and small errors may be invisible. pub fn compute_resolution_scale(ts: &Transform) -> f32 { let sx = Point::from_xy(ts.sx, ts.kx).length(); let sy = Point::from_xy(ts.ky, ts.sy).length(); if sx.is_finite() && sy.is_finite() { let scale = sx.max(sy); if scale > 0.0 { return scale; } } 1.0 } /// Stokes the path. /// /// Can be called multiple times to reuse allocated buffers. /// /// `resolution_scale` can be obtained via /// [`compute_resolution_scale`](Self::compute_resolution_scale). pub fn stroke(&mut self, path: &Path, stroke: &Stroke, resolution_scale: f32) -> Option { let width = NonZeroPositiveF32::new(stroke.width)?; self.stroke_inner( path, width, stroke.miter_limit, stroke.line_cap, stroke.line_join, resolution_scale, ) } fn stroke_inner( &mut self, path: &Path, width: NonZeroPositiveF32, miter_limit: f32, line_cap: LineCap, mut line_join: LineJoin, res_scale: f32, ) -> Option { // TODO: stroke_rect optimization let mut inv_miter_limit = 0.0; if line_join == LineJoin::Miter { if miter_limit <= 1.0 { line_join = LineJoin::Bevel; } else { inv_miter_limit = miter_limit.invert(); } } if line_join == LineJoin::MiterClip { inv_miter_limit = miter_limit.invert(); } self.res_scale = res_scale; // The '4' below matches the fill scan converter's error term. self.inv_res_scale = (res_scale * 4.0).invert(); self.inv_res_scale_squared = self.inv_res_scale.sqr(); self.radius = width.get().half(); self.inv_miter_limit = inv_miter_limit; self.first_normal = Point::zero(); self.prev_normal = Point::zero(); self.first_unit_normal = Point::zero(); self.prev_unit_normal = Point::zero(); self.first_pt = Point::zero(); self.prev_pt = Point::zero(); self.first_outer_pt = Point::zero(); self.first_outer_pt_index_in_contour = 0; self.segment_count = -1; self.prev_is_line = false; self.capper = cap_factory(line_cap); self.joiner = join_factory(line_join); // Need some estimate of how large our final result (fOuter) // and our per-contour temp (fInner) will be, so we don't spend // extra time repeatedly growing these arrays. // // 1x for inner == 'wag' (worst contour length would be better guess) self.inner.clear(); self.inner.reserve(path.verbs.len(), path.points.len()); // 3x for result == inner + outer + join (swag) self.outer.clear(); self.outer .reserve(path.verbs.len() * 3, path.points.len() * 3); self.cusper.clear(); self.stroke_type = StrokeType::Outer; self.recursion_depth = 0; self.found_tangents = false; self.join_completed = false; let mut last_segment_is_line = false; let mut iter = path.segments(); iter.set_auto_close(true); while let Some(segment) = iter.next() { match segment { PathSegment::MoveTo(p) => { self.move_to(p); } PathSegment::LineTo(p) => { self.line_to(p, Some(&iter)); last_segment_is_line = true; } PathSegment::QuadTo(p1, p2) => { self.quad_to(p1, p2); last_segment_is_line = false; } PathSegment::CubicTo(p1, p2, p3) => { self.cubic_to(p1, p2, p3); last_segment_is_line = false; } PathSegment::Close => { if line_cap != LineCap::Butt { // If the stroke consists of a moveTo followed by a close, treat it // as if it were followed by a zero-length line. Lines without length // can have square and round end caps. if self.has_only_move_to() { self.line_to(self.move_to_pt(), None); last_segment_is_line = true; continue; } // If the stroke consists of a moveTo followed by one or more zero-length // verbs, then followed by a close, treat is as if it were followed by a // zero-length line. Lines without length can have square & round end caps. if self.is_current_contour_empty() { last_segment_is_line = true; continue; } } self.close(last_segment_is_line); } } } self.finish(last_segment_is_line) } fn builders(&mut self) -> SwappableBuilders { SwappableBuilders { inner: &mut self.inner, outer: &mut self.outer, } } fn move_to_pt(&self) -> Point { self.first_pt } fn move_to(&mut self, p: Point) { if self.segment_count > 0 { self.finish_contour(false, false); } self.segment_count = 0; self.first_pt = p; self.prev_pt = p; self.join_completed = false; } fn line_to(&mut self, p: Point, iter: Option<&PathSegmentsIter>) { let teeny_line = self .prev_pt .equals_within_tolerance(p, SCALAR_NEARLY_ZERO * self.inv_res_scale); if fn_ptr_eq(self.capper, butt_capper) && teeny_line { return; } if teeny_line && (self.join_completed || iter.map(|i| i.has_valid_tangent()) == Some(true)) { return; } let mut normal = Point::zero(); let mut unit_normal = Point::zero(); if !self.pre_join_to(p, true, &mut normal, &mut unit_normal) { return; } self.outer.line_to(p.x + normal.x, p.y + normal.y); self.inner.line_to(p.x - normal.x, p.y - normal.y); self.post_join_to(p, normal, unit_normal); } fn quad_to(&mut self, p1: Point, p2: Point) { let quad = [self.prev_pt, p1, p2]; let (reduction, reduction_type) = check_quad_linear(&quad); if reduction_type == ReductionType::Point { // If the stroke consists of a moveTo followed by a degenerate curve, treat it // as if it were followed by a zero-length line. Lines without length // can have square and round end caps. self.line_to(p2, None); return; } if reduction_type == ReductionType::Line { self.line_to(p2, None); return; } if reduction_type == ReductionType::Degenerate { self.line_to(reduction, None); let save_joiner = self.joiner; self.joiner = round_joiner; self.line_to(p2, None); self.joiner = save_joiner; return; } debug_assert_eq!(reduction_type, ReductionType::Quad); let mut normal_ab = Point::zero(); let mut unit_ab = Point::zero(); let mut normal_bc = Point::zero(); let mut unit_bc = Point::zero(); if !self.pre_join_to(p1, false, &mut normal_ab, &mut unit_ab) { self.line_to(p2, None); return; } let mut quad_points = QuadConstruct::default(); self.init_quad( StrokeType::Outer, NormalizedF32::ZERO, NormalizedF32::ONE, &mut quad_points, ); self.quad_stroke(&quad, &mut quad_points); self.init_quad( StrokeType::Inner, NormalizedF32::ZERO, NormalizedF32::ONE, &mut quad_points, ); self.quad_stroke(&quad, &mut quad_points); let ok = set_normal_unit_normal( quad[1], quad[2], self.res_scale, self.radius, &mut normal_bc, &mut unit_bc, ); if !ok { normal_bc = normal_ab; unit_bc = unit_ab; } self.post_join_to(p2, normal_bc, unit_bc); } fn cubic_to(&mut self, pt1: Point, pt2: Point, pt3: Point) { let cubic = [self.prev_pt, pt1, pt2, pt3]; let mut reduction = [Point::zero(); 3]; let mut tangent_pt = Point::zero(); let reduction_type = check_cubic_linear(&cubic, &mut reduction, Some(&mut tangent_pt)); if reduction_type == ReductionType::Point { // If the stroke consists of a moveTo followed by a degenerate curve, treat it // as if it were followed by a zero-length line. Lines without length // can have square and round end caps. self.line_to(pt3, None); return; } if reduction_type == ReductionType::Line { self.line_to(pt3, None); return; } if ReductionType::Degenerate <= reduction_type && ReductionType::Degenerate3 >= reduction_type { self.line_to(reduction[0], None); let save_joiner = self.joiner; self.joiner = round_joiner; if ReductionType::Degenerate2 <= reduction_type { self.line_to(reduction[1], None); } if ReductionType::Degenerate3 == reduction_type { self.line_to(reduction[2], None); } self.line_to(pt3, None); self.joiner = save_joiner; return; } debug_assert_eq!(reduction_type, ReductionType::Quad); let mut normal_ab = Point::zero(); let mut unit_ab = Point::zero(); let mut normal_cd = Point::zero(); let mut unit_cd = Point::zero(); if !self.pre_join_to(tangent_pt, false, &mut normal_ab, &mut unit_ab) { self.line_to(pt3, None); return; } let mut t_values = path_geometry::new_t_values(); let t_values = path_geometry::find_cubic_inflections(&cubic, &mut t_values); let mut last_t = NormalizedF32::ZERO; for index in 0..=t_values.len() { let next_t = t_values .get(index) .cloned() .map(|n| n.to_normalized()) .unwrap_or(NormalizedF32::ONE); let mut quad_points = QuadConstruct::default(); self.init_quad(StrokeType::Outer, last_t, next_t, &mut quad_points); self.cubic_stroke(&cubic, &mut quad_points); self.init_quad(StrokeType::Inner, last_t, next_t, &mut quad_points); self.cubic_stroke(&cubic, &mut quad_points); last_t = next_t; } if let Some(cusp) = path_geometry::find_cubic_cusp(&cubic) { let cusp_loc = path_geometry::eval_cubic_pos_at(&cubic, cusp.to_normalized()); self.cusper.push_circle(cusp_loc.x, cusp_loc.y, self.radius); } // emit the join even if one stroke succeeded but the last one failed // this avoids reversing an inner stroke with a partial path followed by another moveto self.set_cubic_end_normal(&cubic, normal_ab, unit_ab, &mut normal_cd, &mut unit_cd); self.post_join_to(pt3, normal_cd, unit_cd); } fn cubic_stroke(&mut self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> bool { if !self.found_tangents { let result_type = self.tangents_meet(cubic, quad_points); if result_type != ResultType::Quad { let ok = points_within_dist( quad_points.quad[0], quad_points.quad[2], self.inv_res_scale, ); if (result_type == ResultType::Degenerate || ok) && self.cubic_mid_on_line(cubic, quad_points) { self.add_degenerate_line(quad_points); return true; } } else { self.found_tangents = true; } } if self.found_tangents { let result_type = self.compare_quad_cubic(cubic, quad_points); if result_type == ResultType::Quad { let stroke = &quad_points.quad; if self.stroke_type == StrokeType::Outer { self.outer .quad_to(stroke[1].x, stroke[1].y, stroke[2].x, stroke[2].y); } else { self.inner .quad_to(stroke[1].x, stroke[1].y, stroke[2].x, stroke[2].y); } return true; } if result_type == ResultType::Degenerate { if !quad_points.opposite_tangents { self.add_degenerate_line(quad_points); return true; } } } if !quad_points.quad[2].x.is_finite() || !quad_points.quad[2].x.is_finite() { return false; // just abort if projected quad isn't representable } self.recursion_depth += 1; if self.recursion_depth > RECURSIVE_LIMITS[self.found_tangents as usize] { return false; // just abort if projected quad isn't representable } let mut half = QuadConstruct::default(); if !half.init_with_start(quad_points) { self.add_degenerate_line(quad_points); self.recursion_depth -= 1; return true; } if !self.cubic_stroke(cubic, &mut half) { return false; } if !half.init_with_end(quad_points) { self.add_degenerate_line(quad_points); self.recursion_depth -= 1; return true; } if !self.cubic_stroke(cubic, &mut half) { return false; } self.recursion_depth -= 1; true } fn cubic_mid_on_line(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> bool { let mut stroke_mid = Point::zero(); self.cubic_quad_mid(cubic, quad_points, &mut stroke_mid); let dist = pt_to_line(stroke_mid, quad_points.quad[0], quad_points.quad[2]); dist < self.inv_res_scale_squared } fn cubic_quad_mid(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct, mid: &mut Point) { let mut cubic_mid_pt = Point::zero(); self.cubic_perp_ray(cubic, quad_points.mid_t, &mut cubic_mid_pt, mid, None); } // Given a cubic and t, return the point on curve, // its perpendicular, and the perpendicular tangent. fn cubic_perp_ray( &self, cubic: &[Point; 4], t: NormalizedF32, t_pt: &mut Point, on_pt: &mut Point, tangent: Option<&mut Point>, ) { *t_pt = path_geometry::eval_cubic_pos_at(cubic, t); let mut dxy = path_geometry::eval_cubic_tangent_at(cubic, t); let mut chopped = [Point::zero(); 7]; if dxy.x == 0.0 && dxy.y == 0.0 { let mut c_points: &[Point] = cubic; if t.get().is_nearly_zero() { dxy = cubic[2] - cubic[0]; } else if (1.0 - t.get()).is_nearly_zero() { dxy = cubic[3] - cubic[1]; } else { // If the cubic inflection falls on the cusp, subdivide the cubic // to find the tangent at that point. // // Unwrap never fails, because we already checked that `t` is not 0/1, let t = NormalizedF32Exclusive::new(t.get()).unwrap(); path_geometry::chop_cubic_at2(cubic, t, &mut chopped); dxy = chopped[3] - chopped[2]; if dxy.x == 0.0 && dxy.y == 0.0 { dxy = chopped[3] - chopped[1]; c_points = &chopped; } } if dxy.x == 0.0 && dxy.y == 0.0 { dxy = c_points[3] - c_points[0]; } } self.set_ray_points(*t_pt, &mut dxy, on_pt, tangent); } fn set_cubic_end_normal( &mut self, cubic: &[Point; 4], normal_ab: Point, unit_normal_ab: Point, normal_cd: &mut Point, unit_normal_cd: &mut Point, ) { let mut ab = cubic[1] - cubic[0]; let mut cd = cubic[3] - cubic[2]; let mut degenerate_ab = degenerate_vector(ab); let mut degenerate_cb = degenerate_vector(cd); if degenerate_ab && degenerate_cb { *normal_cd = normal_ab; *unit_normal_cd = unit_normal_ab; return; } if degenerate_ab { ab = cubic[2] - cubic[0]; degenerate_ab = degenerate_vector(ab); } if degenerate_cb { cd = cubic[3] - cubic[1]; degenerate_cb = degenerate_vector(cd); } if degenerate_ab || degenerate_cb { *normal_cd = normal_ab; *unit_normal_cd = unit_normal_ab; return; } let res = set_normal_unit_normal2(cd, self.radius, normal_cd, unit_normal_cd); debug_assert!(res); } fn compare_quad_cubic( &self, cubic: &[Point; 4], quad_points: &mut QuadConstruct, ) -> ResultType { // get the quadratic approximation of the stroke self.cubic_quad_ends(cubic, quad_points); let result_type = self.intersect_ray(IntersectRayType::CtrlPt, quad_points); if result_type != ResultType::Quad { return result_type; } // project a ray from the curve to the stroke // points near midpoint on quad, midpoint on cubic let mut ray0 = Point::zero(); let mut ray1 = Point::zero(); self.cubic_perp_ray(cubic, quad_points.mid_t, &mut ray1, &mut ray0, None); self.stroke_close_enough(&quad_points.quad.clone(), &[ray0, ray1], quad_points) } // Given a cubic and a t range, find the start and end if they haven't been found already. fn cubic_quad_ends(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) { if !quad_points.start_set { let mut cubic_start_pt = Point::zero(); self.cubic_perp_ray( cubic, quad_points.start_t, &mut cubic_start_pt, &mut quad_points.quad[0], Some(&mut quad_points.tangent_start), ); quad_points.start_set = true; } if !quad_points.end_set { let mut cubic_end_pt = Point::zero(); self.cubic_perp_ray( cubic, quad_points.end_t, &mut cubic_end_pt, &mut quad_points.quad[2], Some(&mut quad_points.tangent_end), ); quad_points.end_set = true; } } fn close(&mut self, is_line: bool) { self.finish_contour(true, is_line); } fn finish_contour(&mut self, close: bool, curr_is_line: bool) { if self.segment_count > 0 { if close { (self.joiner)( self.prev_unit_normal, self.prev_pt, self.first_unit_normal, self.radius, self.inv_miter_limit, self.prev_is_line, curr_is_line, self.builders(), ); self.outer.close(); // now add inner as its own contour let pt = self.inner.last_point().unwrap_or_default(); self.outer.move_to(pt.x, pt.y); self.outer.reverse_path_to(&self.inner); self.outer.close(); } else { // add caps to start and end // cap the end let pt = self.inner.last_point().unwrap_or_default(); let other_path = if curr_is_line { Some(&self.inner) } else { None }; (self.capper)( self.prev_pt, self.prev_normal, pt, other_path, &mut self.outer, ); self.outer.reverse_path_to(&self.inner); // cap the start let other_path = if self.prev_is_line { Some(&self.inner) } else { None }; (self.capper)( self.first_pt, -self.first_normal, self.first_outer_pt, other_path, &mut self.outer, ); self.outer.close(); } if !self.cusper.is_empty() { self.outer.push_path_builder(&self.cusper); self.cusper.clear(); } } // since we may re-use `inner`, we rewind instead of reset, to save on // reallocating its internal storage. self.inner.clear(); self.segment_count = -1; self.first_outer_pt_index_in_contour = self.outer.points.len(); } fn pre_join_to( &mut self, p: Point, curr_is_line: bool, normal: &mut Point, unit_normal: &mut Point, ) -> bool { debug_assert!(self.segment_count >= 0); let prev_x = self.prev_pt.x; let prev_y = self.prev_pt.y; let normal_set = set_normal_unit_normal( self.prev_pt, p, self.res_scale, self.radius, normal, unit_normal, ); if !normal_set { if fn_ptr_eq(self.capper, butt_capper) { return false; } // Square caps and round caps draw even if the segment length is zero. // Since the zero length segment has no direction, set the orientation // to upright as the default orientation. *normal = Point::from_xy(self.radius, 0.0); *unit_normal = Point::from_xy(1.0, 0.0); } if self.segment_count == 0 { self.first_normal = *normal; self.first_unit_normal = *unit_normal; self.first_outer_pt = Point::from_xy(prev_x + normal.x, prev_y + normal.y); self.outer .move_to(self.first_outer_pt.x, self.first_outer_pt.y); self.inner.move_to(prev_x - normal.x, prev_y - normal.y); } else { // we have a previous segment (self.joiner)( self.prev_unit_normal, self.prev_pt, *unit_normal, self.radius, self.inv_miter_limit, self.prev_is_line, curr_is_line, self.builders(), ); } self.prev_is_line = curr_is_line; true } fn post_join_to(&mut self, p: Point, normal: Point, unit_normal: Point) { self.join_completed = true; self.prev_pt = p; self.prev_unit_normal = unit_normal; self.prev_normal = normal; self.segment_count += 1; } fn init_quad( &mut self, stroke_type: StrokeType, start: NormalizedF32, end: NormalizedF32, quad_points: &mut QuadConstruct, ) { self.stroke_type = stroke_type; self.found_tangents = false; quad_points.init(start, end); } fn quad_stroke(&mut self, quad: &[Point; 3], quad_points: &mut QuadConstruct) -> bool { let result_type = self.compare_quad_quad(quad, quad_points); if result_type == ResultType::Quad { let path = if self.stroke_type == StrokeType::Outer { &mut self.outer } else { &mut self.inner }; path.quad_to( quad_points.quad[1].x, quad_points.quad[1].y, quad_points.quad[2].x, quad_points.quad[2].y, ); return true; } if result_type == ResultType::Degenerate { self.add_degenerate_line(quad_points); return true; } self.recursion_depth += 1; if self.recursion_depth > RECURSIVE_LIMITS[QUAD_RECURSIVE_LIMIT] { return false; // just abort if projected quad isn't representable } let mut half = QuadConstruct::default(); half.init_with_start(quad_points); if !self.quad_stroke(quad, &mut half) { return false; } half.init_with_end(quad_points); if !self.quad_stroke(quad, &mut half) { return false; } self.recursion_depth -= 1; true } fn compare_quad_quad( &mut self, quad: &[Point; 3], quad_points: &mut QuadConstruct, ) -> ResultType { // get the quadratic approximation of the stroke if !quad_points.start_set { let mut quad_start_pt = Point::zero(); self.quad_perp_ray( quad, quad_points.start_t, &mut quad_start_pt, &mut quad_points.quad[0], Some(&mut quad_points.tangent_start), ); quad_points.start_set = true; } if !quad_points.end_set { let mut quad_end_pt = Point::zero(); self.quad_perp_ray( quad, quad_points.end_t, &mut quad_end_pt, &mut quad_points.quad[2], Some(&mut quad_points.tangent_end), ); quad_points.end_set = true; } let result_type = self.intersect_ray(IntersectRayType::CtrlPt, quad_points); if result_type != ResultType::Quad { return result_type; } // project a ray from the curve to the stroke let mut ray0 = Point::zero(); let mut ray1 = Point::zero(); self.quad_perp_ray(quad, quad_points.mid_t, &mut ray1, &mut ray0, None); self.stroke_close_enough(&quad_points.quad.clone(), &[ray0, ray1], quad_points) } // Given a point on the curve and its derivative, scale the derivative by the radius, and // compute the perpendicular point and its tangent. fn set_ray_points( &self, tp: Point, dxy: &mut Point, on_p: &mut Point, mut tangent: Option<&mut Point>, ) { if !dxy.set_length(self.radius) { *dxy = Point::from_xy(self.radius, 0.0); } let axis_flip = self.stroke_type as i32 as f32; // go opposite ways for outer, inner on_p.x = tp.x + axis_flip * dxy.y; on_p.y = tp.y - axis_flip * dxy.x; if let Some(ref mut tangent) = tangent { tangent.x = on_p.x + dxy.x; tangent.y = on_p.y + dxy.y; } } // Given a quad and t, return the point on curve, // its perpendicular, and the perpendicular tangent. fn quad_perp_ray( &self, quad: &[Point; 3], t: NormalizedF32, tp: &mut Point, on_p: &mut Point, tangent: Option<&mut Point>, ) { *tp = path_geometry::eval_quad_at(quad, t); let mut dxy = path_geometry::eval_quad_tangent_at(quad, t); if dxy.is_zero() { dxy = quad[2] - quad[0]; } self.set_ray_points(*tp, &mut dxy, on_p, tangent); } fn add_degenerate_line(&mut self, quad_points: &QuadConstruct) { if self.stroke_type == StrokeType::Outer { self.outer .line_to(quad_points.quad[2].x, quad_points.quad[2].y); } else { self.inner .line_to(quad_points.quad[2].x, quad_points.quad[2].y); } } fn stroke_close_enough( &self, stroke: &[Point; 3], ray: &[Point; 2], quad_points: &mut QuadConstruct, ) -> ResultType { let half = NormalizedF32::new_clamped(0.5); let stroke_mid = path_geometry::eval_quad_at(stroke, half); // measure the distance from the curve to the quad-stroke midpoint, compare to radius if points_within_dist(ray[0], stroke_mid, self.inv_res_scale) { // if the difference is small if sharp_angle(&quad_points.quad) { return ResultType::Split; } return ResultType::Quad; } // measure the distance to quad's bounds (quick reject) // an alternative : look for point in triangle if !pt_in_quad_bounds(stroke, ray[0], self.inv_res_scale) { // if far, subdivide return ResultType::Split; } // measure the curve ray distance to the quad-stroke let mut roots = path_geometry::new_t_values(); let roots = intersect_quad_ray(ray, stroke, &mut roots); if roots.len() != 1 { return ResultType::Split; } let quad_pt = path_geometry::eval_quad_at(stroke, roots[0].to_normalized()); let error = self.inv_res_scale * (1.0 - (roots[0].get() - 0.5).abs() * 2.0); if points_within_dist(ray[0], quad_pt, error) { // if the difference is small, we're done if sharp_angle(&quad_points.quad) { return ResultType::Split; } return ResultType::Quad; } // otherwise, subdivide ResultType::Split } // Find the intersection of the stroke tangents to construct a stroke quad. // Return whether the stroke is a degenerate (a line), a quad, or must be split. // Optionally compute the quad's control point. fn intersect_ray( &self, intersect_ray_type: IntersectRayType, quad_points: &mut QuadConstruct, ) -> ResultType { let start = quad_points.quad[0]; let end = quad_points.quad[2]; let a_len = quad_points.tangent_start - start; let b_len = quad_points.tangent_end - end; // Slopes match when denom goes to zero: // axLen / ayLen == bxLen / byLen // (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen // byLen * axLen == ayLen * bxLen // byLen * axLen - ayLen * bxLen ( == denom ) let denom = a_len.cross(b_len); if denom == 0.0 || !denom.is_finite() { quad_points.opposite_tangents = a_len.dot(b_len) < 0.0; return ResultType::Degenerate; } quad_points.opposite_tangents = false; let ab0 = start - end; let mut numer_a = b_len.cross(ab0); let numer_b = a_len.cross(ab0); if (numer_a >= 0.0) == (numer_b >= 0.0) { // if the control point is outside the quad ends // if the perpendicular distances from the quad points to the opposite tangent line // are small, a straight line is good enough let dist1 = pt_to_line(start, end, quad_points.tangent_end); let dist2 = pt_to_line(end, start, quad_points.tangent_start); if dist1.max(dist2) <= self.inv_res_scale_squared { return ResultType::Degenerate; } return ResultType::Split; } // check to see if the denominator is teeny relative to the numerator // if the offset by one will be lost, the ratio is too large numer_a /= denom; let valid_divide = numer_a > numer_a - 1.0; if valid_divide { if intersect_ray_type == IntersectRayType::CtrlPt { // the intersection of the tangents need not be on the tangent segment // so 0 <= numerA <= 1 is not necessarily true quad_points.quad[1].x = start.x * (1.0 - numer_a) + quad_points.tangent_start.x * numer_a; quad_points.quad[1].y = start.y * (1.0 - numer_a) + quad_points.tangent_start.y * numer_a; } return ResultType::Quad; } quad_points.opposite_tangents = a_len.dot(b_len) < 0.0; // if the lines are parallel, straight line is good enough ResultType::Degenerate } // Given a cubic and a t-range, determine if the stroke can be described by a quadratic. fn tangents_meet(&self, cubic: &[Point; 4], quad_points: &mut QuadConstruct) -> ResultType { self.cubic_quad_ends(cubic, quad_points); self.intersect_ray(IntersectRayType::ResultType, quad_points) } fn finish(&mut self, is_line: bool) -> Option { self.finish_contour(false, is_line); // Swap out the outer builder. let mut buf = PathBuilder::new(); core::mem::swap(&mut self.outer, &mut buf); buf.finish() } fn has_only_move_to(&self) -> bool { self.segment_count == 0 } fn is_current_contour_empty(&self) -> bool { self.inner.is_zero_length_since_point(0) && self .outer .is_zero_length_since_point(self.first_outer_pt_index_in_contour) } } fn cap_factory(cap: LineCap) -> CapProc { match cap { LineCap::Butt => butt_capper, LineCap::Round => round_capper, LineCap::Square => square_capper, } } fn butt_capper(_: Point, _: Point, stop: Point, _: Option<&PathBuilder>, path: &mut PathBuilder) { path.line_to(stop.x, stop.y); } fn round_capper( pivot: Point, normal: Point, stop: Point, _: Option<&PathBuilder>, path: &mut PathBuilder, ) { let mut parallel = normal; parallel.rotate_cw(); let projected_center = pivot + parallel; path.conic_points_to( projected_center + normal, projected_center, SCALAR_ROOT_2_OVER_2, ); path.conic_points_to(projected_center - normal, stop, SCALAR_ROOT_2_OVER_2); } fn square_capper( pivot: Point, normal: Point, stop: Point, other_path: Option<&PathBuilder>, path: &mut PathBuilder, ) { let mut parallel = normal; parallel.rotate_cw(); if other_path.is_some() { path.set_last_point(Point::from_xy( pivot.x + normal.x + parallel.x, pivot.y + normal.y + parallel.y, )); path.line_to( pivot.x - normal.x + parallel.x, pivot.y - normal.y + parallel.y, ); } else { path.line_to( pivot.x + normal.x + parallel.x, pivot.y + normal.y + parallel.y, ); path.line_to( pivot.x - normal.x + parallel.x, pivot.y - normal.y + parallel.y, ); path.line_to(stop.x, stop.y); } } fn join_factory(join: LineJoin) -> JoinProc { match join { LineJoin::Miter => miter_joiner, LineJoin::MiterClip => miter_clip_joiner, LineJoin::Round => round_joiner, LineJoin::Bevel => bevel_joiner, } } fn is_clockwise(before: Point, after: Point) -> bool { before.x * after.y > before.y * after.x } #[derive(Copy, Clone, PartialEq, Debug)] enum AngleType { Nearly180, Sharp, Shallow, NearlyLine, } fn dot_to_angle_type(dot: f32) -> AngleType { if dot >= 0.0 { // shallow or line if (1.0 - dot).is_nearly_zero() { AngleType::NearlyLine } else { AngleType::Shallow } } else { // sharp or 180 if (1.0 + dot).is_nearly_zero() { AngleType::Nearly180 } else { AngleType::Sharp } } } fn handle_inner_join(pivot: Point, after: Point, inner: &mut PathBuilder) { // In the degenerate case that the stroke radius is larger than our segments // just connecting the two inner segments may "show through" as a funny // diagonal. To pseudo-fix this, we go through the pivot point. This adds // an extra point/edge, but I can't see a cheap way to know when this is // not needed :( inner.line_to(pivot.x, pivot.y); inner.line_to(pivot.x - after.x, pivot.y - after.y); } fn bevel_joiner( before_unit_normal: Point, pivot: Point, after_unit_normal: Point, radius: f32, _: f32, _: bool, _: bool, mut builders: SwappableBuilders, ) { let mut after = after_unit_normal.scaled(radius); if !is_clockwise(before_unit_normal, after_unit_normal) { builders.swap(); after = -after; } builders.outer.line_to(pivot.x + after.x, pivot.y + after.y); handle_inner_join(pivot, after, builders.inner); } fn round_joiner( before_unit_normal: Point, pivot: Point, after_unit_normal: Point, radius: f32, _: f32, _: bool, _: bool, mut builders: SwappableBuilders, ) { let dot_prod = before_unit_normal.dot(after_unit_normal); let angle_type = dot_to_angle_type(dot_prod); if angle_type == AngleType::NearlyLine { return; } let mut before = before_unit_normal; let mut after = after_unit_normal; let mut dir = PathDirection::CW; if !is_clockwise(before, after) { builders.swap(); before = -before; after = -after; dir = PathDirection::CCW; } let ts = Transform::from_row(radius, 0.0, 0.0, radius, pivot.x, pivot.y); let mut conics = [path_geometry::Conic::default(); 5]; let conics = path_geometry::Conic::build_unit_arc(before, after, dir, ts, &mut conics); if let Some(conics) = conics { for conic in conics { builders .outer .conic_points_to(conic.points[1], conic.points[2], conic.weight); } after.scale(radius); handle_inner_join(pivot, after, builders.inner); } } #[inline] fn miter_joiner( before_unit_normal: Point, pivot: Point, after_unit_normal: Point, radius: f32, inv_miter_limit: f32, prev_is_line: bool, curr_is_line: bool, builders: SwappableBuilders, ) { miter_joiner_inner( before_unit_normal, pivot, after_unit_normal, radius, inv_miter_limit, false, prev_is_line, curr_is_line, builders, ); } #[inline] fn miter_clip_joiner( before_unit_normal: Point, pivot: Point, after_unit_normal: Point, radius: f32, inv_miter_limit: f32, prev_is_line: bool, curr_is_line: bool, builders: SwappableBuilders, ) { miter_joiner_inner( before_unit_normal, pivot, after_unit_normal, radius, inv_miter_limit, true, prev_is_line, curr_is_line, builders, ); } fn miter_joiner_inner( before_unit_normal: Point, pivot: Point, after_unit_normal: Point, radius: f32, inv_miter_limit: f32, miter_clip: bool, prev_is_line: bool, mut curr_is_line: bool, mut builders: SwappableBuilders, ) { fn do_blunt_or_clipped( builders: SwappableBuilders, pivot: Point, radius: f32, prev_is_line: bool, curr_is_line: bool, mut before: Point, mut mid: Point, mut after: Point, inv_miter_limit: f32, miter_clip: bool, ) { after.scale(radius); if miter_clip { mid.normalize(); let cos_beta = before.dot(mid); let sin_beta = before.cross(mid); let x = if sin_beta.abs() <= SCALAR_NEARLY_ZERO { 1.0 / inv_miter_limit } else { ((1.0 / inv_miter_limit) - cos_beta) / sin_beta }; before.scale(radius); let mut before_tangent = before; before_tangent.rotate_cw(); let mut after_tangent = after; after_tangent.rotate_ccw(); let c1 = pivot + before + before_tangent.scaled(x); let c2 = pivot + after + after_tangent.scaled(x); if prev_is_line { builders.outer.set_last_point(c1); } else { builders.outer.line_to(c1.x, c1.y); } builders.outer.line_to(c2.x, c2.y); } if !curr_is_line { builders.outer.line_to(pivot.x + after.x, pivot.y + after.y); } handle_inner_join(pivot, after, builders.inner); } fn do_miter( builders: SwappableBuilders, pivot: Point, radius: f32, prev_is_line: bool, curr_is_line: bool, mid: Point, mut after: Point, ) { after.scale(radius); if prev_is_line { builders .outer .set_last_point(Point::from_xy(pivot.x + mid.x, pivot.y + mid.y)); } else { builders.outer.line_to(pivot.x + mid.x, pivot.y + mid.y); } if !curr_is_line { builders.outer.line_to(pivot.x + after.x, pivot.y + after.y); } handle_inner_join(pivot, after, builders.inner); } // negate the dot since we're using normals instead of tangents let dot_prod = before_unit_normal.dot(after_unit_normal); let angle_type = dot_to_angle_type(dot_prod); let mut before = before_unit_normal; let mut after = after_unit_normal; let mut mid; if angle_type == AngleType::NearlyLine { return; } if angle_type == AngleType::Nearly180 { curr_is_line = false; mid = (after - before).scaled(radius / 2.0); do_blunt_or_clipped( builders, pivot, radius, prev_is_line, curr_is_line, before, mid, after, inv_miter_limit, miter_clip, ); return; } let ccw = !is_clockwise(before, after); if ccw { builders.swap(); before = -before; after = -after; } // Before we enter the world of square-roots and divides, // check if we're trying to join an upright right angle // (common case for stroking rectangles). If so, special case // that (for speed an accuracy). // Note: we only need to check one normal if dot==0 if dot_prod == 0.0 && inv_miter_limit <= SCALAR_ROOT_2_OVER_2 { mid = (before + after).scaled(radius); do_miter( builders, pivot, radius, prev_is_line, curr_is_line, mid, after, ); return; } // choose the most accurate way to form the initial mid-vector if angle_type == AngleType::Sharp { mid = Point::from_xy(after.y - before.y, before.x - after.x); if ccw { mid = -mid; } } else { mid = Point::from_xy(before.x + after.x, before.y + after.y); } // midLength = radius / sinHalfAngle // if (midLength > miterLimit * radius) abort // if (radius / sinHalf > miterLimit * radius) abort // if (1 / sinHalf > miterLimit) abort // if (1 / miterLimit > sinHalf) abort // My dotProd is opposite sign, since it is built from normals and not tangents // hence 1 + dot instead of 1 - dot in the formula let sin_half_angle = (1.0 + dot_prod).half().sqrt(); if sin_half_angle < inv_miter_limit { curr_is_line = false; do_blunt_or_clipped( builders, pivot, radius, prev_is_line, curr_is_line, before, mid, after, inv_miter_limit, miter_clip, ); return; } mid.set_length(radius / sin_half_angle); do_miter( builders, pivot, radius, prev_is_line, curr_is_line, mid, after, ); } fn set_normal_unit_normal( before: Point, after: Point, scale: f32, radius: f32, normal: &mut Point, unit_normal: &mut Point, ) -> bool { if !unit_normal.set_normalize((after.x - before.x) * scale, (after.y - before.y) * scale) { return false; } unit_normal.rotate_ccw(); *normal = unit_normal.scaled(radius); true } fn set_normal_unit_normal2( vec: Point, radius: f32, normal: &mut Point, unit_normal: &mut Point, ) -> bool { if !unit_normal.set_normalize(vec.x, vec.y) { return false; } unit_normal.rotate_ccw(); *normal = unit_normal.scaled(radius); true } fn fn_ptr_eq(f1: CapProc, f2: CapProc) -> bool { core::ptr::eq(f1 as *const (), f2 as *const ()) } #[derive(Debug)] struct QuadConstruct { // The state of the quad stroke under construction. quad: [Point; 3], // the stroked quad parallel to the original curve tangent_start: Point, // a point tangent to quad[0] tangent_end: Point, // a point tangent to quad[2] start_t: NormalizedF32, // a segment of the original curve mid_t: NormalizedF32, end_t: NormalizedF32, start_set: bool, // state to share common points across structs end_set: bool, opposite_tangents: bool, // set if coincident tangents have opposite directions } impl Default for QuadConstruct { fn default() -> Self { Self { quad: Default::default(), tangent_start: Point::default(), tangent_end: Point::default(), start_t: NormalizedF32::ZERO, mid_t: NormalizedF32::ZERO, end_t: NormalizedF32::ZERO, start_set: false, end_set: false, opposite_tangents: false, } } } impl QuadConstruct { // return false if start and end are too close to have a unique middle fn init(&mut self, start: NormalizedF32, end: NormalizedF32) -> bool { self.start_t = start; self.mid_t = NormalizedF32::new_clamped((start.get() + end.get()).half()); self.end_t = end; self.start_set = false; self.end_set = false; self.start_t < self.mid_t && self.mid_t < self.end_t } fn init_with_start(&mut self, parent: &Self) -> bool { if !self.init(parent.start_t, parent.mid_t) { return false; } self.quad[0] = parent.quad[0]; self.tangent_start = parent.tangent_start; self.start_set = true; true } fn init_with_end(&mut self, parent: &Self) -> bool { if !self.init(parent.mid_t, parent.end_t) { return false; } self.quad[2] = parent.quad[2]; self.tangent_end = parent.tangent_end; self.end_set = true; true } } fn check_quad_linear(quad: &[Point; 3]) -> (Point, ReductionType) { let degenerate_ab = degenerate_vector(quad[1] - quad[0]); let degenerate_bc = degenerate_vector(quad[2] - quad[1]); if degenerate_ab & degenerate_bc { return (Point::zero(), ReductionType::Point); } if degenerate_ab | degenerate_bc { return (Point::zero(), ReductionType::Line); } if !quad_in_line(quad) { return (Point::zero(), ReductionType::Quad); } let t = path_geometry::find_quad_max_curvature(quad); if t == NormalizedF32::ZERO || t == NormalizedF32::ONE { return (Point::zero(), ReductionType::Line); } ( path_geometry::eval_quad_at(quad, t), ReductionType::Degenerate, ) } fn degenerate_vector(v: Point) -> bool { !v.can_normalize() } /// Given quad, see if all there points are in a line. /// Return true if the inside point is close to a line connecting the outermost points. /// /// Find the outermost point by looking for the largest difference in X or Y. /// Since the XOR of the indices is 3 (0 ^ 1 ^ 2) /// the missing index equals: outer_1 ^ outer_2 ^ 3. fn quad_in_line(quad: &[Point; 3]) -> bool { let mut pt_max = -1.0; let mut outer1 = 0; let mut outer2 = 0; for index in 0..2 { for inner in index + 1..3 { let test_diff = quad[inner] - quad[index]; let test_max = test_diff.x.abs().max(test_diff.y.abs()); if pt_max < test_max { outer1 = index; outer2 = inner; pt_max = test_max; } } } debug_assert!(outer1 <= 1); debug_assert!(outer2 >= 1 && outer2 <= 2); debug_assert!(outer1 < outer2); let mid = outer1 ^ outer2 ^ 3; const CURVATURE_SLOP: f32 = 0.000005; // this multiplier is pulled out of the air let line_slop = pt_max * pt_max * CURVATURE_SLOP; pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= line_slop } // returns the distance squared from the point to the line fn pt_to_line(pt: Point, line_start: Point, line_end: Point) -> f32 { let dxy = line_end - line_start; let ab0 = pt - line_start; let numer = dxy.dot(ab0); let denom = dxy.dot(dxy); let t = numer / denom; if t >= 0.0 && t <= 1.0 { let hit = Point::from_xy( line_start.x * (1.0 - t) + line_end.x * t, line_start.y * (1.0 - t) + line_end.y * t, ); hit.distance_to_sqd(pt) } else { pt.distance_to_sqd(line_start) } } // Intersect the line with the quad and return the t values on the quad where the line crosses. fn intersect_quad_ray<'a>( line: &[Point; 2], quad: &[Point; 3], roots: &'a mut [NormalizedF32Exclusive; 3], ) -> &'a [NormalizedF32Exclusive] { let vec = line[1] - line[0]; let mut r = [0.0; 3]; for n in 0..3 { r[n] = (quad[n].y - line[0].y) * vec.x - (quad[n].x - line[0].x) * vec.y; } let mut a = r[2]; let mut b = r[1]; let c = r[0]; a += c - 2.0 * b; // A = a - 2*b + c b -= c; // B = -(b - c) let len = path_geometry::find_unit_quad_roots(a, 2.0 * b, c, roots); &roots[0..len] } fn points_within_dist(near_pt: Point, far_pt: Point, limit: f32) -> bool { near_pt.distance_to_sqd(far_pt) <= limit * limit } fn sharp_angle(quad: &[Point; 3]) -> bool { let mut smaller = quad[1] - quad[0]; let mut larger = quad[1] - quad[2]; let smaller_len = smaller.length_sqd(); let mut larger_len = larger.length_sqd(); if smaller_len > larger_len { core::mem::swap(&mut smaller, &mut larger); larger_len = smaller_len; } if !smaller.set_length(larger_len) { return false; } let dot = smaller.dot(larger); dot > 0.0 } // Return true if the point is close to the bounds of the quad. This is used as a quick reject. fn pt_in_quad_bounds(quad: &[Point; 3], pt: Point, inv_res_scale: f32) -> bool { let x_min = quad[0].x.min(quad[1].x).min(quad[2].x); if pt.x + inv_res_scale < x_min { return false; } let x_max = quad[0].x.max(quad[1].x).max(quad[2].x); if pt.x - inv_res_scale > x_max { return false; } let y_min = quad[0].y.min(quad[1].y).min(quad[2].y); if pt.y + inv_res_scale < y_min { return false; } let y_max = quad[0].y.max(quad[1].y).max(quad[2].y); if pt.y - inv_res_scale > y_max { return false; } true } fn check_cubic_linear( cubic: &[Point; 4], reduction: &mut [Point; 3], tangent_pt: Option<&mut Point>, ) -> ReductionType { let degenerate_ab = degenerate_vector(cubic[1] - cubic[0]); let degenerate_bc = degenerate_vector(cubic[2] - cubic[1]); let degenerate_cd = degenerate_vector(cubic[3] - cubic[2]); if degenerate_ab & degenerate_bc & degenerate_cd { return ReductionType::Point; } if degenerate_ab as i32 + degenerate_bc as i32 + degenerate_cd as i32 == 2 { return ReductionType::Line; } if !cubic_in_line(cubic) { if let Some(tangent_pt) = tangent_pt { *tangent_pt = if degenerate_ab { cubic[2] } else { cubic[1] }; } return ReductionType::Quad; } let mut t_values = [NormalizedF32::ZERO; 3]; let t_values = path_geometry::find_cubic_max_curvature(cubic, &mut t_values); let mut r_count = 0; // Now loop over the t-values, and reject any that evaluate to either end-point for t in t_values { if 0.0 >= t.get() || t.get() >= 1.0 { continue; } reduction[r_count] = path_geometry::eval_cubic_pos_at(cubic, *t); if reduction[r_count] != cubic[0] && reduction[r_count] != cubic[3] { r_count += 1; } } match r_count { 0 => ReductionType::Line, 1 => ReductionType::Degenerate, 2 => ReductionType::Degenerate2, 3 => ReductionType::Degenerate3, _ => unreachable!(), } } /// Given a cubic, determine if all four points are in a line. /// /// Return true if the inner points is close to a line connecting the outermost points. /// /// Find the outermost point by looking for the largest difference in X or Y. /// Given the indices of the outermost points, and that outer_1 is greater than outer_2, /// this table shows the index of the smaller of the remaining points: /// /// ```text /// outer_2 /// 0 1 2 3 /// outer_1 ---------------- /// 0 | - 2 1 1 /// 1 | - - 0 0 /// 2 | - - - 0 /// 3 | - - - - /// ``` /// /// If outer_1 == 0 and outer_2 == 1, the smaller of the remaining indices (2 and 3) is 2. /// /// This table can be collapsed to: (1 + (2 >> outer_2)) >> outer_1 /// /// Given three indices (outer_1 outer_2 mid_1) from 0..3, the remaining index is: /// /// ```text /// mid_2 == (outer_1 ^ outer_2 ^ mid_1) /// ``` fn cubic_in_line(cubic: &[Point; 4]) -> bool { let mut pt_max = -1.0; let mut outer1 = 0; let mut outer2 = 0; for index in 0..3 { for inner in index + 1..4 { let test_diff = cubic[inner] - cubic[index]; let test_max = test_diff.x.abs().max(test_diff.y.abs()); if pt_max < test_max { outer1 = index; outer2 = inner; pt_max = test_max; } } } debug_assert!(outer1 <= 2); debug_assert!(outer2 >= 1 && outer2 <= 3); debug_assert!(outer1 < outer2); let mid1 = (1 + (2 >> outer2)) >> outer1; debug_assert!(mid1 <= 2); debug_assert!(outer1 != mid1 && outer2 != mid1); let mid2 = outer1 ^ outer2 ^ mid1; debug_assert!(mid2 >= 1 && mid2 <= 3); debug_assert!(mid2 != outer1 && mid2 != outer2 && mid2 != mid1); debug_assert!(((1 << outer1) | (1 << outer2) | (1 << mid1) | (1 << mid2)) == 0x0f); let line_slop = pt_max * pt_max * 0.00001; // this multiplier is pulled out of the air pt_to_line(cubic[mid1], cubic[outer1], cubic[outer2]) <= line_slop && pt_to_line(cubic[mid2], cubic[outer1], cubic[outer2]) <= line_slop } #[rustfmt::skip] #[cfg(test)] mod tests { use super::*; impl PathSegment { fn new_move_to(x: f32, y: f32) -> Self { PathSegment::MoveTo(Point::from_xy(x, y)) } fn new_line_to(x: f32, y: f32) -> Self { PathSegment::LineTo(Point::from_xy(x, y)) } // fn new_quad_to(x1: f32, y1: f32, x: f32, y: f32) -> Self { // PathSegment::QuadTo(Point::from_xy(x1, y1), Point::from_xy(x, y)) // } // fn new_cubic_to(x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) -> Self { // PathSegment::CubicTo(Point::from_xy(x1, y1), Point::from_xy(x2, y2), Point::from_xy(x, y)) // } fn new_close() -> Self { PathSegment::Close } } // Make sure that subpath auto-closing is enabled. #[test] fn auto_close() { // A triangle. let mut pb = PathBuilder::new(); pb.move_to(10.0, 10.0); pb.line_to(20.0, 50.0); pb.line_to(30.0, 10.0); pb.close(); let path = pb.finish().unwrap(); let stroke = Stroke::default(); let stroke_path = PathStroker::new().stroke(&path, &stroke, 1.0).unwrap(); let mut iter = stroke_path.segments(); iter.set_auto_close(true); assert_eq!(iter.next().unwrap(), PathSegment::new_move_to(10.485071, 9.878732)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.485071, 49.878731)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.0, 50.0)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(19.514929, 49.878731)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(29.514929, 9.878732)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.0, 10.0)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.0, 10.5)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.0, 10.5)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.0, 10.0)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(10.485071, 9.878732)); assert_eq!(iter.next().unwrap(), PathSegment::new_close()); assert_eq!(iter.next().unwrap(), PathSegment::new_move_to(9.3596115, 9.5)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(30.640388, 9.5)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(20.485071, 50.121269)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(19.514929, 50.121269)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(9.514929, 10.121268)); assert_eq!(iter.next().unwrap(), PathSegment::new_line_to(9.3596115, 9.5)); assert_eq!(iter.next().unwrap(), PathSegment::new_close()); } // From skia/tests/StrokeTest.cpp #[test] fn cubic_1() { let mut pb = PathBuilder::new(); pb.move_to(51.0161362, 1511.52478); pb.cubic_to( 51.0161362, 1511.52478, 51.0161362, 1511.52478, 51.0161362, 1511.52478, ); let path = pb.finish().unwrap(); let mut stroke = Stroke::default(); stroke.width = 0.394537568; assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_none()); } // From skia/tests/StrokeTest.cpp #[test] fn cubic_2() { let mut pb = PathBuilder::new(); pb.move_to(f32::from_bits(0x424c1086), f32::from_bits(0x44bcf0cb)); // 51.0161362, 1511.52478 pb.cubic_to( f32::from_bits(0x424c107c), f32::from_bits(0x44bcf0cb), // 51.0160980, 1511.52478 f32::from_bits(0x424c10c2), f32::from_bits(0x44bcf0cb), // 51.0163651, 1511.52478 f32::from_bits(0x424c1119), f32::from_bits(0x44bcf0ca), // 51.0166969, 1511.52466 ); let path = pb.finish().unwrap(); let mut stroke = Stroke::default(); stroke.width = 0.394537568; assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some()); } // From skia/tests/StrokeTest.cpp // From skbug.com/6491. The large stroke width can cause numerical instabilities. #[test] fn big() { // Skia uses `kStrokeAndFill_Style` here, but we do not support it. let mut pb = PathBuilder::new(); pb.move_to(f32::from_bits(0x46380000), f32::from_bits(0xc6380000)); // 11776, -11776 pb.line_to(f32::from_bits(0x46a00000), f32::from_bits(0xc6a00000)); // 20480, -20480 pb.line_to(f32::from_bits(0x468c0000), f32::from_bits(0xc68c0000)); // 17920, -17920 pb.line_to(f32::from_bits(0x46100000), f32::from_bits(0xc6100000)); // 9216, -9216 pb.line_to(f32::from_bits(0x46380000), f32::from_bits(0xc6380000)); // 11776, -11776 pb.close(); let path = pb.finish().unwrap(); let mut stroke = Stroke::default(); stroke.width = 1.49679073e+10; assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some()); } // From skia/tests/StrokerTest.cpp #[test] fn quad_stroker_one_off() { let mut pb = PathBuilder::new(); pb.move_to(f32::from_bits(0x43c99223), f32::from_bits(0x42b7417e)); pb.quad_to( f32::from_bits(0x4285d839), f32::from_bits(0x43ed6645), f32::from_bits(0x43c941c8), f32::from_bits(0x42b3ace3), ); let path = pb.finish().unwrap(); let mut stroke = Stroke::default(); stroke.width = 164.683548; assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some()); } // From skia/tests/StrokerTest.cpp #[test] fn cubic_stroker_one_off() { let mut pb = PathBuilder::new(); pb.move_to(f32::from_bits(0x433f5370), f32::from_bits(0x43d1f4b3)); pb.cubic_to( f32::from_bits(0x4331cb76), f32::from_bits(0x43ea3340), f32::from_bits(0x4388f498), f32::from_bits(0x42f7f08d), f32::from_bits(0x43f1cd32), f32::from_bits(0x42802ec1), ); let path = pb.finish().unwrap(); let mut stroke = Stroke::default(); stroke.width = 42.835968; assert!(PathStroker::new().stroke(&path, &stroke, 1.0).is_some()); } } tiny-skia-path-0.11.3/src/transform.rs000064400000000000000000000353101046102023000157220ustar 00000000000000// 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::{NonZeroRect, 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`. /// /// `angle` in degrees. 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. /// /// `angle` in degrees. 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 } /// Converts `Rect` into a bounding box `Transform`. #[inline] pub fn from_bbox(bbox: NonZeroRect) -> Self { Transform::from_row(bbox.width(), 0.0, 0.0, bbox.height(), bbox.x(), bbox.y()) } /// 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 finite and has non-zero scale. pub fn is_valid(&self) -> bool { if self.is_finite() { let (sx, sy) = self.get_scale(); !(sx.is_nearly_zero_within_tolerance(f32::EPSILON) || sy.is_nearly_zero_within_tolerance(f32::EPSILON)) } else { false } } /// Checks that transform is identity. pub fn is_identity(&self) -> bool { *self == Transform::default() } /// Checks that transform is scale-only. pub fn is_scale(&self) -> bool { self.has_scale() && !self.has_skew() && !self.has_translate() } /// Checks that transform is skew-only. pub fn is_skew(&self) -> bool { !self.has_scale() && self.has_skew() && !self.has_translate() } /// Checks that transform is translate-only. pub fn is_translate(&self) -> bool { !self.has_scale() && !self.has_skew() && self.has_translate() } /// Checks that transform contains only scale and translate. pub fn is_scale_translate(&self) -> bool { (self.has_scale() || self.has_translate()) && !self.has_skew() } /// Checks that transform contains a scale part. pub fn has_scale(&self) -> bool { self.sx != 1.0 || self.sy != 1.0 } /// Checks that transform contains a skew part. pub fn has_skew(&self) -> bool { self.kx != 0.0 || self.ky != 0.0 } /// Checks that transform contains a translate part. pub fn has_translate(&self) -> bool { self.tx != 0.0 || self.ty != 0.0 } /// Returns transform's scale part. pub fn get_scale(&self) -> (f32, f32) { let x_scale = (self.sx * self.sx + self.kx * self.kx).sqrt(); let y_scale = (self.ky * self.ky + self.sy * self.sy).sqrt(); (x_scale, y_scale) } /// 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-rotates the current transform. /// /// `angle` in degrees. #[must_use] pub fn pre_rotate(&self, angle: f32) -> Self { self.pre_concat(Transform::from_rotate(angle)) } /// Post-rotates the current transform. /// /// `angle` in degrees. #[must_use] pub fn post_rotate(&self, angle: f32) -> Self { self.post_concat(Transform::from_rotate(angle)) } /// Pre-rotates the current transform by the specified position. /// /// `angle` in degrees. #[must_use] pub fn pre_rotate_at(&self, angle: f32, tx: f32, ty: f32) -> Self { self.pre_concat(Transform::from_rotate_at(angle, tx, ty)) } /// Post-rotates the current transform by the specified position. /// /// `angle` in degrees. #[must_use] pub fn post_rotate_at(&self, angle: f32, tx: f32, ty: f32) -> Self { self.post_concat(Transform::from_rotate_at(angle, 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 points using the current transform. pub fn map_point(&self, point: &mut Point) { if self.is_identity() { // Do nothing. } else if self.is_translate() { point.x += self.tx; point.y += self.ty; } else if self.is_scale_translate() { point.x = point.x * self.sx + self.tx; point.y = point.y * self.sy + self.ty; } else { let x = point.x * self.sx + point.y * self.kx + self.tx; let y = point.x * self.ky + point.y * self.sy + self.ty; point.x = x; point.y = y; } } /// 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 { // Allow the trivial case to be inlined. if self.is_identity() { return Some(*self); } invert(self) } } fn invert(ts: &Transform) -> Option { 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 { 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)); } }