mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Implement dynamic table attributes to generalize the graphic-specific Table type (#4050)
* Feature-gate serde derives behind cfg_attr in all runtime node graph type crates * Refactor Table to move its hard-coded fields into an attributes field * Encapsulate TableRow/TableRowRef/TableRowMut attribute fields behind accessor methods * Remove TaggedValue::GraphicUnused * Refactor Table<T> to use dynamic attributes instead fixed names * Fix code review soundness concerns * Add todo work * Replace row-oriented Table<T> API with column-oriented access * Fix attribute propagation bugs ---------
This commit is contained in:
@@ -6,6 +6,10 @@ description = "SVG rendering for Graphene"
|
||||
authors = ["Graphite Authors <contact@graphite.art>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde", "core-types/serde", "vector-types/serde", "graphic-types/serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
@@ -14,7 +18,6 @@ graphene-hash = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
log = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
@@ -22,7 +25,7 @@ usvg = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
vector-types = { workspace = true }
|
||||
graphic-types = { workspace = true }
|
||||
|
||||
|
||||
# Workspace dependencies
|
||||
vello = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::render_ext::RenderExt;
|
||||
use crate::to_peniko::BlendModeExt;
|
||||
use core_types::AlphaBlending;
|
||||
use core_types::CacheHash;
|
||||
use core_types::blending::BlendMode;
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
@@ -68,7 +69,8 @@ pub fn checkerboard_brush() -> peniko::Brush {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
enum MaskType {
|
||||
Clip,
|
||||
Mask,
|
||||
@@ -316,7 +318,8 @@ fn draw_raster_outline(scene: &mut Scene, outline_transform: &DAffine2, render_p
|
||||
|
||||
// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
|
||||
// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
|
||||
#[derive(Debug, Default, Clone, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RenderMetadata {
|
||||
pub upstream_footprints: HashMap<NodeId, Footprint>,
|
||||
pub local_transforms: HashMap<NodeId, DAffine2>,
|
||||
@@ -408,41 +411,44 @@ impl Render for Graphic {
|
||||
Graphic::Vector(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.first_element_source_id.insert(element_id, *row.source_node_id);
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
let source_node_id: Option<NodeId> = table.attribute_cloned_or_default("source_node_id", 0);
|
||||
let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0);
|
||||
|
||||
metadata.first_element_source_id.insert(element_id, source_node_id);
|
||||
metadata.local_transforms.insert(element_id, transform);
|
||||
}
|
||||
}
|
||||
Graphic::RasterCPU(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
Graphic::RasterGPU(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
Graphic::Color(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
Graphic::Gradient(table) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
|
||||
// TODO: Find a way to handle more than the first row
|
||||
if let Some(row) = table.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *row.transform);
|
||||
if !table.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,65 +642,69 @@ impl Render for Artboard {
|
||||
|
||||
impl Render for Table<Artboard> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for artboard in self.iter() {
|
||||
artboard.element.render_svg(render, render_params);
|
||||
for element in self.iter_element_values() {
|
||||
element.render_svg(render, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
for element in self.iter_element_values() {
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||
for row in self.iter() {
|
||||
row.element.collect_metadata(metadata, footprint, *row.source_node_id);
|
||||
for index in 0..self.len() {
|
||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
||||
self.element(index).unwrap().collect_metadata(metadata, footprint, source_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for row in self.iter() {
|
||||
row.element.add_upstream_click_targets(click_targets);
|
||||
for element in self.iter_element_values() {
|
||||
element.add_upstream_click_targets(click_targets);
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.iter().count() > 0
|
||||
!self.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Table<Graphic> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
let mut iter = self.iter().peekable();
|
||||
let mut mask_state = None;
|
||||
|
||||
while let Some(row) = iter.next() {
|
||||
for index in 0..self.len() {
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(*row.transform);
|
||||
let matrix = format_transform_matrix(transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
|
||||
let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
|
||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
||||
|
||||
if next_clips && mask_state.is_none() {
|
||||
let uuid = generate_uuid();
|
||||
let mask_type = if row.element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
|
||||
let mask_type = if element.can_reduce_to_clip_path() { MaskType::Clip } else { MaskType::Mask };
|
||||
mask_state = Some((uuid, mask_type));
|
||||
let mut svg = SvgRender::new();
|
||||
row.element.render_svg(&mut svg, &render_params.for_clipper());
|
||||
element.render_svg(&mut svg, &render_params.for_clipper());
|
||||
|
||||
write!(&mut attributes.0.svg_defs, r##"{}"##, svg.svg_defs).unwrap();
|
||||
mask_type.write_to_defs(&mut attributes.0.svg_defs, uuid, svg.svg.to_svg_string());
|
||||
@@ -710,19 +720,20 @@ impl Render for Table<Graphic> {
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
row.element.render_svg(render, render_params);
|
||||
element.render_svg(render, render_params);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
let mut iter = self.iter().peekable();
|
||||
let mut mask_element_and_transform = None;
|
||||
|
||||
while let Some(row) = iter.next() {
|
||||
let transform = transform * *row.transform;
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let transform = transform * row_transform;
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
let mut layer = false;
|
||||
|
||||
@@ -732,9 +743,9 @@ impl Render for Table<Graphic> {
|
||||
};
|
||||
let mut bounds = RenderBoundingBox::None;
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || (render_params.render_mode != RenderMode::Outline && alpha_blending.blend_mode != BlendMode::default()) {
|
||||
bounds = row.element.bounding_box(transform, true);
|
||||
bounds = element.bounding_box(transform, true);
|
||||
|
||||
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
||||
scene.push_layer(
|
||||
@@ -748,17 +759,17 @@ impl Render for Table<Graphic> {
|
||||
}
|
||||
}
|
||||
|
||||
let next_clips = iter.peek().is_some_and(|next_row| next_row.element.had_clip_enabled());
|
||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
||||
if next_clips && mask_element_and_transform.is_none() {
|
||||
mask_element_and_transform = Some((row.element, transform));
|
||||
mask_element_and_transform = Some((element, transform));
|
||||
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
} else if let Some((mask_element, transform_mask)) = mask_element_and_transform {
|
||||
if !next_clips {
|
||||
mask_element_and_transform = None;
|
||||
}
|
||||
if !layer {
|
||||
bounds = row.element.bounding_box(transform, true);
|
||||
bounds = element.bounding_box(transform, true);
|
||||
}
|
||||
|
||||
if let RenderBoundingBox::Rectangle(bounds) = bounds {
|
||||
@@ -775,14 +786,14 @@ impl Render for Table<Graphic> {
|
||||
);
|
||||
}
|
||||
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
|
||||
if matches!(bounds, RenderBoundingBox::Rectangle(_)) {
|
||||
scene.pop_layer();
|
||||
scene.pop_layer();
|
||||
}
|
||||
} else {
|
||||
row.element.render_to_vello(scene, transform, context, render_params);
|
||||
element.render_to_vello(scene, transform, context, render_params);
|
||||
}
|
||||
|
||||
if layer {
|
||||
@@ -792,27 +803,33 @@ impl Render for Table<Graphic> {
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
for row in self.iter() {
|
||||
let mut footprint = footprint;
|
||||
footprint.transform *= *row.transform;
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
||||
let element = self.element(index).unwrap();
|
||||
|
||||
if let Some(element_id) = row.source_node_id {
|
||||
row.element.collect_metadata(metadata, footprint, Some(*element_id));
|
||||
let mut footprint = footprint;
|
||||
footprint.transform *= row_transform;
|
||||
|
||||
if let Some(element_id) = source_node_id {
|
||||
element.collect_metadata(metadata, footprint, Some(element_id));
|
||||
} else {
|
||||
// Recurse through anonymous wrapper rows to reach nested content with source_node_ids
|
||||
row.element.collect_metadata(metadata, footprint, None);
|
||||
element.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(element_id) = element_id {
|
||||
let mut all_upstream_click_targets = Vec::new();
|
||||
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_click_targets = Vec::new();
|
||||
row.element.add_upstream_click_targets(&mut new_click_targets);
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(*row.transform)
|
||||
click_target.apply_transform(row_transform)
|
||||
}
|
||||
|
||||
all_upstream_click_targets.extend(new_click_targets);
|
||||
@@ -823,13 +840,15 @@ impl Render for Table<Graphic> {
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let element = self.element(index).unwrap();
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
row.element.add_upstream_click_targets(&mut new_click_targets);
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(*row.transform)
|
||||
click_target.apply_transform(row_transform)
|
||||
}
|
||||
|
||||
click_targets.extend(new_click_targets);
|
||||
@@ -837,25 +856,28 @@ impl Render for Table<Graphic> {
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.iter().any(|row| row.element.contains_artboard())
|
||||
self.iter_element_values().any(|element| element.contains_artboard())
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
for row in self.iter_mut() {
|
||||
row.element.new_ids_from_hash(*row.source_node_id);
|
||||
let (elements, source_node_ids) = self.element_and_attribute_slices_mut::<Option<NodeId>>("source_node_id");
|
||||
for (element, source_node_id) in elements.iter_mut().zip(source_node_ids.iter()) {
|
||||
element.new_ids_from_hash(*source_node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Table<Vector> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let multiplied_transform = *row.transform;
|
||||
let vector = &row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let multiplied_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", 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.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
|
||||
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
|
||||
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
|
||||
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
|
||||
@@ -867,7 +889,7 @@ impl Render for Table<Vector> {
|
||||
|
||||
let mut path = String::new();
|
||||
|
||||
for mut bezpath in row.element.stroke_bezpath_iter() {
|
||||
for mut bezpath in vector.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
path.push_str(bezpath.to_svg().as_str());
|
||||
}
|
||||
@@ -880,7 +902,7 @@ impl Render for Table<Vector> {
|
||||
|
||||
let path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());
|
||||
let can_draw_aligned_stroke = path_is_closed && vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered());
|
||||
let can_use_paint_order = !(row.element.style.fill().is_none() || !row.element.style.fill().is_opaque() || mask_type == MaskType::Clip);
|
||||
let can_use_paint_order = !(vector.style.fill().is_none() || !vector.style.fill().is_opaque() || mask_type == MaskType::Clip);
|
||||
|
||||
let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
|
||||
let wants_stroke_below = vector.style.stroke().map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);
|
||||
@@ -892,7 +914,7 @@ impl Render for Table<Vector> {
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_and_stroke = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
@@ -909,16 +931,16 @@ impl Render for Table<Vector> {
|
||||
let push_id = needs_separate_alignment_fill.then_some({
|
||||
let id = format!("alignment-{}", generate_uuid());
|
||||
|
||||
let mut element = row.element.clone();
|
||||
element.style.clear_stroke();
|
||||
element.style.set_fill(Fill::solid(Color::BLACK));
|
||||
let mut cloned_vector = vector.clone();
|
||||
cloned_vector.style.clear_stroke();
|
||||
cloned_vector.style.set_fill(Fill::solid(Color::BLACK));
|
||||
|
||||
let vector_row = Table::new_from_row(TableRow {
|
||||
element,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
transform: *row.transform,
|
||||
source_node_id: None,
|
||||
});
|
||||
let vector_row = Table::new_from_row(
|
||||
TableRow::new_from_element(cloned_vector)
|
||||
.with_attribute("transform", multiplied_transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<NodeId>),
|
||||
);
|
||||
|
||||
(id, mask_type, vector_row)
|
||||
});
|
||||
@@ -935,7 +957,7 @@ impl Render for Table<Vector> {
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_only = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
@@ -961,7 +983,7 @@ impl Render for Table<Vector> {
|
||||
if let Some((ref id, mask_type, ref vector_row)) = push_id {
|
||||
let mut svg = SvgRender::new();
|
||||
vector_row.render_svg(&mut svg, &render_params.for_alignment(applied_stroke_transform));
|
||||
let stroke = row.element.style.stroke().unwrap();
|
||||
let stroke = vector.style.stroke().unwrap();
|
||||
let weight = stroke.effective_width() * max_scale(applied_stroke_transform);
|
||||
let quad = Quad::from_box(transformed_bounds).inflate(weight);
|
||||
let (x, y) = quad.top_left().into();
|
||||
@@ -986,7 +1008,7 @@ impl Render for Table<Vector> {
|
||||
render_params.aligned_strokes = can_draw_aligned_stroke;
|
||||
render_params.override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
|
||||
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
if needs_separate_alignment_fill || use_face_fill {
|
||||
style.clear_fill();
|
||||
}
|
||||
@@ -1003,13 +1025,13 @@ impl Render for Table<Vector> {
|
||||
attributes.push("fill-rule", "evenodd");
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1021,7 +1043,7 @@ impl Render for Table<Vector> {
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
let mut style = vector.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_and_stroke = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
@@ -1040,11 +1062,14 @@ impl Render for Table<Vector> {
|
||||
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||
use graphic_types::vector_types::vector::style::{GradientType, StrokeCap, StrokeJoin};
|
||||
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
use graphic_types::vector_types::vector;
|
||||
|
||||
let multiplied_transform = parent_transform * *row.transform;
|
||||
let has_real_stroke = row.element.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let Some(element) = self.element(index) else { continue };
|
||||
let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let multiplied_transform = parent_transform * row_transform;
|
||||
let has_real_stroke = element.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let mut applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
|
||||
let mut element_transform = set_stroke_transform
|
||||
@@ -1058,11 +1083,11 @@ impl Render for Table<Vector> {
|
||||
multiplied_transform
|
||||
};
|
||||
}
|
||||
let layer_bounds = row.element.bounding_box().unwrap_or_default();
|
||||
let layer_bounds = element.bounding_box().unwrap_or_default();
|
||||
|
||||
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
|
||||
let mut path = kurbo::BezPath::new();
|
||||
for mut bezpath in row.element.stroke_bezpath_iter() {
|
||||
for mut bezpath in element.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
for element in bezpath {
|
||||
path.push(element);
|
||||
@@ -1072,14 +1097,14 @@ impl Render for Table<Vector> {
|
||||
// If we're using opacity or a blend mode, we need to push a layer
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => row.alpha_blending.blend_mode.to_peniko(),
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
};
|
||||
let mut layer = false;
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
||||
layer = true;
|
||||
let weight = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let weight = element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let quad = Quad::from_box(layer_bounds).inflate(weight * max_scale(applied_stroke_transform));
|
||||
let layer_bounds = quad.bounding_box();
|
||||
scene.push_layer(
|
||||
@@ -1092,13 +1117,13 @@ impl Render for Table<Vector> {
|
||||
}
|
||||
|
||||
let can_draw_aligned_stroke =
|
||||
row.element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && row.element.stroke_bezier_paths().all(|path| path.closed());
|
||||
element.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && element.stroke_bezier_paths().all(|path| path.closed());
|
||||
|
||||
let use_layer = can_draw_aligned_stroke;
|
||||
let wants_stroke_below = row.element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
|
||||
let wants_stroke_below = element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
|
||||
|
||||
// Closures to avoid duplicated fill/stroke drawing logic
|
||||
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match row.element.style.fill() {
|
||||
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath, fill_rule: peniko::Fill| match element.style.fill() {
|
||||
Fill::Solid(color) => {
|
||||
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
|
||||
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
|
||||
@@ -1112,7 +1137,7 @@ impl Render for Table<Vector> {
|
||||
});
|
||||
}
|
||||
|
||||
let bounds = row.element.nonzero_bounding_box();
|
||||
let bounds = element.nonzero_bounding_box();
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let inverse_parent_transform = if parent_transform.matrix2.determinant() != 0. {
|
||||
@@ -1164,10 +1189,10 @@ impl Render for Table<Vector> {
|
||||
};
|
||||
|
||||
// Branching vectors without regions (e.g. mesh grids) need face-by-face fill rendering.
|
||||
let use_face_fill = row.element.use_face_fill();
|
||||
let use_face_fill = element.use_face_fill();
|
||||
let do_fill = |scene: &mut Scene| {
|
||||
if use_face_fill {
|
||||
for mut face_path in row.element.construct_faces().filter(|face| face.area() >= 0.) {
|
||||
for mut face_path in element.construct_faces().filter(|face| face.area() >= 0.) {
|
||||
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
let mut kurbo_path = kurbo::BezPath::new();
|
||||
for element in face_path {
|
||||
@@ -1175,7 +1200,7 @@ impl Render for Table<Vector> {
|
||||
}
|
||||
do_fill_path(scene, &kurbo_path, peniko::Fill::NonZero);
|
||||
}
|
||||
} else if row.element.is_branching() {
|
||||
} else if element.is_branching() {
|
||||
do_fill_path(scene, &path, peniko::Fill::EvenOdd);
|
||||
} else {
|
||||
do_fill_path(scene, &path, peniko::Fill::NonZero);
|
||||
@@ -1183,7 +1208,7 @@ impl Render for Table<Vector> {
|
||||
};
|
||||
|
||||
let do_stroke = |scene: &mut Scene, width_scale: f64| {
|
||||
if let Some(stroke) = row.element.style.stroke() {
|
||||
if let Some(stroke) = element.style.stroke() {
|
||||
let color = match stroke.color {
|
||||
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
|
||||
None => peniko::Color::TRANSPARENT,
|
||||
@@ -1224,24 +1249,24 @@ impl Render for Table<Vector> {
|
||||
}
|
||||
_ => {
|
||||
if use_layer {
|
||||
let mut element = row.element.clone();
|
||||
element.style.clear_stroke();
|
||||
element.style.set_fill(Fill::solid(Color::BLACK));
|
||||
let mut cloned_element = element.clone();
|
||||
cloned_element.style.clear_stroke();
|
||||
cloned_element.style.set_fill(Fill::solid(Color::BLACK));
|
||||
|
||||
let vector_table = Table::new_from_row(TableRow {
|
||||
element,
|
||||
alpha_blending: *row.alpha_blending,
|
||||
transform: *row.transform,
|
||||
source_node_id: None,
|
||||
});
|
||||
let vector_table = Table::new_from_row(
|
||||
TableRow::new_from_element(cloned_element)
|
||||
.with_attribute("transform", row_transform)
|
||||
.with_attribute("alpha_blending", alpha_blending)
|
||||
.with_attribute("source_node_id", None::<NodeId>),
|
||||
);
|
||||
|
||||
let bounds = row.element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||
let weight = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds);
|
||||
let weight = element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let quad = Quad::from_box(bounds).inflate(weight * max_scale(applied_stroke_transform));
|
||||
let bounds = quad.bounding_box();
|
||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||
|
||||
let compose = if row.element.style.stroke().is_some_and(|x| x.align == StrokeAlign::Outside) {
|
||||
let compose = if element.style.stroke().is_some_and(|x| x.align == StrokeAlign::Outside) {
|
||||
peniko::Compose::SrcOut
|
||||
} else {
|
||||
peniko::Compose::SrcIn
|
||||
@@ -1278,7 +1303,7 @@ impl Render for Table<Vector> {
|
||||
Stroke,
|
||||
}
|
||||
|
||||
let order = match row.element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) {
|
||||
let order = match element.style.stroke().is_some_and(|stroke| !stroke.paint_order.is_default()) {
|
||||
true => [Op::Stroke, Op::Fill],
|
||||
false => [Op::Fill, Op::Stroke], // Default
|
||||
};
|
||||
@@ -1301,11 +1326,12 @@ impl Render for Table<Vector> {
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||
for row in self.iter() {
|
||||
let transform = *row.transform;
|
||||
let vector = row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let source_node_id: Option<NodeId> = self.attribute_cloned_or_default("source_node_id", index);
|
||||
|
||||
if let Some(element_id) = caller_element_id.or(*row.source_node_id) {
|
||||
if let Some(element_id) = caller_element_id.or(source_node_id) {
|
||||
// When recovering element_id from the row's source_node_id (because the caller
|
||||
// passed None), also store the transform metadata that Graphic::collect_metadata
|
||||
// normally provides but skipped due to the None element_id.
|
||||
@@ -1356,32 +1382,35 @@ impl Render for Table<Vector> {
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for row in self.iter() {
|
||||
let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let filled = row.element.style.fill() != &Fill::None;
|
||||
for index in 0..self.len() {
|
||||
let Some(vector) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
|
||||
let stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::effective_width);
|
||||
let filled = vector.style.fill() != &Fill::None;
|
||||
let fill = |mut subpath: Subpath<_>| {
|
||||
if filled {
|
||||
subpath.set_closed(true);
|
||||
}
|
||||
subpath
|
||||
};
|
||||
click_targets.extend(row.element.stroke_bezier_paths().map(fill).map(|subpath| {
|
||||
click_targets.extend(vector.stroke_bezier_paths().map(fill).map(|subpath| {
|
||||
let mut click_target = ClickTarget::new_with_subpath(subpath, stroke_width);
|
||||
click_target.apply_transform(*row.transform);
|
||||
click_target.apply_transform(transform);
|
||||
click_target
|
||||
}));
|
||||
|
||||
// For free-floating anchors, we need to add a click target for each
|
||||
let single_anchors_targets = row.element.point_domain.ids().iter().filter_map(|&point_id| {
|
||||
if row.element.any_connected(point_id) {
|
||||
let single_anchors_targets = vector.point_domain.ids().iter().filter_map(|&point_id| {
|
||||
if vector.any_connected(point_id) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let anchor = row.element.point_domain.position_from_id(point_id).unwrap_or_default();
|
||||
let anchor = vector.point_domain.position_from_id(point_id).unwrap_or_default();
|
||||
let point = FreePoint::new(point_id, anchor);
|
||||
|
||||
let mut click_target = ClickTarget::new_with_free_point(point);
|
||||
click_target.apply_transform(*row.transform);
|
||||
click_target.apply_transform(transform);
|
||||
Some(click_target)
|
||||
});
|
||||
click_targets.extend(single_anchors_targets);
|
||||
@@ -1389,18 +1418,19 @@ impl Render for Table<Vector> {
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||
for row in self.iter_mut() {
|
||||
row.element.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
for vector in self.iter_element_values_mut() {
|
||||
vector.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Table<Raster<CPU>> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let image = row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
|
||||
let transform = *row.transform;
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
|
||||
if image.data.is_empty() {
|
||||
continue;
|
||||
@@ -1427,13 +1457,13 @@ impl Render for Table<Raster<CPU>> {
|
||||
attributes.push("width", size.x.to_string());
|
||||
attributes.push("height", size.y.to_string());
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
@@ -1467,12 +1497,12 @@ impl Render for Table<Raster<CPU>> {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1480,13 +1510,13 @@ impl Render for Table<Raster<CPU>> {
|
||||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let image = &row.element;
|
||||
for index in 0..self.len() {
|
||||
let Some(image) = self.element(index) else { continue };
|
||||
if image.data.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
@@ -1501,8 +1531,10 @@ impl Render for Table<Raster<CPU>> {
|
||||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform = transform * *row.transform;
|
||||
let outline_transform: DAffine2 = transform * transform_attribute;
|
||||
draw_raster_outline(scene, &outline_transform, render_params);
|
||||
|
||||
if layer {
|
||||
@@ -1512,7 +1544,7 @@ impl Render for Table<Raster<CPU>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
let image_transform = transform * transform_attribute * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
|
||||
let image_brush = peniko::ImageBrush::new(peniko::ImageData {
|
||||
data: image.to_flat_u8().0.into(),
|
||||
@@ -1538,8 +1570,8 @@ impl Render for Table<Raster<CPU>> {
|
||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one row of the raster table
|
||||
if let Some(raster) = self.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *raster.transform);
|
||||
if !self.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, self.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1557,8 +1589,9 @@ impl Render for Table<Raster<GPU>> {
|
||||
}
|
||||
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for index in 0..self.len() {
|
||||
let Some(raster) = self.element(index) else { continue };
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
let blend_mode = match render_params.render_mode {
|
||||
RenderMode::Outline => peniko::Mix::Normal,
|
||||
_ => alpha_blending.blend_mode.to_peniko(),
|
||||
@@ -1575,8 +1608,10 @@ impl Render for Table<Raster<GPU>> {
|
||||
layer = true;
|
||||
}
|
||||
|
||||
let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
|
||||
if let RenderMode::Outline = render_params.render_mode {
|
||||
let outline_transform = transform * *row.transform;
|
||||
let outline_transform = transform * transform_attribute;
|
||||
draw_raster_outline(scene, &outline_transform, render_params);
|
||||
|
||||
if layer {
|
||||
@@ -1586,8 +1621,8 @@ impl Render for Table<Raster<GPU>> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let width = row.element.data().width();
|
||||
let height = row.element.data().height();
|
||||
let width = raster.data().width();
|
||||
let height = raster.data().height();
|
||||
let image = peniko::ImageBrush::new(peniko::ImageData {
|
||||
data: peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),
|
||||
format: peniko::ImageFormat::Rgba8,
|
||||
@@ -1596,9 +1631,9 @@ impl Render for Table<Raster<GPU>> {
|
||||
alpha_type: peniko::ImageAlphaType::Alpha,
|
||||
})
|
||||
.with_extend(peniko::Extend::Repeat);
|
||||
let image_transform = transform * *row.transform * DAffine2::from_scale(1. / DVec2::new(width as f64, height as f64));
|
||||
let image_transform = transform * transform_attribute * DAffine2::from_scale(1. / DVec2::new(width as f64, height as f64));
|
||||
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
|
||||
context.resource_overrides.push((image, row.element.data().clone()));
|
||||
context.resource_overrides.push((image, raster.data().clone()));
|
||||
|
||||
if layer {
|
||||
scene.pop_layer()
|
||||
@@ -1613,8 +1648,8 @@ impl Render for Table<Raster<GPU>> {
|
||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
// TODO: Find a way to handle more than one row of the raster table
|
||||
if let Some(raster) = self.iter().next() {
|
||||
metadata.local_transforms.insert(element_id, *raster.transform);
|
||||
if !self.is_empty() {
|
||||
metadata.local_transforms.insert(element_id, self.attribute_cloned_or_default("transform", 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1632,25 +1667,24 @@ impl Render for Table<Raster<GPU>> {
|
||||
// later replace with the current viewport transform before each render.
|
||||
impl Render for Table<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
|
||||
render.leaf_tag("polyline", |attributes| {
|
||||
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
|
||||
let max = u64::MAX;
|
||||
attributes.push("points", format!("{max},{max} -{max},{max} -{max},-{max} {max},-{max}"));
|
||||
|
||||
let color = row.element;
|
||||
attributes.push("fill", format!("#{}", color.to_rgb_hex_srgb_from_gamma()));
|
||||
if color.a() < 1. {
|
||||
attributes.push("fill-opacity", ((color.a() * 1000.).round() / 1000.).to_string());
|
||||
}
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1659,12 +1693,10 @@ impl Render for Table<Color> {
|
||||
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
for row in self.iter() {
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
|
||||
let color = row.element;
|
||||
let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);
|
||||
|
||||
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||
@@ -1688,14 +1720,17 @@ impl Render for Table<Color> {
|
||||
impl Render for Table<GradientStops> {
|
||||
// TODO: Fix infinite gradient rendering
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for row in self.iter() {
|
||||
for index in 0..self.len() {
|
||||
let Some(gradient) = self.element(index) else { continue };
|
||||
let transform: DAffine2 = self.attribute_cloned_or_default("transform", index);
|
||||
let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index);
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
// Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead
|
||||
let max = u64::MAX;
|
||||
attributes.push("points", format!("{max},{max} -{max},{max} -{max},-{max} {max},-{max}"));
|
||||
|
||||
let mut stop_string = String::new();
|
||||
for (position, color, original_midpoint) in row.element.interpolated_samples() {
|
||||
for (position, color, original_midpoint) in gradient.interpolated_samples() {
|
||||
let _ = write!(stop_string, r##"<stop offset="{}" stop-color="#{}""##, position, color.to_rgb_hex_srgb_from_gamma());
|
||||
if color.a() < 1. {
|
||||
let _ = write!(stop_string, r#" stop-opacity="{}""#, color.a());
|
||||
@@ -1706,7 +1741,7 @@ impl Render for Table<GradientStops> {
|
||||
stop_string.push_str(" />");
|
||||
}
|
||||
|
||||
let gradient_transform = render_params.footprint.transform * *row.transform;
|
||||
let gradient_transform = render_params.footprint.transform * transform;
|
||||
let gradient_transform_matrix = format_transform_matrix(gradient_transform);
|
||||
let gradient_transform_attribute = if gradient_transform_matrix.is_empty() {
|
||||
String::new()
|
||||
@@ -1739,13 +1774,13 @@ impl Render for Table<GradientStops> {
|
||||
|
||||
attributes.push("fill", format!("url('#{gradient_id}')"));
|
||||
|
||||
let opacity = row.alpha_blending.opacity(render_params.for_mask);
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
if opacity < 1. {
|
||||
attributes.push("opacity", opacity.to_string());
|
||||
}
|
||||
|
||||
if row.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", row.alpha_blending.blend_mode.render());
|
||||
if alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", alpha_blending.blend_mode.render());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1755,12 +1790,11 @@ impl Render for Table<GradientStops> {
|
||||
fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
for row in self.iter() {
|
||||
let alpha_blending = *row.alpha_blending;
|
||||
for (gradient, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<AlphaBlending>("alpha_blending")) {
|
||||
let blend_mode = alpha_blending.blend_mode.to_peniko();
|
||||
let opacity = alpha_blending.opacity(render_params.for_mask);
|
||||
|
||||
let color = row.element.color.first().copied().unwrap_or(Color::MAGENTA);
|
||||
let color = gradient.color.first().copied().unwrap_or(Color::MAGENTA);
|
||||
let vello_color = peniko::Color::new([color.r(), color.g(), color.b(), color.a()]);
|
||||
|
||||
let rect = kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.));
|
||||
|
||||
Reference in New Issue
Block a user