Centralize attribute strings in consts and rename "editor:layer" to "editor:layer_path" (#4076)

* Rename "editor:layer" to "editor:layer_path" and centralize it in a const

* Centralize "editor:merged_layers" in a const

* Centralize all other attributes in consts

* Rename consts with ATTR_ prefix

* Format
This commit is contained in:
Keavon Chambers 2026-04-28 21:53:30 -07:00 committed by GitHub
parent 5774ec215d
commit 0847d7b0ab
25 changed files with 306 additions and 254 deletions

View File

@ -993,7 +993,7 @@ fn table_node_id_path_layout_with_breadcrumb(path: &Table<NodeId>, data: &mut La
/// Mirrors [`dispatch_value_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`].
/// Returns `None` for unrecognized types.
fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// `Table<NodeId>` is interpreted as a path (e.g. the `editor:layer` attribute), so each item's NodeId value
// `Table<NodeId>` is interpreted as a path (e.g. the `editor:layer_path` attribute), so each item's NodeId value
// resolves against the prefix made up of preceding items. Handled before the generic `Table<T>` blanket impl.
if let Some(path) = any.downcast_ref::<Table<NodeId>>() {
return Some(table_node_id_path_layout_with_breadcrumb(path, data));

View File

@ -239,12 +239,12 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
..Default::default()
},
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer` attribute,
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer_path` attribute,
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(1), 0),
NodeInput::value(TaggedValue::String(String::from("editor:layer")), false),
NodeInput::value(TaggedValue::String(graphene_std::ATTR_EDITOR_LAYER_PATH.to_string()), false),
NodeInput::node(NodeId(2), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::write_attribute::IDENTIFIER),
@ -374,12 +374,12 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: DocumentNodeImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
..Default::default()
},
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer` attribute,
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer_path` attribute,
// so editor tools (e.g. selection, click target routing) can trace data back to its owning layer.
DocumentNode {
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::value(TaggedValue::String(String::from("editor:layer")), false),
NodeInput::value(TaggedValue::String(graphene_std::ATTR_EDITOR_LAYER_PATH.to_string()), false),
NodeInput::node(NodeId(1), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic::write_attribute::IDENTIFIER),

View File

@ -10,6 +10,7 @@ use crate::messages::prelude::ViewportMessageHandler;
use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2};
use graphene_std::ATTR_TRANSFORM;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::{self, Subpath};
use graphene_std::table::Table;
@ -1170,7 +1171,7 @@ impl OverlayContextInternal {
// Use the existing bezier_to_path infrastructure to convert Vector to BezPath
let mut path = BezPath::new();
let mut last_point = None;
let transform: DAffine2 = text_table.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = text_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let Some(element) = text_table.element(index) else { continue };
for (_, bezier, start_id, end_id) in element.segment_iter() {

View File

@ -33,6 +33,7 @@ pub use num_traits;
use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
pub use table::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_END, ATTR_NAME, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE};
#[cfg(feature = "wasm")]
pub use tsify;
pub use types::Cow;

View File

@ -5,6 +5,45 @@ use dyn_any::{StaticType, StaticTypeSized};
use glam::DAffine2;
use std::fmt::Debug;
// =====================================================================
// Standard attribute keys used across the data flow
// =====================================================================
/// Attribute key for a row's `DAffine2` transformation, applied when rendering and when accumulating
/// transforms through nested compositions.
pub const ATTR_TRANSFORM: &str = "transform";
/// Attribute key for a row's `AlphaBlending` (blend mode + opacity + fill + clip), composed
/// multiplicatively through nested compositions.
pub const ATTR_ALPHA_BLENDING: &str = "alpha_blending";
/// Attribute key under which each row of an editor-aware layer stores a `Table<NodeId>` describing the
/// path (from the root document network) to the layer node that owns the row. Editor tools use this to
/// route clicks/selection back to the originating layer at any nesting depth.
pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path";
/// Attribute key under which a row stores a `Table<Graphic>` snapshot of the upstream content that fed
/// into a destructive merge (Boolean Operation, Flatten Path, Morph, Rasterize, etc.). The renderer
/// recurses into this snapshot during metadata collection so the editor can still surface click targets
/// for the original child layers after their content has been collapsed into a single output.
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
/// Attribute key for the byte offset where a regex match begins in the input string, set by the
/// `regex_find_all` and `regex_capture` text nodes.
pub const ATTR_START: &str = "start";
/// Attribute key for the byte offset where a regex match ends in the input string, set by the
/// `regex_find_all` and `regex_capture` text nodes.
pub const ATTR_END: &str = "end";
/// Attribute key for a regex named-capture-group's name (empty for unnamed groups), set by the
/// `regex_capture` text node.
pub const ATTR_NAME: &str = "name";
/// Attribute key for a JSON value's type (`"string"`, `"number"`, `"object"`, etc.), set by the
/// `json_query_all` text node alongside each extracted value.
pub const ATTR_TYPE: &str = "type";
// =====================
// TRAIT: AttributeValue
// =====================
@ -747,7 +786,7 @@ impl<T: BoundingBox> BoundingBox for Table<T> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
let mut combined_bounds = None;
for (element, row_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<DAffine2>("transform")) {
for (element, row_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM)) {
match element.bounding_box(transform * row_transform, include_stroke) {
RenderBoundingBox::None => continue,
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
@ -793,10 +832,10 @@ impl<T: graphene_hash::CacheHash> graphene_hash::CacheHash for Table<T> {
for element in self.iter_element_values() {
element.cache_hash(state);
}
for transform in self.iter_attribute_values_or_default::<DAffine2>("transform") {
for transform in self.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM) {
graphene_hash::CacheHash::cache_hash(&transform, state);
}
for alpha_blending in self.iter_attribute_values_or_default::<crate::AlphaBlending>("alpha_blending") {
for alpha_blending in self.iter_attribute_values_or_default::<crate::AlphaBlending>(ATTR_ALPHA_BLENDING) {
alpha_blending.cache_hash(state);
}
}
@ -811,14 +850,14 @@ impl<T: PartialEq> PartialEq for Table<T> {
impl<T> ApplyTransform for Table<T> {
/// Right-multiplies the modification into each row's transform attribute.
fn apply_transform(&mut self, modification: &DAffine2) {
for transform in self.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for transform in self.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform *= *modification;
}
}
/// Left-multiplies the modification into each row's transform attribute.
fn left_apply_transform(&mut self, modification: &DAffine2) {
for transform in self.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for transform in self.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = *modification * *transform;
}
}

View File

@ -1,5 +1,4 @@
use crate::graphic::Graphic;
use core_types::Color;
use core_types::blending::AlphaBlending;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::math::quad::Quad;
@ -7,6 +6,7 @@ use core_types::render_complexity::RenderComplexity;
use core_types::table::{Table, TableRow};
use core_types::transform::Transform;
use core_types::uuid::NodeId;
use core_types::{ATTR_TRANSFORM, Color};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2};
use graphene_hash::CacheHash;
@ -52,7 +52,7 @@ impl BoundingBox for Artboard {
let mut combined_bounds = None;
for (element, row_transform) in self.content.iter_element_values().zip(self.content.iter_attribute_values_or_default::<DAffine2>("transform")) {
for (element, row_transform) in self.content.iter_element_values().zip(self.content.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM)) {
match element.bounding_box(transform * row_transform, include_stroke) {
RenderBoundingBox::None => continue,
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
@ -113,7 +113,7 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
alpha_blending: Vec<AlphaBlending>,
}
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
Ok(match ArtboardFormat::deserialize(deserializer)? {
ArtboardFormat::ArtboardGroup(artboard_group) => artboard_group.artboards.into_iter().map(|(artboard, _)| TableRow::new_from_element(artboard)).collect(),

View File

@ -1,4 +1,3 @@
use core_types::Color;
use core_types::blending::AlphaBlending;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash;
@ -6,6 +5,7 @@ use core_types::ops::TableConvert;
use core_types::render_complexity::RenderComplexity;
use core_types::table::{Table, TableRow};
use core_types::uuid::NodeId;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Color};
use dyn_any::DynAny;
use glam::DAffine2;
use raster_types::{CPU, GPU, Raster};
@ -141,19 +141,19 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
fn flatten_recursive<T>(output: &mut Table<T>, current_graphic_table: Table<Graphic>, extract_variant: fn(Graphic) -> Option<Table<T>>) {
for current_graphic_row in current_graphic_table.into_iter() {
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default("editor:layer");
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform");
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending");
let layer_path: Table<NodeId> = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM);
let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
match current_graphic_row.into_element() {
// Recurse into nested `Table<Graphic>` items, composing the parent's transform onto each child
Graphic::Graphic(mut sub_table) => {
for index in 0..sub_table.len() {
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default("transform", index);
let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default("alpha_blending", index);
let child_transform: DAffine2 = sub_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
sub_table.set_attribute("transform", index, current_transform * child_transform);
sub_table.set_attribute("alpha_blending", index, compose_alpha_blending(current_alpha_blending, child_alpha_blending));
sub_table.set_attribute(ATTR_TRANSFORM, index, current_transform * child_transform);
sub_table.set_attribute(ATTR_ALPHA_BLENDING, index, compose_alpha_blending(current_alpha_blending, child_alpha_blending));
}
flatten_recursive(output, sub_table, extract_variant);
@ -162,13 +162,13 @@ fn flatten_graphic_table<T>(content: Table<Graphic>, extract_variant: fn(Graphic
other => {
if let Some(typed_table) = extract_variant(other) {
for row in typed_table.into_iter() {
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default("alpha_blending");
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
let (element, mut attributes) = row.into_parts();
attributes.insert("transform", current_transform * row_transform);
attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending));
attributes.insert("editor:layer", layer_path.clone());
attributes.insert(ATTR_TRANSFORM, current_transform * row_transform);
attributes.insert(ATTR_ALPHA_BLENDING, compose_alpha_blending(current_alpha_blending, row_alpha_blending));
attributes.insert(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
output.push(TableRow::from_parts(element, attributes));
}
@ -321,7 +321,7 @@ impl Graphic {
pub fn had_clip_enabled(&self) -> bool {
fn all_clipped<T>(table: &Table<T>) -> bool {
table.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending").all(|a| a.clip)
table.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING).all(|a| a.clip)
}
match self {
Graphic::Vector(table) => all_clipped(table),
@ -337,7 +337,7 @@ impl Graphic {
match self {
Graphic::Vector(vector) => vector
.iter_element_values()
.zip(vector.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending"))
.zip(vector.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING))
.all(|(element, alpha_blending)| {
(alpha_blending.opacity > 1. - f32::EPSILON) && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
}),
@ -504,7 +504,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
Table(serde_json::Value),
}
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
Ok(match GraphicFormat::deserialize(deserializer)? {
GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(),

View File

@ -72,7 +72,7 @@ pub mod migrations {
Ok(match VectorFormat::deserialize(deserializer)? {
VectorFormat::Vector(vector) => Table::new_from_element(vector),
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
VectorFormat::OldVectorData(old) => Table::new_from_element(Vector {
style: old.style,

View File

@ -1,9 +1,8 @@
use crate::raster_types::{CPU, Raster};
use crate::{Bitmap, BitmapMut};
use core_types::AlphaBlending;
use core_types::Color;
use core_types::color::float_to_srgb_u8;
use core_types::table::{Table, TableRow};
use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM, AlphaBlending, Color};
// use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently
use core_types::color::*;
use dyn_any::{DynAny, StaticType};
@ -319,7 +318,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
Table::new_from_element(Raster::new_cpu(table.element(0).unwrap().clone()))
}
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
// to recover the elements. Per-item attribute values are populated at runtime by the node graph.
fn old_table_to_new_table<T>(old_table: OldTable<T>) -> Table<T> {
old_table.element.into_iter().map(TableRow::new_from_element).collect()
@ -339,8 +338,8 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
image_frame_table.set_attribute("transform", 0, transform);
image_frame_table.set_attribute("alpha_blending", 0, alpha_blending);
image_frame_table.set_attribute(ATTR_TRANSFORM, 0, transform);
image_frame_table.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending);
image_frame_table
}
FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)),
@ -430,7 +429,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
RasterTableRow(TableRow<Raster<CPU>>),
}
// Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs
// Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs
// to recover the element. Per-item attribute values are populated at runtime by the node graph.
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)),

View File

@ -10,6 +10,7 @@ use core_types::render_complexity::RenderComplexity;
use core_types::table::{Table, TableRow};
use core_types::transform::{Footprint, Transform};
use core_types::uuid::{NodeId, generate_uuid};
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use graphene_hash::CacheHashWrapper;
@ -412,9 +413,9 @@ impl Render for Graphic {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
let layer_path: Table<NodeId> = table.attribute_cloned_or_default("editor:layer", 0);
let layer_path: Table<NodeId> = table.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
let layer = layer_path.iter_element_values().next_back().copied();
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
let transform: DAffine2 = table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.first_element_source_id.insert(element_id, layer);
metadata.local_transforms.insert(element_id, transform);
@ -425,7 +426,7 @@ impl Render for Graphic {
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
Graphic::RasterGPU(table) => {
@ -433,7 +434,7 @@ impl Render for Graphic {
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
Graphic::Color(table) => {
@ -441,7 +442,7 @@ impl Render for Graphic {
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
Graphic::Gradient(table) => {
@ -449,7 +450,7 @@ impl Render for Graphic {
// TODO: Find a way to handle more than the first item
if !table.is_empty() {
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
}
}
}
@ -551,7 +552,7 @@ impl Render for Artboard {
|attributes| {
let matrix = format_transform_matrix(self.transform());
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
if self.clip {
@ -656,7 +657,7 @@ impl Render for Table<Artboard> {
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for index in 0..self.len() {
let layer_path: Table<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied();
self.element(index).unwrap().collect_metadata(metadata, footprint, layer);
}
@ -678,8 +679,8 @@ impl Render for Table<Graphic> {
let mut mask_state = None;
for index in 0..self.len() {
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
let element = self.element(index).unwrap();
render.parent_tag(
@ -687,7 +688,7 @@ impl Render for Table<Graphic> {
|attributes| {
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
let opacity = alpha_blending.opacity(render_params.for_mask);
@ -732,9 +733,9 @@ impl Render for Table<Graphic> {
let mut mask_element_and_transform = None;
for index in 0..self.len() {
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let transform = transform * row_transform;
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
let element = self.element(index).unwrap();
let mut layer = false;
@ -806,8 +807,8 @@ impl Render for Table<Graphic> {
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for index in 0..self.len() {
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied();
let element = self.element(index).unwrap();
@ -817,7 +818,7 @@ impl Render for Table<Graphic> {
if let Some(element_id) = layer {
element.collect_metadata(metadata, footprint, Some(element_id));
} else {
// Recurse through anonymous wrapper items to reach nested content with editor:layer tags
// Recurse through anonymous wrapper items to reach nested content with editor:layer_path tags
element.collect_metadata(metadata, footprint, None);
}
}
@ -826,7 +827,7 @@ impl Render for Table<Graphic> {
let mut all_upstream_click_targets = Vec::new();
for index in 0..self.len() {
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let element = self.element(index).unwrap();
let mut new_click_targets = Vec::new();
element.add_upstream_click_targets(&mut new_click_targets);
@ -844,7 +845,7 @@ impl Render for Table<Graphic> {
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for index in 0..self.len() {
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let element = self.element(index).unwrap();
let mut new_click_targets = Vec::new();
@ -863,7 +864,7 @@ impl Render for Table<Graphic> {
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
let (elements, layers) = self.element_and_attribute_slices_mut::<Table<NodeId>>("editor:layer");
let (elements, layers) = self.element_and_attribute_slices_mut::<Table<NodeId>>(ATTR_EDITOR_LAYER_PATH);
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
element.new_ids_from_hash(layer.iter_element_values().next_back().copied());
}
@ -874,8 +875,8 @@ impl Render for Table<Vector> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() {
let Some(vector) = self.element(index) else { continue };
let multiplied_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let multiplied_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
// 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.style.stroke().filter(|stroke| stroke.weight() > 0.);
@ -915,7 +916,7 @@ impl Render for Table<Vector> {
attributes.push("d", path.clone());
let matrix = format_transform_matrix(element_transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
let mut style = vector.style.clone();
style.clear_stroke();
@ -940,8 +941,8 @@ impl Render for Table<Vector> {
let vector_row = Table::new_from_row(
TableRow::new_from_element(cloned_vector)
.with_attribute("transform", multiplied_transform)
.with_attribute("alpha_blending", alpha_blending),
.with_attribute(ATTR_TRANSFORM, multiplied_transform)
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending),
);
(id, mask_type, vector_row)
@ -957,7 +958,7 @@ impl Render for Table<Vector> {
attributes.push("d", face_d.clone());
let matrix = format_transform_matrix(element_transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
let mut style = vector.style.clone();
style.clear_stroke();
@ -978,7 +979,7 @@ impl Render for Table<Vector> {
attributes.push("d", path.clone());
let matrix = format_transform_matrix(element_transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
let defs = &mut attributes.0.svg_defs;
@ -1043,7 +1044,7 @@ impl Render for Table<Vector> {
attributes.push("d", path);
let matrix = format_transform_matrix(element_transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
let mut style = vector.style.clone();
style.clear_stroke();
@ -1068,8 +1069,8 @@ impl Render for Table<Vector> {
use graphic_types::vector_types::vector;
let Some(element) = self.element(index) else { continue };
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
let multiplied_transform = parent_transform * row_transform;
let has_real_stroke = element.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
@ -1257,8 +1258,8 @@ impl Render for Table<Vector> {
let vector_table = Table::new_from_row(
TableRow::new_from_element(cloned_element)
.with_attribute("transform", row_transform)
.with_attribute("alpha_blending", alpha_blending),
.with_attribute(ATTR_TRANSFORM, row_transform)
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending),
);
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
@ -1329,12 +1330,12 @@ impl Render for Table<Vector> {
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
for index in 0..self.len() {
let Some(vector) = self.element(index) else { continue };
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default("editor:layer", index);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let layer_path: Table<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let layer = layer_path.iter_element_values().next_back().copied();
if let Some(element_id) = caller_element_id.or(layer) {
// When recovering element_id from the item's editor:layer tag (because the caller
// When recovering element_id from the item's editor:layer_path tag (because the caller
// passed None), also store the transform metadata that Graphic::collect_metadata
// normally provides but skipped due to the None element_id.
if caller_element_id.is_none() {
@ -1378,7 +1379,7 @@ impl Render for Table<Vector> {
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
// surface the original child layers' click targets.
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", index);
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
if !upstream_nested_layers.is_empty() {
let mut upstream_footprint = footprint;
upstream_footprint.transform *= transform;
@ -1390,7 +1391,7 @@ impl Render for Table<Vector> {
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for index in 0..self.len() {
let Some(vector) = self.element(index) else { continue };
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::effective_width);
let filled = vector.style.fill() != &Fill::None;
@ -1435,8 +1436,8 @@ impl Render for Table<Raster<CPU>> {
for index in 0..self.len() {
let Some(image) = self.element(index) else { continue };
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
if image.data.is_empty() {
continue;
@ -1457,7 +1458,7 @@ impl Render for Table<Raster<CPU>> {
let matrix = DAffine2::from_scale_angle_translation(transform_values.0, transform_values.1, transform_values.2);
let matrix = format_transform_matrix(matrix);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
attributes.push("width", size.x.to_string());
@ -1500,7 +1501,7 @@ impl Render for Table<Raster<CPU>> {
attributes.push("href", base64_string);
let matrix = format_transform_matrix(transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
attributes.push(ATTR_TRANSFORM, matrix);
}
let opacity = alpha_blending.opacity(render_params.for_mask);
@ -1522,7 +1523,7 @@ impl Render for Table<Raster<CPU>> {
continue;
}
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
let blend_mode = alpha_blending.blend_mode.to_peniko();
let opacity = alpha_blending.opacity(render_params.for_mask);
@ -1537,7 +1538,7 @@ impl Render for Table<Raster<CPU>> {
layer = true;
}
let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index);
let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
if let RenderMode::Outline = render_params.render_mode {
let outline_transform: DAffine2 = transform * transform_attribute;
@ -1577,7 +1578,7 @@ impl Render for Table<Raster<CPU>> {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>`
if !self.is_empty() {
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.local_transforms.insert(element_id, transform);
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
@ -1586,7 +1587,7 @@ impl Render for Table<Raster<CPU>> {
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", 0);
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
if !upstream_nested_layers.is_empty() {
upstream_nested_layers.collect_metadata(metadata, footprint, None);
}
@ -1609,7 +1610,7 @@ impl Render for Table<Raster<GPU>> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for index in 0..self.len() {
let Some(raster) = self.element(index) else { continue };
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
let blend_mode = match render_params.render_mode {
RenderMode::Outline => peniko::Mix::Normal,
_ => alpha_blending.blend_mode.to_peniko(),
@ -1626,7 +1627,7 @@ impl Render for Table<Raster<GPU>> {
layer = true;
}
let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index);
let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
if let RenderMode::Outline = render_params.render_mode {
let outline_transform = transform * transform_attribute;
@ -1667,7 +1668,7 @@ impl Render for Table<Raster<GPU>> {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one item of the `Table<Raster<...>>`
if !self.is_empty() {
let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
metadata.local_transforms.insert(element_id, transform);
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
@ -1676,7 +1677,7 @@ impl Render for Table<Raster<GPU>> {
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>("editor:merged_layers", 0);
let upstream_nested_layers = self.attribute_cloned_or_default::<Table<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
if !upstream_nested_layers.is_empty() {
upstream_nested_layers.collect_metadata(metadata, footprint, None);
}
@ -1697,7 +1698,7 @@ impl Render for Table<Raster<GPU>> {
// later replace with the current viewport transform before each render.
impl Render for Table<Color> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
render.leaf_tag("polyline", |attributes| {
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
let max = u64::MAX;
@ -1723,7 +1724,7 @@ impl Render for Table<Color> {
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko;
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
let blend_mode = alpha_blending.blend_mode.to_peniko();
let opacity = alpha_blending.opacity(render_params.for_mask);
@ -1752,8 +1753,8 @@ impl Render for Table<GradientStops> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for index in 0..self.len() {
let Some(gradient) = self.element(index) else { continue };
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index);
render.leaf_tag("rect", |attributes| {
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
let max = u64::MAX;
@ -1820,7 +1821,7 @@ impl Render for Table<GradientStops> {
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko;
for (gradient, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
for (gradient, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING)) {
let blend_mode = alpha_blending.blend_mode.to_peniko();
let opacity = alpha_blending.opacity(render_params.for_mask);

View File

@ -1,6 +1,7 @@
//! Contains stylistic options for SVG elements.
pub use crate::gradient::*;
use core_types::ATTR_ALPHA_BLENDING;
use core_types::AlphaBlending;
use core_types::Color;
use core_types::color::Alpha;
@ -135,7 +136,7 @@ impl From<Option<Color>> for Fill {
impl From<Table<Color>> for Fill {
fn from(color: Table<Color>) -> Fill {
let alpha = color.attribute_cloned_or_default::<AlphaBlending>("alpha_blending", 0).opacity;
let alpha = color.attribute_cloned_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING, 0).opacity;
let color = color.element(0).copied();
Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha)))
}

View File

@ -1,7 +1,7 @@
use core_types::AlphaBlending;
use core_types::registry::types::Percentage;
use core_types::table::Table;
use core_types::{BlendMode, Color, Ctx};
use core_types::{ATTR_ALPHA_BLENDING, BlendMode, Color, Ctx};
use graphic_types::Graphic;
use graphic_types::Vector;
use graphic_types::raster_types::{CPU, Raster};
@ -18,35 +18,35 @@ impl MultiplyAlpha for Color {
}
impl MultiplyAlpha for Table<Vector> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for Table<Graphic> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for Table<Raster<CPU>> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for Table<Color> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
}
}
impl MultiplyAlpha for Table<GradientStops> {
fn multiply_alpha(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.opacity *= factor as f32;
}
}
@ -62,35 +62,35 @@ impl MultiplyFill for Color {
}
impl MultiplyFill for Table<Vector> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
}
}
impl MultiplyFill for Table<Graphic> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
}
}
impl MultiplyFill for Table<Raster<CPU>> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
}
}
impl MultiplyFill for Table<Color> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
}
}
impl MultiplyFill for Table<GradientStops> {
fn multiply_fill(&mut self, factor: f64) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.fill *= factor as f32;
}
}
@ -102,35 +102,35 @@ trait SetBlendMode {
impl SetBlendMode for Table<Vector> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for Table<Graphic> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for Table<Raster<CPU>> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for Table<Color> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
}
}
impl SetBlendMode for Table<GradientStops> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.blend_mode = blend_mode;
}
}
@ -142,35 +142,35 @@ trait SetClip {
impl SetClip for Table<Vector> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
}
}
impl SetClip for Table<Graphic> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
}
}
impl SetClip for Table<Raster<CPU>> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
}
}
impl SetClip for Table<Color> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
}
}
impl SetClip for Table<GradientStops> {
fn set_clip(&mut self, clip: bool) {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>("alpha_blending") {
for a in self.iter_attribute_values_mut_or_default::<AlphaBlending>(ATTR_ALPHA_BLENDING) {
a.clip = clip;
}
}

View File

@ -10,6 +10,7 @@ use core_types::table::{Table, TableRow};
use core_types::transform::Transform;
use core_types::uuid::NodeId;
use core_types::value::ClonedNode;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM};
use core_types::{AlphaBlending, Ctx, Node};
use glam::{DAffine2, DVec2};
use raster_nodes::blending_nodes::blend_colors;
@ -90,7 +91,7 @@ where
return target;
}
let (elements, transforms) = target.element_and_attribute_slices_mut::<DAffine2>("transform");
let (elements, transforms) = target.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (element, transform_attribute) in elements.iter_mut().zip(transforms.iter()) {
let target_width = element.width;
let target_height = element.height;
@ -280,7 +281,7 @@ async fn brush(
let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
if has_erase_or_restore_strokes {
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute("transform", background_bounds);
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute(ATTR_TRANSFORM, background_bounds);
for stroke in strokes.into_iter().map(|row| row.into_element()) {
let mut brush_texture = cache.get_cached_brush(&stroke.style);
@ -312,14 +313,14 @@ async fn brush(
actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b)));
}
let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform");
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending");
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default("editor:layer");
let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM);
let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING);
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
*image.element_mut(0).unwrap() = actual_image.into_element();
image.set_attribute("transform", 0, transform);
image.set_attribute("alpha_blending", 0, alpha_blending);
image.set_attribute("editor:layer", 0, layer);
image.set_attribute(ATTR_TRANSFORM, 0, transform);
image.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending);
image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
image
}
@ -329,8 +330,8 @@ pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: Ta
let background_size = DVec2::new(background.element().width as f64, background.element().height as f64);
// Transforms a point from the background image to the foreground image
let foreground_transform: DAffine2 = foreground.attribute_cloned_or_default("transform");
let background_transform: DAffine2 = background.attribute_cloned_or_default("transform");
let foreground_transform: DAffine2 = foreground.attribute_cloned_or_default(ATTR_TRANSFORM);
let background_transform: DAffine2 = background.attribute_cloned_or_default(ATTR_TRANSFORM);
let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size);
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space
@ -361,7 +362,7 @@ pub fn blend_stamp_closure(foreground: BrushStampGenerator<Color>, mut backgroun
let background_size = DVec2::new(background.element().width as f64, background.element().height as f64);
// Transforms a point from the background image to the foreground image
let background_transform: DAffine2 = background.attribute_cloned_or_default("transform");
let background_transform: DAffine2 = background.attribute_cloned_or_default(ATTR_TRANSFORM);
let background_to_foreground = background_transform * DAffine2::from_scale(1. / background_size);
// Footprint of the foreground image (0, 0)..(1, 1) in the background image space

View File

@ -1,5 +1,6 @@
use crate::brush_stroke::BrushStroke;
use crate::brush_stroke::BrushStyle;
use core_types::ATTR_TRANSFORM;
use core_types::graphene_hash::CacheHashWrapper;
use core_types::table::TableRow;
use dyn_any::DynAny;
@ -65,7 +66,7 @@ impl BrushCacheImpl {
// Check if the first non-blended stroke is an extension of the last one.
// Transform is set to ZERO (not the default IDENTITY) as a sentinel to mark this item as uninitialized.
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default()).with_attribute("transform", glam::DAffine2::ZERO);
let mut first_stroke_texture = TableRow::new_from_element(Raster::<CPU>::default()).with_attribute(ATTR_TRANSFORM, glam::DAffine2::ZERO);
let mut first_stroke_point_skip = 0;
let strokes = input[num_blended_strokes..].to_vec();
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {

View File

@ -2,7 +2,7 @@ use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::registry::types::{Angle, SignedInteger};
use core_types::table::{Table, TableRow};
use core_types::uuid::NodeId;
use core_types::{AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicTable};
use graphic_types::{Artboard, Vector};
@ -196,8 +196,8 @@ where
// Create and add mirrored items
for mut row in content.into_iter() {
let current_transform: DAffine2 = row.attribute_cloned_or_default("transform");
row.set_attribute("transform", reflected_transform * current_transform);
let current_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.set_attribute(ATTR_TRANSFORM, reflected_transform * current_transform);
result_table.push(row);
}
@ -207,7 +207,7 @@ where
/// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path`
/// with its own trailing entry dropped. The terminating element of the returned path is the document node whose
/// encapsulated network we live in, so the path doubles as a unique reference to that node at any nesting depth.
/// Used as the value source for stamping the `editor:layer` attribute on each item of a layer's output, which lets
/// Used as the value source for stamping the `editor:layer_path` attribute on each item of a layer's output, which lets
/// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether
/// the layer is at the root document network or nested inside a custom subgraph.
#[node_macro::node(name("Path of Subgraph"), category(""))]
@ -296,7 +296,7 @@ pub async fn legacy_layer_extend<T: 'n + Send + Clone>(
let mut base = base;
for mut row in new.into_iter() {
row.set_attribute("editor:layer", layer);
row.set_attribute(ATTR_EDITOR_LAYER_PATH, layer);
base.push(row);
}
@ -348,7 +348,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
for index in 0..current_graphic_table.len() {
let Some(current_element) = current_graphic_table.element(index) else { continue };
let current_element = current_element.clone();
let current_transform: DAffine2 = current_graphic_table.attribute_cloned_or_default("transform", index);
let current_transform: DAffine2 = current_graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let recurse = fully_flatten || recursion_depth == 0;
@ -356,7 +356,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table<Graphic>, fully_flatten
// If we're allowed to recurse, flatten any graphics we encounter
Graphic::Graphic(mut current_element) if recurse => {
// Apply the parent graphic's transform to all child elements
for graphic_transform in current_element.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for graphic_transform in current_element.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*graphic_transform = current_transform * *graphic_transform;
}

View File

@ -3,12 +3,12 @@ use base64::Engine;
#[cfg(target_family = "wasm")]
use canvas_utils::{Canvas, CanvasHandle};
#[cfg(target_family = "wasm")]
use core_types::WasmNotSend;
#[cfg(target_family = "wasm")]
use core_types::math::bbox::Bbox;
use core_types::table::{Table, TableRow};
#[cfg(target_family = "wasm")]
use core_types::transform::Footprint;
#[cfg(target_family = "wasm")]
use core_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, WasmNotSend};
use core_types::{Color, Ctx};
pub use graph_craft::application_io::*;
pub use graph_craft::document::value::RenderOutputType;
@ -210,7 +210,7 @@ where
..Default::default()
};
for transform in data.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for transform in data.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = DAffine2::from_translation(-aabb.start) * *transform;
}
data.render_svg(&mut render, &render_params);
@ -237,7 +237,7 @@ where
let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32);
Table::new_from_row(
TableRow::new_from_element(Raster::new_cpu(image))
.with_attribute("transform", footprint.transform)
.with_attribute("editor:merged_layers", upstream_graphic_table),
.with_attribute(ATTR_TRANSFORM, footprint.transform)
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_table),
)
}

View File

@ -1,6 +1,6 @@
use core_types::table::{Table, TableRow};
use core_types::uuid::NodeId;
use core_types::{AlphaBlending, Color, Ctx};
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AlphaBlending, Color, Ctx};
use glam::{DAffine2, DVec2};
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
use graphic_types::vector_types::vector::PointId;
@ -40,8 +40,8 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
// Replace the transformation matrix with a mutation of the vector points themselves
if result_vector_table.element_mut(0).is_some() {
let transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
result_vector_table.set_attribute("transform", 0, DAffine2::IDENTITY);
let transform: DAffine2 = result_vector_table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
result_vector_table.set_attribute(ATTR_TRANSFORM, 0, DAffine2::IDENTITY);
let result_vector = result_vector_table.element_mut(0).unwrap();
Vector::transform(result_vector, transform);
@ -49,10 +49,10 @@ async fn boolean_operation<I: graphic_types::IntoGraphicTable + 'n + Send + Clon
// Snapshot the input layers as the `editor:merged_layers` attribute so the renderer can recurse into them
// for editor click-target preservation.
result_vector_table.set_attribute("editor:merged_layers", 0, content.clone());
result_vector_table.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, content.clone());
// Clean up the boolean operation result by merging duplicated points
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default("transform", 0);
let merge_transform: DAffine2 = result_vector_table.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
result_vector_table.element_mut(0).unwrap().merge_by_distance_spatial(merge_transform, 0.0001);
}
@ -126,7 +126,7 @@ fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation:
let mut row = if let Some(index) = copy_from_index {
let mut attributes = vector.clone_row_attributes(index);
// The boolean op bakes input transforms into the output geometry, so the result item carries no transform of its own
attributes.insert("transform", DAffine2::IDENTITY);
attributes.insert(ATTR_TRANSFORM, DAffine2::IDENTITY);
let copy_from = vector.element(index).unwrap();
let element = Vector {
style: copy_from.style.clone(),
@ -139,7 +139,7 @@ fn boolean_operation_on_vector_table(vector: &Table<Vector>, boolean_operation:
for index in 0..vector.len() {
let element = vector.element(index).unwrap();
paths.push(to_bez_path(element, vector.attribute_cloned_or_default("transform", index)));
paths.push(to_bez_path(element, vector.attribute_cloned_or_default(ATTR_TRANSFORM, index)));
}
let top = match Topology::<WindingNumber>::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON) {
@ -167,18 +167,18 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
match graphic.clone() {
Graphic::Vector(vector) => {
// Apply the parent graphic's transform to each element of the `Table<Vector>`
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
vector
.into_iter()
.map(|mut sub_vector| {
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default("transform");
*sub_vector.attribute_mut_or_insert_default("transform") = parent_transform * current_transform;
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
sub_vector
})
.collect::<Vec<_>>()
}
Graphic::RasterCPU(image) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let make_row = |transform, layer, alpha_blending| {
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
@ -187,8 +187,8 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
element.style.set_fill(Fill::Solid(Color::BLACK));
TableRow::new_from_element(element)
.with_attribute("alpha_blending", alpha_blending)
.with_attribute("editor:layer", layer)
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
};
// Apply the parent graphic's transform to each raster element, preserving each item's layer
@ -196,15 +196,15 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
// back to the originating raster layer
(0..image.len())
.map(|i| {
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
let layer: Table<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i);
make_row(parent_transform * row_transform, layer, alpha_blending)
})
.collect::<Vec<_>>()
}
Graphic::RasterGPU(image) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let make_row = |transform, layer, alpha_blending| {
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
@ -213,8 +213,8 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
element.style.set_fill(Fill::Solid(Color::BLACK));
TableRow::new_from_element(element)
.with_attribute("alpha_blending", alpha_blending)
.with_attribute("editor:layer", layer)
.with_attribute(ATTR_ALPHA_BLENDING, alpha_blending)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer)
};
// Apply the parent graphic's transform to each raster element, preserving each item's layer
@ -222,17 +222,17 @@ fn flatten_vector(graphic_table: &Table<Graphic>) -> Table<Vector> {
// back to the originating raster layer
(0..image.len())
.map(|i| {
let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i);
let layer: Table<NodeId> = image.attribute_cloned_or_default("editor:layer", i);
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i);
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
let layer: Table<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i);
make_row(parent_transform * row_transform, layer, alpha_blending)
})
.collect::<Vec<_>>()
}
Graphic::Graphic(mut graphic) => {
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index);
let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index);
// Apply the parent graphic's transform to each element of the inner `Table`
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = parent_transform * *transform;
}

View File

@ -6,6 +6,7 @@ use core_types::context::{Ctx, ExtractFootprint};
use core_types::math::bbox::Bbox;
use core_types::table::{Table, TableRow};
use core_types::transform::Transform;
use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM};
use dyn_any::DynAny;
use fastnoise_lite;
use glam::{DAffine2, DVec2, Vec2};
@ -34,7 +35,7 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl
image_frame
.into_iter()
.filter_map(|row| {
let image_frame_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let image_frame_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let (image, mut attributes) = row.into_parts();
// Resize the image using the image crate
@ -86,7 +87,7 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl
// we need to adjust the offset if we truncate the offset calculation
let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size);
attributes.insert("transform", new_transform);
attributes.insert(ATTR_TRANSFORM, new_transform);
Some(TableRow::from_parts(Raster::new_cpu(image), attributes))
})
@ -194,7 +195,7 @@ pub fn mask(
.into_iter()
.filter_map(|mut row| {
let image_size = DVec2::new(row.element().width as f64, row.element().height as f64);
let stencil_transform: DAffine2 = stencil.attribute_cloned_or_default("transform");
let stencil_transform: DAffine2 = stencil.attribute_cloned_or_default(ATTR_TRANSFORM);
let mask_size = stencil_transform.scale_magnitudes();
if mask_size == DVec2::ZERO {
@ -202,7 +203,7 @@ pub fn mask(
}
// Transforms a point from the background image to the foreground image
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let bg_to_fg = transform_attribute * DAffine2::from_scale(1. / image_size);
let stencil_transform_inverse = stencil_transform.inverse();
@ -230,7 +231,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
image
.into_iter()
.map(|mut row| {
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let image_aabb = Bbox::unit().affine_transform(row_transform).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) {
@ -267,7 +268,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
let new_texture_to_layer_space = row_transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale);
*row.element_mut() = Raster::new_cpu(new_image);
row.set_attribute("transform", new_texture_to_layer_space);
row.set_attribute(ATTR_TRANSFORM, new_texture_to_layer_space);
row
})
.collect()
@ -282,8 +283,8 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table<Color>) -> Tab
let image = Image::new(width, height, color);
let mut result_table = Table::new_from_element(Raster::new_cpu(image));
result_table.set_attribute("transform", 0, transform);
result_table.set_attribute("alpha_blending", 0, AlphaBlending::default());
result_table.set_attribute(ATTR_TRANSFORM, 0, transform);
result_table.set_attribute(ATTR_ALPHA_BLENDING, 0, AlphaBlending::default());
// Callers of empty_image can safely unwrap on returned `Table`
result_table
@ -378,7 +379,7 @@ pub fn noise_pattern(
}
}
return Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", transform));
return Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform));
}
};
noise.set_noise_type(Some(noise_type));
@ -436,7 +437,7 @@ pub fn noise_pattern(
}
}
Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", transform))
Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform))
}
#[node_macro::node(category("Raster: Pattern"))]
@ -481,7 +482,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table<Raster<CPU>> {
data,
..Default::default()
}))
.with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size)),
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(offset) * DAffine2::from_scale(size)),
)
}

View File

@ -2,7 +2,7 @@ use crate::gcore::Context;
use core::f64::consts::TAU;
use core_types::registry::types::{Angle, PixelSize};
use core_types::table::Table;
use core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use graphic_types::{Graphic, Vector};
use raster_types::{CPU, Raster};
@ -80,10 +80,10 @@ pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
for row_index in 0..generated_content.len() {
let Some(mut row) = generated_content.clone_row(row_index) else { continue };
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let local_translation = DAffine2::from_translation(local_transform.translation);
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
*row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix;
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
result_table.push(row);
}
@ -126,10 +126,10 @@ async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
for row_index in 0..generated_content.len() {
let Some(mut row) = generated_content.clone_row(row_index) else { continue };
let local_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let local_translation = DAffine2::from_translation(local_transform.translation);
let local_matrix = DAffine2::from_mat2(local_transform.matrix2);
*row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix;
*row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix;
result_table.push(row);
}
@ -156,7 +156,7 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
for points_index in 0..points.len() {
let Some(points_element) = points.element(points_index) else { continue };
let transform: DAffine2 = points.attribute_cloned_or_default("transform", points_index);
let transform: DAffine2 = points.attribute_cloned_or_default(ATTR_TRANSFORM, points_index);
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
@ -165,7 +165,7 @@ async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
let generated_content = content.eval(new_ctx.into_context()).await;
for mut generated_row in generated_content.into_iter() {
generated_row.attribute_mut_or_insert_default::<DAffine2>("transform").translation = transformed_point;
generated_row.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM).translation = transformed_point;
result_table.push(generated_row);
}
};
@ -237,7 +237,7 @@ mod test {
let bounds = generated
.element(index)
.unwrap()
.bounding_box_with_transform(generated.attribute_cloned_or_default("transform", index))
.bounding_box_with_transform(generated.attribute_cloned_or_default(ATTR_TRANSFORM, index))
.unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);

View File

@ -1,5 +1,5 @@
use core_types::Ctx;
use core_types::table::{Table, TableRow};
use core_types::{ATTR_TYPE, Ctx};
use serde_json::Value;
use crate::unescape_string;
@ -248,7 +248,10 @@ fn query_json_all(
let mut results = Vec::new();
resolve_all(&value, &segments, !unquote_strings, &mut results);
results.into_iter().map(|(text, ty)| TableRow::new_from_element(text).with_attribute("type", ty.to_string())).collect()
results
.into_iter()
.map(|(text, ty)| TableRow::new_from_element(text).with_attribute(ATTR_TYPE, ty.to_string()))
.collect()
}
/// A parsed segment of a JSON access path.

View File

@ -1,3 +1,4 @@
use core_types::ATTR_TRANSFORM;
use core_types::table::{Table, TableRow};
use glam::{DAffine2, DVec2};
use parley::GlyphRun;
@ -52,7 +53,7 @@ impl PathBuilder {
if per_glyph_items {
self.vector_table
.push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute("transform", DAffine2::from_translation(glyph_offset)));
.push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset)));
} else {
for subpath in self.glyph_subpaths.drain(..) {
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Table<Vector>` item

View File

@ -1,6 +1,6 @@
use core_types::Ctx;
use core_types::registry::types::SignedInteger;
use core_types::table::{Table, TableRow};
use core_types::{ATTR_END, ATTR_NAME, ATTR_START, Ctx};
/// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string.
#[node_macro::node(category("Text: Regex"))]
@ -143,7 +143,10 @@ fn regex_find(
let start = captured.map_or(0_u64, |m| m.start() as u64);
let end = captured.map_or(0_u64, |m| m.end() as u64);
let name = capture_names.get(i).cloned().flatten().unwrap_or_default();
TableRow::new_from_element(text).with_attribute("start", start).with_attribute("end", end).with_attribute("name", name)
TableRow::new_from_element(text)
.with_attribute(ATTR_START, start)
.with_attribute(ATTR_END, end)
.with_attribute(ATTR_NAME, name)
})
.collect()
}
@ -185,8 +188,8 @@ fn regex_find_all(
.filter_map(|m| m.ok())
.map(|m| {
TableRow::new_from_element(m.as_str().to_string())
.with_attribute("start", m.start() as u64)
.with_attribute("end", m.end() as u64)
.with_attribute(ATTR_START, m.start() as u64)
.with_attribute(ATTR_END, m.end() as u64)
})
.collect()
}

View File

@ -2,7 +2,7 @@ use core::f64;
use core_types::color::Color;
use core_types::table::Table;
use core_types::transform::{ApplyTransform, ScaleType, Transform};
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
use core_types::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl};
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Graphic;
use graphic_types::Vector;
@ -66,7 +66,7 @@ fn reset_transform<T>(
reset_rotation: bool,
reset_scale: bool,
) -> Table<T> {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
if reset_translation {
row_transform.translation = DVec2::ZERO;
}
@ -102,7 +102,7 @@ fn replace_transform<T>(
mut content: Table<T>,
transform: DAffine2,
) -> Table<T> {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for row_transform in content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*row_transform = transform.transform();
}
content
@ -123,7 +123,7 @@ async fn extract_transform<T>(
)]
content: Table<T>,
) -> DAffine2 {
content.attribute_cloned_or_default::<DAffine2>("transform", 0)
content.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0)
}
/// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform.

View File

@ -1,6 +1,6 @@
use core_types::Ctx;
use core_types::table::Table;
use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
use glam::DAffine2;
use graphic_types::Vector;
use vector_types::vector::VectorModification;
@ -21,8 +21,8 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
let len = node_path.len();
node_path.into_iter().take(len.saturating_sub(1)).collect()
};
let existing: Table<NodeId> = vector.attribute_cloned_or_default("editor:layer", 0);
vector.set_attribute("editor:layer", 0, if existing.is_empty() { subgraph_path } else { existing });
let existing: Table<NodeId> = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing });
if vector.len() > 1 {
warn!("The path modify ran on {} vector items. Only the first can be modified.", vector.len());
@ -33,7 +33,7 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
/// Applies the vector path's local transformation to its geometry and resets the transform to the identity.
#[node_macro::node(category("Vector"))]
async fn apply_transform(_ctx: impl Ctx, mut vector: Table<Vector>) -> Table<Vector> {
let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>("transform");
let (elements, transforms) = vector.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) {
for (_, point) in element.point_domain.positions_mut() {
*point = transform.transform_point2(*point);

View File

@ -7,7 +7,7 @@ use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLe
use core_types::table::{Table, TableRow};
use core_types::transform::{Footprint, Transform};
use core_types::uuid::NodeId;
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector;
use graphic_types::raster_types::{CPU, GPU, Raster};
@ -41,7 +41,7 @@ impl VectorTableIterMut for Table<Graphic> {
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
for graphic in self.iter_element_values_mut() {
let Some(vector_table) = graphic.as_vector_mut() else { continue };
let (elements, transforms) = vector_table.element_and_attribute_slices_mut::<DAffine2>("transform");
let (elements, transforms) = vector_table.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
f(vector, *transform);
}
@ -55,7 +55,7 @@ impl VectorTableIterMut for Table<Graphic> {
impl VectorTableIterMut for Table<Vector> {
fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) {
let (elements, transforms) = self.element_and_attribute_slices_mut::<DAffine2>("transform");
let (elements, transforms) = self.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
f(vector, *transform);
}
@ -288,7 +288,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
let do_scale = random_scale_difference.abs() > 1e-6;
let do_rotation = random_rotation.abs() > 1e-6;
let points_transform: DAffine2 = row.attribute_cloned_or_default("transform");
let points_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
for &point in row.element().point_domain.positions() {
let translation = points_transform.transform_point2(point);
@ -317,8 +317,8 @@ async fn copy_to_points<I: 'n + Send + Clone>(
for row_index in 0..content.len() {
let Some(mut row) = content.clone_row(row_index) else { continue };
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
row.set_attribute("transform", transform * row_transform);
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.set_attribute(ATTR_TRANSFORM, transform * row_transform);
result_table.push(row);
}
@ -349,7 +349,7 @@ async fn round_corners(
) -> Table<Vector> {
(0..source.len())
.map(|index| {
let source_transform: DAffine2 = source.attribute_cloned_or_default("transform", index);
let source_transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let source_transform_inverse = source_transform.inverse();
let attributes = source.clone_row_attributes(index);
let source = source.element(index).unwrap();
@ -455,7 +455,7 @@ pub fn merge_by_distance(
MergeByDistanceAlgorithm::Spatial => content
.into_iter()
.map(|mut row| {
let transform: DAffine2 = row.attribute_cloned_or_default("transform");
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.element_mut().merge_by_distance_spatial(transform, distance);
row
})
@ -678,12 +678,12 @@ async fn extrude(_: impl Ctx, mut source: Table<Vector>, direction: DVec2, joini
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Table<Vector>) -> Table<Vector> {
let Some(target) = rectangle.element(0).cloned() else { return content };
let target_transform: DAffine2 = rectangle.attribute_cloned_or_default("transform", 0);
let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
content
.into_iter()
.map(|mut row| {
let transform: DAffine2 = row.attribute_cloned_or_default("transform");
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let vector = std::mem::take(row.element_mut());
// Get the bounding box of the source vector geometry
@ -743,7 +743,7 @@ async fn box_warp(_: impl Ctx, content: Table<Vector>, #[expose] rectangle: Tabl
// Add this to the `Table` and reset the transform since we've applied it directly to the points
*row.element_mut() = result;
row.set_attribute("transform", DAffine2::IDENTITY);
row.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY);
row
})
.collect()
@ -852,8 +852,8 @@ where
RowsOrColumns::Rows => DVec2::new(strip.along_position, strip.cross_position),
RowsOrColumns::Columns => DVec2::new(strip.cross_position, strip.along_position),
};
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
row.set_attribute("transform", DAffine2::from_translation(target_position - top_left) * row_transform);
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform);
strip.along_position += along + separation;
} else {
@ -864,8 +864,8 @@ where
RowsOrColumns::Rows => DVec2::new(0., new_cross),
RowsOrColumns::Columns => DVec2::new(new_cross, 0.),
};
let row_transform: DAffine2 = row.attribute_cloned_or_default("transform");
row.set_attribute("transform", DAffine2::from_translation(target_position - top_left) * row_transform);
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform);
strips.push(Strip {
along_position: along + separation,
@ -896,7 +896,7 @@ async fn auto_tangents(
) -> Table<Vector> {
(0..source.len())
.map(|index| {
let transform: DAffine2 = source.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let attributes = source.clone_row_attributes(index);
let source = source.element(index).unwrap();
@ -1063,7 +1063,7 @@ async fn bounding_box(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
async fn dimensions(_: impl Ctx, content: Table<Vector>) -> DVec2 {
(0..content.len())
.filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default("transform", index)))
.filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM, index)))
.reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)])
.map(|[top_left, bottom_right]| bottom_right - top_left)
.unwrap_or_default()
@ -1115,7 +1115,7 @@ async fn offset_path(_: impl Ctx, content: Table<Vector>, distance: f64, join: S
content
.into_iter()
.map(|mut row| {
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let transform = Affine::new(transform_attribute.to_cols_array());
let vector = std::mem::take(row.element_mut());
@ -1296,14 +1296,14 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
// Concatenate every vector element's subpaths into the single output compound path
for index in 0..flattened.len() {
let Some(element) = flattened.element(index) else { continue };
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default("editor:layer", index);
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index);
let node_id = layer_path.iter_element_values().next_back().map(|node_id| node_id.0).unwrap_or_default();
let mut hasher = DefaultHasher::new();
(index, node_id).hash(&mut hasher);
let collision_hash_seed = hasher.finish();
output.concat(element, flattened.attribute_cloned_or_default("transform", index), collision_hash_seed);
output.concat(element, flattened.attribute_cloned_or_default(ATTR_TRANSFORM, index), collision_hash_seed);
// TODO: Make this instead use the first encountered style
// Use the last encountered style as the output style
@ -1313,13 +1313,13 @@ pub async fn flatten_path<T: IntoGraphicTable + 'n + Send>(_: impl Ctx, #[implem
// Preserve a reference to the original upstream `Table<Graphic>` so the renderer can recurse into it
// when collecting metadata, exposing the original child layers' click targets to editor tools.
// This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge.
output_table.set_attribute("editor:merged_layers", 0, graphic_table);
output_table.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table);
// Adopt the last input item's layer so the editor can also bucket clicks under a contributing child layer
if !flattened.is_empty() {
let primary = flattened.len() - 1;
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default("editor:layer", primary);
output_table.set_attribute("editor:layer", 0, layer_path);
let layer_path: Table<NodeId> = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary);
output_table.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
}
output_table
@ -1365,12 +1365,12 @@ async fn sample_polyline(
style: std::mem::take(&mut row.element_mut().style),
};
// Transfer the stroke transform from the input vector content to the result.
result.style.set_stroke_transform(row.attribute_cloned_or_default("transform"));
result.style.set_stroke_transform(row.attribute_cloned_or_default(ATTR_TRANSFORM));
for local_bezpath in row.element().stroke_bezpath_iter() {
// Apply the transform to compute sample locations in world space (for correct distance-based spacing)
let mut world_bezpath = local_bezpath.clone();
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
world_bezpath.apply_affine(Affine::new(transform_attribute.to_cols_array()));
// Per-segment perimeter lengths (transform-baked) for distance-based spacing
@ -1431,7 +1431,7 @@ async fn simplify(
content
.into_iter()
.map(|mut row| {
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let transform = Affine::new(transform_attribute.to_cols_array());
let inverse_transform = transform.inverse();
@ -1527,7 +1527,7 @@ async fn decimate(
content
.into_iter()
.map(|mut row| {
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let transform = Affine::new(transform_attribute.to_cols_array());
let inverse_transform = transform.inverse();
@ -1710,7 +1710,7 @@ async fn position_on_path(
let mut bezpaths: Vec<_> = (0..content.len())
.flat_map(|index| {
let transform: DAffine2 = content.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index);
content.element(index).unwrap().stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::<Vec<_>>()
})
.collect();
@ -1750,7 +1750,7 @@ async fn tangent_on_path(
let mut bezpaths: Vec<_> = (0..content.len())
.flat_map(|index| {
let transform: DAffine2 = content.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index);
content.element(index).unwrap().stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::<Vec<_>>()
})
.collect();
@ -1948,7 +1948,7 @@ async fn jitter_points(
.into_iter()
.map(|mut row| {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2);
let deltas: Vec<_> = (0..row.element().point_domain.positions().len())
@ -1969,7 +1969,7 @@ async fn jitter_points(
})
.collect();
let transform: DAffine2 = row.attribute_cloned_or_default("transform");
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
apply_point_deltas(row.element_mut(), &deltas, transform);
row
@ -1992,7 +1992,7 @@ async fn offset_points(
content
.into_iter()
.map(|mut row| {
let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform");
let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2);
let deltas: Vec<_> = (0..row.element().point_domain.positions().len())
@ -2005,7 +2005,7 @@ async fn offset_points(
})
.collect();
let transform: DAffine2 = row.attribute_cloned_or_default("transform");
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
apply_point_deltas(row.element_mut(), &deltas, transform);
row
@ -2146,7 +2146,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
let default_polyline = || {
let mut default_path = BezPath::new();
for index in 0..content.len() {
let transform_attribute: DAffine2 = content.attribute_cloned_or_default("transform", index);
let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let origin = transform_attribute.translation;
let point = kurbo::Point::new(origin.x, origin.y);
if index == 0 {
@ -2164,7 +2164,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
// User-provided path: collect all subpaths with transforms applied
let paths: Vec<BezPath> = (0..path.len())
.flat_map(|index| {
let transform: DAffine2 = path.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = path.attribute_cloned_or_default(ATTR_TRANSFORM, index);
path.element(index)
.unwrap()
.stroke_bezpath_iter()
@ -2238,8 +2238,8 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
if content.element(source_index).is_none() || content.element(target_index).is_none() {
return 0.;
}
let source_transform: DAffine2 = content.attribute_cloned_or_default("transform", source_index);
let target_transform: DAffine2 = content.attribute_cloned_or_default("transform", target_index);
let source_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, source_index);
let target_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, target_index);
let (s_angle, s_scale, s_skew) = source_transform.decompose_rotation_scale_skew();
let (t_angle, t_scale, t_skew) = target_transform.decompose_rotation_scale_skew();
@ -2311,8 +2311,8 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
};
// Lerp styles
let source_alpha_blending: AlphaBlending = content.attribute_cloned_or_default("alpha_blending", source_index);
let target_alpha_blending: AlphaBlending = content.attribute_cloned_or_default("alpha_blending", target_index);
let source_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, source_index);
let target_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, target_index);
let vector_alpha_blending = source_alpha_blending.lerp(&target_alpha_blending, time as f32);
// Evaluate the spatial position on the control path for the translation component.
@ -2330,8 +2330,8 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
// This decomposition must match the one used in Stroke::lerp so the renderer's stroke_transform.inverse()
// correctly cancels the element transform, keeping the stroke uniform when Stroke is after Transform.
let lerped_transform = {
let source_transform: DAffine2 = content.attribute_cloned_or_default("transform", source_index);
let target_transform: DAffine2 = content.attribute_cloned_or_default("transform", target_index);
let source_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, source_index);
let target_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, target_index);
let (s_angle, s_scale, s_skew) = source_transform.decompose_rotation_scale_skew();
let (t_angle, t_scale, t_skew) = target_transform.decompose_rotation_scale_skew();
@ -2360,7 +2360,7 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
// in which case we skip pre-compensation to avoid propagating NaN through merged_layers transforms.
if lerped_transform.matrix2.determinant().abs() > f64::EPSILON {
let lerped_inverse = lerped_transform.inverse();
for transform in graphic_table_content.iter_attribute_values_mut_or_default::<DAffine2>("transform") {
for transform in graphic_table_content.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
*transform = lerped_inverse * *transform;
}
}
@ -2372,8 +2372,8 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
let endpoint_element = content.element(endpoint_index).unwrap();
let mut attributes = content.clone_row_attributes(endpoint_index);
attributes.insert("transform", lerped_transform);
attributes.insert("editor:merged_layers", graphic_table_content);
attributes.insert(ATTR_TRANSFORM, lerped_transform);
attributes.insert(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content);
return Table::new_from_row(TableRow::from_parts(endpoint_element.clone(), attributes));
}
@ -2529,14 +2529,14 @@ async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
// The result is a synthesis of source and target, so adopt whichever endpoint the result is closer to as
// the click-target identity (so the editor can route clicks back to one of the contributing layers)
let primary_index = if time < 0.5 { source_index } else { target_index };
let layer_path: Table<NodeId> = content.attribute_cloned_or_default("editor:layer", primary_index);
let layer_path: Table<NodeId> = content.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary_index);
Table::new_from_row(
TableRow::new_from_element(vector)
.with_attribute("transform", lerped_transform)
.with_attribute("alpha_blending", vector_alpha_blending)
.with_attribute("editor:layer", layer_path)
.with_attribute("editor:merged_layers", graphic_table_content),
.with_attribute(ATTR_TRANSFORM, lerped_transform)
.with_attribute(ATTR_ALPHA_BLENDING, vector_alpha_blending)
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content),
)
}
@ -2815,7 +2815,7 @@ fn bevel(_: impl Ctx, source: Table<Vector>, #[default(10.)] distance: Length) -
source
.into_iter()
.map(|row| {
let transform: DAffine2 = row.attribute_cloned_or_default("transform");
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
let (element, attributes) = row.into_parts();
TableRow::from_parts(bevel_algorithm(element, transform, distance), attributes)
@ -2837,7 +2837,7 @@ fn close_path(_: impl Ctx, source: Table<Vector>) -> Table<Vector> {
#[node_macro::node(category("Vector: Measure"), path(core_types::vector))]
fn point_inside(_: impl Ctx, source: Table<Vector>, point: DVec2) -> bool {
source.into_iter().any(|row| {
let transform: DAffine2 = row.attribute_cloned_or_default("transform");
let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
row.element().check_point_inside_shape(transform, point)
})
}
@ -2918,7 +2918,7 @@ async fn index_points(
async fn path_length(_: impl Ctx, source: Table<Vector>) -> f64 {
(0..source.len())
.map(|index| {
let transform: DAffine2 = source.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index);
source
.element(index)
@ -2940,7 +2940,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<Cont
(0..vector.len())
.map(|index| {
let transform: DAffine2 = vector.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let area_scale = transform.matrix2.determinant().abs();
vector.element(index).unwrap().stroke_bezpath_iter().map(|subpath| subpath.area() * area_scale).sum::<f64>()
})
@ -2969,7 +2969,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<
CentroidType::Length => subpath.length_centroid_and_length(None, true),
};
if let Some((subpath_centroid, area_or_length)) = partial {
let transform: DAffine2 = vector.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
let subpath_centroid = transform.transform_point2(subpath_centroid);
sum += area_or_length;
@ -2987,7 +2987,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node<
let summed_positions = (0..vector.len())
.flat_map(|index| {
let transform: DAffine2 = vector.attribute_cloned_or_default("transform", index);
let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index);
vector
.element(index)
.unwrap()
@ -3032,7 +3032,7 @@ mod test {
fn create_vector_row(bezpath: BezPath, transform: DAffine2) -> TableRow<Vector> {
let mut row = Vector::default();
row.append_bezpath(bezpath);
TableRow::new_from_element(row).with_attribute("transform", transform)
TableRow::new_from_element(row).with_attribute(ATTR_TRANSFORM, transform)
}
#[tokio::test]
@ -3054,7 +3054,7 @@ mod test {
// Test a rectangular path with non-zero rotation
let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY));
let mut square = Table::new_from_element(square);
square.with_attribute_mut_or_default("transform", 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4));
square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4));
let bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await;
let bounding_box = bounding_box.element(0).unwrap();
assert_eq!(bounding_box.region_manipulator_groups().count(), 1);
@ -3157,7 +3157,7 @@ mod test {
async fn morph() {
let mut rectangles = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY));
let mut second_rectangle = rectangles.clone_row(0).unwrap();
*second_rectangle.attribute_mut_or_insert_default::<DAffine2>("transform") *= DAffine2::from_translation((-100., -100.).into());
*second_rectangle.attribute_mut_or_insert_default::<DAffine2>(ATTR_TRANSFORM) *= DAffine2::from_translation((-100., -100.).into());
rectangles.push(second_rectangle);
let morphed = super::morph(Footprint::default(), rectangles, 0.5, false, InterpolationDistribution::default(), Table::default()).await;
@ -3168,7 +3168,7 @@ mod test {
vec![DVec2::new(0., 0.), DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]
);
// The interpolated transform carries the midpoint translation (approximate due to arc-length parameterization)
assert!((morphed.attribute_cloned_or_default::<DAffine2>("transform", 0).translation - DVec2::new(-50., -50.)).length() < 1e-3);
assert!((morphed.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0).translation - DVec2::new(-50., -50.)).length() < 1e-3);
}
#[track_caller]
@ -3244,7 +3244,7 @@ mod test {
let vector = Vector::from_bezpath(source);
let mut vector_table = Table::new_from_element(vector.clone());
vector_table.set_attribute("transform", 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)));
vector_table.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)));
let beveled = super::bevel((), Table::new_from_element(vector), 2_f64.sqrt() * 10.);
let beveled = beveled.element(0).unwrap();