mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 03:18:06 +08:00
Add the group variant to Graphic with typed run walks
This commit is contained in:
@@ -336,6 +336,7 @@ impl TableItemLayout for Graphic {
|
||||
Self::Color(list) => list.identifier(),
|
||||
Self::Gradient(list) => list.identifier(),
|
||||
Self::Text(list) => list.identifier(),
|
||||
Self::Group(_) => "Group".to_string(),
|
||||
}
|
||||
}
|
||||
// Don't put a breadcrumb for Graphic
|
||||
@@ -351,6 +352,7 @@ impl TableItemLayout for Graphic {
|
||||
Self::Color(list) => list.layout_with_breadcrumb(data),
|
||||
Self::Gradient(list) => list.layout_with_breadcrumb(data),
|
||||
Self::Text(list) => list.layout_with_breadcrumb(data),
|
||||
Self::Group(_) => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub enum Graphic {
|
||||
Color(List<Color>),
|
||||
Gradient(List<GradientStops>),
|
||||
Text(List<String>),
|
||||
Group(core_types::record::Group),
|
||||
}
|
||||
|
||||
impl Default for Graphic {
|
||||
@@ -238,7 +239,7 @@ pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DA
|
||||
Graphic::RasterGPU(list) => bake_list_transform(list, transform),
|
||||
Graphic::Gradient(list) => bake_list_transform(list, transform),
|
||||
Graphic::Text(list) => bake_list_transform(list, transform),
|
||||
Graphic::Color(_) => {}
|
||||
Graphic::Color(_) | Graphic::Group(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,6 +431,7 @@ impl Graphic {
|
||||
Graphic::Color(list) => all_clipped(list),
|
||||
Graphic::Gradient(list) => all_clipped(list),
|
||||
Graphic::Text(list) => all_clipped(list),
|
||||
Graphic::Group(group) => group_all_clipped(group),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +471,7 @@ impl Graphic {
|
||||
Graphic::Color(list) => list.element(0).is_some_and(|color| color.is_opaque()),
|
||||
Graphic::Gradient(list) => list.element(0).is_some_and(|stops| stops.iter().all(|stop| stop.color.is_opaque())),
|
||||
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Text(_) => false,
|
||||
Graphic::Group(group) => group_is_opaque(group),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,6 +495,7 @@ impl Graphic {
|
||||
Graphic::Color(list) => list.iter_element_values().all(|color| color.a() == 0.),
|
||||
Graphic::Gradient(list) => list.iter_element_values().all(|stops| stops.iter().all(|stop| stop.color.a() == 0.)),
|
||||
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Text(_) => false,
|
||||
Graphic::Group(group) => group_is_fully_transparent(group),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,10 +515,181 @@ impl Graphic {
|
||||
Graphic::RasterCPU(list) => list.is_empty(),
|
||||
Graphic::RasterGPU(list) => list.is_empty(),
|
||||
Graphic::Text(list) => list.is_empty(),
|
||||
Graphic::Group(group) => group_is_empty(group),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One run's attribute offsets, resolved once so the lane loops read raw.
|
||||
struct RunAttrs {
|
||||
transform: Option<usize>,
|
||||
opacity: Option<usize>,
|
||||
opacity_fill: Option<usize>,
|
||||
clipping_mask: Option<usize>,
|
||||
}
|
||||
|
||||
impl RunAttrs {
|
||||
fn of(item: &core_types::record::GroupItem) -> Self {
|
||||
let layout = item.layout();
|
||||
Self {
|
||||
transform: layout.offset_of(ATTR_TRANSFORM, 0),
|
||||
opacity: layout.offset_of(ATTR_OPACITY, 0),
|
||||
opacity_fill: layout.offset_of(ATTR_OPACITY_FILL, 0),
|
||||
clipping_mask: layout.offset_of(ATTR_CLIPPING_MASK, 0),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_or<T: Copy>(item: &core_types::record::GroupItem, offset: Option<usize>, lane: usize, default: T) -> T {
|
||||
match offset {
|
||||
// SAFETY: the offset comes from the item's own layout.
|
||||
Some(offset) => unsafe { item.lanes().get(lane).rec().read(offset) },
|
||||
None => default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn group_row_transform(group: &core_types::record::Group) -> DAffine2 {
|
||||
match &group.row {
|
||||
Some(row) if !row.is_empty() => RunAttrs::read_or(row, RunAttrs::of(row).transform, 0, DAffine2::IDENTITY),
|
||||
_ => DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_is_empty(group: &core_types::record::Group) -> bool {
|
||||
match &group.content {
|
||||
core_types::record::GroupContent::Run(item) => item.is_empty(),
|
||||
core_types::record::GroupContent::Stack(children) => children.iter().all(group_is_empty),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_all_clipped(group: &core_types::record::Group) -> bool {
|
||||
match &group.content {
|
||||
core_types::record::GroupContent::Run(item) => {
|
||||
let attrs = RunAttrs::of(item);
|
||||
(0..item.len()).all(|lane| RunAttrs::read_or(item, attrs.clipping_mask, lane, false))
|
||||
}
|
||||
core_types::record::GroupContent::Stack(children) => children.iter().all(group_all_clipped),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_is_opaque(group: &core_types::record::Group) -> bool {
|
||||
match &group.content {
|
||||
core_types::record::GroupContent::Run(item) => {
|
||||
let attrs = RunAttrs::of(item);
|
||||
let lanes = item.typed_lanes::<Graphic>();
|
||||
!item.is_empty()
|
||||
&& (0..item.len()).all(|lane| {
|
||||
RunAttrs::read_or(item, attrs.opacity, lane, 1.) >= 1.
|
||||
&& RunAttrs::read_or(item, attrs.opacity_fill, lane, 1.) >= 1.
|
||||
&& lanes.as_ref().is_some_and(|lanes| lanes.element_ref(lane).is_opaque())
|
||||
})
|
||||
}
|
||||
core_types::record::GroupContent::Stack(children) => !children.is_empty() && children.iter().all(group_is_opaque),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_is_fully_transparent(group: &core_types::record::Group) -> bool {
|
||||
match &group.content {
|
||||
core_types::record::GroupContent::Run(item) => {
|
||||
let attrs = RunAttrs::of(item);
|
||||
let lanes = item.typed_lanes::<Graphic>();
|
||||
(0..item.len()).all(|lane| {
|
||||
RunAttrs::read_or(item, attrs.opacity, lane, 1.) <= 0. || lanes.as_ref().is_some_and(|lanes| lanes.element_ref(lane).is_fully_transparent())
|
||||
})
|
||||
}
|
||||
core_types::record::GroupContent::Stack(children) => children.iter().all(group_is_fully_transparent),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_bounding_box(group: &core_types::record::Group, transform: DAffine2, include_stroke: bool, thumbnail: bool) -> RenderBoundingBox {
|
||||
fn combine(combined: &mut Option<[DVec2; 2]>, any_infinite: &mut bool, bounds: RenderBoundingBox, thumbnail: bool) -> Option<RenderBoundingBox> {
|
||||
match bounds {
|
||||
RenderBoundingBox::None => None,
|
||||
RenderBoundingBox::Infinite if thumbnail => {
|
||||
*any_infinite = true;
|
||||
None
|
||||
}
|
||||
RenderBoundingBox::Infinite => Some(RenderBoundingBox::Infinite),
|
||||
RenderBoundingBox::Rectangle(bounds) => {
|
||||
*combined = Some(match *combined {
|
||||
Some(existing) => core_types::math::quad::Quad::combine_bounds(existing, bounds),
|
||||
None => bounds,
|
||||
});
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
fn typed_run<T: 'static + BoundingBox>(item: &core_types::record::GroupItem, transform: DAffine2, include_stroke: bool, thumbnail: bool) -> Option<RenderBoundingBox> {
|
||||
let lanes = item.typed_lanes::<T>()?;
|
||||
let transform_offset = RunAttrs::of(item).transform;
|
||||
let mut combined = None;
|
||||
let mut any_infinite = false;
|
||||
for lane in 0..lanes.len() {
|
||||
let lane_transform = transform * RunAttrs::read_or(item, transform_offset, lane, DAffine2::IDENTITY);
|
||||
let element = lanes.element_ref(lane);
|
||||
let bounds = match thumbnail {
|
||||
true => element.thumbnail_bounding_box(lane_transform, include_stroke),
|
||||
false => element.bounding_box(lane_transform, include_stroke),
|
||||
};
|
||||
if let Some(short_circuit) = combine(&mut combined, &mut any_infinite, bounds, thumbnail) {
|
||||
return Some(short_circuit);
|
||||
}
|
||||
}
|
||||
Some(match (combined, any_infinite) {
|
||||
(Some(bounds), _) => RenderBoundingBox::Rectangle(bounds),
|
||||
(None, true) => RenderBoundingBox::Infinite,
|
||||
(None, false) => RenderBoundingBox::None,
|
||||
})
|
||||
}
|
||||
fn run_bounding_box(item: &core_types::record::GroupItem, transform: DAffine2, include_stroke: bool, thumbnail: bool) -> RenderBoundingBox {
|
||||
None.or_else(|| typed_run::<Graphic>(item, transform, include_stroke, thumbnail))
|
||||
.or_else(|| typed_run::<Vector>(item, transform, include_stroke, thumbnail))
|
||||
.or_else(|| typed_run::<Raster<CPU>>(item, transform, include_stroke, thumbnail))
|
||||
.or_else(|| typed_run::<Raster<GPU>>(item, transform, include_stroke, thumbnail))
|
||||
.or_else(|| typed_run::<Color>(item, transform, include_stroke, thumbnail))
|
||||
.or_else(|| typed_run::<GradientStops>(item, transform, include_stroke, thumbnail))
|
||||
.or_else(|| typed_run::<String>(item, transform, include_stroke, thumbnail))
|
||||
.unwrap_or(RenderBoundingBox::Infinite)
|
||||
}
|
||||
match &group.content {
|
||||
core_types::record::GroupContent::Run(item) => run_bounding_box(item, transform, include_stroke, thumbnail),
|
||||
core_types::record::GroupContent::Stack(children) => {
|
||||
let mut combined = None;
|
||||
let mut any_infinite = false;
|
||||
for child in children {
|
||||
let bounds = group_bounding_box(child, transform * group_row_transform(child), include_stroke, thumbnail);
|
||||
if let Some(short_circuit) = combine(&mut combined, &mut any_infinite, bounds, thumbnail) {
|
||||
return short_circuit;
|
||||
}
|
||||
}
|
||||
match (combined, any_infinite) {
|
||||
(Some(bounds), _) => RenderBoundingBox::Rectangle(bounds),
|
||||
(None, true) => RenderBoundingBox::Infinite,
|
||||
(None, false) => RenderBoundingBox::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn group_render_complexity(group: &core_types::record::Group) -> usize {
|
||||
fn typed_run<T: 'static + RenderComplexity>(item: &core_types::record::GroupItem) -> Option<usize> {
|
||||
let lanes = item.typed_lanes::<T>()?;
|
||||
Some((0..lanes.len()).map(|lane| lanes.element_ref(lane).render_complexity()).sum())
|
||||
}
|
||||
match &group.content {
|
||||
core_types::record::GroupContent::Run(item) => None
|
||||
.or_else(|| typed_run::<Graphic>(item))
|
||||
.or_else(|| typed_run::<Vector>(item))
|
||||
.or_else(|| typed_run::<Raster<CPU>>(item))
|
||||
.or_else(|| typed_run::<Raster<GPU>>(item))
|
||||
.or_else(|| typed_run::<Color>(item))
|
||||
.or_else(|| typed_run::<GradientStops>(item))
|
||||
.or_else(|| typed_run::<String>(item))
|
||||
.unwrap_or(item.len()),
|
||||
core_types::record::GroupContent::Stack(children) => children.iter().map(group_render_complexity).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundingBox for Graphic {
|
||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
||||
match self {
|
||||
@@ -525,6 +700,7 @@ impl BoundingBox for Graphic {
|
||||
Graphic::Color(list) => list.bounding_box(transform, include_stroke),
|
||||
Graphic::Gradient(list) => list.bounding_box(transform, include_stroke),
|
||||
Graphic::Text(list) => list.bounding_box(transform, include_stroke),
|
||||
Graphic::Group(group) => group_bounding_box(group, transform, include_stroke, false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,6 +713,7 @@ impl BoundingBox for Graphic {
|
||||
Graphic::Color(color) => color.thumbnail_bounding_box(transform, include_stroke),
|
||||
Graphic::Gradient(gradient) => gradient.thumbnail_bounding_box(transform, include_stroke),
|
||||
Graphic::Text(list) => list.thumbnail_bounding_box(transform, include_stroke),
|
||||
Graphic::Group(group) => group_bounding_box(group, transform, include_stroke, true),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,6 +744,7 @@ impl RenderComplexity for Graphic {
|
||||
Self::Color(list) => list.render_complexity(),
|
||||
Self::Gradient(list) => list.render_complexity(),
|
||||
Self::Text(list) => list.render_complexity(),
|
||||
Self::Group(group) => group_render_complexity(group),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ impl RenderExt for List<Graphic> {
|
||||
let gradient_id = gradient_list.render(svg_defs, item_transform, element_transform, stroke_transform, bounds, render_params, target);
|
||||
format!(r##" {paint_attr}="url(#{gradient_id})""##)
|
||||
}
|
||||
Some(Graphic::Vector(_)) | Some(Graphic::RasterCPU(_)) | Some(Graphic::RasterGPU(_)) | Some(Graphic::Graphic(_)) | Some(Graphic::Text(_)) => {
|
||||
Some(Graphic::Vector(_)) | Some(Graphic::RasterCPU(_)) | Some(Graphic::RasterGPU(_)) | Some(Graphic::Graphic(_)) | Some(Graphic::Text(_)) | Some(Graphic::Group(_)) => {
|
||||
let bounds = if target == PaintTarget::Stroke {
|
||||
// To prevent a wraparound artefact occurring when the tile boundary and the stroke region are perfectly aligned, the local coordinate is expanded slightly.
|
||||
let inverse = |len: f64| if len > 0. { 1. / len } else { 0. };
|
||||
|
||||
@@ -553,6 +553,7 @@ impl Render for Graphic {
|
||||
Graphic::Color(list) => list.render_svg(render, render_params),
|
||||
Graphic::Gradient(list) => list.render_svg(render, render_params),
|
||||
Graphic::Text(list) => list.render_svg(render, render_params),
|
||||
Graphic::Group(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,12 +566,14 @@ impl Render for Graphic {
|
||||
Graphic::Color(list) => list.render_to_vello(scene, transform, context, render_params),
|
||||
Graphic::Gradient(list) => list.render_to_vello(scene, transform, context, render_params),
|
||||
Graphic::Text(list) => list.render_to_vello(scene, transform, context, render_params),
|
||||
Graphic::Group(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
if let Some(element_id) = element_id {
|
||||
match self {
|
||||
Graphic::Group(_) => {}
|
||||
Graphic::Graphic(_) => {
|
||||
metadata.upstream_footprints.insert(element_id, footprint);
|
||||
}
|
||||
@@ -637,6 +640,7 @@ impl Render for Graphic {
|
||||
Graphic::Color(list) => list.collect_metadata(metadata, footprint, element_id),
|
||||
Graphic::Gradient(list) => list.collect_metadata(metadata, footprint, element_id),
|
||||
Graphic::Text(list) => list.collect_metadata(metadata, footprint, element_id),
|
||||
Graphic::Group(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +653,7 @@ impl Render for Graphic {
|
||||
Graphic::Color(list) => list.add_upstream_click_targets(click_targets),
|
||||
Graphic::Gradient(list) => list.add_upstream_click_targets(click_targets),
|
||||
Graphic::Text(list) => list.add_upstream_click_targets(click_targets),
|
||||
Graphic::Group(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,6 +666,7 @@ impl Render for Graphic {
|
||||
Graphic::Color(list) => list.add_upstream_outline_targets(outlines),
|
||||
Graphic::Gradient(list) => list.add_upstream_outline_targets(outlines),
|
||||
Graphic::Text(list) => list.add_upstream_outline_targets(outlines),
|
||||
Graphic::Group(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,6 +679,7 @@ impl Render for Graphic {
|
||||
Graphic::Color(list) => list.contains_artboard(),
|
||||
Graphic::Gradient(list) => list.contains_artboard(),
|
||||
Graphic::Text(list) => list.contains_artboard(),
|
||||
Graphic::Group(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,6 +692,7 @@ impl Render for Graphic {
|
||||
Graphic::Color(_) => (),
|
||||
Graphic::Gradient(_) => (),
|
||||
Graphic::Text(_) => (),
|
||||
Graphic::Group(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1367,7 +1375,7 @@ impl Render for List<Vector> {
|
||||
let brush_transform = kurbo::Affine::new((inverse_element_transform * gradient_to_device).to_cols_array());
|
||||
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &brush, Some(brush_transform), path);
|
||||
}
|
||||
Graphic::Vector(_) | Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Graphic(_) | Graphic::Text(_) => {
|
||||
Graphic::Vector(_) | Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Graphic(_) | Graphic::Text(_) | Graphic::Group(_) => {
|
||||
scene.push_clip_layer(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), path);
|
||||
paint.render_to_vello(scene, multiplied_transform, context, render_params);
|
||||
scene.pop_layer();
|
||||
@@ -1449,7 +1457,7 @@ impl Render for List<Vector> {
|
||||
|
||||
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), &brush, Some(brush_transform), &path);
|
||||
}
|
||||
Graphic::Vector(_) | Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Graphic(_) | Graphic::Text(_) => {
|
||||
Graphic::Vector(_) | Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Graphic(_) | Graphic::Text(_) | Graphic::Group(_) => {
|
||||
let stroked = peniko::kurbo::stroke(path.iter(), &stroke, &StrokeOpts::default(), 0.01);
|
||||
|
||||
scene.push_clip_layer(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &stroked);
|
||||
|
||||
@@ -183,6 +183,7 @@ fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
||||
.flat_map(|index| {
|
||||
let graphic = graphic_list.element(index).unwrap();
|
||||
match graphic.clone() {
|
||||
Graphic::Group(_) => Vec::new(),
|
||||
Graphic::Vector(vector) => {
|
||||
// Apply the parent graphic's transform to each element of the `List<Vector>`
|
||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
|
||||
Reference in New Issue
Block a user