mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
// ============
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>]),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user