mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
merge
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::instances::{Instance, InstanceRef};
|
||||
use graphene_core::subpath::{ManipulatorGroup, PathSegPoints, Subpath, pathseg_points};
|
||||
use graphene_core::table::{Table, TableRow, TableRowRef};
|
||||
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||
use graphene_core::vector::style::Fill;
|
||||
use graphene_core::vector::{PointId, VectorData, VectorDataTable};
|
||||
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
|
||||
use graphene_core::vector::{PointId, Vector};
|
||||
use graphene_core::{Color, Ctx, Graphic};
|
||||
pub use path_bool as path_bool_lib;
|
||||
use path_bool::{FillRule, PathBooleanOperation};
|
||||
use std::ops::Mul;
|
||||
@@ -32,11 +32,11 @@ pub enum BooleanOperation {
|
||||
|
||||
/// Combines the geometric forms of one or more closed paths into a new vector path that results from cutting or joining the paths by the chosen method.
|
||||
#[node_macro::node(category(""))]
|
||||
async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
|
||||
async fn boolean_operation<I: Into<Table<Graphic>> + 'n + Send + Clone>(
|
||||
_: impl Ctx,
|
||||
/// The group of paths to perform the boolean operation on. Nested groups are automatically flattened.
|
||||
#[implementations(GraphicGroupTable, VectorDataTable)]
|
||||
group_of_paths: I,
|
||||
/// The table of vector paths to perform the boolean operation on. Nested tables are automatically flattened.
|
||||
#[implementations(Table<Graphic>, Table<Vector>)]
|
||||
content: I,
|
||||
/// Which boolean operation to perform on the paths.
|
||||
///
|
||||
/// Union combines all paths while cutting out overlapping areas (even the interiors of a single path).
|
||||
@@ -44,55 +44,55 @@ async fn boolean_operation<I: Into<GraphicGroupTable> + 'n + Send + Clone>(
|
||||
/// Intersection cuts away all but the overlapping areas shared by every path.
|
||||
/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.
|
||||
operation: BooleanOperation,
|
||||
) -> VectorDataTable {
|
||||
let group_of_paths = group_of_paths.into();
|
||||
) -> Table<Vector> {
|
||||
let content = content.into();
|
||||
|
||||
// The first index is the bottom of the stack
|
||||
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).instance_ref_iter(), operation);
|
||||
let mut result_vector_table = boolean_operation_on_vector_table(flatten_vector(&content).iter(), operation);
|
||||
|
||||
// Replace the transformation matrix with a mutation of the vector points themselves
|
||||
if let Some(result_vector_data) = result_vector_data_table.instance_mut_iter().next() {
|
||||
let transform = *result_vector_data.transform;
|
||||
*result_vector_data.transform = DAffine2::IDENTITY;
|
||||
if let Some(result_vector) = result_vector_table.iter_mut().next() {
|
||||
let transform = *result_vector.transform;
|
||||
*result_vector.transform = DAffine2::IDENTITY;
|
||||
|
||||
VectorData::transform(result_vector_data.instance, transform);
|
||||
result_vector_data.instance.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector_data.instance.upstream_graphic_group = Some(group_of_paths.clone());
|
||||
Vector::transform(result_vector.element, transform);
|
||||
result_vector.element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_vector.element.upstream_nested_layers = Some(content.clone());
|
||||
|
||||
// Clean up the boolean operation result by merging duplicated points
|
||||
result_vector_data.instance.merge_by_distance_spatial(*result_vector_data.transform, 0.0001);
|
||||
result_vector.element.merge_by_distance_spatial(*result_vector.transform, 0.0001);
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
result_vector_table
|
||||
}
|
||||
|
||||
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> VectorDataTable {
|
||||
fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone, boolean_operation: BooleanOperation) -> Table<Vector> {
|
||||
match boolean_operation {
|
||||
BooleanOperation::Union => union(vector_data),
|
||||
BooleanOperation::SubtractFront => subtract(vector_data),
|
||||
BooleanOperation::SubtractBack => subtract(vector_data.rev()),
|
||||
BooleanOperation::Intersect => intersect(vector_data),
|
||||
BooleanOperation::Difference => difference(vector_data),
|
||||
BooleanOperation::Union => union(vector),
|
||||
BooleanOperation::SubtractFront => subtract(vector),
|
||||
BooleanOperation::SubtractBack => subtract(vector.rev()),
|
||||
BooleanOperation::Intersect => intersect(vector),
|
||||
BooleanOperation::Difference => difference(vector),
|
||||
}
|
||||
}
|
||||
|
||||
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
|
||||
// Reverse vector data so that the result style is the style of the first vector data
|
||||
let mut vector_data_reversed = vector_data.rev();
|
||||
fn union<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>>) -> Table<Vector> {
|
||||
// Reverse the vector table rows so that the result style is the style of the first vector row
|
||||
let mut vector_reversed = vector.rev();
|
||||
|
||||
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data_reversed.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
|
||||
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
|
||||
let mut result_vector_table = Table::new_from_row(vector_reversed.next().map(|x| x.into_cloned()).unwrap_or_default());
|
||||
let mut first_row = result_vector_table.iter_mut().next().expect("Expected the one row we just pushed");
|
||||
|
||||
// Loop over all vector data and union it with the result
|
||||
let default = Instance::default();
|
||||
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.to_instance_ref()));
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
// Loop over all vector table rows and union it with the result
|
||||
let default = TableRow::default();
|
||||
let mut second_vector = Some(vector_reversed.next().unwrap_or(default.as_ref()));
|
||||
while let Some(lower_vector) = second_vector {
|
||||
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
|
||||
|
||||
let result = &mut first_instance.instance;
|
||||
let result = &mut first_row.element;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
|
||||
@@ -103,27 +103,32 @@ fn union<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, Vector
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
second_vector_data = vector_data_reversed.next();
|
||||
second_vector = vector_reversed.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
result_vector_table
|
||||
}
|
||||
|
||||
fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.into_iter();
|
||||
fn subtract<'a>(vector: impl Iterator<Item = TableRowRef<'a, Vector>>) -> Table<Vector> {
|
||||
let mut vector = vector.into_iter();
|
||||
|
||||
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
|
||||
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
|
||||
let mut result_vector_table = Table::new_from_row(vector.next().map(|x| x.into_cloned()).unwrap_or_default());
|
||||
let mut first_row = result_vector_table.iter_mut().next().expect("Expected the one row we just pushed");
|
||||
let first_row_transform = if first_row.transform.matrix2.determinant() != 0. {
|
||||
first_row.transform.inverse()
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
};
|
||||
|
||||
let mut next_vector_data = vector_data.next();
|
||||
let mut next_vector = vector.next();
|
||||
|
||||
while let Some(lower_vector_data) = next_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
while let Some(lower_vector) = next_vector {
|
||||
let transform_of_lower_into_space_of_upper = first_row_transform * *lower_vector.transform;
|
||||
|
||||
let result = &mut first_instance.instance;
|
||||
let result = &mut first_row.element;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
|
||||
@@ -134,29 +139,29 @@ fn subtract<'a>(vector_data: impl Iterator<Item = InstanceRef<'a, VectorData>>)
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
|
||||
next_vector_data = vector_data.next();
|
||||
next_vector = vector.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
result_vector_table
|
||||
}
|
||||
|
||||
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>>) -> VectorDataTable {
|
||||
let mut vector_data = vector_data.rev();
|
||||
fn intersect<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>>) -> Table<Vector> {
|
||||
let mut vector = vector.rev();
|
||||
|
||||
let mut result_vector_data_table = VectorDataTable::new_instance(vector_data.next().map(|x| x.to_instance_cloned()).unwrap_or_default());
|
||||
let mut first_instance = result_vector_data_table.instance_mut_iter().next().expect("Expected the one instance we just pushed");
|
||||
let mut result_vector_table = Table::new_from_row(vector.next().map(|x| x.into_cloned()).unwrap_or_default());
|
||||
let mut first_row = result_vector_table.iter_mut().next().expect("Expected the one row we just pushed");
|
||||
|
||||
let default = Instance::default();
|
||||
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.to_instance_ref()));
|
||||
let default = TableRow::default();
|
||||
let mut second_vector = Some(vector.next().unwrap_or(default.as_ref()));
|
||||
|
||||
// For each vector data, set the result to the intersection of that data and the result
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
// For each vector table row, set the result to the intersection of that path and the current result
|
||||
while let Some(lower_vector) = second_vector {
|
||||
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
|
||||
|
||||
let result = &mut first_instance.instance;
|
||||
let result = &mut first_row.element;
|
||||
|
||||
let upper_path_string = to_path(result, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
@@ -166,127 +171,162 @@ fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, Ve
|
||||
result.point_domain = boolean_operation_result.point_domain;
|
||||
result.segment_domain = boolean_operation_result.segment_domain;
|
||||
result.region_domain = boolean_operation_result.region_domain;
|
||||
second_vector_data = vector_data.next();
|
||||
second_vector = vector.next();
|
||||
}
|
||||
|
||||
result_vector_data_table
|
||||
result_vector_table
|
||||
}
|
||||
|
||||
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = InstanceRef<'a, VectorData>> + Clone) -> VectorDataTable {
|
||||
let mut vector_data_iter = vector_data.clone().rev();
|
||||
let mut any_intersection = Instance::default();
|
||||
let default = Instance::default();
|
||||
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.to_instance_ref()));
|
||||
fn difference<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone) -> Table<Vector> {
|
||||
let mut vector_iter = vector.clone().rev();
|
||||
let mut any_intersection = TableRow::default();
|
||||
let default = TableRow::default();
|
||||
let mut second_vector = Some(vector_iter.next().unwrap_or(default.as_ref()));
|
||||
|
||||
// Find where all vector data intersect at least once
|
||||
while let Some(lower_vector_data) = second_vector_data {
|
||||
let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter();
|
||||
let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union);
|
||||
let first_instance = unioned.instance_ref_iter().next().expect("Expected at least one instance after the boolean union");
|
||||
// Find where all vector table row paths intersect at least once
|
||||
while let Some(lower_vector) = second_vector {
|
||||
let filtered_vector = vector.clone().filter(|v| *v != lower_vector).collect::<Vec<_>>().into_iter();
|
||||
let unioned = boolean_operation_on_vector_table(filtered_vector, BooleanOperation::Union);
|
||||
let first_row = unioned.iter().next().expect("Expected at least one row after the boolean union");
|
||||
|
||||
let transform_of_lower_into_space_of_upper = first_instance.transform.inverse() * *lower_vector_data.transform;
|
||||
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
|
||||
|
||||
let upper_path_string = to_path(first_instance.instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector_data.instance, transform_of_lower_into_space_of_upper);
|
||||
let upper_path_string = to_path(first_row.element, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
|
||||
let mut instance = from_path(&boolean_intersection_string);
|
||||
instance.style = first_instance.instance.style.clone();
|
||||
let boolean_intersection_result = Instance {
|
||||
instance,
|
||||
mask: None,
|
||||
transform: *first_instance.transform,
|
||||
alpha_blending: *first_instance.alpha_blending,
|
||||
source_node_id: *first_instance.source_node_id,
|
||||
let mut element = from_path(&boolean_intersection_string);
|
||||
element.style = first_row.element.style.clone();
|
||||
let boolean_intersection_result = TableRow {
|
||||
element,
|
||||
mask: first_row.mask.clone(),
|
||||
transform: *first_row.transform,
|
||||
alpha_blending: *first_row.alpha_blending,
|
||||
source_node_id: *first_row.source_node_id,
|
||||
};
|
||||
|
||||
let transform_of_lower_into_space_of_upper = boolean_intersection_result.transform.inverse() * any_intersection.transform;
|
||||
|
||||
let upper_path_string = to_path(&boolean_intersection_result.instance, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(&any_intersection.instance, transform_of_lower_into_space_of_upper);
|
||||
let upper_path_string = to_path(&boolean_intersection_result.element, DAffine2::IDENTITY);
|
||||
let lower_path_string = to_path(&any_intersection.element, transform_of_lower_into_space_of_upper);
|
||||
|
||||
#[allow(unused_unsafe)]
|
||||
let union_result = from_path(&unsafe { boolean_union(upper_path_string, lower_path_string) });
|
||||
any_intersection.instance = union_result;
|
||||
any_intersection.element = union_result;
|
||||
|
||||
any_intersection.transform = boolean_intersection_result.transform;
|
||||
any_intersection.instance.style = boolean_intersection_result.instance.style.clone();
|
||||
any_intersection.element.style = boolean_intersection_result.element.style.clone();
|
||||
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
|
||||
|
||||
second_vector_data = vector_data_iter.next();
|
||||
second_vector = vector_iter.next();
|
||||
}
|
||||
|
||||
// Subtract the area where they intersect at least once from the union of all vector data
|
||||
let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union);
|
||||
boolean_operation_on_vector_data_table(union.instance_ref_iter().chain(std::iter::once(any_intersection.to_instance_ref())), BooleanOperation::SubtractFront)
|
||||
// Subtract the area where they intersect at least once from the union of all vector paths
|
||||
let union = boolean_operation_on_vector_table(vector, BooleanOperation::Union);
|
||||
boolean_operation_on_vector_table(union.iter().chain(std::iter::once(any_intersection.as_ref())), BooleanOperation::SubtractFront)
|
||||
}
|
||||
|
||||
fn flatten_vector_data(graphic_group_table: &GraphicGroupTable) -> VectorDataTable {
|
||||
graphic_group_table
|
||||
.instance_ref_iter()
|
||||
fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
|
||||
graphic_table
|
||||
.iter()
|
||||
.flat_map(|element| {
|
||||
match element.instance.clone() {
|
||||
GraphicElement::VectorData(vector_data) => {
|
||||
// Apply the parent group's transform to each element of vector data
|
||||
vector_data
|
||||
.instance_iter()
|
||||
.map(|mut sub_vector_data| {
|
||||
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
|
||||
match element.element.clone() {
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the vector table
|
||||
vector
|
||||
.into_iter()
|
||||
.map(|mut sub_vector| {
|
||||
sub_vector.transform = *element.transform * sub_vector.transform;
|
||||
|
||||
sub_vector_data
|
||||
sub_vector
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
GraphicElement::RasterDataCPU(image) => {
|
||||
let make_instance = |transform| {
|
||||
Graphic::RasterCPU(image) => {
|
||||
let make_row = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector data table row from the rectangular subpath, with a default black fill
|
||||
let mut instance = VectorData::from_subpath(subpath);
|
||||
instance.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
// Create a vector table row from the rectangular subpath, with a default black fill
|
||||
let mut element = Vector::from_subpath(subpath);
|
||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
Instance { instance, ..Default::default() }
|
||||
TableRow { element, ..Default::default() }
|
||||
};
|
||||
|
||||
// Apply the parent group's transform to each element of raster data
|
||||
image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::<Vec<_>>()
|
||||
// Apply the parent graphic's transform to each raster element
|
||||
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
|
||||
}
|
||||
GraphicElement::RasterDataGPU(image) => {
|
||||
let make_instance = |transform| {
|
||||
Graphic::RasterGPU(image) => {
|
||||
let make_row = |transform| {
|
||||
// Convert the image frame into a rectangular subpath with the image's transform
|
||||
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
subpath.apply_transform(transform);
|
||||
|
||||
// Create a vector data table row from the rectangular subpath, with a default black fill
|
||||
let mut instance = VectorData::from_subpath(subpath);
|
||||
instance.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
// Create a vector table row from the rectangular subpath, with a default black fill
|
||||
let mut element = Vector::from_subpath(subpath);
|
||||
element.style.set_fill(Fill::Solid(Color::BLACK));
|
||||
|
||||
Instance { instance, ..Default::default() }
|
||||
TableRow { element, ..Default::default() }
|
||||
};
|
||||
|
||||
// Apply the parent group's transform to each element of raster data
|
||||
image.instance_ref_iter().map(|instance| make_instance(*element.transform * *instance.transform)).collect::<Vec<_>>()
|
||||
// Apply the parent graphic's transform to each raster element
|
||||
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
|
||||
}
|
||||
GraphicElement::GraphicGroup(mut graphic_group) => {
|
||||
// Apply the parent group's transform to each element of inner group
|
||||
for sub_element in graphic_group.instance_mut_iter() {
|
||||
Graphic::Graphic(mut graphic) => {
|
||||
// Apply the parent graphic's transform to each element of inner table
|
||||
for sub_element in graphic.iter_mut() {
|
||||
*sub_element.transform = *element.transform * *sub_element.transform;
|
||||
}
|
||||
|
||||
// Recursively flatten the inner group into vector data
|
||||
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).instance_ref_iter(), BooleanOperation::Union);
|
||||
// Recursively flatten the inner table into the output vector table
|
||||
let unioned = boolean_operation_on_vector_table(flatten_vector(&graphic).iter(), BooleanOperation::Union);
|
||||
|
||||
unioned.instance_iter().collect::<Vec<_>>()
|
||||
unioned.into_iter().collect::<Vec<_>>()
|
||||
}
|
||||
Graphic::Color(color) => color
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let mut element = Vector::default();
|
||||
element.style.set_fill(Fill::Solid(row.element));
|
||||
element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
TableRow {
|
||||
element,
|
||||
mask: row.mask.clone(),
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
Graphic::Gradient(gradient) => gradient
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
let mut element = Vector::default();
|
||||
element.style.set_fill(Fill::Gradient(graphene_core::gradient::Gradient {
|
||||
stops: row.element,
|
||||
..Default::default()
|
||||
}));
|
||||
element.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
TableRow {
|
||||
element,
|
||||
mask: row.mask.clone(),
|
||||
transform: row.transform,
|
||||
alpha_blending: row.alpha_blending,
|
||||
source_node_id: row.source_node_id,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
|
||||
fn to_path(vector: &Vector, transform: DAffine2) -> Vec<path_bool::PathSegment> {
|
||||
let mut path = Vec::new();
|
||||
for subpath in vector.stroke_bezier_paths() {
|
||||
to_path_segments(&mut path, &subpath, transform);
|
||||
@@ -298,20 +338,29 @@ fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &Subpath<Po
|
||||
use path_bool::PathSegment;
|
||||
let mut global_start = None;
|
||||
let mut global_end = DVec2::ZERO;
|
||||
|
||||
for bezier in subpath.iter() {
|
||||
const EPS: f64 = 1e-8;
|
||||
let transformed = bezier.apply_transformation(|pos| transform.transform_point2(pos).mul(EPS.recip()).round().mul(EPS));
|
||||
let start = transformed.start;
|
||||
let end = transformed.end;
|
||||
let transform_point = |pos: DVec2| transform.transform_point2(pos).mul(EPS.recip()).round().mul(EPS);
|
||||
|
||||
let PathSegPoints { p0, p1, p2, p3 } = pathseg_points(bezier);
|
||||
|
||||
let p0 = transform_point(p0);
|
||||
let p1 = p1.map(transform_point);
|
||||
let p2 = p2.map(transform_point);
|
||||
let p3 = transform_point(p3);
|
||||
|
||||
if global_start.is_none() {
|
||||
global_start = Some(start);
|
||||
global_start = Some(p0);
|
||||
}
|
||||
global_end = end;
|
||||
let segment = match transformed.handles {
|
||||
bezier_rs::BezierHandles::Linear => PathSegment::Line(start, end),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => PathSegment::Quadratic(start, handle, end),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => PathSegment::Cubic(start, handle_start, handle_end, end),
|
||||
global_end = p3;
|
||||
|
||||
let segment = match (p1, p2) {
|
||||
(None, None) => PathSegment::Line(p0, p3),
|
||||
(None, Some(p2)) | (Some(p2), None) => PathSegment::Quadratic(p0, p2, p3),
|
||||
(Some(p1), Some(p2)) => PathSegment::Cubic(p0, p1, p2, p3),
|
||||
};
|
||||
|
||||
path.push(segment);
|
||||
}
|
||||
if let Some(start) = global_start {
|
||||
@@ -319,7 +368,7 @@ fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &Subpath<Po
|
||||
}
|
||||
}
|
||||
|
||||
fn from_path(path_data: &[Path]) -> VectorData {
|
||||
fn from_path(path_data: &[Path]) -> Vector {
|
||||
const EPSILON: f64 = 1e-5;
|
||||
|
||||
fn is_close(a: DVec2, b: DVec2) -> bool {
|
||||
@@ -330,7 +379,7 @@ fn from_path(path_data: &[Path]) -> VectorData {
|
||||
|
||||
for path in path_data.iter().filter(|path| !path.is_empty()) {
|
||||
let cubics: Vec<[DVec2; 4]> = path.iter().map(|segment| segment.to_cubic()).collect();
|
||||
let mut groups = Vec::new();
|
||||
let mut manipulators_list = Vec::new();
|
||||
let mut current_start = None;
|
||||
|
||||
for (index, cubic) in cubics.iter().enumerate() {
|
||||
@@ -338,32 +387,32 @@ fn from_path(path_data: &[Path]) -> VectorData {
|
||||
|
||||
if current_start.is_none() || !is_close(start, current_start.unwrap()) {
|
||||
// Start a new subpath
|
||||
if !groups.is_empty() {
|
||||
all_subpaths.push(Subpath::new(std::mem::take(&mut groups), true));
|
||||
if !manipulators_list.is_empty() {
|
||||
all_subpaths.push(Subpath::new(std::mem::take(&mut manipulators_list), true));
|
||||
}
|
||||
// Use the correct in-handle (None) and out-handle for the start point
|
||||
groups.push(ManipulatorGroup::new(start, None, Some(handle1)));
|
||||
manipulators_list.push(ManipulatorGroup::new(start, None, Some(handle1)));
|
||||
} else {
|
||||
// Update the out-handle of the previous point
|
||||
if let Some(last) = groups.last_mut() {
|
||||
if let Some(last) = manipulators_list.last_mut() {
|
||||
last.out_handle = Some(handle1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the end point with the correct in-handle and out-handle (None)
|
||||
groups.push(ManipulatorGroup::new(end, Some(handle2), None));
|
||||
manipulators_list.push(ManipulatorGroup::new(end, Some(handle2), None));
|
||||
|
||||
current_start = Some(end);
|
||||
|
||||
// Check if this is the last segment
|
||||
if index == cubics.len() - 1 {
|
||||
all_subpaths.push(Subpath::new(groups, true));
|
||||
groups = Vec::new(); // Reset groups for the next path
|
||||
all_subpaths.push(Subpath::new(manipulators_list, true));
|
||||
manipulators_list = Vec::new(); // Reset manipulators for the next path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VectorData::from_subpaths(all_subpaths, false)
|
||||
Vector::from_subpaths(all_subpaths, false)
|
||||
}
|
||||
|
||||
type Path = Vec<path_bool::PathSegment>;
|
||||
|
||||
Reference in New Issue
Block a user