Move the paint and merged-layer markers to graphic-types as optional references

This commit is contained in:
Dennis Kobert
2026-08-22 10:23:02 +00:00
parent a8d257e7d6
commit e4adf16eba
12 changed files with 112 additions and 63 deletions

View File

@@ -164,10 +164,45 @@ pub fn default_value(name: &str) -> Option<Box<dyn AnyAttributeValue>> {
///
/// The trailing `= expr` is the name-specific default; without it the value
/// type's `Default` applies. A `&T` value carries the eval lifetime, so its
/// default must be `'static` data.
/// default must be `'static` data. An `Option<&T>` value is an optional
/// parked reference whose default is `None`, for attributes whose absence
/// means something a present value cannot.
#[macro_export]
macro_rules! attribute {
() => {};
($(#[$meta:meta])* $vis:vis $marker:ident($name:literal): Option<&$value:ty>; $($rest:tt)*) => {
$(#[$meta])*
$vis struct $marker;
impl $crate::attribute::Attribute for $marker {
const NAME: &'static str = $name;
type Value<'e> = ::core::option::Option<&'e $value>;
unsafe fn read_erased(ptr: *const u8) -> ::std::boxed::Box<dyn $crate::list::AnyAttributeValue> {
::std::boxed::Box::new(unsafe { ptr.cast::<::core::option::Option<&$value>>().read() }.map(|value| <$value as ::std::borrow::ToOwned>::to_owned(value)))
}
const REPARK: ::core::option::Option<unsafe fn(&dyn $crate::list::AnyAttributeValue, *mut u8, &$crate::arena::Arena) -> ::core::option::Option<()>> = {
unsafe fn repark(value: &dyn $crate::list::AnyAttributeValue, dst: *mut u8, arena: &$crate::arena::Arena) -> ::core::option::Option<()> {
let owned: &::core::option::Option<<$value as ::std::borrow::ToOwned>::Owned> =
value.as_any().downcast_ref().expect("an optional reference attribute replays its owned clone");
let parked = match owned {
::core::option::Option::Some(owned) => {
let (parked, _) = arena.alloc(<$value as ::std::borrow::ToOwned>::to_owned(::std::borrow::Borrow::borrow(owned)))?;
::core::option::Option::Some(::std::borrow::Borrow::borrow(parked))
}
::core::option::Option::None => ::core::option::Option::None,
};
unsafe { dst.cast::<::core::option::Option<&$value>>().write(parked) };
::core::option::Option::Some(())
}
::core::option::Option::Some(repark)
};
}
$crate::attribute!(@register $marker);
$crate::attribute!($($rest)*);
};
($(#[$meta:meta])* $vis:vis $marker:ident($name:literal): &$value:ty $(= $default:expr)?; $($rest:tt)*) => {
$(#[$meta])*
$vis struct $marker;

View File

@@ -31,8 +31,8 @@ pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphene_hash;
pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END, ATTR_FONT_SIZE,
ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_TEXT_FRAME, ATTR_END, ATTR_FONT_SIZE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT,
ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_START, ATTR_TRANSFORM, ATTR_TYPE,
};
pub use memo::MemoHash;
pub use no_std_types::AsU32;

View File

@@ -10,9 +10,11 @@ use std::fmt::Debug;
// =================================================
// Standard attribute keys used across the data flow
// =================================================
// Each name is declared once by its marker in [`crate::attribute`], which
// documents the attribute and fixes its value type. The constants re-export
// the markers' names for the string-keyed legacy readers and writers.
// Each name is declared once by its marker, which documents the attribute and
// fixes its value type. The constants re-export the markers' names for the
// string-keyed legacy readers and writers. Markers whose value types live
// below core-types are declared in their types' crates (vector-types, the
// text nodes, graphic-types), each with its own name constants.
pub const ATTR_TRANSFORM: &str = crate::attribute::Transform::NAME;
pub const ATTR_BLEND_MODE: &str = crate::attribute::BlendMode::NAME;
@@ -36,18 +38,6 @@ pub const ATTR_MAX_WIDTH: &str = crate::attribute::MaxWidth::NAME;
pub const ATTR_MAX_HEIGHT: &str = crate::attribute::MaxHeight::NAME;
pub const ATTR_LETTER_TILT: &str = crate::attribute::LetterTilt::NAME;
// The remaining names' value types live in graphic-types. Their markers and
// constants move there with the marker wave.
/// `List<Graphic>` snapshot of the upstream content that fed into a destructive merge
/// (Boolean Operation, Rasterize, etc.), so the editor can still surface click targets for
/// the original child layers after their content has been collapsed.
pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers";
/// Vector graphics object's filled area paint, of type List<T> where T is any graphic type.
pub const ATTR_FILL: &str = "fill";
/// Vector graphics object's stroke paint, of type List<T> where T is any graphic type.
pub const ATTR_STROKE: &str = "stroke";
// ===========================
// Implicit attribute defaults
// ===========================

View File

@@ -1,6 +1,7 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::graphene_hash::CacheHash;
use core_types::list::{ATTR_FILL, ATTR_STROKE, AttributeValueDyn, Item, ItemAttributeValues, List};
use crate::markers::{ATTR_FILL, ATTR_STROKE};
use core_types::list::{AttributeValueDyn, Item, ItemAttributeValues, List};
use core_types::ops::{FromAnchorPosition, ListConvert};
use core_types::render_complexity::RenderComplexity;
use core_types::uuid::NodeId;

View File

@@ -1,5 +1,6 @@
pub mod artboard;
pub mod graphic;
pub mod markers;
// Re-export all transitive dependencies so downstream crates only need to depend on graphic-types
pub use core_types;
@@ -9,6 +10,7 @@ pub use vector_types;
// Re-export commonly used types at the crate root
pub use artboard::Artboard;
pub use graphic::{Graphic, IntoGraphicList, TryFromGraphic, Vector};
pub use markers::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE};
pub mod migrations {
use crate::Vector;

View File

@@ -0,0 +1,45 @@
//! Attribute markers whose value types live in this crate, with their name
//! constants for the string-keyed legacy readers and writers.
//!
//! The list-valued markers deep-copy by cloning the list, so their values must
//! stay free of arena-borrowing content such as [`Graphic::Group`].
use crate::Graphic;
use core_types::attribute::Attribute;
use core_types::list::List;
core_types::attribute! {
/// Vector graphics object's filled area paint, a graphic list in the canonical paint form.
/// An absent value means no fill.
pub Fill("fill"): Option<&List<Graphic>>;
/// Vector graphics object's stroke paint, a graphic list in the canonical paint form.
/// An absent value means no stroke paint.
pub Stroke("stroke"): Option<&List<Graphic>>;
/// Snapshot of the upstream content that fed into a destructive merge (Boolean Operation,
/// Rasterize, etc.), so the editor can still surface click targets for the original child
/// layers after their content has been collapsed.
pub EditorMergedLayers("editor:merged_layers"): Option<&List<Graphic>>;
}
pub const ATTR_FILL: &str = Fill::NAME;
pub const ATTR_STROKE: &str = Stroke::NAME;
pub const ATTR_EDITOR_MERGED_LAYERS: &str = EditorMergedLayers::NAME;
#[cfg(test)]
mod tests {
use super::*;
use core_types::attribute::info;
use std::any::TypeId;
#[test]
fn the_census_carries_this_crates_names() {
for name in ["fill", "stroke", "editor:merged_layers"] {
assert_eq!(info(name).unwrap().value_type, TypeId::of::<Option<&'static List<Graphic>>>());
}
}
#[test]
fn an_absent_paint_defaults_to_none() {
assert_eq!(<Fill as Attribute>::default(), None);
}
}

View File

@@ -7,14 +7,14 @@ use core_types::bounds::RenderBoundingBox;
use core_types::color::Color;
use core_types::color::SRGBA8;
use core_types::consts::DEFAULT_FONT_SIZE;
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List};
use core_types::list::{Item, List};
use core_types::math::quad::Quad;
use core_types::render_complexity::RenderComplexity;
use core_types::transform::Footprint;
use core_types::uuid::{NodeId, generate_uuid};
use core_types::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_FONT_SIZE, ATTR_LETTER_SPACING,
ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM,
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_TEXT_FRAME, ATTR_FONT_SIZE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT,
ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM,
};
use dyn_any::DynAny;
use glam::{DAffine2, DMat2, DVec2};
@@ -26,7 +26,7 @@ use graphic_types::vector_types::gradient::{GradientStops, GradientType};
use graphic_types::vector_types::subpath::Subpath;
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
use graphic_types::vector_types::vector::style::{PaintOrder, RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
use graphic_types::{Artboard, Graphic, Vector};
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Artboard, Graphic, Vector};
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
use num_traits::Zero;
use skrifa::instance::{LocationRef, NormalizedCoord, Size};

View File

@@ -2,45 +2,19 @@
//! constants for the string-keyed legacy readers and writers.
use core_types::attribute::Attribute;
use core_types::list::AnyAttributeValue;
core_types::attribute! {
/// Gradient's spread behavior past its endpoints (`Pad`, `Reflect`, or `Repeat`).
pub SpreadMethod("spread_method"): crate::gradient::GradientSpreadMethod;
/// Gradient's shape (`Linear` or `Radial`).
pub GradientType("gradient_type"): crate::gradient::GradientType;
/// Optional `Vector` that overrides the item's own geometry for click-target generation.
/// Used by the 'Text' node for per-glyph bounding-box rectangles so glyphs are selectable
/// by clicking anywhere within their bounds, not just the filled letterform. An absent
/// value means the item's own geometry is the click target.
pub EditorClickTarget("editor:click_target"): Option<&crate::Vector>;
}
/// Optional `Vector` that overrides the item's own geometry for click-target generation.
/// Used by the 'Text' node for per-glyph bounding-box rectangles so glyphs are selectable
/// by clicking anywhere within their bounds, not just the filled letterform. An absent
/// value means the item's own geometry is the click target.
pub struct EditorClickTarget;
impl Attribute for EditorClickTarget {
const NAME: &'static str = "editor:click_target";
type Value<'e> = Option<&'e crate::Vector>;
unsafe fn read_erased(ptr: *const u8) -> Box<dyn AnyAttributeValue> {
Box::new(unsafe { ptr.cast::<Option<&crate::Vector>>().read() }.cloned())
}
const REPARK: Option<unsafe fn(&dyn AnyAttributeValue, *mut u8, &core_types::arena::Arena) -> Option<()>> = {
unsafe fn repark(value: &dyn AnyAttributeValue, dst: *mut u8, arena: &core_types::arena::Arena) -> Option<()> {
let owned: &Option<crate::Vector> = value.as_any().downcast_ref().expect("an optional vector attribute replays its owned clone");
let parked = match owned {
Some(vector) => Some(arena.alloc(vector.clone())?.0),
None => None,
};
unsafe { dst.cast::<Option<&crate::Vector>>().write(parked) };
Some(())
}
Some(repark)
};
}
core_types::attribute!(@register EditorClickTarget);
pub const ATTR_SPREAD_METHOD: &str = SpreadMethod::NAME;
pub const ATTR_GRADIENT_TYPE: &str = GradientType::NAME;
pub const ATTR_EDITOR_CLICK_TARGET: &str = EditorClickTarget::NAME;

View File

@@ -3,10 +3,10 @@ use core_types::gpoll::Interrupt;
use core_types::list::{AttributeDyn, AttributeValueDyn, Item, List, ListDyn};
use core_types::registry::types::{Angle, SignedInteger};
use core_types::uuid::NodeId;
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, Color, Context, Ctx, DeriveCtx};
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, Color, Context, Ctx, DeriveCtx};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector};
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, Artboard, Vector};
use raster_types::{CPU, GPU, Raster};
use vector_types::gradient::{GradientSpreadMethod, GradientType};
use vector_types::{GradientStop, GradientStops, ReferencePoint};

View File

@@ -11,8 +11,10 @@ use core_types::runtime::SourceFuture;
#[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::{ATTR_TRANSFORM, WasmNotSend};
use core_types::{Color, Ctx};
#[cfg(target_family = "wasm")]
use graphic_types::ATTR_EDITOR_MERGED_LAYERS;
pub use graph_craft::application_io::resource::{Resource, ResourceHash};
pub use graph_craft::application_io::*;
pub use graph_craft::document::value::RenderOutputType;

View File

@@ -1,6 +1,6 @@
use core_types::list::{ATTR_FILL, Item, List};
use core_types::list::{Item, List};
use core_types::uuid::NodeId;
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{bake_paint_transforms, set_paint_attribute};
use graphic_types::vector_types::gradient::{GradientSpreadMethod, GradientType};
@@ -8,7 +8,7 @@ use graphic_types::vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
use graphic_types::vector_types::vector::PointId;
use graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use graphic_types::{Graphic, Vector};
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, Graphic, Vector};
use linesweeper::topology::Topology;
use linesweeper::{BinaryOp, FillRule, binary_op};
use smallvec::SmallVec;

View File

@@ -4,16 +4,16 @@ use core::hash::{Hash, Hasher};
use core_types::blending::BlendMode;
use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::gpoll::Interrupt;
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, ItemAttributeValues, List, ListDyn};
use core_types::list::{Item, ItemAttributeValues, List, ListDyn};
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
use core_types::transform::{Footprint, Transform};
use core_types::uuid::NodeId;
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx, DeriveCtx};
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color, Ctx, DeriveCtx};
use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Vector;
use graphic_types::graphic::{bake_paint_transforms, graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute_at};
use graphic_types::raster_types::{CPU, GPU, Raster};
use graphic_types::{Graphic, IntoGraphicList};
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList};
use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape};
use rand::{Rng, SeedableRng};