mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 14:58:05 +08:00
Migrate attribute call sites from string keys to typed keys
This commit is contained in:
@@ -512,6 +512,14 @@ impl ListDyn {
|
||||
.find_map(|(k, attribute)| if k == key { attribute.get_any(index)?.downcast_ref::<U>() } else { None })
|
||||
}
|
||||
|
||||
/// Returns a reference to the attribute value at the given runtime key and item index, downcast to `U`, if present and matching.
|
||||
/// For keys known at compile time use [`Self::attr`]; this variant is for keys only known at runtime (e.g. the attribute nodes).
|
||||
pub fn attribute_dyn<U: 'static>(&self, key: &str, index: usize) -> Option<&U> {
|
||||
self.attributes
|
||||
.iter()
|
||||
.find_map(|(k, attribute)| if k == key { attribute.get_any(index)?.downcast_ref::<U>() } else { None })
|
||||
}
|
||||
|
||||
/// Returns a reference to the value of the typed attribute at the given item index, if present.
|
||||
pub fn attr<A: Attr>(&self, index: usize) -> Option<&A::Value> {
|
||||
self.attribute(A::name(), index)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use core_types::Color;
|
||||
use core_types::attr::{self, Attr};
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, ItemAttributeValues, List, NodeIdPath};
|
||||
use core_types::list::{Item, ItemAttributeValues, List};
|
||||
use core_types::ops::FromAnchorPosition;
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use raster_types::{CPU, GPU, Raster};
|
||||
@@ -119,32 +120,32 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
||||
// Whether the parent carries each attribute: a structural fact (column presence), never a value comparison.
|
||||
// Flattening composes a parent attribute onto its children only when the parent has it,
|
||||
// so an absent parent attribute never invents a column the children didn't already have.
|
||||
let parent_has_transform = current_graphic_item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some();
|
||||
let parent_has_opacity = current_graphic_item.attribute::<f64>(ATTR_OPACITY).is_some();
|
||||
let parent_has_fill = current_graphic_item.attribute::<f64>(ATTR_OPACITY_FILL).is_some();
|
||||
let parent_has_layer_path = current_graphic_item.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH).is_some();
|
||||
let parent_has_transform = current_graphic_item.attr::<attr::Transform>().is_some();
|
||||
let parent_has_opacity = current_graphic_item.attr::<attr::Opacity>().is_some();
|
||||
let parent_has_fill = current_graphic_item.attr::<attr::OpacityFill>().is_some();
|
||||
let parent_has_layer_path = current_graphic_item.attr::<attr::editor::LayerPath>().is_some();
|
||||
|
||||
let layer_path: NodeIdPath = current_graphic_item.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
||||
let current_transform: DAffine2 = current_graphic_item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
let current_opacity: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||
let current_fill: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||
let layer_path = current_graphic_item.attr_cloned_or_default::<attr::editor::LayerPath>();
|
||||
let current_transform = current_graphic_item.attr_cloned_or_default::<attr::Transform>();
|
||||
let current_opacity = current_graphic_item.attr_cloned_or_default::<attr::Opacity>();
|
||||
let current_fill = current_graphic_item.attr_cloned_or_default::<attr::OpacityFill>();
|
||||
|
||||
match current_graphic_item.into_element() {
|
||||
// Compose the parent's transform/opacity/fill onto each child, but only for attributes the parent carries.
|
||||
// A child lacking one is padded with the composition identity (`1.` for opacity/fill, identity for transform), so composing through it is a no-op.
|
||||
Graphic::Graphic(mut sub_list) => {
|
||||
if parent_has_transform {
|
||||
for v in sub_list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for v in sub_list.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*v = current_transform * *v;
|
||||
}
|
||||
}
|
||||
if parent_has_opacity {
|
||||
for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY) {
|
||||
for v in sub_list.iter_attr_values_mut_or_default::<attr::Opacity>() {
|
||||
*v *= current_opacity;
|
||||
}
|
||||
}
|
||||
if parent_has_fill {
|
||||
for v in sub_list.iter_attribute_values_mut_or_default::<f64>(ATTR_OPACITY_FILL) {
|
||||
for v in sub_list.iter_attr_values_mut_or_default::<attr::OpacityFill>() {
|
||||
*v *= current_fill;
|
||||
}
|
||||
}
|
||||
@@ -155,22 +156,22 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
||||
other => {
|
||||
if let Some(typed_list) = extract_variant(other) {
|
||||
for mut item in typed_list.into_iter() {
|
||||
// Each `|| item.attribute(...)` keeps an attribute the item itself carries
|
||||
// Each `|| item.attr::<...>()` keeps an attribute the item itself carries
|
||||
// (recomposed with the parent's identity value) even when the parent lacks it
|
||||
if parent_has_transform || item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some() {
|
||||
let item_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||
item.set_attribute(ATTR_TRANSFORM, current_transform * item_transform);
|
||||
if parent_has_transform || item.attr::<attr::Transform>().is_some() {
|
||||
let item_transform = item.attr_cloned_or_default::<attr::Transform>();
|
||||
item.set_attr::<attr::Transform>(current_transform * item_transform);
|
||||
}
|
||||
if parent_has_opacity || item.attribute::<f64>(ATTR_OPACITY).is_some() {
|
||||
let item_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||
item.set_attribute(ATTR_OPACITY, current_opacity * item_opacity);
|
||||
if parent_has_opacity || item.attr::<attr::Opacity>().is_some() {
|
||||
let item_opacity = item.attr_cloned_or_default::<attr::Opacity>();
|
||||
item.set_attr::<attr::Opacity>(current_opacity * item_opacity);
|
||||
}
|
||||
if parent_has_fill || item.attribute::<f64>(ATTR_OPACITY_FILL).is_some() {
|
||||
let item_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||
item.set_attribute(ATTR_OPACITY_FILL, current_fill * item_fill);
|
||||
if parent_has_fill || item.attr::<attr::OpacityFill>().is_some() {
|
||||
let item_fill = item.attr_cloned_or_default::<attr::OpacityFill>();
|
||||
item.set_attr::<attr::OpacityFill>(current_fill * item_fill);
|
||||
}
|
||||
if parent_has_layer_path {
|
||||
item.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
||||
item.set_attr::<attr::editor::LayerPath>(layer_path.clone());
|
||||
}
|
||||
|
||||
output.push(item);
|
||||
@@ -192,9 +193,9 @@ pub fn is_paint_present(graphic_list: &List<Graphic>) -> bool {
|
||||
graphic_list.element(0).is_some_and(|graphic| !graphic.is_empty())
|
||||
}
|
||||
|
||||
/// Look up the paint graphics stored under attribute for a vector item, in the canonical `List<Graphic>` form.
|
||||
pub fn graphic_list_at<'a>(list: &'a List<Vector>, index: usize, attribute: &str) -> Option<Cow<'a, List<Graphic>>> {
|
||||
list.attribute::<List<Graphic>>(attribute, index)
|
||||
/// Look up the paint graphics stored under the typed attribute for a vector item, in the canonical `List<Graphic>` form.
|
||||
pub fn graphic_list_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> Option<Cow<'_, List<Graphic>>> {
|
||||
list.attr::<A>(index)
|
||||
.map(Cow::Borrowed)
|
||||
// Treat a blank paint attribute as absent so an empty attribute doesn't count as painted
|
||||
.filter(|graphic_list| is_paint_present(graphic_list))
|
||||
@@ -202,25 +203,25 @@ pub fn graphic_list_at<'a>(list: &'a List<Vector>, index: usize, attribute: &str
|
||||
|
||||
/// Whether the item carries a non-blank canonical `List<Graphic>` paint attribute,
|
||||
/// checked by borrowing without cloning the renderable list.
|
||||
pub fn has_paint_at(list: &List<Vector>, index: usize, attribute: &str) -> bool {
|
||||
list.attribute::<List<Graphic>>(attribute, index).is_some_and(is_paint_present)
|
||||
pub fn has_paint_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> bool {
|
||||
list.attr::<A>(index).is_some_and(is_paint_present)
|
||||
}
|
||||
|
||||
/// Stores a paint attribute in its canonical `List<Graphic>` form, the only representation paint readers accept.
|
||||
pub fn set_paint_attribute(attributes: &mut ItemAttributeValues, key: &str, paint: impl IntoGraphicList) {
|
||||
attributes.insert(key, paint.into_graphic_list());
|
||||
pub fn set_paint_attribute<A: Attr<Value = List<Graphic>>>(attributes: &mut ItemAttributeValues, paint: impl IntoGraphicList) {
|
||||
attributes.set_attr::<A>(paint.into_graphic_list());
|
||||
}
|
||||
|
||||
/// Stores a paint attribute at a list index in its canonical `List<Graphic>` form, the only representation paint readers accept.
|
||||
pub fn set_paint_attribute_at<T>(list: &mut List<T>, index: usize, key: &str, paint: impl IntoGraphicList) {
|
||||
list.set_attribute(key, index, paint.into_graphic_list());
|
||||
pub fn set_paint_attribute_at<A: Attr<Value = List<Graphic>>, T>(list: &mut List<T>, index: usize, paint: impl IntoGraphicList) {
|
||||
list.set_attr::<A>(index, paint.into_graphic_list());
|
||||
}
|
||||
|
||||
/// Bake the provided transform into the per-item transforms of the paint graphics stored under the
|
||||
/// canonical `List<Graphic>` fill and stroke attributes.
|
||||
pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DAffine2) {
|
||||
fn bake_list_transform<T>(list: &mut List<T>, transform: DAffine2) {
|
||||
for item_transform in list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||
for item_transform in list.iter_attr_values_mut_or_default::<attr::Transform>() {
|
||||
*item_transform = transform * *item_transform;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +241,7 @@ pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DA
|
||||
}
|
||||
}
|
||||
|
||||
for paint_key in [ATTR_FILL, ATTR_STROKE] {
|
||||
for paint_key in [crate::attr::Fill::name(), crate::attr::Stroke::name()] {
|
||||
if let Some(graphics) = attributes.get_mut::<List<Graphic>>(paint_key) {
|
||||
bake_graphic_paint_transform(graphics, transform);
|
||||
}
|
||||
@@ -306,10 +307,10 @@ impl IntoGraphicList for List<Vector> {
|
||||
fn into_graphic_list(self) -> List<Graphic> {
|
||||
// Propagate the `editor:layer_path` column (if present) from item 0 onto the wrapper Graphic item so a
|
||||
// subsequent `flatten_graphic_list` doesn't drop the inner Vector's layer stamp
|
||||
let layer_path = self.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, 0).cloned();
|
||||
let layer_path = self.attr::<attr::editor::LayerPath>(0).cloned();
|
||||
let mut graphic_list = List::new_from_element(Graphic::Vector(self));
|
||||
if let Some(layer_path) = layer_path {
|
||||
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
|
||||
graphic_list.set_attr::<attr::editor::LayerPath>(0, layer_path);
|
||||
}
|
||||
graphic_list
|
||||
}
|
||||
@@ -341,10 +342,10 @@ impl IntoGraphicList for List<Gradient> {
|
||||
|
||||
impl IntoGraphicList for List<String> {
|
||||
fn into_graphic_list(self) -> List<Graphic> {
|
||||
let layer_path = self.attribute::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, 0).cloned();
|
||||
let layer_path = self.attr::<attr::editor::LayerPath>(0).cloned();
|
||||
let mut graphic_list = List::new_from_element(Graphic::Text(self));
|
||||
if let Some(layer_path) = layer_path {
|
||||
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
|
||||
graphic_list.set_attr::<attr::editor::LayerPath>(0, layer_path);
|
||||
}
|
||||
graphic_list
|
||||
}
|
||||
@@ -416,7 +417,7 @@ impl Graphic {
|
||||
|
||||
pub fn had_clip_enabled(&self) -> bool {
|
||||
fn all_clipped<T>(list: &List<T>) -> bool {
|
||||
list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
|
||||
list.iter_attr_values_or_default::<attr::ClippingMask>().all(|clip| clip)
|
||||
}
|
||||
|
||||
match self {
|
||||
@@ -435,12 +436,12 @@ impl Graphic {
|
||||
match self {
|
||||
Graphic::Vector(vector) => (0..vector.len()).all(|index| {
|
||||
let Some(element) = vector.element(index) else { return false };
|
||||
let opacity: f64 = vector.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity = vector.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
|
||||
let fill_opaque_or_absent = graphic_list_at(vector, index, ATTR_FILL).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_opaque()));
|
||||
let fill_opaque_or_absent = graphic_list_at::<crate::attr::Fill>(vector, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_opaque()));
|
||||
|
||||
let stroke_invisible_or_transparent = element.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke())
|
||||
|| graphic_list_at(vector, index, ATTR_STROKE).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()));
|
||||
|| graphic_list_at::<crate::attr::Stroke>(vector, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()));
|
||||
|
||||
opacity > 1. - f64::EPSILON && fill_opaque_or_absent && stroke_invisible_or_transparent
|
||||
}),
|
||||
@@ -453,15 +454,17 @@ impl Graphic {
|
||||
Graphic::None => false,
|
||||
Graphic::Graphic(list) => !list.is_empty() && list.iter_element_values().all(Graphic::is_opaque),
|
||||
Graphic::Vector(list) => {
|
||||
let is_paint_opaque_at = |key: &str, index: usize| graphic_list_at(list, index, key).is_some_and(|graphic_list| graphic_list.element(0).is_some_and(|graphic| graphic.is_opaque()));
|
||||
fn is_paint_opaque_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> bool {
|
||||
graphic_list_at::<A>(list, index).is_some_and(|graphic_list| graphic_list.element(0).is_some_and(|graphic| graphic.is_opaque()))
|
||||
}
|
||||
|
||||
!list.is_empty()
|
||||
&& (0..list.len()).all(|i| {
|
||||
let Some(vector) = list.element(i) else { return false };
|
||||
let opacity: f64 = list.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let opacity_fill: f64 = list.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let fill_opaque = opacity_fill >= 1. - f64::EPSILON && is_paint_opaque_at(ATTR_FILL, i);
|
||||
let stroke_opaque_or_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_opaque_at(ATTR_STROKE, i);
|
||||
let opacity = list.attr_cloned_or_default::<attr::Opacity>(i);
|
||||
let opacity_fill = list.attr_cloned_or_default::<attr::OpacityFill>(i);
|
||||
let fill_opaque = opacity_fill >= 1. - f64::EPSILON && is_paint_opaque_at::<crate::attr::Fill>(list, i);
|
||||
let stroke_opaque_or_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_opaque_at::<crate::attr::Stroke>(list, i);
|
||||
opacity >= 1. - f64::EPSILON && fill_opaque && stroke_opaque_or_invisible
|
||||
})
|
||||
}
|
||||
@@ -477,16 +480,17 @@ impl Graphic {
|
||||
Graphic::Graphic(list) => list.iter_element_values().all(Graphic::is_fully_transparent),
|
||||
Graphic::Vector(list) => (0..list.len()).all(|i| {
|
||||
let Some(vector) = list.element(i) else { return false };
|
||||
let is_paint_fully_transparent_at =
|
||||
|key: &str, index: usize| graphic_list_at(list, index, key).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()));
|
||||
fn is_paint_fully_transparent_at<A: Attr<Value = List<Graphic>>>(list: &List<Vector>, index: usize) -> bool {
|
||||
graphic_list_at::<A>(list, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()))
|
||||
}
|
||||
|
||||
let opacity: f64 = list.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
||||
let opacity = list.attr_cloned_or_default::<attr::Opacity>(i);
|
||||
if opacity <= f64::EPSILON {
|
||||
return true;
|
||||
}
|
||||
let opacity_fill: f64 = list.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
||||
let fill_invisible = opacity_fill <= f64::EPSILON || is_paint_fully_transparent_at(ATTR_FILL, i);
|
||||
let stroke_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_fully_transparent_at(ATTR_STROKE, i);
|
||||
let opacity_fill = list.attr_cloned_or_default::<attr::OpacityFill>(i);
|
||||
let fill_invisible = opacity_fill <= f64::EPSILON || is_paint_fully_transparent_at::<crate::attr::Fill>(list, i);
|
||||
let stroke_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_fully_transparent_at::<crate::attr::Stroke>(list, i);
|
||||
fill_invisible && stroke_invisible
|
||||
}),
|
||||
Graphic::Color(list) => list.iter_element_values().all(|color| color.a() == 0.),
|
||||
@@ -644,7 +648,7 @@ mod tests {
|
||||
fn flatten_does_not_invent_attributes() {
|
||||
let graphics = List::new_from_element(vector_graphic());
|
||||
let flattened: List<Vector> = graphics.into_flattened_list();
|
||||
for key in [ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, ATTR_EDITOR_LAYER_PATH] {
|
||||
for key in [attr::Opacity::name(), attr::OpacityFill::name(), attr::Transform::name(), attr::editor::LayerPath::name()] {
|
||||
assert!(!flattened.attribute_keys().any(|k| k == key), "flatten invented the `{key}` attribute");
|
||||
}
|
||||
}
|
||||
@@ -653,20 +657,19 @@ mod tests {
|
||||
#[test]
|
||||
fn flatten_propagates_present_attributes() {
|
||||
let mut graphics = List::new_from_element(vector_graphic());
|
||||
graphics.set_attribute(ATTR_OPACITY, 0, 0.5_f64);
|
||||
graphics.set_attr::<attr::Opacity>(0, 0.5);
|
||||
let flattened: List<Vector> = graphics.into_flattened_list();
|
||||
assert_eq!(flattened.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 0), 0.5);
|
||||
assert_eq!(flattened.attr_cloned_or_default::<attr::Opacity>(0), 0.5);
|
||||
|
||||
let mut group = List::new_from_element(Graphic::Graphic(List::new_from_element(vector_graphic())));
|
||||
group.set_attribute(ATTR_OPACITY, 0, 0.5_f64);
|
||||
group.set_attr::<attr::Opacity>(0, 0.5);
|
||||
let flattened: List<Vector> = group.into_flattened_list();
|
||||
assert_eq!(flattened.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 0), 0.5);
|
||||
assert_eq!(flattened.attr_cloned_or_default::<attr::Opacity>(0), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod graphic_is_opaque_tests {
|
||||
use core_types::ATTR_SPREAD_METHOD;
|
||||
use vector_types::{GradientSpreadMethod, GradientStop};
|
||||
|
||||
use super::*;
|
||||
@@ -678,7 +681,7 @@ mod graphic_is_opaque_tests {
|
||||
|
||||
fn gradient_graphic(gradient: Gradient) -> Graphic {
|
||||
let mut gradient_list = List::new_from_element(gradient);
|
||||
gradient_list.set_attribute(ATTR_SPREAD_METHOD, 0, GradientSpreadMethod::Pad);
|
||||
gradient_list.set_attr::<vector_types::attr::SpreadMethod>(0, GradientSpreadMethod::Pad);
|
||||
Graphic::Gradient(gradient_list)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Render, RenderSvgSegmentList, SvgRender};
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::list::List;
|
||||
use core_types::uuid::generate_uuid;
|
||||
use core_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
|
||||
use core_types::{Color, attr};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphic_types::Graphic;
|
||||
use graphic_types::vector_types::gradient::GradientType;
|
||||
@@ -93,9 +93,9 @@ impl RenderExt for List<Gradient> {
|
||||
let mut stop = String::new();
|
||||
|
||||
let Some(stops) = self.element(0) else { return 0 };
|
||||
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0);
|
||||
let local_gradient_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0);
|
||||
let gradient_type = self.attr_cloned_or_default::<vector_types::attr::GradientType>(0);
|
||||
let local_gradient_transform = self.attr_cloned_or_default::<attr::Transform>(0);
|
||||
let spread_method = self.attr_cloned_or_default::<vector_types::attr::SpreadMethod>(0);
|
||||
|
||||
for (position, color, original_midpoint) in stops.interpolated_samples() {
|
||||
stop.push_str("<stop");
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
use crate::render_ext::{PaintTarget, RenderExt};
|
||||
use crate::to_peniko::{BlendModeExt, ToPenikoColor};
|
||||
use core_types::CacheHash;
|
||||
use core_types::attr;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::BoundingBox;
|
||||
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, NodeIdPath};
|
||||
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_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_FONT,
|
||||
ATTR_FONT_SIZE, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD,
|
||||
ATTR_TEXT_ALIGN, ATTR_TRANSFORM,
|
||||
};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_hash::CacheHashWrapper;
|
||||
use graphene_resource::Resource;
|
||||
use graphic_types::attr as graphic_attr;
|
||||
use graphic_types::graphic::{graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute};
|
||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
||||
use graphic_types::vector_types::gradient::{Gradient, GradientType};
|
||||
@@ -39,6 +36,8 @@ use std::fmt::Write;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use text_nodes::attr as text_attr;
|
||||
use vector_types::attr as vector_attr;
|
||||
use vector_types::gradient::GradientSpreadMethod;
|
||||
use vello::*;
|
||||
|
||||
@@ -360,7 +359,7 @@ fn emit_svg_fill_path(
|
||||
attributes.push("d", d);
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let defs = &mut attributes.0.svg_defs;
|
||||
let fill_attribute = fill_graphic_list
|
||||
@@ -397,9 +396,9 @@ pub(crate) fn gradient_placement(transform: DAffine2, gradient_type: GradientTyp
|
||||
fn create_peniko_gradient_brush(gradient_list: &List<Gradient>, multiplied_transform: &DAffine2) -> Option<(peniko::Brush, DAffine2)> {
|
||||
let stops = gradient_list.element(0)?;
|
||||
|
||||
let gradient_type: GradientType = gradient_list.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, 0);
|
||||
let gradient_transform: DAffine2 = gradient_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let spread_method: GradientSpreadMethod = gradient_list.attribute_cloned_or_default(ATTR_SPREAD_METHOD, 0);
|
||||
let gradient_type = gradient_list.attr_cloned_or_default::<vector_attr::GradientType>(0);
|
||||
let gradient_transform = gradient_list.attr_cloned_or_default::<attr::Transform>(0);
|
||||
let spread_method = gradient_list.attr_cloned_or_default::<vector_attr::SpreadMethod>(0);
|
||||
|
||||
let mut peniko_stops = peniko::ColorStops::new();
|
||||
for (position, color, _) in stops.interpolated_samples() {
|
||||
@@ -457,10 +456,10 @@ pub struct RenderMetadata {
|
||||
pub text_frames: HashMap<NodeId, DAffine2>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
pub vector_data: HashMap<NodeId, Arc<Vector>>,
|
||||
/// Per-layer `ATTR_FILL` row attribute, exposed so message handlers can read it.
|
||||
/// Per-layer `graphic_types::attr::Fill` row attribute, exposed so message handlers can read it.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub fill_attributes: HashMap<NodeId, Arc<List<Graphic>>>,
|
||||
/// Per-layer `ATTR_STROKE` row attribute, exposed so message handlers can read it.
|
||||
/// Per-layer `graphic_types::attr::Stroke` row attribute, exposed so message handlers can read it.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pub stroke_attributes: HashMap<NodeId, Arc<List<Graphic>>>,
|
||||
pub backgrounds: Vec<Background>,
|
||||
@@ -581,9 +580,9 @@ impl Render for Graphic {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
let layer_path: List<NodeId> = list.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, 0).0;
|
||||
let layer_path: List<NodeId> = list.attr_cloned_or_default::<attr::editor::LayerPath>(0).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
let transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let transform = list.attr_cloned_or_default::<attr::Transform>(0);
|
||||
|
||||
metadata.first_element_source_id.insert(element_id, layer);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
@@ -594,7 +593,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::RasterGPU(list) => {
|
||||
@@ -602,7 +601,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::Color(list) => {
|
||||
@@ -610,7 +609,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::Gradient(list) => {
|
||||
@@ -618,7 +617,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
Graphic::Text(list) => {
|
||||
@@ -626,7 +625,7 @@ impl Render for Graphic {
|
||||
|
||||
// TODO: Find a way to handle more than the first item
|
||||
if !list.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, list.attribute_cloned_or_default(ATTR_TRANSFORM, 0));
|
||||
metadata.local_transforms.insert(element_id, list.attr_cloned_or_default::<attr::Transform>(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,10 +698,10 @@ impl Render for Graphic {
|
||||
|
||||
/// Reads the artboard metadata for the item at `index` from a `List<Artboard>`.
|
||||
fn read_artboard_attributes(list: &List<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
|
||||
let location: DVec2 = list.attribute_cloned_or_default(ATTR_LOCATION, index);
|
||||
let dimensions: DVec2 = list.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
|
||||
let background: Color = list.attribute_cloned_or_default(ATTR_BACKGROUND, index);
|
||||
let clip: bool = list.attribute_cloned_or_default(ATTR_CLIP, index);
|
||||
let location = list.attr_cloned_or_default::<attr::Location>(index);
|
||||
let dimensions = list.attr_cloned_or_default::<attr::Dimensions>(index);
|
||||
let background = list.attr_cloned_or_default::<attr::Background>(index);
|
||||
let clip = list.attr_cloned_or_default::<attr::Clip>(index);
|
||||
(location, dimensions, background, clip)
|
||||
}
|
||||
|
||||
@@ -737,7 +736,7 @@ impl Render for List<Artboard> {
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(DAffine2::from_translation(location));
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
if clip {
|
||||
@@ -800,7 +799,7 @@ impl Render for List<Artboard> {
|
||||
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
|
||||
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
|
||||
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let element_id = layer_path.iter_element_values().next_back().copied();
|
||||
|
||||
if let Some(element_id) = element_id {
|
||||
@@ -823,7 +822,7 @@ impl Render for List<Artboard> {
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let dimensions: DVec2 = self.attribute_cloned_or_default(ATTR_DIMENSIONS, index);
|
||||
let dimensions = self.attr_cloned_or_default::<attr::Dimensions>(index);
|
||||
let subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, dimensions);
|
||||
click_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));
|
||||
}
|
||||
@@ -839,10 +838,10 @@ impl Render for List<Graphic> {
|
||||
let mut mask_state = None;
|
||||
|
||||
for index in 0..self.len() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
render.parent_tag(
|
||||
@@ -850,7 +849,7 @@ impl Render for List<Graphic> {
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
@@ -895,11 +894,11 @@ impl Render for List<Graphic> {
|
||||
let mut mask_element_and_transform = None;
|
||||
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let transform = transform * item_transform;
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
let mut layer = false;
|
||||
@@ -971,8 +970,8 @@ impl Render for List<Graphic> {
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
@@ -992,7 +991,7 @@ impl Render for List<Graphic> {
|
||||
let mut all_upstream_outlines = Vec::new();
|
||||
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
let mut new_click_targets = Vec::new();
|
||||
@@ -1019,7 +1018,7 @@ impl Render for List<Graphic> {
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
@@ -1035,7 +1034,7 @@ impl Render for List<Graphic> {
|
||||
|
||||
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_outlines = Vec::new();
|
||||
|
||||
@@ -1054,7 +1053,7 @@ impl Render for List<Graphic> {
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
let (elements, layers) = self.element_and_attribute_slices_mut::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH);
|
||||
let (elements, layers) = self.element_and_attr_slices_mut::<attr::editor::LayerPath>();
|
||||
for (element, layer) in elements.iter_mut().zip(layers.iter()) {
|
||||
element.new_ids_from_hash(layer.0.iter_element_values().next_back().copied());
|
||||
}
|
||||
@@ -1065,10 +1064,10 @@ impl Render for List<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 item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(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.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||
@@ -1097,10 +1096,10 @@ impl Render for List<Vector> {
|
||||
MaskType::Mask
|
||||
};
|
||||
|
||||
let fill_graphic_list = graphic_list_at(self, index, ATTR_FILL);
|
||||
let fill_graphic_list = graphic_list_at::<graphic_attr::Fill>(self, index);
|
||||
let fill_graphic = fill_graphic_list.as_ref().and_then(|l| l.element(0));
|
||||
|
||||
let stroke_graphic_list = graphic_list_at(self, index, ATTR_STROKE);
|
||||
let stroke_graphic_list = graphic_list_at::<graphic_attr::Stroke>(self, index);
|
||||
let stroke_graphic = stroke_graphic_list.as_ref().and_then(|l| l.element(0));
|
||||
|
||||
let path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());
|
||||
@@ -1135,8 +1134,8 @@ impl Render for List<Vector> {
|
||||
|
||||
// The mask must draw at full alpha so the SVG `<mask>`/`<clipPath>` fully zeroes the path interior.
|
||||
// The wrapping SVG group (above) handles the user-set opacity.
|
||||
let mut mask_item = Item::new_from_element(cloned_vector).with_attribute(ATTR_TRANSFORM, item_transform);
|
||||
set_paint_attribute(mask_item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
let mut mask_item = Item::new_from_element(cloned_vector).with_attr::<attr::Transform>(item_transform);
|
||||
set_paint_attribute::<graphic_attr::Fill>(mask_item.attributes_mut(), List::new_from_element(Color::BLACK));
|
||||
let vector_item = List::new_from_item(mask_item);
|
||||
|
||||
(id, mask_type, vector_item)
|
||||
@@ -1164,7 +1163,7 @@ impl Render for List<Vector> {
|
||||
attributes.push("d", path.clone());
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let defs = &mut attributes.0.svg_defs;
|
||||
@@ -1281,10 +1280,10 @@ impl Render for List<Vector> {
|
||||
use graphic_types::vector_types::vector;
|
||||
|
||||
let Some(element) = self.element(index) else { continue };
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let multiplied_transform = parent_transform * item_transform;
|
||||
let has_real_stroke = element.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform_is_invertible(*transform));
|
||||
@@ -1310,8 +1309,8 @@ impl Render for List<Vector> {
|
||||
}
|
||||
}
|
||||
|
||||
let fill_graphic_list = graphic_list_at(self, index, ATTR_FILL);
|
||||
let stroke_graphic_list = graphic_list_at(self, index, ATTR_STROKE);
|
||||
let fill_graphic_list = graphic_list_at::<graphic_attr::Fill>(self, index);
|
||||
let stroke_graphic_list = graphic_list_at::<graphic_attr::Stroke>(self, index);
|
||||
|
||||
// If we're using opacity or a blend mode, we need to push a layer
|
||||
let blend_mode = match render_params.render_mode {
|
||||
@@ -1484,8 +1483,8 @@ impl Render for List<Vector> {
|
||||
|
||||
// The mask must draw at full alpha so `SrcOut` fully zeroes the path interior.
|
||||
// The outer opacity/blend layer (above) handles the user-set opacity.
|
||||
let mut mask_item = Item::new_from_element(cloned_element).with_attribute(ATTR_TRANSFORM, item_transform);
|
||||
set_paint_attribute(mask_item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||
let mut mask_item = Item::new_from_element(cloned_element).with_attr::<attr::Transform>(item_transform);
|
||||
set_paint_attribute::<graphic_attr::Fill>(mask_item.attributes_mut(), List::new_from_element(Color::BLACK));
|
||||
let vector_list = List::new_from_item(mask_item);
|
||||
|
||||
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||
@@ -1559,7 +1558,7 @@ impl Render for List<Vector> {
|
||||
// Aggregate all items' targets per element_id so multi-item lists (e.g. the "Text to Vector Glyphs" node) produce hit areas for every glyph.
|
||||
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
|
||||
let item_zero_transform: DAffine2 = if !self.is_empty() {
|
||||
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
|
||||
self.attr_cloned_or_default::<attr::Transform>(0)
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
};
|
||||
@@ -1574,8 +1573,8 @@ impl Render for List<Vector> {
|
||||
|
||||
for index in 0..self.len() {
|
||||
let Some(source) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
|
||||
if let Some(element_id) = caller_element_id.or(layer) {
|
||||
@@ -1588,7 +1587,7 @@ impl Render for List<Vector> {
|
||||
}
|
||||
|
||||
// Use click-target override if the item provides one (e.g. 'Text' node's per-glyph bboxes)
|
||||
let click_target_vector = self.attribute::<Vector>(ATTR_EDITOR_CLICK_TARGET, index).unwrap_or(source);
|
||||
let click_target_vector = self.attr::<vector_attr::editor::ClickTarget>(index).unwrap_or(source);
|
||||
|
||||
let item_relative_transform = item_zero_inverse * transform;
|
||||
|
||||
@@ -1608,16 +1607,16 @@ impl Render for List<Vector> {
|
||||
if let std::collections::hash_map::Entry::Vacant(e) = metadata.vector_data.entry(element_id) {
|
||||
e.insert(Arc::new(source.clone()));
|
||||
|
||||
if let Some(fill_graphic) = graphic_list_at(self, index, ATTR_FILL) {
|
||||
if let Some(fill_graphic) = graphic_list_at::<graphic_attr::Fill>(self, index) {
|
||||
metadata.fill_attributes.insert(element_id, Arc::new(fill_graphic.into_owned()));
|
||||
}
|
||||
if let Some(stroke_graphic) = graphic_list_at(self, index, ATTR_STROKE) {
|
||||
if let Some(stroke_graphic) = graphic_list_at::<graphic_attr::Stroke>(self, index) {
|
||||
metadata.stroke_attributes.insert(element_id, Arc::new(stroke_graphic.into_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
// Surface `editor:text_frame` for the Text tool's drag cage
|
||||
if let Some(&frame) = self.attribute::<DAffine2>(ATTR_EDITOR_TEXT_FRAME, index) {
|
||||
if let Some(&frame) = self.attr::<attr::editor::TextFrame>(index) {
|
||||
metadata.text_frames.entry(element_id).or_insert(frame);
|
||||
}
|
||||
}
|
||||
@@ -1625,7 +1624,7 @@ impl Render for List<Vector> {
|
||||
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
||||
// Combine Paths, 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::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
|
||||
let upstream_nested_layers = self.attr_cloned_or_default::<graphic_attr::editor::MergedLayers>(index);
|
||||
if !upstream_nested_layers.is_empty() {
|
||||
let mut upstream_footprint = footprint;
|
||||
upstream_footprint.transform *= transform;
|
||||
@@ -1645,10 +1644,10 @@ impl Render for List<Vector> {
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for index in 0..self.len() {
|
||||
let Some(source) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
// Use click-target override geometry if the item provides one (e.g. 'Text' node's per-glyph bounding boxes)
|
||||
let vector = self.attribute::<Vector>(ATTR_EDITOR_CLICK_TARGET, index).unwrap_or(source);
|
||||
let vector = self.attr::<vector_attr::editor::ClickTarget>(index).unwrap_or(source);
|
||||
|
||||
extend_targets_from_vector(click_targets, self, index, vector, transform);
|
||||
}
|
||||
@@ -1658,7 +1657,7 @@ impl Render for List<Vector> {
|
||||
// Source geometry only, ignoring `editor:click_target`, so outlines reflect actual letterforms
|
||||
for index in 0..self.len() {
|
||||
let Some(source) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
extend_targets_from_vector(outlines, self, index, source, transform);
|
||||
}
|
||||
@@ -1674,7 +1673,7 @@ impl Render for List<Vector> {
|
||||
/// Build one `CompoundPath` (non-zero fill rule, so holes like the inside of an "O" work
|
||||
/// correctly) plus one `FreePoint` per disconnected anchor, apply the transform, and append.
|
||||
fn extend_targets_from_vector(targets: &mut Vec<ClickTarget>, vector_list: &List<Vector>, index: usize, geometry: &Vector, transform: DAffine2) {
|
||||
let filled = has_paint_at(vector_list, index, ATTR_FILL);
|
||||
let filled = has_paint_at::<graphic_attr::Fill>(vector_list, index);
|
||||
|
||||
let mut subpaths: Vec<Subpath<_>> = geometry.stroke_bezier_paths().collect();
|
||||
let all_subpaths_closed = subpaths.iter().all(|subpath| subpath.closed());
|
||||
@@ -1730,10 +1729,10 @@ impl Render for List<Raster<CPU>> {
|
||||
for index in 0..self.len() {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
|
||||
if image.data.is_empty() {
|
||||
continue;
|
||||
@@ -1752,7 +1751,7 @@ impl Render for List<Raster<CPU>> {
|
||||
let matrix = transform * DAffine2::from_scale(1. / size);
|
||||
let matrix = format_transform_matrix(matrix);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
attributes.push("width", size.x.to_string());
|
||||
@@ -1795,7 +1794,7 @@ impl Render for List<Raster<CPU>> {
|
||||
attributes.push("href", base64_string);
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push(ATTR_TRANSFORM, matrix);
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
@@ -1817,9 +1816,9 @@ impl Render for List<Raster<CPU>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
@@ -1834,7 +1833,7 @@ impl Render for List<Raster<CPU>> {
|
||||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform_attribute = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform: DAffine2 = transform * transform_attribute;
|
||||
@@ -1874,7 +1873,7 @@ impl Render for List<Raster<CPU>> {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
||||
if !self.is_empty() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let transform = self.attr_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,
|
||||
@@ -1883,7 +1882,7 @@ impl Render for List<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::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
|
||||
let upstream_nested_layers = self.attr_cloned_or_default::<graphic_attr::editor::MergedLayers>(0);
|
||||
if !upstream_nested_layers.is_empty() {
|
||||
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
@@ -1906,10 +1905,10 @@ impl Render for List<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 blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let clip_attr: bool = self.attribute_cloned_or_default(ATTR_CLIPPING_MASK, index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let clip_attr = self.attr_cloned_or_default::<attr::ClippingMask>(index);
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => blend_mode_attr.to_peniko(),
|
||||
@@ -1928,7 +1927,7 @@ impl Render for List<Raster<GPU>> {
|
||||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let transform_attribute = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform = transform * transform_attribute;
|
||||
@@ -1969,7 +1968,7 @@ impl Render for List<Raster<GPU>> {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
||||
if !self.is_empty() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0);
|
||||
let transform = self.attr_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,
|
||||
@@ -1978,7 +1977,7 @@ impl Render for List<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::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, 0);
|
||||
let upstream_nested_layers = self.attr_cloned_or_default::<graphic_attr::editor::MergedLayers>(0);
|
||||
if !upstream_nested_layers.is_empty() {
|
||||
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
@@ -2000,9 +1999,9 @@ impl Render for List<Raster<GPU>> {
|
||||
impl Render for List<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for (index, color) in self.iter_element_values().enumerate() {
|
||||
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
render.leaf_tag("polyline", |attributes| {
|
||||
// Stand-in for an infinite background. Chrome's SVG renderer keeps internal coordinates in f32 and loses
|
||||
// precision past ~2^24 (~16.7 million), causing tile-boundary artifacts that pop in and out during panning.
|
||||
@@ -2031,9 +2030,9 @@ impl Render for List<Color> {
|
||||
use vello::peniko;
|
||||
|
||||
for (index, color) in self.iter_element_values().enumerate() {
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
@@ -2071,12 +2070,12 @@ impl Render for List<Gradient> {
|
||||
|
||||
for index in 0..self.len() {
|
||||
let Some(gradient) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let spread_method: GradientSpreadMethod = self.attribute_cloned_or_default(ATTR_SPREAD_METHOD, index);
|
||||
let gradient_type: GradientType = self.attribute_cloned_or_default(ATTR_GRADIENT_TYPE, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let spread_method = self.attr_cloned_or_default::<vector_attr::SpreadMethod>(index);
|
||||
let gradient_type = self.attr_cloned_or_default::<vector_attr::GradientType>(index);
|
||||
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
||||
render.leaf_tag(tag, |attributes| {
|
||||
if let Some((min, size)) = thumbnail_rect {
|
||||
@@ -2160,13 +2159,13 @@ impl Render for List<Gradient> {
|
||||
for (((index, gradient), spread_method), gradient_type) in self
|
||||
.iter_element_values()
|
||||
.enumerate()
|
||||
.zip(self.iter_attribute_values_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD))
|
||||
.zip(self.iter_attribute_values_or_default::<GradientType>(ATTR_GRADIENT_TYPE))
|
||||
.zip(self.iter_attr_values_or_default::<vector_attr::SpreadMethod>())
|
||||
.zip(self.iter_attr_values_or_default::<vector_attr::GradientType>())
|
||||
{
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let gradient_transform = parent_transform * transform;
|
||||
|
||||
let blend_mode = blend_mode_attr.to_peniko();
|
||||
@@ -2314,16 +2313,16 @@ fn draw_glyph_run_to_bezpaths(glyph_run: &parley::GlyphRun<'_, ()>, x_offset: f3
|
||||
fn text_item_size_and_transform(list: &List<String>, index: usize) -> Option<(DVec2, DAffine2)> {
|
||||
let text = list.element(index)?;
|
||||
let font: Resource = {
|
||||
let f: Resource = list.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
let f = list.attr_cloned_or_default::<text_attr::Font>(index);
|
||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
let font_size: f64 = list.attribute_cloned_or(ATTR_FONT_SIZE, index, DEFAULT_FONT_SIZE);
|
||||
let line_height: f64 = list.attribute_cloned_or(ATTR_LINE_HEIGHT, index, 1.2);
|
||||
let letter_spacing: f64 = list.attribute_cloned_or(ATTR_LETTER_SPACING, index, 0.);
|
||||
let max_width: Option<f64> = list.attribute_cloned_or(ATTR_MAX_WIDTH, index, None);
|
||||
let max_height: Option<f64> = list.attribute_cloned_or(ATTR_MAX_HEIGHT, index, None);
|
||||
let align: text_nodes::TextAlign = list.attribute_cloned_or_default(ATTR_TEXT_ALIGN, index);
|
||||
let transform: DAffine2 = list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let font_size = list.attr_cloned_or::<attr::FontSize>(index, DEFAULT_FONT_SIZE);
|
||||
let line_height = list.attr_cloned_or::<attr::LineHeight>(index, 1.2);
|
||||
let letter_spacing = list.attr_cloned_or_default::<attr::LetterSpacing>(index);
|
||||
let max_width = list.attr_cloned_or_default::<attr::MaxWidth>(index);
|
||||
let max_height = list.attr_cloned_or_default::<attr::MaxHeight>(index);
|
||||
let align = list.attr_cloned_or_default::<text_attr::TextAlign>(index);
|
||||
let transform = list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
|
||||
let typesetting = text_nodes::TypesettingConfig {
|
||||
font_size,
|
||||
@@ -2375,7 +2374,7 @@ pub fn graphic_list_bounding_box(list: &List<Graphic>, transform: DAffine2) -> R
|
||||
let mut any_infinite = false;
|
||||
|
||||
for index in 0..list.len() {
|
||||
let item_transform = transform * list.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, index);
|
||||
let item_transform = transform * list.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let Some(graphic) = list.element(index) else { continue };
|
||||
let bounds = match graphic {
|
||||
Graphic::Text(text_list) => text_list_bounding_box(text_list, item_transform),
|
||||
@@ -2409,21 +2408,21 @@ impl Render for List<String> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let font: Resource = {
|
||||
let f: Resource = self.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
let f = self.attr_cloned_or_default::<text_attr::Font>(index);
|
||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
let font_size: f64 = self.attribute_cloned_or(ATTR_FONT_SIZE, index, DEFAULT_FONT_SIZE);
|
||||
let line_height: f64 = self.attribute_cloned_or(ATTR_LINE_HEIGHT, index, 1.2);
|
||||
let letter_spacing: f64 = self.attribute_cloned_or(ATTR_LETTER_SPACING, index, 0.);
|
||||
let max_width: Option<f64> = self.attribute_cloned_or(ATTR_MAX_WIDTH, index, None);
|
||||
let max_height: Option<f64> = self.attribute_cloned_or(ATTR_MAX_HEIGHT, index, None);
|
||||
let letter_tilt: f64 = self.attribute_cloned_or(ATTR_LETTER_TILT, index, 0.);
|
||||
let align: text_nodes::TextAlign = self.attribute_cloned_or_default(ATTR_TEXT_ALIGN, index);
|
||||
let font_size = self.attr_cloned_or::<attr::FontSize>(index, DEFAULT_FONT_SIZE);
|
||||
let line_height = self.attr_cloned_or::<attr::LineHeight>(index, 1.2);
|
||||
let letter_spacing = self.attr_cloned_or_default::<attr::LetterSpacing>(index);
|
||||
let max_width = self.attr_cloned_or_default::<attr::MaxWidth>(index);
|
||||
let max_height = self.attr_cloned_or_default::<attr::MaxHeight>(index);
|
||||
let letter_tilt = self.attr_cloned_or_default::<attr::LetterTilt>(index);
|
||||
let align = self.attr_cloned_or_default::<text_attr::TextAlign>(index);
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
let typesetting = text_nodes::TypesettingConfig {
|
||||
@@ -2494,21 +2493,21 @@ impl Render for List<String> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let item_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
let item_transform = self.attr_cloned_or_default::<attr::Transform>(index);
|
||||
let font: Resource = {
|
||||
let f: Resource = self.attribute_cloned_or_default(ATTR_FONT, index);
|
||||
let f = self.attr_cloned_or_default::<text_attr::Font>(index);
|
||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f }
|
||||
};
|
||||
let font_size: f64 = self.attribute_cloned_or(ATTR_FONT_SIZE, index, DEFAULT_FONT_SIZE);
|
||||
let line_height: f64 = self.attribute_cloned_or(ATTR_LINE_HEIGHT, index, 1.2);
|
||||
let letter_spacing: f64 = self.attribute_cloned_or(ATTR_LETTER_SPACING, index, 0.);
|
||||
let max_width: Option<f64> = self.attribute_cloned_or(ATTR_MAX_WIDTH, index, None);
|
||||
let max_height: Option<f64> = self.attribute_cloned_or(ATTR_MAX_HEIGHT, index, None);
|
||||
let letter_tilt: f64 = self.attribute_cloned_or(ATTR_LETTER_TILT, index, 0.);
|
||||
let align: text_nodes::TextAlign = self.attribute_cloned_or_default(ATTR_TEXT_ALIGN, index);
|
||||
let blend_mode_attr: BlendMode = self.attribute_cloned_or_default(ATTR_BLEND_MODE, index);
|
||||
let opacity_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY, index, 1.);
|
||||
let opacity_fill_attr: f64 = self.attribute_cloned_or(ATTR_OPACITY_FILL, index, 1.);
|
||||
let font_size = self.attr_cloned_or::<attr::FontSize>(index, DEFAULT_FONT_SIZE);
|
||||
let line_height = self.attr_cloned_or::<attr::LineHeight>(index, 1.2);
|
||||
let letter_spacing = self.attr_cloned_or_default::<attr::LetterSpacing>(index);
|
||||
let max_width = self.attr_cloned_or_default::<attr::MaxWidth>(index);
|
||||
let max_height = self.attr_cloned_or_default::<attr::MaxHeight>(index);
|
||||
let letter_tilt = self.attr_cloned_or_default::<attr::LetterTilt>(index);
|
||||
let align = self.attr_cloned_or_default::<text_attr::TextAlign>(index);
|
||||
let blend_mode_attr = self.attr_cloned_or_default::<attr::BlendMode>(index);
|
||||
let opacity_attr = self.attr_cloned_or_default::<attr::Opacity>(index);
|
||||
let opacity_fill_attr = self.attr_cloned_or_default::<attr::OpacityFill>(index);
|
||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||
|
||||
let typesetting = text_nodes::TypesettingConfig {
|
||||
@@ -2559,7 +2558,7 @@ impl Render for List<String> {
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||
// Click targets are baked relative to item 0's transform, which `Graphic::collect_metadata` records as `local_transforms[element_id]`.
|
||||
let item_zero_transform: DAffine2 = if !self.is_empty() {
|
||||
self.attribute_cloned_or_default(ATTR_TRANSFORM, 0)
|
||||
self.attr_cloned_or_default::<attr::Transform>(0)
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
};
|
||||
@@ -2572,7 +2571,7 @@ impl Render for List<String> {
|
||||
let mut accumulated_click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
||||
|
||||
for index in 0..self.len() {
|
||||
let layer_path: List<NodeId> = self.attribute_cloned_or_default::<NodeIdPath>(ATTR_EDITOR_LAYER_PATH, index).0;
|
||||
let layer_path: List<NodeId> = self.attr_cloned_or_default::<attr::editor::LayerPath>(index).0;
|
||||
let layer = layer_path.iter_element_values().next_back().copied();
|
||||
let Some(element_id) = caller_element_id.or(layer) else { continue };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user