1- use core_types:: blending:: AlphaBlending ;
1+ use core_types:: blending:: BlendMode ;
22use core_types:: bounds:: { BoundingBox , RenderBoundingBox } ;
33use core_types:: graphene_hash:: CacheHash ;
44use core_types:: ops:: TableConvert ;
55use core_types:: render_complexity:: RenderComplexity ;
66use core_types:: table:: { Table , TableRow } ;
77use core_types:: uuid:: NodeId ;
8- use core_types:: { ATTR_ALPHA_BLENDING , ATTR_EDITOR_LAYER_PATH , ATTR_TRANSFORM , Color } ;
8+ use core_types:: { ATTR_CLIPPING_MASK , ATTR_EDITOR_LAYER_PATH , ATTR_OPACITY , ATTR_OPACITY_FILL , ATTR_TRANSFORM , Color } ;
99use dyn_any:: DynAny ;
1010use glam:: DAffine2 ;
1111use raster_types:: { CPU , GPU , Raster } ;
@@ -130,47 +130,57 @@ impl From<Table<GradientStops>> for Graphic {
130130/// Deeply flattens a `Table<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
131131/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`Table`s composes transforms and opacity.
132132fn flatten_graphic_table < T > ( content : Table < Graphic > , extract_variant : fn ( Graphic ) -> Option < Table < T > > ) -> Table < T > {
133- fn compose_alpha_blending ( parent : AlphaBlending , child : AlphaBlending ) -> AlphaBlending {
134- AlphaBlending {
135- blend_mode : child. blend_mode ,
136- opacity : parent. opacity * child. opacity ,
137- fill : child. fill ,
138- clip : child. clip ,
139- }
140- }
141-
142133 fn flatten_recursive < T > ( output : & mut Table < T > , current_graphic_table : Table < Graphic > , extract_variant : fn ( Graphic ) -> Option < Table < T > > ) {
143134 for current_graphic_row in current_graphic_table. into_iter ( ) {
144135 let layer_path: Table < NodeId > = current_graphic_row. attribute_cloned_or_default ( ATTR_EDITOR_LAYER_PATH ) ;
145136 let current_transform: DAffine2 = current_graphic_row. attribute_cloned_or_default ( ATTR_TRANSFORM ) ;
146- let current_alpha_blending: AlphaBlending = current_graphic_row. attribute_cloned_or_default ( ATTR_ALPHA_BLENDING ) ;
137+ let current_opacity: f64 = current_graphic_row. attribute_cloned_or ( ATTR_OPACITY , 1. ) ;
138+ let current_fill: f64 = current_graphic_row. attribute_cloned_or ( ATTR_OPACITY_FILL , 1. ) ;
147139
148140 match current_graphic_row. into_element ( ) {
149- // Recurse into nested `Table<Graphic>` items, composing the parent's transform onto each child
141+ // Compose the parent's transform, opacity, and fill onto each child row
150142 Graphic :: Graphic ( mut sub_table) => {
151- for index in 0 ..sub_table. len ( ) {
152- let child_transform: DAffine2 = sub_table. attribute_cloned_or_default ( ATTR_TRANSFORM , index) ;
153- let child_alpha_blending: AlphaBlending = sub_table. attribute_cloned_or_default ( ATTR_ALPHA_BLENDING , index) ;
143+ // Identity default means a missing column still composes correctly
144+ for v in sub_table. iter_attribute_values_mut_or_default :: < DAffine2 > ( ATTR_TRANSFORM ) {
145+ * v = current_transform * * v;
146+ }
154147
155- sub_table. set_attribute ( ATTR_TRANSFORM , index, current_transform * child_transform) ;
156- sub_table. set_attribute ( ATTR_ALPHA_BLENDING , index, compose_alpha_blending ( current_alpha_blending, child_alpha_blending) ) ;
148+ // f64 defaults to 0, but opacity/fill default to 1, so missing columns must be set rather than multiplied
149+ if let Some ( values) = sub_table. iter_attribute_values_mut :: < f64 > ( ATTR_OPACITY ) {
150+ for v in values {
151+ * v *= current_opacity;
152+ }
153+ } else {
154+ for v in sub_table. iter_attribute_values_mut_or_default :: < f64 > ( ATTR_OPACITY ) {
155+ * v = current_opacity;
156+ }
157+ }
158+ if let Some ( values) = sub_table. iter_attribute_values_mut :: < f64 > ( ATTR_OPACITY_FILL ) {
159+ for v in values {
160+ * v *= current_fill;
161+ }
162+ } else {
163+ for v in sub_table. iter_attribute_values_mut_or_default :: < f64 > ( ATTR_OPACITY_FILL ) {
164+ * v = current_fill;
165+ }
157166 }
158167
159168 flatten_recursive ( output, sub_table, extract_variant) ;
160169 }
161- // Try to extract the target variant; if it matches, push its items with composed transform and opacity
170+ // Extract the target variant and push its items with composed transform, opacity, and fill
162171 other => {
163172 if let Some ( typed_table) = extract_variant ( other) {
164- for row in typed_table. into_iter ( ) {
165- let row_transform: DAffine2 = row . attribute_cloned_or_default ( ATTR_TRANSFORM ) ;
166- let row_alpha_blending : AlphaBlending = row . attribute_cloned_or_default ( ATTR_ALPHA_BLENDING ) ;
167- let ( element , mut attributes ) = row . into_parts ( ) ;
173+ for mut item in typed_table. into_iter ( ) {
174+ let row_transform: DAffine2 = item . attribute_cloned_or_default ( ATTR_TRANSFORM ) ;
175+ let row_opacity : f64 = item . attribute_cloned_or ( ATTR_OPACITY , 1. ) ;
176+ let row_fill : f64 = item . attribute_cloned_or ( ATTR_OPACITY_FILL , 1. ) ;
168177
169- attributes. insert ( ATTR_TRANSFORM , current_transform * row_transform) ;
170- attributes. insert ( ATTR_ALPHA_BLENDING , compose_alpha_blending ( current_alpha_blending, row_alpha_blending) ) ;
171- attributes. insert ( ATTR_EDITOR_LAYER_PATH , layer_path. clone ( ) ) ;
178+ item. set_attribute ( ATTR_TRANSFORM , current_transform * row_transform) ;
179+ item. set_attribute ( ATTR_OPACITY , current_opacity * row_opacity) ;
180+ item. set_attribute ( ATTR_OPACITY_FILL , current_fill * row_fill) ;
181+ item. set_attribute ( ATTR_EDITOR_LAYER_PATH , layer_path. clone ( ) ) ;
172182
173- output. push ( TableRow :: from_parts ( element , attributes ) ) ;
183+ output. push ( item ) ;
174184 }
175185 }
176186 }
@@ -321,8 +331,9 @@ impl Graphic {
321331
322332 pub fn had_clip_enabled ( & self ) -> bool {
323333 fn all_clipped < T > ( table : & Table < T > ) -> bool {
324- table. iter_attribute_values_or_default :: < AlphaBlending > ( ATTR_ALPHA_BLENDING ) . all ( |a| a . clip )
334+ table. iter_attribute_values_or_default :: < bool > ( ATTR_CLIPPING_MASK ) . all ( |clip| clip)
325335 }
336+
326337 match self {
327338 Graphic :: Vector ( table) => all_clipped ( table) ,
328339 Graphic :: Graphic ( table) => all_clipped ( table) ,
@@ -335,12 +346,11 @@ impl Graphic {
335346
336347 pub fn can_reduce_to_clip_path ( & self ) -> bool {
337348 match self {
338- Graphic :: Vector ( vector) => vector
339- . iter_element_values ( )
340- . zip ( vector. iter_attribute_values_or_default :: < AlphaBlending > ( ATTR_ALPHA_BLENDING ) )
341- . all ( |( element, alpha_blending) | {
342- ( alpha_blending. opacity > 1. - f32:: EPSILON ) && element. style . fill ( ) . is_opaque ( ) && element. style . stroke ( ) . is_none_or ( |stroke| !stroke. has_renderable_stroke ( ) )
343- } ) ,
349+ Graphic :: Vector ( vector) => ( 0 ..vector. len ( ) ) . all ( |index| {
350+ let Some ( element) = vector. element ( index) else { return false } ;
351+ let opacity: f64 = vector. attribute_cloned_or ( ATTR_OPACITY , index, 1. ) ;
352+ opacity > 1. - f64:: EPSILON && element. style . fill ( ) . is_opaque ( ) && element. style . stroke ( ) . is_none_or ( |stroke| !stroke. has_renderable_stroke ( ) )
353+ } ) ,
344354 _ => false ,
345355 }
346356 }
@@ -474,12 +484,23 @@ impl<T: Clone> OmitIndex for Table<T> {
474484pub fn migrate_graphic < ' de , D : serde:: Deserializer < ' de > > ( deserializer : D ) -> Result < Table < Graphic > , D :: Error > {
475485 use serde:: Deserialize ;
476486
487+ /// Mirrors the removed `AlphaBlending` struct for legacy document deserialization.
488+ #[ derive( Clone , Debug , Default , PartialEq ) ]
489+ #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
490+ #[ cfg_attr( feature = "serde" , serde( default ) ) ]
491+ pub struct LegacyAlphaBlending {
492+ pub blend_mode : BlendMode ,
493+ pub opacity : f32 ,
494+ pub fill : f32 ,
495+ pub clip : bool ,
496+ }
497+
477498 #[ derive( Clone , Debug , PartialEq , DynAny , Default ) ]
478499 #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
479500 pub struct OldGraphicGroup {
480501 elements : Vec < ( Graphic , Option < NodeId > ) > ,
481502 transform : DAffine2 ,
482- alpha_blending : AlphaBlending ,
503+ alpha_blending : LegacyAlphaBlending ,
483504 }
484505 #[ derive( Clone , Debug , PartialEq , DynAny , Default ) ]
485506 #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
@@ -502,7 +523,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res
502523 #[ cfg_attr( feature = "serde" , serde( alias = "instances" , alias = "instance" ) ) ]
503524 element : Vec < T > ,
504525 transform : Vec < DAffine2 > ,
505- alpha_blending : Vec < AlphaBlending > ,
526+ alpha_blending : Vec < LegacyAlphaBlending > ,
506527 }
507528
508529 #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
0 commit comments