Rename graphic subtypes to remove their "data" and "group" suffixes (#2990)

* Rename VectorData to Vector

* Rename other VectorData* types to Vector*

* Move assorted data types out of vector_data.rs into misc.rs

* Rename vector_data.rs to vector_types.rs and remove the vector_types module folder

* Rename other references to "vector data"

* Remove label widgets for raster/vector/group to use "-" instead

* Rename RasterData to Raster

* Rename GraphicGroup to Group

* Fix migrations and rename graphic_element.rs -> graphic.rs

* Rename TaggedValue::ArtboardGroup -> TaggedValue::Artboard
This commit is contained in:
Keavon Chambers
2025-08-04 04:53:25 -07:00
committed by GitHub
parent 853c26cbc1
commit c98477d8ed
72 changed files with 1820 additions and 1901 deletions

View File

@@ -157,7 +157,7 @@ raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]),
There is also the more general `register_node!` for nodes that do not need to run per pixel.
```rs
register_node!(graphene_core::transform_nodes::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
register_node!(graphene_core::transform_nodes::SetTransformNode<_>, input: Vector, params: [DAffine2]),
```
## Debugging

View File

@@ -5,7 +5,7 @@ use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::transform::TransformMut;
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2};
@@ -14,7 +14,7 @@ use std::hash::Hash;
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Artboard {
pub graphic_group: Table<Graphic>,
pub group: Table<Graphic>,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
@@ -31,7 +31,7 @@ impl Default for Artboard {
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: Table::new(),
group: Table::new(),
label: "Artboard".to_string(),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
@@ -47,7 +47,7 @@ impl BoundingBox for Artboard {
if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
[self.group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
.into_iter()
.flatten()
.reduce(Quad::combine_bounds)
@@ -68,7 +68,7 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
#[serde(untagged)]
enum EitherFormat {
ArtboardGroup(ArtboardGroup),
ArtboardGroupTable(Table<Artboard>),
ArtboardTable(Table<Artboard>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
@@ -84,7 +84,7 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
EitherFormat::ArtboardTable(artboard_group_table) => artboard_group_table,
})
}
@@ -99,7 +99,7 @@ async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> DAffine2,
@@ -112,7 +112,6 @@ async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
clip: bool,
) -> Artboard {
let location = location.as_ivec2();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
@@ -120,13 +119,19 @@ async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
let group = contents.eval(new_ctx.into_context()).await.into();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let location = location.min(location + dimensions);
let dimensions = dimensions.abs();
Artboard {
graphic_group: graphic_group.into(),
group,
label,
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
location,
dimensions,
background,
clip,
}

View File

@@ -1,7 +1,7 @@
use crate::raster_types::{CPU, Raster};
use crate::registry::types::Percentage;
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{BlendMode, Color, Ctx, Graphic};
pub(super) trait MultiplyAlpha {
@@ -13,7 +13,7 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyAlpha for Table<VectorData> {
impl MultiplyAlpha for Table<Vector> {
fn multiply_alpha(&mut self, factor: f64) {
for row in self.iter_mut() {
row.alpha_blending.opacity *= factor as f32;
@@ -43,7 +43,7 @@ impl MultiplyFill for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyFill for Table<VectorData> {
impl MultiplyFill for Table<Vector> {
fn multiply_fill(&mut self, factor: f64) {
for row in self.iter_mut() {
row.alpha_blending.fill *= factor as f32;
@@ -69,7 +69,7 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
}
impl SetBlendMode for Table<VectorData> {
impl SetBlendMode for Table<Vector> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for row in self.iter_mut() {
row.alpha_blending.blend_mode = blend_mode;
@@ -95,7 +95,7 @@ trait SetClip {
fn set_clip(&mut self, clip: bool);
}
impl SetClip for Table<VectorData> {
impl SetClip for Table<Vector> {
fn set_clip(&mut self, clip: bool) {
for row in self.iter_mut() {
row.alpha_blending.clip = clip;
@@ -122,7 +122,7 @@ fn blend_mode<T: SetBlendMode>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
)]
mut value: T,
@@ -138,7 +138,7 @@ fn opacity<T: MultiplyAlpha>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
)]
mut value: T,
@@ -154,7 +154,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
)]
mut value: T,

View File

@@ -1,11 +1,11 @@
use crate::raster_types::{CPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Color, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<VectorData>, DAffine2, Color, Option<Color>)] value: T) -> T {
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<Vector>, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value

View File

@@ -4,7 +4,7 @@ use crate::math::quad::Quad;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Color, Ctx};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -13,90 +13,88 @@ use std::hash::Hash;
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum Graphic {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<Graphic>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<VectorData>),
RasterDataCPU(Table<Raster<CPU>>),
RasterDataGPU(Table<Raster<GPU>>),
Group(Table<Graphic>),
Vector(Table<Vector>),
RasterCPU(Table<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>),
}
impl Default for Graphic {
fn default() -> Self {
Self::GraphicGroup(Default::default())
Self::Group(Default::default())
}
}
// GraphicGroup
// Group
impl From<Table<Graphic>> for Graphic {
fn from(graphic_group: Table<Graphic>) -> Self {
Graphic::GraphicGroup(graphic_group)
fn from(group: Table<Graphic>) -> Self {
Graphic::Group(group)
}
}
// VectorData
impl From<VectorData> for Graphic {
fn from(vector_data: VectorData) -> Self {
Graphic::VectorData(Table::new_from_element(vector_data))
// Vector
impl From<Vector> for Graphic {
fn from(vector: Vector) -> Self {
Graphic::Vector(Table::new_from_element(vector))
}
}
impl From<Table<VectorData>> for Graphic {
fn from(vector_data: Table<VectorData>) -> Self {
Graphic::VectorData(vector_data)
impl From<Table<Vector>> for Graphic {
fn from(vector: Table<Vector>) -> Self {
Graphic::Vector(vector)
}
}
impl From<VectorData> for Table<Graphic> {
fn from(vector_data: VectorData) -> Self {
Table::new_from_element(Graphic::VectorData(Table::new_from_element(vector_data)))
impl From<Vector> for Table<Graphic> {
fn from(vector: Vector) -> Self {
Table::new_from_element(Graphic::Vector(Table::new_from_element(vector)))
}
}
impl From<Table<VectorData>> for Table<Graphic> {
fn from(vector_data: Table<VectorData>) -> Self {
Table::new_from_element(Graphic::VectorData(vector_data))
impl From<Table<Vector>> for Table<Graphic> {
fn from(vector: Table<Vector>) -> Self {
Table::new_from_element(Graphic::Vector(vector))
}
}
// Raster<CPU>
impl From<Raster<CPU>> for Graphic {
fn from(raster_data: Raster<CPU>) -> Self {
Graphic::RasterDataCPU(Table::new_from_element(raster_data))
fn from(raster: Raster<CPU>) -> Self {
Graphic::RasterCPU(Table::new_from_element(raster))
}
}
impl From<Table<Raster<CPU>>> for Graphic {
fn from(raster_data: Table<Raster<CPU>>) -> Self {
Graphic::RasterDataCPU(raster_data)
fn from(raster: Table<Raster<CPU>>) -> Self {
Graphic::RasterCPU(raster)
}
}
impl From<Raster<CPU>> for Table<Graphic> {
fn from(raster_data: Raster<CPU>) -> Self {
Table::new_from_element(Graphic::RasterDataCPU(Table::new_from_element(raster_data)))
fn from(raster: Raster<CPU>) -> Self {
Table::new_from_element(Graphic::RasterCPU(Table::new_from_element(raster)))
}
}
impl From<Table<Raster<CPU>>> for Table<Graphic> {
fn from(raster_data_table: Table<Raster<CPU>>) -> Self {
Table::new_from_element(Graphic::RasterDataCPU(raster_data_table))
fn from(raster: Table<Raster<CPU>>) -> Self {
Table::new_from_element(Graphic::RasterCPU(raster))
}
}
// Raster<GPU>
impl From<Raster<GPU>> for Graphic {
fn from(raster_data: Raster<GPU>) -> Self {
Graphic::RasterDataGPU(Table::new_from_element(raster_data))
fn from(raster: Raster<GPU>) -> Self {
Graphic::RasterGPU(Table::new_from_element(raster))
}
}
impl From<Table<Raster<GPU>>> for Graphic {
fn from(raster_data: Table<Raster<GPU>>) -> Self {
Graphic::RasterDataGPU(raster_data)
fn from(raster: Table<Raster<GPU>>) -> Self {
Graphic::RasterGPU(raster)
}
}
impl From<Raster<GPU>> for Table<Graphic> {
fn from(raster_data: Raster<GPU>) -> Self {
Table::new_from_element(Graphic::RasterDataGPU(Table::new_from_element(raster_data)))
fn from(raster: Raster<GPU>) -> Self {
Table::new_from_element(Graphic::RasterGPU(Table::new_from_element(raster)))
}
}
impl From<Table<Raster<GPU>>> for Table<Graphic> {
fn from(raster_data_table: Table<Raster<GPU>>) -> Self {
Table::new_from_element(Graphic::RasterDataGPU(raster_data_table))
fn from(raster: Table<Raster<GPU>>) -> Self {
Table::new_from_element(Graphic::RasterGPU(raster))
}
}
@@ -115,58 +113,58 @@ impl From<DAffine2> for Table<Graphic> {
impl Graphic {
pub fn as_group(&self) -> Option<&Table<Graphic>> {
match self {
Graphic::GraphicGroup(group) => Some(group),
Graphic::Group(group) => Some(group),
_ => None,
}
}
pub fn as_group_mut(&mut self) -> Option<&mut Table<Graphic>> {
match self {
Graphic::GraphicGroup(group) => Some(group),
Graphic::Group(group) => Some(group),
_ => None,
}
}
pub fn as_vector_data(&self) -> Option<&Table<VectorData>> {
pub fn as_vector(&self) -> Option<&Table<Vector>> {
match self {
Graphic::VectorData(data) => Some(data),
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_vector_data_mut(&mut self) -> Option<&mut Table<VectorData>> {
pub fn as_vector_mut(&mut self) -> Option<&mut Table<Vector>> {
match self {
Graphic::VectorData(data) => Some(data),
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
match self {
Graphic::RasterDataCPU(raster) => Some(raster),
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
}
}
pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {
match self {
Graphic::RasterDataCPU(raster) => Some(raster),
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
}
}
pub fn had_clip_enabled(&self) -> bool {
match self {
Graphic::VectorData(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::GraphicGroup(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::RasterDataCPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::RasterDataGPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::Vector(vector) => vector.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::Group(group) => group.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::RasterCPU(raster) => raster.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::RasterGPU(raster) => raster.iter_ref().all(|row| row.alpha_blending.clip),
}
}
pub fn can_reduce_to_clip_path(&self) -> bool {
match self {
Graphic::VectorData(vector_data_table) => vector_data_table.iter_ref().all(|row| {
Graphic::Vector(vector) => vector.iter_ref().all(|row| {
let style = &row.element.style;
let alpha_blending = &row.alpha_blending;
(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
@@ -179,10 +177,10 @@ impl Graphic {
impl BoundingBox for Graphic {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
match self {
Graphic::VectorData(vector_data) => vector_data.bounding_box(transform, include_stroke),
Graphic::RasterDataCPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::RasterDataGPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform, include_stroke),
Graphic::Vector(vector) => vector.bounding_box(transform, include_stroke),
Graphic::RasterCPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::Group(group) => group.bounding_box(transform, include_stroke),
}
}
}
@@ -198,8 +196,8 @@ impl BoundingBox for Table<Graphic> {
#[node_macro::node(category(""))]
async fn layer<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
#[implementations(Graphic, VectorData, Raster<CPU>, Raster<GPU>)] element: I,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>)] element: I,
node_path: Vec<NodeId>,
) -> Table<I> {
// Get the penultimate element of the node path, or None if the path is too short
@@ -220,7 +218,7 @@ async fn to_element<Data: Into<Graphic> + 'n>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
DAffine2,
@@ -235,7 +233,7 @@ async fn to_group<Data: Into<Table<Graphic>> + 'n>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
)]
@@ -255,16 +253,16 @@ async fn flatten_group(_: impl Ctx, group: Table<Graphic>, fully_flatten: bool)
let recurse = fully_flatten || recursion_depth == 0;
match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter
Graphic::GraphicGroup(mut current_element) if recurse => {
// If we're allowed to recurse, flatten any groups we encounter
Graphic::Group(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_row.transform * *graphic_element.transform;
for graphic in current_element.iter_mut() {
*graphic.transform = *current_row.transform * *graphic.transform;
}
flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
// Handle any leaf elements we encounter, which can be either non-group elements or groups that we don't want to flatten
_ => {
output_group_table.push(TableRow {
element: current_element,
@@ -284,36 +282,36 @@ async fn flatten_group(_: impl Ctx, group: Table<Graphic>, fully_flatten: bool)
}
#[node_macro::node(category("Vector"))]
async fn flatten_vector(_: impl Ctx, group: Table<Graphic>) -> Table<VectorData> {
async fn flatten_vector(_: impl Ctx, group: Table<Graphic>) -> Table<Vector> {
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
fn flatten_group(output_group_table: &mut Table<VectorData>, current_group_table: Table<Graphic>) {
for current_graphic_element_row in current_group_table.iter_ref() {
let current_element = current_graphic_element_row.element.clone();
let reference = *current_graphic_element_row.source_node_id;
fn flatten_group(output_group_table: &mut Table<Vector>, current_group_table: Table<Graphic>) {
for current_graphic_row in current_group_table.iter_ref() {
let current_graphic = current_graphic_row.element.clone();
let source_node_id = *current_graphic_row.source_node_id;
match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter
Graphic::GraphicGroup(mut current_element) => {
match current_graphic {
// If we're allowed to recurse, flatten any groups we encounter
Graphic::Group(mut current_graphic_table) => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_graphic_element_row.transform * *graphic_element.transform;
for graphic in current_graphic_table.iter_mut() {
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
}
flatten_group(output_group_table, current_element);
flatten_group(output_group_table, current_graphic_table);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
Graphic::VectorData(vector_table) => {
// Handle any leaf elements we encounter, which can be either non-group elements or groups that we don't want to flatten
Graphic::Vector(vector_table) => {
for current_vector_row in vector_table.iter_ref() {
output_group_table.push(TableRow {
element: current_vector_row.element.clone(),
transform: *current_graphic_element_row.transform * *current_vector_row.transform,
transform: *current_graphic_row.transform * *current_vector_row.transform,
alpha_blending: AlphaBlending {
blend_mode: current_vector_row.alpha_blending.blend_mode,
opacity: current_graphic_element_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
fill: current_vector_row.alpha_blending.fill,
clip: current_vector_row.alpha_blending.clip,
},
source_node_id: reference,
source_node_id,
});
}
}
@@ -339,7 +337,7 @@ fn index<T: AtIndex + Clone + Default>(
Vec<Option<Color>>,
Vec<f64>, Vec<u64>,
Vec<DVec2>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
)]
@@ -379,7 +377,7 @@ impl<T: Clone> AtIndex for Table<T> {
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
pub fn migrate_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
@@ -402,32 +400,32 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = Table::new();
for (graphic_element, source_node_id) in old.elements {
graphic_group_table.push(TableRow {
element: graphic_element,
let mut group_table = Table::new();
for (graphic, source_node_id) in old.elements {
group_table.push(TableRow {
element: graphic,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
group_table
}
EitherFormat::Table(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
let mut graphic_group_table = Table::new();
let mut group_table = Table::new();
for row in old_table.iter_ref() {
for (graphic_element, source_node_id) in &row.element.elements {
graphic_group_table.push(TableRow {
element: graphic_element.clone(),
for (graphic, source_node_id) in &row.element.elements {
group_table.push(TableRow {
element: graphic.clone(),
transform: *row.transform,
alpha_blending: *row.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
group_table
} else if let Ok(new_table) = serde_json::from_value::<Table<Graphic>>(value) {
new_table
} else {

View File

@@ -11,7 +11,7 @@ pub mod debug;
pub mod extract_xy;
pub mod generic;
pub mod gradient;
pub mod graphic_element;
pub mod graphic;
pub mod logic;
pub mod math;
pub mod memo;
@@ -41,7 +41,7 @@ pub use graphene_core_shaders::AsU32;
pub use graphene_core_shaders::blending;
pub use graphene_core_shaders::choice_type;
pub use graphene_core_shaders::color;
pub use graphic_element::Graphic;
pub use graphic::Graphic;
pub use memo::MemoHash;
pub use num_traits;
use std::any::TypeId;

View File

@@ -5,19 +5,19 @@ use crate::gradient::GradientStops;
use crate::graphene_core::registry::types::TextArea;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Context, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Text"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<VectorData>)] value: T) -> String {
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<Vector>)] value: T) -> String {
format!("{:?}", value)
}
#[node_macro::node(category("Text"))]
fn serialize<T: serde::Serialize>(
_: impl Ctx,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>)] value: T,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)] value: T,
) -> String {
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
}
@@ -60,7 +60,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> DVec2,
Context -> DAffine2,
Context -> Table<Artboard>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
@@ -81,7 +81,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> DVec2,
Context -> DAffine2,
Context -> Table<Artboard>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,

View File

@@ -3,7 +3,7 @@ use crate::AlphaBlending;
use crate::color::float_to_srgb_u8;
use crate::raster_types::Raster;
use crate::table::{Table, TableRow};
use crate::vector::VectorData;
use crate::vector::Vector;
use core::hash::{Hash, Hasher};
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
@@ -239,7 +239,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<VectorData>),
VectorData(Table<Vector>),
RasterFrame(RasterFrame),
}
@@ -283,21 +283,20 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Table<ImageFrame<Color>>),
ImageFrameTable(Table<Image<Color>>),
RasterDataTable(Table<Raster<CPU>>),
ImageFrameTable(Table<ImageFrame<Color>>),
ImageTable(Table<Image<Color>>),
RasterTable(Table<Raster<CPU>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
*image_frame_table.iter_mut().next().unwrap().transform = transform;
*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => Table::new_from_element(Raster::new_cpu(
FormatVersions::ImageFrameTable(image_frame) => Table::new_from_element(Raster::new_cpu(
image_frame
.iter_ref()
.next()
@@ -306,8 +305,8 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
.image
.clone(),
)),
FormatVersions::ImageFrameTable(image_frame_table) => Table::new_from_element(Raster::new_cpu(image_frame_table.iter_ref().next().unwrap().element.clone())),
FormatVersions::RasterDataTable(raster_data_table) => raster_data_table,
FormatVersions::ImageTable(table) => Table::new_from_element(Raster::new_cpu(table.iter_ref().next().unwrap().element.clone())),
FormatVersions::RasterTable(table) => table,
})
}
@@ -338,7 +337,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<VectorData>),
VectorData(Table<Vector>),
RasterFrame(RasterFrame),
}
@@ -382,9 +381,9 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Table<ImageFrame<Color>>),
RasterDataTable(Table<Raster<CPU>>),
ImageTableRow(TableRow<Raster<CPU>>),
ImageFrameTable(Table<ImageFrame<Color>>),
RasterTable(Table<Raster<CPU>>),
RasterTableRow(TableRow<Raster<CPU>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
@@ -398,12 +397,12 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
source_node_id: None,
},
FormatVersions::ImageFrame(image_frame) => TableRow {
FormatVersions::ImageFrameTable(image_frame) => TableRow {
element: Raster::new_cpu(image_frame.iter_ref().next().unwrap().element.image.clone()),
..Default::default()
},
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.iter().next().unwrap_or_default(),
FormatVersions::ImageTableRow(image_table_row) => image_table_row,
FormatVersions::RasterTable(image_frame_table) => image_frame_table.iter().next().unwrap_or_default(),
FormatVersions::RasterTableRow(image_table_row) => image_table_row,
})
}

View File

@@ -1,6 +1,6 @@
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Artboard, Color, Graphic};
use glam::DVec2;
@@ -18,22 +18,22 @@ impl<T: RenderComplexity> RenderComplexity for Table<T> {
impl RenderComplexity for Artboard {
fn render_complexity(&self) -> usize {
self.graphic_group.render_complexity()
self.group.render_complexity()
}
}
impl RenderComplexity for Graphic {
fn render_complexity(&self) -> usize {
match self {
Self::GraphicGroup(table) => table.render_complexity(),
Self::VectorData(table) => table.render_complexity(),
Self::RasterDataCPU(table) => table.render_complexity(),
Self::RasterDataGPU(table) => table.render_complexity(),
Self::Group(table) => table.render_complexity(),
Self::Vector(table) => table.render_complexity(),
Self::RasterCPU(table) => table.render_complexity(),
Self::RasterGPU(table) => table.render_complexity(),
}
}
}
impl RenderComplexity for VectorData {
impl RenderComplexity for Vector {
fn render_complexity(&self) -> usize {
self.segment_domain.ids().len()
}

View File

@@ -1,6 +1,6 @@
use super::TextAlign;
use crate::table::{Table, TableRow};
use crate::vector::{PointId, VectorData};
use crate::vector::{PointId, Vector};
use bezier_rs::{ManipulatorGroup, Subpath};
use core::cell::RefCell;
use glam::{DAffine2, DVec2};
@@ -24,7 +24,7 @@ struct PathBuilder {
current_subpath: Subpath<PointId>,
origin: DVec2,
glyph_subpaths: Vec<Subpath<PointId>>,
vector_table: Table<VectorData>,
vector_table: Table<Vector>,
scale: f64,
id: PointId,
}
@@ -52,13 +52,13 @@ impl PathBuilder {
if per_glyph_instances {
self.vector_table.push(TableRow {
element: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
element: Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
transform: DAffine2::from_translation(glyph_offset),
..Default::default()
});
} else {
for subpath in self.glyph_subpaths.drain(..) {
// Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData`
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element
self.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);
}
}
@@ -205,15 +205,15 @@ fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
Some(layout)
}
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<VectorData> {
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
let Some(layout) = layout_text(str, font_data, typesetting) else {
return Table::new_from_element(VectorData::default());
return Table::new_from_element(Vector::default());
};
let mut path_builder = PathBuilder {
current_subpath: Subpath::new(Vec::new(), false),
glyph_subpaths: Vec::new(),
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(VectorData::default()) },
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(Vector::default()) },
scale: layout.scale() as f64,
id: PointId::ZERO,
origin: DVec2::default(),
@@ -228,7 +228,7 @@ pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
}
if path_builder.vector_table.is_empty() {
path_builder.vector_table = Table::new_from_element(VectorData::default());
path_builder.vector_table = Table::new_from_element(Vector::default());
}
path_builder.vector_table

View File

@@ -1,7 +1,7 @@
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform};
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use core::f64;
use glam::{DAffine2, DVec2};
@@ -12,7 +12,7 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[implementations(
Context -> DAffine2,
Context -> DVec2,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
@@ -43,7 +43,7 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[node_macro::node(category(""))]
fn replace_transform<Data, TransformInput: Transform>(
_: impl Ctx,
#[implementations(Table<VectorData>, Table<Raster<CPU>>, Table<Graphic>)] mut data: Table<Data>,
#[implementations(Table<Vector>, Table<Raster<CPU>>, Table<Graphic>)] mut data: Table<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Table<Data> {
for data_transform in data.iter_mut() {
@@ -57,13 +57,13 @@ async fn extract_transform<T>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
)]
vector_data: Table<T>,
vector: Table<T>,
) -> DAffine2 {
vector_data.iter_ref().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
vector.iter_ref().next().map(|row| *row.transform).unwrap_or_default()
}
#[node_macro::node(category("Math: Transform"))]
@@ -90,7 +90,7 @@ fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
async fn boundless_footprint<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
@@ -108,7 +108,7 @@ async fn boundless_footprint<T: 'n + 'static>(
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,

View File

@@ -167,8 +167,7 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
let name = match name.as_str() {
"f32" => "f64".to_string(),
"graphene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic::Graphic>".to_string(),
"graphene_core::raster::image::ImageFrame<Color>"
| "graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>"
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<Color>>"
@@ -176,8 +175,13 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
| "graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>" => {
"graphene_core::table::Table<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
}
"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::vector::vector_data::VectorData"
| "graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>"
| "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>"
| "graphene_core::table::Table<graphene_core::vector::vector_data::Vector>" => "graphene_core::table::Table<graphene_core::vector::vector_types::Vector>".to_string(),
"graphene_core::instances::Instances<graphene_core::graphic_element::Artboard>" => "graphene_core::table::Table<graphene_core::artboard::Artboard>".to_string(),
"graphene_core::vector::vector_data::modification::VectorModification" => "graphene_core::vector::vector_modification::VectorModification".to_string(),
"graphene_core::table::Table<graphene_core::graphic_element::Graphic>" => "graphene_core::table::Table<graphene_core::graphic::Graphic>".to_string(),
_ => name,
};

View File

@@ -1,16 +1,16 @@
use crate::raster_types::{CPU, Raster};
use crate::table::{Table, TableRowRef};
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, OwnedContextImpl};
use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: Table<VectorData>,
points: Table<Vector>,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
@@ -51,7 +51,7 @@ async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
@@ -99,7 +99,7 @@ mod test {
use super::*;
use crate::Node;
use crate::extract_xy::{ExtractXyNode, XY};
use crate::vector::VectorData;
use crate::vector::Vector;
use bezier_rs::Subpath;
use glam::DVec2;
use std::pin::Pin;
@@ -128,7 +128,7 @@ mod test {
);
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = Table::new_from_element(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors_linear(positions, false)));
let generated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(generated.len(), positions.len());
for (position, generated_row) in positions.into_iter().zip(generated.iter_ref()) {

View File

@@ -1,6 +1,8 @@
use crate::vector::{PointDomain, PointId, SegmentDomain, VectorData, VectorDataIndex};
use crate::vector::{PointDomain, PointId, SegmentDomain, SegmentId, Vector};
use glam::{DAffine2, DVec2};
use petgraph::graph::{EdgeIndex, NodeIndex, UnGraph};
use petgraph::prelude::UnGraphMap;
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
pub trait MergeByDistanceExt {
@@ -9,10 +11,10 @@ pub trait MergeByDistanceExt {
fn merge_by_distance_spatial(&mut self, transform: DAffine2, distance: f64);
}
impl MergeByDistanceExt for VectorData {
impl MergeByDistanceExt for Vector {
fn merge_by_distance_topological(&mut self, distance: f64) {
// Treat self as an undirected graph
let indices = VectorDataIndex::build_from(self);
let indices = VectorIndex::build_from(self);
// TODO: We lose information on the winding order by using an undirected graph. Switch to a directed graph and fix the algorithm to handle that.
// Graph containing only short edges, referencing the data graph
@@ -207,8 +209,94 @@ impl MergeByDistanceExt for VectorData {
}
}
// Create new vector data
// Create new vector geometry
self.point_domain = new_point_domain;
self.segment_domain = new_segment_domain;
}
}
/// All the fixed fields of a point from the point domain.
pub(crate) struct Point {
pub id: PointId,
pub position: DVec2,
}
/// Useful indexes to speed up various operations on [`Vector`].
///
/// Important: It is the user's responsibility to ensure the indexes remain valid after mutations to the data.
pub struct VectorIndex {
/// Points and segments form a graph. Store it here in a form amenable to graph algorithms.
///
/// Currently, segment data is not stored as it is not used, but it could easily be added.
pub(crate) point_graph: UnGraph<Point, ()>,
pub(crate) segment_to_edge: FxHashMap<SegmentId, EdgeIndex>,
/// Get the offset from the point ID.
pub(crate) point_to_offset: FxHashMap<PointId, usize>,
// TODO: faces
}
impl VectorIndex {
/// Construct a [`VectorIndex`] by building indexes from the given [`Vector`]. Takes `O(n)` time.
pub fn build_from(data: &Vector) -> Self {
let point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();
let mut point_to_node = FxHashMap::default();
let mut segment_to_edge = FxHashMap::default();
let mut graph = UnGraph::new_undirected();
for (point_id, position) in data.point_domain.iter() {
let idx = graph.add_node(Point { id: point_id, position });
point_to_node.insert(point_id, idx);
}
for (segment_id, start_offset, end_offset, ..) in data.segment_domain.iter() {
let start_id = data.point_domain.ids()[start_offset];
let end_id = data.point_domain.ids()[end_offset];
let edge = graph.add_edge(point_to_node[&start_id], point_to_node[&end_id], ());
segment_to_edge.insert(segment_id, edge);
}
Self {
point_graph: graph,
segment_to_edge,
point_to_offset,
}
}
/// Fetch the length of given segment's chord. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if no segment with the given ID is found.
pub fn segment_chord_length(&self, id: SegmentId) -> f64 {
let edge_idx = self.segment_to_edge[&id];
let (start, end) = self.point_graph.edge_endpoints(edge_idx).unwrap();
let start_position = self.point_graph.node_weight(start).unwrap().position;
let end_position = self.point_graph.node_weight(end).unwrap().position;
(start_position - end_position).length()
}
/// Get the ends of a segment. Takes `O(1)` time.
///
/// The IDs will be ordered [smallest, largest] so they can be used to find other segments with the same endpoints, regardless of direction.
///
/// # Panics
///
/// This function will panic if the ID is not present.
pub fn segment_ends(&self, id: SegmentId) -> [NodeIndex; 2] {
let (start, end) = self.point_graph.edge_endpoints(self.segment_to_edge[&id]).unwrap();
if start < end { [start, end] } else { [end, start] }
}
/// Get the physical location of a point. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if `id` isn't in the data.
pub fn point_position(&self, id: PointId, data: &Vector) -> DVec2 {
let offset = self.point_to_offset[&id];
data.point_domain.positions()[offset]
}
}

View File

@@ -3,21 +3,22 @@ use super::{PointId, SegmentId, StrokeId};
use crate::Ctx;
use crate::registry::types::{Angle, PixelSize};
use crate::table::Table;
use crate::vector::{HandleId, VectorData};
use crate::vector::Vector;
use crate::vector::misc::HandleId;
use bezier_rs::Subpath;
use glam::DVec2;
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData>;
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector>;
}
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@@ -33,7 +34,7 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
}
}
@@ -44,9 +45,9 @@ fn circle(
#[unit(" px")]
#[default(50.)]
radius: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let radius = radius.abs();
Table::new_from_element(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
Table::new_from_element(Vector::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -61,8 +62,8 @@ fn arc(
#[range((0., 360.))]
sweep_angle: Angle,
arc_type: ArcType,
) -> Table<VectorData> {
Table::new_from_element(VectorData::from_subpath(Subpath::new_arc(
) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(Subpath::new_arc(
radius,
start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU,
@@ -84,12 +85,12 @@ fn ellipse(
#[unit(" px")]
#[default(25)]
radius_y: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let mut ellipse = Vector::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
@@ -114,7 +115,7 @@ fn rectangle<T: CornerRadius>(
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
) -> Table<VectorData> {
) -> Table<Vector> {
corner_radius.generate(DVec2::new(width, height), clamped)
}
@@ -129,10 +130,10 @@ fn regular_polygon<T: AsU64>(
#[unit(" px")]
#[default(50)]
radius: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let points = sides.as_u64();
let radius: f64 = radius * 2.;
Table::new_from_element(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
Table::new_from_element(Vector::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
}
#[node_macro::node(category("Vector: Shape"))]
@@ -149,17 +150,17 @@ fn star<T: AsU64>(
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.;
let inner_diameter = radius_2 * 2.;
Table::new_from_element(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
Table::new_from_element(Vector::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<VectorData> {
Table::new_from_element(VectorData::from_subpath(Subpath::new_line(start, end)))
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(Subpath::new_line(start, end)))
}
trait GridSpacing {
@@ -189,11 +190,11 @@ fn grid<T: GridSpacing>(
#[default(10)] columns: u32,
#[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> Table<VectorData> {
) -> Table<Vector> {
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into();
let mut vector_data = VectorData::default();
let mut vector = Vector::default();
let mut segment_id = SegmentId::ZERO;
let mut point_id = PointId::ZERO;
@@ -203,13 +204,13 @@ fn grid<T: GridSpacing>(
for y in 0..rows {
for x in 0..columns {
// Add current point to the grid
let current_index = vector_data.point_domain.ids().len();
vector_data.point_domain.push(point_id.next_id(), DVec2::new(x_spacing * x as f64, y_spacing * y as f64));
let current_index = vector.point_domain.ids().len();
vector.point_domain.push(point_id.next_id(), DVec2::new(x_spacing * x as f64, y_spacing * y as f64));
// Helper function to connect points with line segments
let mut push_segment = |to_index: Option<usize>| {
if let Some(other_index) = to_index {
vector_data
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
}
@@ -233,7 +234,7 @@ fn grid<T: GridSpacing>(
for y in 0..rows {
for x in 0..columns {
// Add current point to the grid with offset for odd columns
let current_index = vector_data.point_domain.ids().len();
let current_index = vector.point_domain.ids().len();
let a_angles_eaten = x.div_ceil(2) as f64;
let b_angles_eaten = (x / 2) as f64;
@@ -241,12 +242,12 @@ fn grid<T: GridSpacing>(
let offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a;
let position = DVec2::new(spacing.x * x as f64, spacing.y * y as f64 + offset_y_fraction * spacing.x);
vector_data.point_domain.push(point_id.next_id(), position);
vector.point_domain.push(point_id.next_id(), position);
// Helper function to connect points with line segments
let mut push_segment = |to_index: Option<usize>| {
if let Some(other_index) = to_index {
vector_data
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
}
@@ -271,7 +272,7 @@ fn grid<T: GridSpacing>(
}
}
Table::new_from_element(vector_data)
Table::new_from_element(vector)
}
#[cfg(test)]

View File

@@ -1,5 +1,6 @@
use super::PointId;
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
use crate::vector::{SegmentId, Vector};
use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath};
use dyn_any::DynAny;
use glam::DVec2;
@@ -136,9 +137,9 @@ pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> P
}
pub fn subpath_to_kurbo_bezpath(subpath: Subpath<PointId>) -> BezPath {
let maniputor_groups = subpath.manipulator_groups();
let manipulator_groups = subpath.manipulator_groups();
let closed = subpath.closed();
bezpath_from_manipulator_groups(maniputor_groups, closed)
bezpath_from_manipulator_groups(manipulator_groups, closed)
}
pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>], closed: bool) -> BezPath {
@@ -181,8 +182,8 @@ pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup
kurbo::PathEl::LineTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None),
kurbo::PathEl::QuadTo(point, point1) => ManipulatorGroup::new(point_to_dvec2(point1), Some(point_to_dvec2(point)), None),
kurbo::PathEl::CurveTo(point, point1, point2) => {
if let Some(last_maipulator_group) = manipulator_groups.last_mut() {
last_maipulator_group.out_handle = Some(point_to_dvec2(point));
if let Some(last_manipulator_group) = manipulator_groups.last_mut() {
last_manipulator_group.out_handle = Some(point_to_dvec2(point));
}
ManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None)
}
@@ -237,3 +238,182 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum ManipulatorPointId {
/// A control anchor - the start or end point of a bézier.
Anchor(PointId),
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
PrimaryHandle(SegmentId),
/// The end handle on a cubic bézier.
EndHandle(SegmentId),
}
impl ManipulatorPointId {
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
#[must_use]
#[track_caller]
pub fn get_position(&self, vector: &Vector) -> Option<DVec2> {
match self {
ManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id),
ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
ManipulatorPointId::EndHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
}
}
pub fn get_anchor_position(&self, vector: &Vector) -> Option<DVec2> {
match self {
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector).and_then(|id| vector.point_domain.position_from_id(id)),
_ => self.get_position(vector),
}
}
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
#[must_use]
pub fn get_handle_pair(self, vector: &Vector) -> Option<[HandleId; 2]> {
match self {
ManipulatorPointId::Anchor(point) => vector.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let other = vector.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let other = vector.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
}
}
/// Finds all the connected handles of a point.
/// For an anchor it is all the connected handles.
/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
pub fn get_all_connected_handles(self, vector: &Vector) -> Option<Vec<HandleId>> {
match self {
ManipulatorPointId::Anchor(point) => {
let connected = vector.all_connected(point).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
}
}
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
#[must_use]
pub fn get_anchor(self, vector: &Vector) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
ManipulatorPointId::PrimaryHandle(segment) => vector.segment_start_from_id(segment),
ManipulatorPointId::EndHandle(segment) => vector.segment_end_from_id(segment),
}
}
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
#[must_use]
pub fn as_handle(self) -> Option<HandleId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
ManipulatorPointId::Anchor(_) => None,
}
}
/// Attempt to convert self to an anchor, returning None for a handle.
#[must_use]
pub fn as_anchor(self) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
_ => None,
}
}
pub fn get_segment(self) -> Option<SegmentId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) | ManipulatorPointId::EndHandle(segment) => Some(segment),
_ => None,
}
}
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
Primary,
/// The second handle on a cubic bézier.
End,
}
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub struct HandleId {
pub ty: HandleType,
pub segment: SegmentId,
}
impl std::fmt::Display for HandleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.ty {
// I haven't checked if "out" and "in" are reversed, or are accurate translations of the "primary" and "end" terms used in the `HandleType` enum, so this naming is an assumption.
HandleType::Primary => write!(f, "{} out", self.segment.inner()),
HandleType::End => write!(f, "{} in", self.segment.inner()),
}
}
}
impl HandleId {
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
#[must_use]
pub const fn primary(segment: SegmentId) -> Self {
Self { ty: HandleType::Primary, segment }
}
/// Construct a handle for the end handle on a cubic bézier.
#[must_use]
pub const fn end(segment: SegmentId) -> Self {
Self { ty: HandleType::End, segment }
}
/// Convert to [`ManipulatorPointId`].
#[must_use]
pub fn to_manipulator_point(self) -> ManipulatorPointId {
match self.ty {
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
}
}
/// Calculate the magnitude of the handle from the anchor.
pub fn length(self, vector: &Vector) -> f64 {
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector) else {
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
return 0.;
};
let handle_position = self.to_manipulator_point().get_position(vector);
handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
}
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
#[must_use]
pub fn opposite(self) -> Self {
match self.ty {
HandleType::Primary => Self::end(self.segment),
HandleType::End => Self::primary(self.segment),
}
}
}

View File

@@ -4,11 +4,13 @@ pub mod generator_nodes;
pub mod misc;
mod reference_point;
pub mod style;
mod vector_data;
mod vector_attributes;
mod vector_modification;
mod vector_nodes;
mod vector_types;
pub use bezier_rs;
pub use reference_point::*;
pub use style::PathStyle;
pub use vector_data::*;
pub use vector_nodes::*;
pub use vector_types::*;

View File

@@ -24,7 +24,7 @@ impl std::fmt::Display for Fill {
match self {
Self::None => write!(f, "None"),
Self::Solid(color) => write!(f, "#{} (Alpha: {}%)", color.to_rgb_hex_srgb(), color.a() * 100.),
Self::Gradient(gradient) => write!(f, "{}", gradient),
Self::Gradient(gradient) => write!(f, "{gradient}"),
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::vector::misc::dvec2_to_point;
use crate::vector::vector_data::{HandleId, VectorData};
use crate::vector::misc::{HandleId, dvec2_to_point};
use crate::vector::vector_types::Vector;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@@ -673,7 +673,7 @@ impl FoundSubpath {
}
}
impl VectorData {
impl Vector {
/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.
fn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {
let start = dvec2_to_point(self.point_domain.positions()[start]);
@@ -896,7 +896,7 @@ impl VectorData {
}
StrokePathIter {
vector_data: self,
vector: self,
points,
skip: 0,
done_one: false,
@@ -952,13 +952,11 @@ impl VectorData {
self.stroke_bezier_paths().flat_map(|mut path| std::mem::take(path.manipulator_groups_mut()))
}
/// Get manipulator by id
pub fn manipulator_group_id(&self, id: impl Into<PointId>) -> Option<ManipulatorGroup<PointId>> {
let id = id.into();
self.manipulator_groups().find(|group| group.id == id)
}
/// Transforms this vector data
pub fn transform(&mut self, transform: DAffine2) {
self.point_domain.transform(transform);
self.segment_domain.transform(transform);
@@ -1026,7 +1024,7 @@ impl StrokePathIterPointMetadata {
#[derive(Clone)]
pub struct StrokePathIter<'a> {
vector_data: &'a VectorData,
vector: &'a Vector,
points: Vec<StrokePathIterPointMetadata>,
skip: usize,
done_one: bool,
@@ -1056,29 +1054,29 @@ impl Iterator for StrokePathIter<'_> {
let Some(val) = self.points[point_index].take_first() else {
// Dead end
groups.push(ManipulatorGroup {
anchor: self.vector_data.point_domain.positions()[point_index],
anchor: self.vector.point_domain.positions()[point_index],
in_handle,
out_handle: None,
id: self.vector_data.point_domain.ids()[point_index],
id: self.vector.point_domain.ids()[point_index],
});
break;
};
let mut handles = self.vector_data.segment_domain.handles()[val.segment_index];
let mut handles = self.vector.segment_domain.handles()[val.segment_index];
if val.start_from_end {
handles = handles.reversed();
}
let next_point_index = if val.start_from_end {
self.vector_data.segment_domain.start_point()[val.segment_index]
self.vector.segment_domain.start_point()[val.segment_index]
} else {
self.vector_data.segment_domain.end_point()[val.segment_index]
self.vector.segment_domain.end_point()[val.segment_index]
};
groups.push(ManipulatorGroup {
anchor: self.vector_data.point_domain.positions()[point_index],
anchor: self.vector.point_domain.positions()[point_index],
in_handle,
out_handle: handles.start(),
id: self.vector_data.point_domain.ids()[point_index],
id: self.vector.point_domain.ids()[point_index],
});
in_handle = handles.end();
@@ -1102,7 +1100,7 @@ impl bezier_rs::Identifier for PointId {
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
/// Represents the conversion of IDs used when concatenating vector paths with conflicting IDs.
pub struct IdMap {
pub point_offset: usize,
pub point_map: HashMap<PointId, PointId>,

View File

@@ -1,90 +0,0 @@
use super::{PointId, SegmentId, VectorData};
use glam::DVec2;
use petgraph::graph::{EdgeIndex, NodeIndex, UnGraph};
use rustc_hash::FxHashMap;
/// All the fixed fields of a point from the point domain.
pub struct Point {
pub id: PointId,
pub position: DVec2,
}
/// Useful indexes to speed up various operations on `VectorData`.
///
/// Important: It is the user's responsibility to ensure the indexes remain valid after mutations to the data.
pub struct VectorDataIndex {
/// Points and segments form a graph. Store it here in a form amenable to graph algorithms.
///
/// Currently, segment data is not stored as it is not used, but it could easily be added.
pub(crate) point_graph: UnGraph<Point, ()>,
pub(crate) segment_to_edge: FxHashMap<SegmentId, EdgeIndex>,
/// Get the offset from the point ID.
pub(crate) point_to_offset: FxHashMap<PointId, usize>,
// TODO: faces
}
impl VectorDataIndex {
/// Construct a [`VectorDataIndex`] by building indexes from the given [`VectorData`]. Takes `O(n)` time.
pub fn build_from(data: &VectorData) -> Self {
let point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();
let mut point_to_node = FxHashMap::default();
let mut segment_to_edge = FxHashMap::default();
let mut graph = UnGraph::new_undirected();
for (point_id, position) in data.point_domain.iter() {
let idx = graph.add_node(Point { id: point_id, position });
point_to_node.insert(point_id, idx);
}
for (segment_id, start_offset, end_offset, ..) in data.segment_domain.iter() {
let start_id = data.point_domain.ids()[start_offset];
let end_id = data.point_domain.ids()[end_offset];
let edge = graph.add_edge(point_to_node[&start_id], point_to_node[&end_id], ());
segment_to_edge.insert(segment_id, edge);
}
Self {
point_graph: graph,
segment_to_edge,
point_to_offset,
}
}
/// Fetch the length of given segment's chord. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if no segment with the given ID is found.
pub fn segment_chord_length(&self, id: SegmentId) -> f64 {
let edge_idx = self.segment_to_edge[&id];
let (start, end) = self.point_graph.edge_endpoints(edge_idx).unwrap();
let start_position = self.point_graph.node_weight(start).unwrap().position;
let end_position = self.point_graph.node_weight(end).unwrap().position;
(start_position - end_position).length()
}
/// Get the ends of a segment. Takes `O(1)` time.
///
/// The IDs will be ordered [smallest, largest] so they can be used to find other segments with the same endpoints, regardless of direction.
///
/// # Panics
///
/// This function will panic if the ID is not present.
pub fn segment_ends(&self, id: SegmentId) -> [NodeIndex; 2] {
let (start, end) = self.point_graph.edge_endpoints(self.segment_to_edge[&id]).unwrap();
if start < end { [start, end] } else { [end, start] }
}
/// Get the physical location of a point. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if `id` isn't in the data.
pub fn point_position(&self, id: PointId, data: &VectorData) -> DVec2 {
let offset = self.point_to_offset[&id];
data.point_domain.positions()[offset]
}
}

View File

@@ -1,14 +1,16 @@
use super::*;
use crate::Ctx;
use crate::table::TableRow;
use crate::table::{Table, TableRow};
use crate::uuid::{NodeId, generate_uuid};
use crate::vector::misc::{HandleId, HandleType, point_to_dvec2};
use bezier_rs::BezierHandles;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use kurbo::{BezPath, PathEl, Point};
use std::collections::{HashMap, HashSet};
use std::hash::BuildHasher;
/// Represents a procedural change to the [`PointDomain`] in [`VectorData`].
/// Represents a procedural change to the [`PointDomain`] in [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PointModification {
add: Vec<PointId>,
@@ -58,12 +60,12 @@ impl PointModification {
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
Self {
add: vector_data.point_domain.ids().to_vec(),
add: vector.point_domain.ids().to_vec(),
remove: HashSet::new(),
delta: vector_data.point_domain.ids().iter().copied().zip(vector_data.point_domain.positions().iter().cloned()).collect(),
delta: vector.point_domain.ids().iter().copied().zip(vector.point_domain.positions().iter().cloned()).collect(),
}
}
@@ -79,7 +81,7 @@ impl PointModification {
}
}
/// Represents a procedural change to the [`SegmentDomain`] in [`VectorData`].
/// Represents a procedural change to the [`SegmentDomain`] in [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SegmentModification {
add: Vec<SegmentId>,
@@ -177,11 +179,11 @@ impl SegmentModification {
let Some(&stroke) = self.stroke.get(&add_id) else { continue };
let Some(start_index) = point_domain.resolve_id(start) else {
warn!("invalid start id: {:#?}", start);
warn!("invalid start id: {start:#?}");
continue;
};
let Some(end_index) = point_domain.resolve_id(end) else {
warn!("invalid end id: {:#?}", end);
warn!("invalid end id: {end:#?}");
continue;
};
@@ -206,27 +208,25 @@ impl SegmentModification {
assert!(
segment_domain.start_point().iter().all(|&index| index < point_domain.ids().len()),
"index should be in range {:#?}",
segment_domain
"index should be in range {segment_domain:#?}"
);
assert!(
segment_domain.end_point().iter().all(|&index| index < point_domain.ids().len()),
"index should be in range {:#?}",
segment_domain
"index should be in range {segment_domain:#?}"
);
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
let point_id = |(&segment, &index)| (segment, vector_data.point_domain.ids()[index]);
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
let point_id = |(&segment, &index)| (segment, vector.point_domain.ids()[index]);
Self {
add: vector_data.segment_domain.ids().to_vec(),
add: vector.segment_domain.ids().to_vec(),
remove: HashSet::new(),
start_point: vector_data.segment_domain.ids().iter().zip(vector_data.segment_domain.start_point()).map(point_id).collect(),
end_point: vector_data.segment_domain.ids().iter().zip(vector_data.segment_domain.end_point()).map(point_id).collect(),
handle_primary: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
handle_end: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
stroke: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.stroke().iter().cloned()).collect(),
start_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.start_point()).map(point_id).collect(),
end_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.end_point()).map(point_id).collect(),
handle_primary: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
handle_end: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
stroke: vector.segment_domain.ids().iter().copied().zip(vector.segment_domain.stroke().iter().cloned()).collect(),
}
}
@@ -251,7 +251,7 @@ impl SegmentModification {
}
}
/// Represents a procedural change to the [`RegionDomain`] in [`VectorData`].
/// Represents a procedural change to the [`RegionDomain`] in [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RegionModification {
add: Vec<RegionId>,
@@ -284,18 +284,18 @@ impl RegionModification {
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
Self {
add: vector_data.region_domain.ids().to_vec(),
add: vector.region_domain.ids().to_vec(),
remove: HashSet::new(),
segment_range: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.segment_range().iter().cloned()).collect(),
fill: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.fill().iter().cloned()).collect(),
segment_range: vector.region_domain.ids().iter().copied().zip(vector.region_domain.segment_range().iter().cloned()).collect(),
fill: vector.region_domain.ids().iter().copied().zip(vector.region_domain.fill().iter().cloned()).collect(),
}
}
}
/// Represents a procedural change to the [`VectorData`].
/// Represents a procedural change to the [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct VectorModification {
points: PointModification,
@@ -327,27 +327,27 @@ pub enum VectorModificationType {
}
impl VectorModification {
/// Apply this modification to the specified [`VectorData`].
pub fn apply(&self, vector_data: &mut VectorData) {
self.points.apply(&mut vector_data.point_domain, &mut vector_data.segment_domain);
self.segments.apply(&mut vector_data.segment_domain, &vector_data.point_domain);
self.regions.apply(&mut vector_data.region_domain);
/// Apply this modification to the specified [`Vector`].
pub fn apply(&self, vector: &mut Vector) {
self.points.apply(&mut vector.point_domain, &mut vector.segment_domain);
self.segments.apply(&mut vector.segment_domain, &vector.point_domain);
self.regions.apply(&mut vector.region_domain);
let valid = |val: &[HandleId; 2]| vector_data.segment_domain.ids().contains(&val[0].segment) && vector_data.segment_domain.ids().contains(&val[1].segment);
vector_data
let valid = |val: &[HandleId; 2]| vector.segment_domain.ids().contains(&val[0].segment) && vector.segment_domain.ids().contains(&val[1].segment);
vector
.colinear_manipulators
.retain(|val| !self.remove_g1_continuous.contains(val) && !self.remove_g1_continuous.contains(&[val[1], val[0]]) && valid(val));
for handles in &self.add_g1_continuous {
if !vector_data.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
vector_data.colinear_manipulators.push(*handles);
if !vector.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
vector.colinear_manipulators.push(*handles);
}
}
}
/// Add a [`VectorModificationType`] to this modification.
pub fn modify(&mut self, vector_data_modification: &VectorModificationType) {
match vector_data_modification {
pub fn modify(&mut self, vector_modification: &VectorModificationType) {
match vector_modification {
VectorModificationType::InsertSegment { id, points, handles } => self.segments.push(*id, *points, *handles, StrokeId::ZERO),
VectorModificationType::InsertPoint { id, position } => self.points.push(*id, *position),
@@ -400,13 +400,13 @@ impl VectorModification {
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
Self {
points: PointModification::create_from_vector(vector_data),
segments: SegmentModification::create_from_vector(vector_data),
regions: RegionModification::create_from_vector(vector_data),
add_g1_continuous: vector_data.colinear_manipulators.iter().copied().collect(),
points: PointModification::create_from_vector(vector),
segments: SegmentModification::create_from_vector(vector),
regions: RegionModification::create_from_vector(vector),
add_g1_continuous: vector.colinear_manipulators.iter().copied().collect(),
remove_g1_continuous: HashSet::new(),
}
}
@@ -420,38 +420,38 @@ impl Hash for VectorModification {
/// Applies a diff modification to a vector path.
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: Table<VectorData>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<VectorData> {
if vector_data.is_empty() {
vector_data.push(TableRow::default());
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<Vector> {
if vector.is_empty() {
vector.push(TableRow::default());
}
let row = vector_data.get_mut(0).expect("push should give one item");
let row = vector.get_mut(0).expect("push should give one item");
modification.apply(row.element);
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
*row.source_node_id = row.source_node_id.or(this_node_path);
if vector_data.len() > 1 {
warn!("The path modify ran on {} rows of vector data. Only the first can be modified.", vector_data.len());
if vector.len() > 1 {
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
}
vector_data
vector
}
/// Applies the vector path's local transformation to its geometry and resets it to the identity.
#[node_macro::node(category("Vector"))]
async fn apply_transform(_ctx: impl Ctx, mut vector_data: Table<VectorData>) -> Table<VectorData> {
for row in vector_data.iter_mut() {
let vector_data = row.element;
async fn apply_transform(_ctx: impl Ctx, mut vector: Table<Vector>) -> Table<Vector> {
for row in vector.iter_mut() {
let vector = row.element;
let transform = *row.transform;
for (_, point) in vector_data.point_domain.positions_mut() {
for (_, point) in vector.point_domain.positions_mut() {
*point = transform.transform_point2(*point);
}
*row.transform = DAffine2::IDENTITY;
}
vector_data
vector
}
// Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
@@ -524,11 +524,11 @@ pub struct AppendBezpath<'a> {
last_segment_id: Option<SegmentId>,
point_id: PointId,
segment_id: SegmentId,
vector_data: &'a mut VectorData,
vector: &'a mut Vector,
}
impl<'a> AppendBezpath<'a> {
fn new(vector_data: &'a mut VectorData) -> Self {
fn new(vector: &'a mut Vector) -> Self {
Self {
first_point: None,
last_point: None,
@@ -536,9 +536,9 @@ impl<'a> AppendBezpath<'a> {
last_point_index: None,
first_segment_id: None,
last_segment_id: None,
point_id: vector_data.point_domain.next_id(),
segment_id: vector_data.segment_domain.next_id(),
vector_data,
point_id: vector.point_domain.next_id(),
segment_id: vector.segment_domain.next_id(),
vector,
}
}
@@ -555,28 +555,28 @@ impl<'a> AppendBezpath<'a> {
// Create a new segment.
let next_segment_id = self.segment_id.next_id();
self.vector_data
self.vector
.segment_domain
.push(next_segment_id, self.last_point_index.unwrap(), self.first_point_index.unwrap(), handle, StrokeId::ZERO);
// Create a new region.
let next_region_id = self.vector_data.region_domain.next_id();
let next_region_id = self.vector.region_domain.next_id();
let first_segment_id = self.first_segment_id.unwrap_or(next_segment_id);
let last_segment_id = next_segment_id;
self.vector_data.region_domain.push(next_region_id, first_segment_id..=last_segment_id, FillId::ZERO);
self.vector.region_domain.push(next_region_id, first_segment_id..=last_segment_id, FillId::ZERO);
}
fn append_segment(&mut self, end_point: Point, handle: BezierHandles) {
// Append the point.
let next_point_index = self.vector_data.point_domain.ids().len();
let next_point_index = self.vector.point_domain.ids().len();
let next_point_id = self.point_id.next_id();
self.vector_data.point_domain.push(next_point_id, point_to_dvec2(end_point));
self.vector.point_domain.push(next_point_id, point_to_dvec2(end_point));
// Append the segment.
let next_segment_id = self.segment_id.next_id();
self.vector_data
self.vector
.segment_domain
.push(next_segment_id, self.last_point_index.unwrap(), next_point_index, handle, StrokeId::ZERO);
@@ -593,8 +593,8 @@ impl<'a> AppendBezpath<'a> {
self.last_point = Some(point);
// Append the first point.
let next_point_index = self.vector_data.point_domain.ids().len();
self.vector_data.point_domain.push(self.point_id.next_id(), point_to_dvec2(point));
let next_point_index = self.vector.point_domain.ids().len();
self.vector.point_domain.push(self.point_id.next_id(), point_to_dvec2(point));
// Update the state.
self.first_point_index = Some(next_point_index);
@@ -610,8 +610,8 @@ impl<'a> AppendBezpath<'a> {
self.last_segment_id = None;
}
pub fn append_bezpath(vector_data: &'a mut VectorData, bezpath: BezPath) {
let mut this = Self::new(vector_data);
pub fn append_bezpath(vector: &'a mut Vector, bezpath: BezPath) {
let mut this = Self::new(vector);
let mut elements = bezpath.elements().iter().peekable();
while let Some(element) = elements.next() {
@@ -656,12 +656,11 @@ impl<'a> AppendBezpath<'a> {
}
}
pub trait VectorDataExt {
/// Appends a Kurbo BezPath to the vector data.
pub trait VectorExt {
fn append_bezpath(&mut self, bezpath: BezPath);
}
impl VectorDataExt for VectorData {
impl VectorExt for Vector {
fn append_bezpath(&mut self, bezpath: BezPath) {
AppendBezpath::append_bezpath(self, bezpath);
}
@@ -689,16 +688,16 @@ mod tests {
#[test]
fn modify_new() {
let vector_data = VectorData::from_subpaths(
let vector = Vector::from_subpaths(
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
false,
);
let modify = VectorModification::create_from_vector(&vector_data);
let modify = VectorModification::create_from_vector(&vector);
let mut new = VectorData::default();
let mut new = Vector::default();
modify.apply(&mut new);
assert_eq!(vector_data, new);
assert_eq!(vector, new);
}
#[test]
@@ -715,32 +714,32 @@ mod tests {
false,
),
];
let mut vector_data = VectorData::from_subpaths(subpaths, false);
let mut vector = Vector::from_subpaths(subpaths, false);
let mut modify_new = VectorModification::create_from_vector(&vector_data);
let mut modify_new = VectorModification::create_from_vector(&vector);
let mut modify_original = VectorModification::default();
for modification in [&mut modify_new, &mut modify_original] {
let point = vector_data.point_domain.ids()[0];
let point = vector.point_domain.ids()[0];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X * 0.5 });
let point = vector_data.point_domain.ids()[9];
let point = vector.point_domain.ids()[9];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X });
}
let mut new = VectorData::default();
let mut new = Vector::default();
modify_new.apply(&mut new);
modify_original.apply(&mut vector_data);
modify_original.apply(&mut vector);
assert_eq!(vector_data, new);
assert_eq!(vector_data.point_domain.positions()[0], DVec2::X);
assert_eq!(vector_data.point_domain.positions()[9], DVec2::new(11., 0.));
assert_eq!(vector, new);
assert_eq!(vector.point_domain.positions()[0], DVec2::X);
assert_eq!(vector.point_domain.positions()[9], DVec2::new(11., 0.));
assert_eq!(
vector_data.segment_bezier_iter().nth(8).unwrap().1,
vector.segment_bezier_iter().nth(8).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.))
);
assert_eq!(
vector_data.segment_bezier_iter().nth(9).unwrap().1,
vector.segment_bezier_iter().nth(9).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.))
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,24 @@
mod attributes;
mod indexed;
mod modification;
use super::misc::{dvec2_to_point, point_to_dvec2};
use super::misc::dvec2_to_point;
use super::style::{PathStyle, Stroke};
pub use super::vector_attributes::*;
pub use super::vector_modification::*;
use crate::bounds::BoundingBox;
use crate::math::quad::Quad;
use crate::table::Table;
use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::vector::misc::{HandleId, ManipulatorPointId};
use crate::{AlphaBlending, Color, Graphic};
pub use attributes::*;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use core::borrow::Borrow;
use core::hash::Hash;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
pub use indexed::VectorDataIndex;
use kurbo::{Affine, BezPath, Rect, Shape};
pub use modification::*;
use std::collections::HashMap;
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<VectorData>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct OldVectorData {
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<Table<Graphic>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
VectorData(VectorData),
OldVectorData(OldVectorData),
VectorDataTable(Table<VectorData>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => Table::new_from_element(vector_data),
EitherFormat::OldVectorData(old) => {
let mut vector_data_table = Table::new_from_element(VectorData {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain,
segment_domain: old.segment_domain,
region_domain: old.region_domain,
upstream_graphic_group: old.upstream_graphic_group,
});
*vector_data_table.iter_mut().next().unwrap().transform = old.transform;
*vector_data_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_data_table
}
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
})
}
/// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
///
/// Segments are connected if they share endpoints.
/// Represents vector graphics data, composed of Bézier curves in a path or mesh arrangement.
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct VectorData {
pub struct Vector {
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
@@ -88,11 +29,11 @@ pub struct VectorData {
pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<Table<Graphic>>,
// Used to store the upstream group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_group: Option<Table<Graphic>>,
}
impl Default for VectorData {
impl Default for Vector {
fn default() -> Self {
Self {
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
@@ -100,12 +41,12 @@ impl Default for VectorData {
point_domain: PointDomain::new(),
segment_domain: SegmentDomain::new(),
region_domain: RegionDomain::new(),
upstream_graphic_group: None,
upstream_group: None,
}
}
}
impl std::hash::Hash for VectorData {
impl std::hash::Hash for Vector {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.point_domain.hash(state);
self.segment_domain.hash(state);
@@ -115,8 +56,8 @@ impl std::hash::Hash for VectorData {
}
}
impl VectorData {
/// Push a subpath to the vector data
impl Vector {
/// Add a Bezier-rs subpath to this path.
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
let stroke_id = StrokeId::ZERO;
@@ -188,40 +129,40 @@ impl VectorData {
self.point_domain.push(id, point.position);
}
/// Construct some new vector data from a single subpath with an identity transform and black fill.
/// Construct some new vector path from a single Bezier-rs subpath with an identity transform and black fill.
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
Self::from_subpaths([subpath], false)
}
/// Construct some new vector data from a single [`BezPath`] with an identity transform and black fill.
/// Construct some new vector path from a single [`BezPath`] with an identity transform and black fill.
pub fn from_bezpath(bezpath: BezPath) -> Self {
let mut vector_data = Self::default();
vector_data.append_bezpath(bezpath);
vector_data
let mut vector = Self::default();
vector.append_bezpath(bezpath);
vector
}
/// Construct some new vector data from subpaths with an identity transform and black fill.
/// Construct some new vector path from Bezier-rs subpaths with an identity transform and black fill.
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
let mut vector_data = Self::default();
let mut vector = Self::default();
for subpath in subpaths.into_iter() {
vector_data.append_subpath(subpath, preserve_id);
vector.append_subpath(subpath, preserve_id);
}
vector_data
vector
}
pub fn from_target_types(target_types: impl IntoIterator<Item = impl Borrow<ClickTargetType>>, preserve_id: bool) -> Self {
let mut vector_data = Self::default();
let mut vector = Self::default();
for target_type in target_types.into_iter() {
match target_type.borrow() {
ClickTargetType::Subpath(subpath) => vector_data.append_subpath(subpath, preserve_id),
ClickTargetType::FreePoint(point) => vector_data.append_free_point(point, preserve_id),
ClickTargetType::Subpath(subpath) => vector.append_subpath(subpath, preserve_id),
ClickTargetType::FreePoint(point) => vector.append_free_point(point, preserve_id),
}
}
vector_data
vector
}
/// Compute the bounding boxes of the bezpaths without any transform
@@ -374,12 +315,12 @@ impl VectorData {
self.point_domain.resolve_id(point).map_or(0, |point| self.segment_domain.connected_count(point))
}
pub fn check_point_inside_shape(&self, vector_data_transform: DAffine2, point: DVec2) -> bool {
pub fn check_point_inside_shape(&self, transform: DAffine2, point: DVec2) -> bool {
let number = self
.stroke_bezpath_iter()
.map(|mut bezpath| {
// TODO: apply transform to points instead of modifying the paths
bezpath.apply_affine(Affine::new(vector_data_transform.to_cols_array()));
bezpath.apply_affine(Affine::new(transform.to_cols_array()));
bezpath.close_path();
let bbox = bezpath.bounding_box();
(bezpath, bbox)
@@ -493,7 +434,7 @@ impl VectorData {
}
}
impl BoundingBox for Table<VectorData> {
impl BoundingBox for Table<Vector> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref()
.flat_map(|row| {
@@ -516,212 +457,84 @@ impl BoundingBox for Table<VectorData> {
}
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum ManipulatorPointId {
/// A control anchor - the start or end point of a bézier.
Anchor(PointId),
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
PrimaryHandle(SegmentId),
/// The end handle on a cubic bézier.
EndHandle(SegmentId),
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {
use serde::Deserialize;
impl ManipulatorPointId {
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
#[must_use]
#[track_caller]
pub fn get_position(&self, vector_data: &VectorData) -> Option<DVec2> {
match self {
ManipulatorPointId::Anchor(id) => vector_data.point_domain.position_from_id(*id),
ManipulatorPointId::PrimaryHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
ManipulatorPointId::EndHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct OldVectorData {
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain,
// Used to store the upstream group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<Table<Graphic>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
Vector(Vector),
OldVectorData(OldVectorData),
VectorTable(Table<Vector>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::Vector(vector) => Table::new_from_element(vector),
EitherFormat::OldVectorData(old) => {
let mut vector_table = Table::new_from_element(Vector {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain,
segment_domain: old.segment_domain,
region_domain: old.region_domain,
upstream_group: old.upstream_graphic_group,
});
*vector_table.iter_mut().next().unwrap().transform = old.transform;
*vector_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_table
}
}
pub fn get_anchor_position(&self, vector_data: &VectorData) -> Option<DVec2> {
match self {
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector_data).and_then(|id| vector_data.point_domain.position_from_id(id)),
_ => self.get_position(vector_data),
}
}
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
#[must_use]
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
match self {
ManipulatorPointId::Anchor(point) => vector_data.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
}
}
/// Finds all the connected handles of a point.
/// For an anchor it is all the connected handles.
/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
pub fn get_all_connected_handles(self, vector_data: &VectorData) -> Option<Vec<HandleId>> {
match self {
ManipulatorPointId::Anchor(point) => {
let connected = vector_data.all_connected(point).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let connected = vector_data.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let connected = vector_data.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
}
}
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
#[must_use]
pub fn get_anchor(self, vector_data: &VectorData) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
ManipulatorPointId::PrimaryHandle(segment) => vector_data.segment_start_from_id(segment),
ManipulatorPointId::EndHandle(segment) => vector_data.segment_end_from_id(segment),
}
}
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
#[must_use]
pub fn as_handle(self) -> Option<HandleId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
ManipulatorPointId::Anchor(_) => None,
}
}
/// Attempt to convert self to an anchor, returning None for a handle.
#[must_use]
pub fn as_anchor(self) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
_ => None,
}
}
pub fn get_segment(self) -> Option<SegmentId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) | ManipulatorPointId::EndHandle(segment) => Some(segment),
_ => None,
}
}
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
Primary,
/// The second handle on a cubic bézier.
End,
}
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub struct HandleId {
pub ty: HandleType,
pub segment: SegmentId,
}
impl std::fmt::Display for HandleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.ty {
// I haven't checked if "out" and "in" are reversed, or are accurate translations of the "primary" and "end" terms used in the `HandleType` enum, so this naming is an assumption.
HandleType::Primary => write!(f, "{} out", self.segment.inner()),
HandleType::End => write!(f, "{} in", self.segment.inner()),
}
}
}
impl HandleId {
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
#[must_use]
pub const fn primary(segment: SegmentId) -> Self {
Self { ty: HandleType::Primary, segment }
}
/// Construct a handle for the end handle on a cubic bézier.
#[must_use]
pub const fn end(segment: SegmentId) -> Self {
Self { ty: HandleType::End, segment }
}
/// Convert to [`ManipulatorPointId`].
#[must_use]
pub fn to_manipulator_point(self) -> ManipulatorPointId {
match self.ty {
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
}
}
/// Calculate the magnitude of the handle from the anchor.
pub fn length(self, vector_data: &VectorData) -> f64 {
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector_data) else {
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
return 0.;
};
let handle_position = self.to_manipulator_point().get_position(vector_data);
handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
}
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
#[must_use]
pub fn opposite(self) -> Self {
match self.ty {
HandleType::Primary => Self::end(self.segment),
HandleType::End => Self::primary(self.segment),
}
}
}
#[cfg(test)]
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
assert_eq!(generated.closed(), expected.closed());
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
assert_eq!(generated.in_handle, expected.in_handle);
assert_eq!(generated.out_handle, expected.out_handle);
assert_eq!(generated.anchor, expected.anchor);
}
}
EitherFormat::VectorTable(vector_table) => vector_table,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
assert_eq!(generated.closed(), expected.closed());
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
assert_eq!(generated.in_handle, expected.in_handle);
assert_eq!(generated.out_handle, expected.out_handle);
assert_eq!(generated.anchor, expected.anchor);
}
}
}
#[test]
fn construct_closed_subpath() {
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpath(&circle);
assert_eq!(vector_data.point_domain.ids().len(), 4);
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let vector = Vector::from_subpath(&circle);
assert_eq!(vector.point_domain.ids().len(), 4);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths.len(), 4);
assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[circle]);
}
@@ -729,12 +542,12 @@ mod tests {
fn construct_open_subpath() {
let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let subpath = bezier_rs::Subpath::from_bezier(&bezier);
let vector_data = VectorData::from_subpath(&subpath);
assert_eq!(vector_data.point_domain.ids().len(), 2);
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let vector = Vector::from_subpath(&subpath);
assert_eq!(vector.point_domain.ids().len(), 2);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths, vec![bezier]);
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[subpath]);
}
@@ -744,14 +557,14 @@ mod tests {
let curve = bezier_rs::Subpath::from_bezier(&curve);
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpaths([&curve, &circle], false);
assert_eq!(vector_data.point_domain.ids().len(), 6);
let vector = Vector::from_subpaths([&curve, &circle], false);
assert_eq!(vector.point_domain.ids().len(), 6);
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths.len(), 5);
assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[curve, circle]);
}
}

View File

@@ -4,7 +4,7 @@ use glam::{DAffine2, DVec2};
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};
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};
@@ -35,7 +35,7 @@ pub enum BooleanOperation {
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(Table<Graphic>, Table<VectorData>)]
#[implementations(Table<Graphic>, Table<Vector>)]
group_of_paths: I,
/// Which boolean operation to perform on the paths.
///
@@ -44,55 +44,55 @@ async fn boolean_operation<I: Into<Table<Graphic>> + '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,
) -> Table<VectorData> {
) -> Table<Vector> {
let group_of_paths = group_of_paths.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).iter_ref(), operation);
let mut result_vector_table = boolean_operation_on_vector_table(flatten_vector(&group_of_paths).iter_ref(), operation);
// Replace the transformation matrix with a mutation of the vector points themselves
if let Some(result_vector_data) = result_vector_data_table.iter_mut().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.element, transform);
result_vector_data.element.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.element.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_group = Some(group_of_paths.clone());
// Clean up the boolean operation result by merging duplicated points
result_vector_data.element.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 = TableRowRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> Table<VectorData> {
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 = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
// 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 = Table::new_from_row(vector_data_reversed.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row 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
// Loop over all vector table rows and union it with the result
let default = TableRow::default();
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.as_ref()));
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
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_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, 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,27 @@ fn union<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'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 = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
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 = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row 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 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_row.transform.inverse() * *lower_vector_data.transform;
while let Some(lower_vector) = next_vector {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, 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 +134,29 @@ fn subtract<'a>(vector_data: impl Iterator<Item = TableRowRef<'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 = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
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 = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row 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 = TableRow::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.as_ref()));
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_row.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_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, 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,28 +166,28 @@ fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'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 = TableRowRef<'a, VectorData>> + Clone) -> Table<VectorData> {
let mut vector_data_iter = vector_data.clone().rev();
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_data = Some(vector_data_iter.next().unwrap_or(default.as_ref()));
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);
// 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_ref().next().expect("Expected at least one row after the boolean union");
let transform_of_lower_into_space_of_upper = first_row.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_row.element, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, 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_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@@ -213,70 +213,70 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, V
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.iter_ref().chain(std::iter::once(any_intersection.as_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_ref().chain(std::iter::once(any_intersection.as_ref())), BooleanOperation::SubtractFront)
}
fn flatten_vector_data(graphic_group_table: &Table<Graphic>) -> Table<VectorData> {
graphic_group_table
fn flatten_vector(group_table: &Table<Graphic>) -> Table<Vector> {
group_table
.iter_ref()
.flat_map(|element| {
match element.element.clone() {
Graphic::VectorData(vector_data) => {
// Apply the parent group's transform to each element of vector data
vector_data
Graphic::Vector(vector) => {
// Apply the parent group's transform to each element of the vector table
vector
.iter()
.map(|mut sub_vector_data| {
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
.map(|mut sub_vector| {
sub_vector.transform = *element.transform * sub_vector.transform;
sub_vector_data
sub_vector
})
.collect::<Vec<_>>()
}
Graphic::RasterDataCPU(image) => {
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 element = VectorData::from_subpath(subpath);
// 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));
TableRow { element, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
// Apply the parent group's transform to each raster element
image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
}
Graphic::RasterDataGPU(image) => {
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 element = VectorData::from_subpath(subpath);
// 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));
TableRow { element, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
// Apply the parent group's transform to each raster element
image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
}
Graphic::GraphicGroup(mut graphic_group) => {
Graphic::Group(mut group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.iter_mut() {
for sub_element in group.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).iter_ref(), BooleanOperation::Union);
// Recursively flatten the inner group into the vector table
let unioned = boolean_operation_on_vector_table(flatten_vector(&group).iter_ref(), BooleanOperation::Union);
unioned.iter().collect::<Vec<_>>()
}
@@ -285,7 +285,7 @@ fn flatten_vector_data(graphic_group_table: &Table<Graphic>) -> Table<VectorData
.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);
@@ -318,7 +318,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 {
@@ -362,7 +362,7 @@ fn from_path(path_data: &[Path]) -> VectorData {
}
}
VectorData::from_subpaths(all_subpaths, false)
Vector::from_subpaths(all_subpaths, false)
}
type Path = Vec<path_bool::PathSegment>;

View File

@@ -920,7 +920,7 @@ impl NodeNetwork {
if !node.visible && node.implementation != identity_node {
node.implementation = identity_node;
// Connect layer node to the graphic group below
// Connect layer node to the group below
node.inputs.drain(1..);
node.manual_composition = None;
self.nodes.insert(id, node);

View File

@@ -12,7 +12,7 @@ use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::ReferencePoint;
use graphene_core::uuid::NodeId;
use graphene_core::vector::VectorData;
use graphene_core::vector::Vector;
use graphene_core::vector::style::Fill;
use graphene_core::{Artboard, Color, Graphic, MemoHash, Node, Type};
use graphene_svg_renderer::RenderMetadata;
@@ -180,17 +180,19 @@ tagged_value! {
// ===========
// TABLE TYPES
// ===========
#[serde(alias = "GraphicElement")]
Graphic(Graphic),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code
VectorData(Table<VectorData>),
GraphicUnused(Graphic), // TODO: This is unused but removing it causes `cargo test` to infinitely recurse its type solving; figure out why and then remove this
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::vector::migrate_vector"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "VectorData")]
Vector(Table<Vector>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ImageFrame")]
RasterData(Table<Raster<CPU>>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(Table<Graphic>),
#[serde(alias = "ImageFrame", alias = "RasterData")]
Raster(Table<Raster<CPU>>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic::migrate_group"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "GraphicGroup")]
Group(Table<Graphic>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::artboard::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(Table<Artboard>),
#[serde(alias = "ArtboardGroup")]
Artboard(Table<Artboard>),
// ============
// STRUCT TYPES
// ============

View File

@@ -1,6 +1,6 @@
use graph_craft::wasm_application_io::WasmEditorApi;
pub use graphene_core::text::*;
use graphene_core::{Ctx, table::Table, vector::VectorData};
use graphene_core::{Ctx, table::Table, vector::Vector};
#[node_macro::node(category(""))]
fn text<'i: 'n>(
@@ -28,10 +28,10 @@ fn text<'i: 'n>(
#[default(0.)]
tilt: f64,
align: TextAlign,
/// Splits each text glyph into its own row in the table of vector data.
/// Splits each text glyph into its own row in the table of vector geometry.
#[default(false)]
per_glyph_instances: bool,
) -> Table<VectorData> {
) -> Table<Vector> {
let typesetting = TypesettingConfig {
font_size,
line_height_ratio,

View File

@@ -9,7 +9,7 @@ use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData;
use graphene_core::vector::Vector;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, Graphic, OwnedContextImpl, WasmNotSend};
use graphene_svg_renderer::RenderMetadata;
use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
@@ -215,7 +215,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl Render, editor: &
async fn rasterize<T: WasmNotSend + 'n>(
_: impl Ctx,
#[implementations(
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
)]
@@ -283,7 +283,7 @@ async fn render<'a: 'n, T: 'n + Render + WasmNotSend>(
render_config: RenderConfig,
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
#[implementations(
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Graphic>,
Context -> Table<Artboard>,

View File

@@ -13,7 +13,7 @@ use graphene_core::render_complexity::RenderComplexity;
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::{Footprint, Transform};
use graphene_core::uuid::{NodeId, generate_uuid};
use graphene_core::vector::VectorData;
use graphene_core::vector::Vector;
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
use graphene_core::{Artboard, Graphic};
@@ -365,7 +365,7 @@ impl Render for Table<Graphic> {
}
}
if let Some(graphic_group_id) = element_id {
if let Some(group_id) = element_id {
let mut all_upstream_click_targets = Vec::new();
for row in self.iter_ref() {
@@ -379,7 +379,7 @@ impl Render for Table<Graphic> {
all_upstream_click_targets.extend(new_click_targets);
}
metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets);
metadata.click_targets.insert(group_id, all_upstream_click_targets);
}
}
@@ -408,20 +408,20 @@ impl Render for Table<Graphic> {
}
}
impl Render for Table<VectorData> {
impl Render for Table<Vector> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for row in self.iter_ref() {
let multiplied_transform = *row.transform;
let vector_data = &row.element;
let vector = &row.element;
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
let has_real_stroke = vector_data.style.stroke().filter(|stroke| stroke.weight() > 0.);
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
let transformed_bounds = vector_data.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let layer_bounds = vector.bounding_box().unwrap_or_default();
let transformed_bounds = vector.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let mut path = String::new();
@@ -429,12 +429,12 @@ impl Render for Table<VectorData> {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
}
let connected = vector_data.stroke_bezier_paths().all(|path| path.closed());
let can_draw_aligned_stroke = vector_data.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && connected;
let connected = vector.stroke_bezier_paths().all(|path| path.closed());
let can_draw_aligned_stroke = vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && connected;
let mut push_id = None;
if can_draw_aligned_stroke {
let mask_type = if vector_data.style.stroke().unwrap().align == StrokeAlign::Inside {
let mask_type = if vector.style.stroke().unwrap().align == StrokeAlign::Inside {
MaskType::Clip
} else {
MaskType::Mask
@@ -566,7 +566,7 @@ impl Render for Table<VectorData> {
element.style.clear_stroke();
element.style.set_fill(Fill::solid(Color::BLACK));
let vector_data = Table::new_from_row(TableRow {
let vector_table = Table::new_from_row(TableRow {
element,
alpha_blending: *row.alpha_blending,
transform: *row.transform,
@@ -580,7 +580,7 @@ impl Render for Table<VectorData> {
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
vector_data.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
vector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
scene.push_layer(peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect);
}
@@ -730,11 +730,11 @@ impl Render for Table<VectorData> {
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
for row in self.iter_ref() {
let transform = *row.transform;
let vector_data = row.element;
let vector = row.element;
if let Some(element_id) = element_id {
let stroke_width = vector_data.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = vector_data.style.fill() != &Fill::None;
let stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = vector.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| {
if filled {
subpath.set_closed(true);
@@ -743,9 +743,9 @@ impl Render for Table<VectorData> {
};
// For free-floating anchors, we need to add a click target for each
let single_anchors_targets = vector_data.point_domain.ids().iter().filter_map(|&point_id| {
if vector_data.connected_count(point_id) == 0 {
let anchor = vector_data.point_domain.position_from_id(point_id).unwrap_or_default();
let single_anchors_targets = vector.point_domain.ids().iter().filter_map(|&point_id| {
if vector.connected_count(point_id) == 0 {
let anchor = vector.point_domain.position_from_id(point_id).unwrap_or_default();
let point = FreePoint::new(point_id, anchor);
Some(ClickTarget::new_with_free_point(point))
@@ -754,7 +754,7 @@ impl Render for Table<VectorData> {
}
});
let click_targets = vector_data
let click_targets = vector
.stroke_bezier_paths()
.map(fill)
.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width))
@@ -764,9 +764,9 @@ impl Render for Table<VectorData> {
metadata.click_targets.entry(element_id).or_insert(click_targets);
}
if let Some(upstream_graphic_group) = &vector_data.upstream_graphic_group {
if let Some(upstream_group) = &vector.upstream_group {
footprint.transform *= transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None);
upstream_group.collect_metadata(metadata, footprint, None);
}
}
}
@@ -853,7 +853,7 @@ impl Render for Artboard {
},
// Artboard contents
|render| {
self.graphic_group.render_svg(render, render_params);
self.group.render_svg(render, render_params);
},
);
}
@@ -875,9 +875,9 @@ impl Render for Artboard {
let blend_mode = peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcOver);
scene.push_layer(blend_mode, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
}
// Since the graphic group's transform is right multiplied in when rendering the graphic group, we just need to right multiply by the offset here.
// Since the group's transform is right multiplied in when rendering the group, we just need to right multiply by the offset here.
let child_transform = transform * DAffine2::from_translation(self.location.as_dvec2());
self.graphic_group.render_to_vello(scene, child_transform, context, render_params);
self.group.render_to_vello(scene, child_transform, context, render_params);
if self.clip {
scene.pop_layer();
}
@@ -894,7 +894,7 @@ impl Render for Artboard {
}
}
footprint.transform *= self.transform();
self.graphic_group.collect_metadata(metadata, footprint, None);
self.group.collect_metadata(metadata, footprint, None);
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@@ -1140,38 +1140,38 @@ impl Render for Table<Raster<GPU>> {
impl Render for Graphic {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self {
Graphic::VectorData(vector_data) => vector_data.render_svg(render, render_params),
Graphic::RasterDataCPU(raster) => raster.render_svg(render, render_params),
Graphic::RasterDataGPU(_raster) => (),
Graphic::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
Graphic::Vector(vector) => vector.render_svg(render, render_params),
Graphic::RasterCPU(raster) => raster.render_svg(render, render_params),
Graphic::RasterGPU(_) => (),
Graphic::Group(group) => group.render_svg(render, render_params),
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
match self {
Graphic::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context, render_params),
Graphic::RasterDataCPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::RasterDataGPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(vector) => vector.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::Group(group) => group.render_to_vello(scene, transform, context, render_params),
}
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id {
match self {
Graphic::GraphicGroup(_) => {
Graphic::Group(_) => {
metadata.upstream_footprints.insert(element_id, footprint);
}
Graphic::VectorData(vector_data) => {
Graphic::Vector(vector) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.iter_ref().next() {
metadata.first_element_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform);
if let Some(vector) = vector.iter_ref().next() {
metadata.first_element_source_id.insert(element_id, *vector.source_node_id);
metadata.local_transforms.insert(element_id, *vector.transform);
}
}
Graphic::RasterDataCPU(raster_frame) => {
Graphic::RasterCPU(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images
@@ -1179,7 +1179,7 @@ impl Render for Graphic {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
Graphic::RasterDataGPU(raster_frame) => {
Graphic::RasterGPU(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images
@@ -1191,37 +1191,37 @@ impl Render for Graphic {
}
match self {
Graphic::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
Graphic::RasterDataCPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::RasterDataGPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(vector) => vector.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::Group(group) => group.collect_metadata(metadata, footprint, element_id),
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self {
Graphic::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets),
Graphic::RasterDataCPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::RasterDataGPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets),
Graphic::Vector(vector) => vector.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::Group(group) => group.add_upstream_click_targets(click_targets),
}
}
fn contains_artboard(&self) -> bool {
match self {
Graphic::VectorData(vector_data) => vector_data.contains_artboard(),
Graphic::GraphicGroup(graphic_group) => graphic_group.contains_artboard(),
Graphic::RasterDataCPU(raster) => raster.contains_artboard(),
Graphic::RasterDataGPU(raster) => raster.contains_artboard(),
Graphic::Vector(vector) => vector.contains_artboard(),
Graphic::Group(group) => group.contains_artboard(),
Graphic::RasterCPU(raster) => raster.contains_artboard(),
Graphic::RasterGPU(raster) => raster.contains_artboard(),
}
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self {
Graphic::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
Graphic::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
Graphic::RasterDataCPU(_) => (),
Graphic::RasterDataGPU(_) => (),
Graphic::Vector(vector) => vector.new_ids_from_hash(reference),
Graphic::Group(group) => group.new_ids_from_hash(reference),
Graphic::RasterCPU(_) => (),
Graphic::RasterGPU(_) => (),
}
}
}

View File

@@ -16,7 +16,7 @@ use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::table::Table;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
#[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
use node_registry_macros::{async_node, convert_node, into_node};
@@ -31,9 +31,9 @@ use wgpu_executor::{WgpuSurface, WindowHandle};
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: Table<VectorData>, to: Table<VectorData>),
into_node!(from: Table<VectorData>, to: Graphic),
into_node!(from: Table<VectorData>, to: Table<Graphic>),
into_node!(from: Table<Vector>, to: Table<Vector>),
into_node!(from: Table<Vector>, to: Graphic),
into_node!(from: Table<Vector>, to: Table<Graphic>),
into_node!(from: Table<Graphic>, to: Table<Graphic>),
into_node!(from: Table<Graphic>, to: Graphic),
into_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>),
@@ -43,7 +43,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
into_node!(from: Table<Raster<CPU>>, to: Table<Graphic>),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
@@ -78,7 +78,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
@@ -94,7 +94,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),

View File

@@ -910,7 +910,7 @@ mod tests {
let attr = quote!(category("Vector: Shape"));
let input = quote!(
/// Test
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData {
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> Vector {
// Implementation details...
}
);
@@ -937,7 +937,7 @@ mod tests {
ty: parse_quote!(impl Ctx),
implementations: Punctuated::new(),
},
output_type: parse_quote!(VectorData),
output_type: parse_quote!(Vector),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("radius"),