Fix opacity attributes from defaulting to 0 rather than 1 (#4219)

* Fix opacity attribute padding to default to 1 instead of 0

* Perf fixes
This commit is contained in:
Keavon Chambers
2026-06-09 23:00:57 -07:00
committed by GitHub
parent 02a40a860f
commit 863b1f9993

View File

@@ -77,6 +77,30 @@ pub const ATTR_SPREAD_METHOD: &str = "spread_method";
/// Gradient's `GradientType` (`Linear` or `Radial`).
pub const ATTR_GRADIENT_TYPE: &str = "gradient_type";
// ===========================
// Implicit attribute defaults
// ===========================
/// Overrides the type's default value for certain attributes.
fn implicit_default_value(key: &str) -> Option<Box<dyn AnyAttributeValue>> {
match key {
ATTR_OPACITY | ATTR_OPACITY_FILL => Some(Box::new(1_f64)),
_ => None,
}
}
/// Appends `count` copies of `key`'s implicit default to `attribute` (see [`implicit_default_value`]).
fn pad_with_implicit_default(key: &str, attribute: &mut Box<dyn AnyAttribute>, count: usize) {
match implicit_default_value(key) {
Some(default) => attribute.push_repeated(&*default, count),
None => {
for _ in 0..count {
attribute.push_default();
}
}
}
}
// ========================
// TRAIT: AnyAttributeValue
// ========================
@@ -99,8 +123,8 @@ pub trait AnyAttributeValue: std::any::Any + Send + Sync {
/// Returns a debug-formatted string representation of this value.
fn display_string(&self) -> String;
/// Wraps this scalar value into a new attribute with `preceding_defaults` default values before this value.
fn into_attribute(self: Box<Self>, preceding_defaults: usize) -> Box<dyn AnyAttribute>;
/// Wraps this scalar value into a new attribute, preceded by `preceding_defaults` implicit defaults for `key`.
fn into_attribute(self: Box<Self>, key: &str, preceding_defaults: usize) -> Box<dyn AnyAttribute>;
}
impl<T: Clone + Send + Sync + Default + Sized + Debug + PartialEq + CacheHash + 'static> AnyAttributeValue for T {
@@ -129,11 +153,12 @@ impl<T: Clone + Send + Sync + Default + Sized + Debug + PartialEq + CacheHash +
format!("{:?}", self)
}
/// Wraps this scalar value into a new attribute, padded with `preceding_defaults` default values before it.
fn into_attribute(self: Box<Self>, preceding_defaults: usize) -> Box<dyn AnyAttribute> {
let mut data = vec![T::default(); preceding_defaults];
data.push(*self);
Box::new(Attribute(data))
/// Wraps this scalar value into a new attribute, preceded by `preceding_defaults` implicit defaults for `key`.
fn into_attribute(self: Box<Self>, key: &str, preceding_defaults: usize) -> Box<dyn AnyAttribute> {
let mut attribute: Box<dyn AnyAttribute> = Box::new(Attribute::<T>(Vec::with_capacity(preceding_defaults + 1)));
pad_with_implicit_default(key, &mut attribute, preceding_defaults);
attribute.push(self);
attribute
}
}
@@ -164,6 +189,10 @@ pub trait AnyAttribute: std::any::Any + Send + Sync {
/// Pushes a default value onto the end of this attribute.
fn push_default(&mut self);
/// Appends `count` copies of `value` (downcast to this attribute's type, or the type default if it
/// doesn't match), filling in bulk to avoid per-element boxing and dispatch.
fn push_repeated(&mut self, value: &dyn AnyAttributeValue, count: usize);
/// Sets the value at the given index, padding with defaults if the attribute is shorter than `index`.
/// Falls back to a default if the value's type doesn't match.
fn set_at(&mut self, index: usize, value: Box<dyn AnyAttributeValue>);
@@ -258,6 +287,12 @@ impl<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>
self.0.push(T::default());
}
/// Appends `count` copies of `value`, downcast to `T` (or `T::default()` if the type doesn't match).
fn push_repeated(&mut self, value: &dyn AnyAttributeValue, count: usize) {
let value = value.as_any().downcast_ref::<T>().cloned().unwrap_or_default();
self.0.resize(self.0.len() + count, value);
}
/// Sets the value at the given index, padding with defaults if the attribute is shorter than `index`.
/// Falls back to a default if the value's type doesn't match.
fn set_at(&mut self, index: usize, value: Box<dyn AnyAttributeValue>) {
@@ -345,13 +380,11 @@ impl AttributeDyn {
}
/// Builds a new attribute matching `target_len` items, taking values from this attribute (wrapping if shorter, truncating if longer).
pub fn cloned_to_length(&self, target_len: usize) -> Box<dyn AnyAttribute> {
pub fn cloned_to_length(&self, key: &str, target_len: usize) -> Box<dyn AnyAttribute> {
let mut result = self.0.new_with_defaults(0);
let source_len = self.0.len();
if source_len == 0 {
for _ in 0..target_len {
result.push_default();
}
pad_with_implicit_default(key, &mut result, target_len);
return result;
}
for i in 0..target_len {
@@ -663,19 +696,19 @@ impl Attributes {
fn push_item(&mut self, item: ItemAttributeValues) {
let mut item_entries = item.0;
// Push values into existing attributes, or a default if the item lacks that attribute
// Push values into existing attributes, or the implicit default if the item lacks that attribute
for (attribute_key, attribute) in &mut self.attributes {
if let Some(position) = item_entries.iter().position(|(k, _)| k == attribute_key) {
let (_, value) = item_entries.swap_remove(position);
attribute.push(value);
} else {
attribute.push_default();
pad_with_implicit_default(attribute_key, attribute, 1);
}
}
// Create new attributes for any remaining item values, padded with defaults for prior items
// Create new attributes for any remaining item values, padded with implicit defaults for prior items
for (key, value) in item_entries {
self.attributes.push((key, value.into_attribute(self.len)));
self.attributes.push((key.clone(), value.into_attribute(&key, self.len)));
}
self.len += 1;
@@ -687,21 +720,20 @@ impl Attributes {
let other_len = other.len;
let mut other_entries = other.attributes;
// Extend matching attributes, or pad self's attributes with defaults for the other's item count
// Extend matching attributes, or pad self's attributes with implicit defaults for the other's item count
for (key, self_attribute) in &mut self.attributes {
if let Some(position) = other_entries.iter().position(|(k, _)| k == key) {
let (_, other_attribute) = other_entries.swap_remove(position);
self_attribute.extend(other_attribute);
} else {
for _ in 0..other_len {
self_attribute.push_default();
}
pad_with_implicit_default(key, self_attribute, other_len);
}
}
// Remaining other attributes are new, so we pad with defaults for self's existing items
// Remaining other attributes are new, so we pad with implicit defaults for self's existing items
for (key, other_attribute) in other_entries {
let mut combined = other_attribute.new_with_defaults(self.len);
let mut combined = other_attribute.new_with_defaults(0);
pad_with_implicit_default(&key, &mut combined, self.len);
combined.extend(other_attribute);
self.attributes.push((key, combined));
}
@@ -723,9 +755,16 @@ impl Attributes {
}
}
/// Creates a new attribute of type `T` filled with `key`'s implicit default for all existing items.
fn new_attribute_padded<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&self, key: &str) -> Box<dyn AnyAttribute> {
let mut attribute: Box<dyn AnyAttribute> = Box::new(Attribute::<T>(Vec::with_capacity(self.len)));
pad_with_implicit_default(key, &mut attribute, self.len);
attribute
}
/// Finds or creates an attribute for the given key and type, returning its position.
/// If an attribute with the key exists but has the wrong type, it is removed and replaced with a new attribute of the correct type, padded with defaults.
/// A newly created attribute is filled with `T::default()` for all existing items.
/// If an attribute with the key exists but has the wrong type, it is removed and replaced with a new attribute of the correct type, padded with implicit defaults.
/// A newly created attribute is filled with `key`'s implicit default for all existing items.
fn find_or_create_attribute<T: Clone + Send + Sync + Default + Debug + PartialEq + CacheHash + 'static>(&mut self, key: &str) -> usize {
match self.attributes.iter().position(|(k, _)| k == key) {
Some(position) => {
@@ -733,12 +772,14 @@ impl Attributes {
position
} else {
self.attributes.remove(position);
self.attributes.push((key.to_string(), Box::new(Attribute::<T>(vec![T::default(); self.len]))));
let attribute = self.new_attribute_padded::<T>(key);
self.attributes.push((key.to_string(), attribute));
self.attributes.len() - 1
}
}
None => {
self.attributes.push((key.to_string(), Box::new(Attribute::<T>(vec![T::default(); self.len]))));
let attribute = self.new_attribute_padded::<T>(key);
self.attributes.push((key.to_string(), attribute));
self.attributes.len() - 1
}
}
@@ -995,7 +1036,7 @@ impl<T> List<T> {
pub fn set_attribute_dyn(&mut self, key: impl Into<String>, source: AttributeDyn) {
let key = key.into();
self.attributes.attributes.retain(|(k, _)| k != &key);
let new_attribute = source.cloned_to_length(self.element.len());
let new_attribute = source.cloned_to_length(&key, self.element.len());
self.attributes.attributes.push((key, new_attribute));
}
@@ -1006,10 +1047,9 @@ impl<T> List<T> {
if let Some(position) = self.attributes.attributes.iter().position(|(k, _)| k == &key) {
self.attributes.attributes[position].1.set_at(index, value.0);
} else {
let mut new_attribute = value.0.into_attribute(index);
while new_attribute.len() < self.element.len() {
new_attribute.push_default();
}
let mut new_attribute = value.0.into_attribute(&key, index);
let trailing_defaults = self.element.len().saturating_sub(new_attribute.len());
pad_with_implicit_default(&key, &mut new_attribute, trailing_defaults);
self.attributes.attributes.push((key, new_attribute));
}
}
@@ -1345,3 +1385,41 @@ impl<T> DoubleEndedIterator for ItemIter<T> {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
// An item that doesn't set opacity must read as fully opaque even once a sibling introduces the
// opacity attribute, otherwise the dense store pads it with f64's `0.` default and it vanishes.
#[test]
fn implicit_opacity_default_is_opaque() {
// Collecting items (the path Boolean Operation takes when merging operands)
let mut collected = List::<()>::new();
collected.push(Item::new_from_element(()));
collected.push(Item::new_from_element(()).with_attribute(ATTR_OPACITY, 1_f64));
assert_eq!(collected.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 0), 1.);
// Extending one list with another
let mut base = List::<()>::new();
base.push(Item::new_from_element(()));
let mut tail = List::<()>::new();
tail.push(Item::new_from_element(()).with_attribute(ATTR_OPACITY_FILL, 1_f64));
base.extend(tail);
assert_eq!(base.attribute_cloned_or_default::<f64>(ATTR_OPACITY_FILL, 0), 1.);
// Setting one item's opacity leaves the others opaque, not transparent
let mut indexed = List::<()>::new();
indexed.push(Item::new_from_element(()));
indexed.push(Item::new_from_element(()));
indexed.set_attribute(ATTR_OPACITY, 1, 0.5_f64);
assert_eq!(indexed.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 0), 1.);
assert_eq!(indexed.attribute_cloned_or_default::<f64>(ATTR_OPACITY, 1), 0.5);
// A non-opacity numeric attribute still falls back to its type default
let mut other = List::<()>::new();
other.push(Item::new_from_element(()));
other.push(Item::new_from_element(()).with_attribute(ATTR_START, 5_u64));
assert_eq!(other.attribute_cloned_or_default::<u64>(ATTR_START, 0), 0);
}
}