This commit is contained in:
phailhaus 2025-07-06 23:01:19 +08:00 committed by GitHub
commit 088f3cf8c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 104 additions and 8 deletions

View file

@ -27,7 +27,7 @@ use graphene_std::vector::misc::GridType;
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::style::{GradientType, PaintOrder, ShapeRenderingModes, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
@ -230,6 +230,7 @@ pub(crate) fn property_from_type(
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<ShapeRenderingModes>() => enum_choice::<ShapeRenderingModes>().for_socket(default_info).property_row(),
// =====
// OTHER
// =====

View file

@ -468,17 +468,42 @@ impl Default for Stroke {
}
}
/// The shape-rendering attribute provides hints to the renderer about what tradeoffs to make when rendering shapes like paths, circles, or rectangles.
/// See https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/shape-rendering
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
#[widget(Dropdown)]
pub enum ShapeRenderingModes {
#[default]
Auto,
OptimizeSpeed,
CrispEdges,
GeometricPrecision,
}
impl ShapeRenderingModes {
pub fn svg_name(&self) -> &'static str {
match self {
ShapeRenderingModes::Auto => "auto",
ShapeRenderingModes::OptimizeSpeed => "optimizeSpeed",
ShapeRenderingModes::CrispEdges => "crispEdges",
ShapeRenderingModes::GeometricPrecision => "geometricPrecision",
}
}
}
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, DynAny, specta::Type)]
pub struct PathStyle {
pub stroke: Option<Stroke>,
pub fill: Fill,
pub shape_rendering: ShapeRenderingModes,
}
impl std::hash::Hash for PathStyle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.stroke.hash(state);
self.fill.hash(state);
self.shape_rendering.hash(state);
}
}
@ -491,13 +516,15 @@ impl std::fmt::Display for PathStyle {
None => "None".to_string(),
};
write!(f, "Fill: {fill}\nStroke: {stroke}")
let shape_rendering = &self.shape_rendering;
write!(f, "Fill: {fill}\nStroke: {stroke}\nShape-Rendering: {shape_rendering}")
}
}
impl PathStyle {
pub const fn new(stroke: Option<Stroke>, fill: Fill) -> Self {
Self { stroke, fill }
pub const fn new(stroke: Option<Stroke>, fill: Fill, shape_rendering: ShapeRenderingModes) -> Self {
Self { stroke, fill, shape_rendering }
}
pub fn lerp(&self, other: &Self, time: f64) -> Self {
@ -521,6 +548,7 @@ impl PathStyle {
}
(None, None) => None,
},
shape_rendering: self.shape_rendering,
}
}
@ -598,6 +626,11 @@ impl PathStyle {
self.stroke = Some(stroke);
}
/// Replace the path's [ShapeRenderingModes] with a provided one.
pub fn set_shape_rendering(&mut self, shape_rendering: ShapeRenderingModes) {
self.shape_rendering = shape_rendering;
}
/// Set the path's fill to None.
///
/// # Example

View file

@ -97,7 +97,7 @@ pub struct VectorData {
impl Default for VectorData {
fn default() -> Self {
Self {
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None, super::style::ShapeRenderingModes::Auto),
colinear_manipulators: Vec::new(),
point_domain: PointDomain::new(),
segment_domain: SegmentDomain::new(),

View file

@ -2,7 +2,7 @@ use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_po
use super::algorithms::offset_subpath::offset_subpath;
use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
use super::misc::{CentroidType, point_to_dvec2};
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::style::{Fill, Gradient, GradientStops, ShapeRenderingModes, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataExt, VectorDataTable};
use crate::bounds::BoundingBox;
use crate::instances::{Instance, InstanceMut, Instances};
@ -214,6 +214,35 @@ where
vector_data
}
/// Set the shape-render SVG property for all input vectors. Determines anti-aliasing, but output varies by renderer.
/// Not implimented in Vello
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
async fn shape_render_mode<V>(
_: impl Ctx,
#[implementations(
VectorDataTable,
VectorDataTable,
VectorDataTable,
VectorDataTable,
GraphicGroupTable,
GraphicGroupTable,
GraphicGroupTable,
GraphicGroupTable
)]
/// The vector elements, or group of vector elements, to apply the shape render mode to.
mut vector_data: V,
render_mode: ShapeRenderingModes,
) -> V
where
V: VectorDataTableIterMut + 'n + Send,
{
for vector in vector_data.vector_iter_mut() {
vector.instance.style.set_shape_rendering(render_mode);
}
vector_data
}
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn repeat<I: 'n + Send + Clone>(
_: impl Ctx,

View file

@ -248,6 +248,7 @@ tagged_value! {
ReferencePoint(graphene_core::transform::ReferencePoint),
CentroidType(graphene_core::vector::misc::CentroidType),
BooleanOperation(graphene_path_bool::BooleanOperation),
ShapeRenderingModes(graphene_core::vector::style::ShapeRenderingModes),
}
impl TaggedValue {

View file

@ -3,7 +3,7 @@ use glam::{DAffine2, DVec2};
use graphene_core::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
use graphene_core::gradient::{Gradient, GradientType};
use graphene_core::uuid::generate_uuid;
use graphene_core::vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin, ViewMode};
use graphene_core::vector::style::{Fill, PaintOrder, PathStyle, ShapeRenderingModes, Stroke, StrokeAlign, StrokeCap, StrokeJoin, ViewMode};
use std::fmt::Write;
pub trait RenderExt {
@ -200,6 +200,26 @@ impl RenderExt for Stroke {
}
}
impl RenderExt for ShapeRenderingModes {
type Output = String;
/// Provide the SVG attributes for the stroke.
fn render(
&self,
_svg_defs: &mut String,
_element_transform: DAffine2,
_stroke_transform: DAffine2,
_bounds: [DVec2; 2],
_transformed_bounds: [DVec2; 2],
_aligned_strokes: bool,
_override_paint_order: bool,
_render_params: &RenderParams,
) -> Self::Output {
let svg_name = self.svg_name();
format!(" shape-rendering=\"{svg_name}\"")
}
}
impl RenderExt for PathStyle {
type Output = String;
@ -271,7 +291,19 @@ impl RenderExt for PathStyle {
)
})
.unwrap_or_default();
format!("{fill_attribute}{stroke_attribute}")
let shape_rendering_attribute = self.shape_rendering.render(
svg_defs,
element_transform,
stroke_transform,
bounds,
transformed_bounds,
aligned_strokes,
override_paint_order,
render_params,
);
format!("{fill_attribute}{stroke_attribute}{shape_rendering_attribute}")
}
}
}