( _: impl Ctx, source: List<Vector>, /// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread). #[default(0.5)] #[range] #[soft(0..1)] spread: f64, /// If active
| 992 | /// Automatically constructs tangents (Bézier handles) for anchor points in a vector path. |
| 993 | #[node_macro::node(category("Vector: Modifier"), name("Auto-Tangents"), path(core_types::vector))] |
| 994 | async fn auto_tangents( |
| 995 | _: impl Ctx, |
| 996 | source: List<Vector>, |
| 997 | /// The amount of spread for the auto-tangents, from 0 (sharp corner) to 1 (full spread). |
| 998 | #[default(0.5)] |
| 999 | #[range] |
| 1000 | #[soft(0..1)] |
| 1001 | spread: f64, |
| 1002 | /// If active, existing non-zero handles won't be affected. |
| 1003 | #[default(true)] |
| 1004 | preserve_existing: bool, |
| 1005 | ) -> List<Vector> { |
| 1006 | (0..source.len()) |
| 1007 | .map(|index| { |
| 1008 | let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); |
| 1009 | let attributes = source.clone_item_attributes(index); |
| 1010 | let source = source.element(index).unwrap(); |
| 1011 | |
| 1012 | let mut result = Vector { |
| 1013 | stroke: source.stroke.clone(), |
| 1014 | ..Default::default() |
| 1015 | }; |
| 1016 | |
| 1017 | for mut subpath in source.stroke_bezier_paths() { |
| 1018 | subpath.apply_transform(transform); |
| 1019 | |
| 1020 | let manipulators_list = subpath.manipulator_groups(); |
| 1021 | if manipulators_list.len() < 2 { |
| 1022 | // Not enough points for softening or handle removal |
| 1023 | result.append_subpath(subpath, true); |
| 1024 | continue; |
| 1025 | } |
| 1026 | |
| 1027 | let mut new_manipulators_list = Vec::with_capacity(manipulators_list.len()); |
| 1028 | // Track which manipulator indices were given auto-tangent (colinear) handles |
| 1029 | let mut auto_tangented = vec![false; manipulators_list.len()]; |
| 1030 | let is_closed = subpath.closed(); |
| 1031 | |
| 1032 | for i in 0..manipulators_list.len() { |
| 1033 | let current = &manipulators_list[i]; |
| 1034 | let is_endpoint = !is_closed && (i == 0 || i == manipulators_list.len() - 1); |
| 1035 | |
| 1036 | if preserve_existing { |
| 1037 | // Check if this point has handles that are meaningfully different from the anchor |
| 1038 | let has_handles = (current.in_handle.is_some() && !current.in_handle.unwrap().abs_diff_eq(current.anchor, 1e-5)) |
| 1039 | || (current.out_handle.is_some() && !current.out_handle.unwrap().abs_diff_eq(current.anchor, 1e-5)); |
| 1040 | |
| 1041 | // If the point already has handles, keep it as is |
| 1042 | if has_handles { |
| 1043 | new_manipulators_list.push(*current); |
| 1044 | continue; |
| 1045 | } |
| 1046 | } |
| 1047 | |
| 1048 | // If spread is 0, remove handles for this point, making it a sharp corner |
| 1049 | if spread == 0. { |
| 1050 | new_manipulators_list.push(ManipulatorGroup { |
| 1051 | anchor: current.anchor, |
nothing calls this directly
no test coverage detected