mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 15:48:12 +08:00
Delete the legacy fill and stroke paint markers
The appearance column is now the only paint channel. The Fill and Stroke attribute markers, their name constants, the LanePaint push with its two-hop reach, and the interior paint placement all go away; nodes emit and readers cascade the appearance alone. PaintReach shrinks to the cascade's own-wins arbitration, the legacy conversion converts the group content inside appearance paint cells, and transform baking lands on those cells too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f726e712bc
commit
d278fd666d
@@ -2774,7 +2774,7 @@ impl DocumentMessageHandler {
|
|||||||
|
|
||||||
let has_fill = fill_graphic_list.is_some_and(|list| is_paint_present(list));
|
let has_fill = fill_graphic_list.is_some_and(|list| is_paint_present(list));
|
||||||
// `Vector.stroke` captures stroke geometry, even with weight 0 or transparent paint.
|
// `Vector.stroke` captures stroke geometry, even with weight 0 or transparent paint.
|
||||||
// So stroke visibility must be checked from `ATTR_STROKE`, the paint source of truth.
|
// So stroke visibility must be checked from the stroke paint snapshot, the paint source of truth.
|
||||||
let stroke_visible = stroke_graphic_list.is_some_and(|list| list.element(0).is_some_and(|g| !g.is_fully_transparent()));
|
let stroke_visible = stroke_graphic_list.is_some_and(|list| list.element(0).is_some_and(|g| !g.is_fully_transparent()));
|
||||||
let has_stroke = stroke.as_ref().is_some_and(|s| s.has_renderable_stroke()) && stroke_visible;
|
let has_stroke = stroke.as_ref().is_some_and(|s| s.has_renderable_stroke()) && stroke_visible;
|
||||||
|
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ pub struct DocumentMetadata {
|
|||||||
/// Vector data keyed by layer ID, used as fallback when no Path node exists.
|
/// Vector data keyed by layer ID, used as fallback when no Path node exists.
|
||||||
/// This provides accurate SegmentIds for layers without explicit Path nodes.
|
/// This provides accurate SegmentIds for layers without explicit Path nodes.
|
||||||
pub layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>,
|
pub layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>,
|
||||||
/// Per-layer `ATTR_FILL` attribute, exposed so message handlers can read paint
|
/// Per-layer fill paint snapshot, exposed so message handlers can read paint
|
||||||
/// information that lives on the list.
|
/// information that lives on the list.
|
||||||
pub layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>,
|
pub layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>,
|
||||||
/// Per-layer `ATTR_STROKE` attribute, exposed so message handlers can read
|
/// Per-layer stroke paint snapshot, exposed so message handlers can read
|
||||||
/// stroke paint information that lives on the list.
|
/// stroke paint information that lives on the list.
|
||||||
pub layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>,
|
pub layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>,
|
||||||
/// Transform from document space to viewport space.
|
/// Transform from document space to viewport space.
|
||||||
|
|||||||
@@ -3439,12 +3439,12 @@ impl NodeNetworkInterface {
|
|||||||
self.document_metadata.layer_vector_data = new_layer_vector_data;
|
self.document_metadata.layer_vector_data = new_layer_vector_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the per-layer `ATTR_FILL` snapshot.
|
/// Update the per-layer fill paint snapshot.
|
||||||
pub fn update_fill_attributes(&mut self, new_layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>) {
|
pub fn update_fill_attributes(&mut self, new_layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>) {
|
||||||
self.document_metadata.layer_fill_attributes = new_layer_fill_attributes;
|
self.document_metadata.layer_fill_attributes = new_layer_fill_attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the per-layer `ATTR_STROKE` snapshot.
|
/// Update the per-layer stroke paint snapshot.
|
||||||
pub fn update_stroke_attributes(&mut self, new_layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>) {
|
pub fn update_stroke_attributes(&mut self, new_layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>) {
|
||||||
self.document_metadata.layer_stroke_attributes = new_layer_stroke_attributes;
|
self.document_metadata.layer_stroke_attributes = new_layer_stroke_attributes;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ mod run_tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::graphic::test_support::{native_group_paint, unit_square_at};
|
use crate::graphic::test_support::{native_group_paint, unit_square_at};
|
||||||
use crate::graphic::{group_to_legacy_list, map_groups_to_legacy};
|
use crate::graphic::{group_to_legacy_list, map_groups_to_legacy};
|
||||||
use crate::markers::{Fill, Stroke};
|
use crate::markers::EditorMergedLayers;
|
||||||
use core_types::attribute::Attribute;
|
use core_types::attribute::Attribute;
|
||||||
use core_types::lane::LaneSource;
|
use core_types::lane::LaneSource;
|
||||||
use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed};
|
use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed};
|
||||||
@@ -504,9 +504,9 @@ mod run_tests {
|
|||||||
let vector = unit_square_at(DVec2::ZERO);
|
let vector = unit_square_at(DVec2::ZERO);
|
||||||
|
|
||||||
let source = core_types::arena::Arena::new(1 << 16).unwrap();
|
let source = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<EditorMergedLayers>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vector.clone()).unwrap();
|
let lane = builder.push(vector.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||||
let group = core_types::record::Group { row: None, content: builder.finish() };
|
let group = core_types::record::Group { row: None, content: builder.finish() };
|
||||||
let expected = group_to_legacy_list(&group);
|
let expected = group_to_legacy_list(&group);
|
||||||
let owned = map_groups_to_owned(&Graphic::Group(group));
|
let owned = map_groups_to_owned(&Graphic::Group(group));
|
||||||
@@ -548,9 +548,9 @@ mod run_tests {
|
|||||||
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
||||||
|
|
||||||
let vector = unit_square_at(DVec2::new(4., 4.));
|
let vector = unit_square_at(DVec2::new(4., 4.));
|
||||||
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<EditorMergedLayers>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vector.clone()).unwrap();
|
let lane = builder.push(vector.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let owned = item.copy_out();
|
let owned = item.copy_out();
|
||||||
let expected = map_groups_to_legacy(paint.element(0).unwrap());
|
let expected = map_groups_to_legacy(paint.element(0).unwrap());
|
||||||
@@ -561,7 +561,7 @@ mod run_tests {
|
|||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let replayed = owned.replay(&arena).expect("the arena holds the replay");
|
let replayed = owned.replay(&arena).expect("the arena holds the replay");
|
||||||
let run = RunView::<Vector>::new(&replayed).expect("the run holds vector elements");
|
let run = RunView::<Vector>::new(&replayed).expect("the run holds vector elements");
|
||||||
let served = run.attr::<Fill>(0).expect("the fill replays present");
|
let served = run.attr::<EditorMergedLayers>(0).expect("the fill replays present");
|
||||||
assert_eq!(map_groups_to_legacy(served.element(0).unwrap()), expected);
|
assert_eq!(map_groups_to_legacy(served.element(0).unwrap()), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,12 +574,12 @@ mod run_tests {
|
|||||||
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
||||||
|
|
||||||
let vector = unit_square_at(DVec2::new(4., 4.));
|
let vector = unit_square_at(DVec2::new(4., 4.));
|
||||||
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<EditorMergedLayers>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vector.clone()).unwrap();
|
let lane = builder.push(vector.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let layout = item.layout().clone();
|
let layout = item.layout().clone();
|
||||||
let offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
let offset = layout.offset_of(EditorMergedLayers::NAME, 0).unwrap();
|
||||||
// SAFETY: the item's lane is a live record of `layout`.
|
// SAFETY: the item's lane is a live record of `layout`.
|
||||||
let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) };
|
let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) };
|
||||||
let expected = map_groups_to_legacy(paint.element(0).unwrap());
|
let expected = map_groups_to_legacy(paint.element(0).unwrap());
|
||||||
@@ -609,8 +609,8 @@ mod run_tests {
|
|||||||
) -> (core_types::record::Layout, core_types::record::MaterializedSpan, Vec<u64>) {
|
) -> (core_types::record::Layout, core_types::record::MaterializedSpan, Vec<u64>) {
|
||||||
use core_types::record::{Layout, MaterializedSpan, Promotion, element_write, write_field};
|
use core_types::record::{Layout, MaterializedSpan, Promotion, element_write, write_field};
|
||||||
|
|
||||||
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[FieldWrite::of::<Fill>(0)]);
|
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[FieldWrite::of::<EditorMergedLayers>(0)]);
|
||||||
let offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
let offset = layout.offset_of(EditorMergedLayers::NAME, 0).unwrap();
|
||||||
let stride = layout.lane_stride();
|
let stride = layout.lane_stride();
|
||||||
let mut buffer = vec![0u64; (lanes * stride).div_ceil(8)];
|
let mut buffer = vec![0u64; (lanes * stride).div_ceil(8)];
|
||||||
let base = buffer.as_mut_ptr().cast::<u8>();
|
let base = buffer.as_mut_ptr().cast::<u8>();
|
||||||
@@ -633,7 +633,7 @@ mod run_tests {
|
|||||||
|
|
||||||
/// The promoted paint of one lane, at the layout the promote published.
|
/// The promoted paint of one lane, at the layout the promote published.
|
||||||
fn promoted_paint<'p>(span: &core_types::record::MaterializedSpan, layout: &core_types::record::Layout, lane: usize, persistent: &'p core_types::arena::Arena) -> &'p List<Graphic<'p>> {
|
fn promoted_paint<'p>(span: &core_types::record::MaterializedSpan, layout: &core_types::record::Layout, lane: usize, persistent: &'p core_types::arena::Arena) -> &'p List<Graphic<'p>> {
|
||||||
let offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
let offset = layout.offset_of(EditorMergedLayers::NAME, 0).unwrap();
|
||||||
let batch = span.batch(persistent, layout).expect("the span resolves in its own region");
|
let batch = span.batch(persistent, layout).expect("the span resolves in its own region");
|
||||||
// SAFETY: the promote wrote a record of `layout` into every lane.
|
// SAFETY: the promote wrote a record of `layout` into every lane.
|
||||||
unsafe { batch.get(lane).rec().read::<Option<&List<Graphic>>>(offset) }.expect("the paint promotes present")
|
unsafe { batch.get(lane).rec().read::<Option<&List<Graphic>>>(offset) }.expect("the paint promotes present")
|
||||||
@@ -713,7 +713,7 @@ mod run_tests {
|
|||||||
let mut paint = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(4., 4.))));
|
let mut paint = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(4., 4.))));
|
||||||
// SAFETY: the erased native list serves only while `transient` is live; the
|
// SAFETY: the erased native list serves only while `transient` is live; the
|
||||||
// promote under test replaces its borrows.
|
// promote under test replaces its borrows.
|
||||||
paint.set_attribute::<Option<List<Graphic>>>(Stroke::NAME, 0, Some(unsafe { core_types::record::erase_static(native) }));
|
paint.set_attribute::<Option<List<Graphic>>>("probe:paint", 0, Some(unsafe { core_types::record::erase_static(native) }));
|
||||||
let (paint, _) = transient.alloc_sized_keyed(paint, 0).unwrap();
|
let (paint, _) = transient.alloc_sized_keyed(paint, 0).unwrap();
|
||||||
|
|
||||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
let (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
||||||
@@ -723,7 +723,7 @@ mod run_tests {
|
|||||||
|
|
||||||
transient.reset();
|
transient.reset();
|
||||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||||
let held = served.attribute::<Option<List<Graphic>>>(Stroke::NAME, 0).expect("the stroke attribute rides the promoted list");
|
let held = served.attribute::<Option<List<Graphic>>>("probe:paint", 0).expect("the stroke attribute rides the promoted list");
|
||||||
let held = held.as_ref().expect("the stroke is present");
|
let held = held.as_ref().expect("the stroke is present");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map_groups_to_legacy(held.element(0).unwrap()),
|
map_groups_to_legacy(held.element(0).unwrap()),
|
||||||
@@ -742,7 +742,7 @@ mod run_tests {
|
|||||||
// whose value holds none: neither denies the move.
|
// whose value holds none: neither denies the move.
|
||||||
paint.set_attribute::<f64>("opacity", 0, 0.5);
|
paint.set_attribute::<f64>("opacity", 0, 0.5);
|
||||||
paint.set_attribute::<Color>("probe:color", 0, Color::BLACK);
|
paint.set_attribute::<Color>("probe:color", 0, Color::BLACK);
|
||||||
paint.set_attribute::<Option<List<Graphic>>>(Stroke::NAME, 0, Some(List::new_from_element(Graphic::Color(Color::WHITE))));
|
paint.set_attribute::<Option<List<Graphic>>>("probe:paint", 0, Some(List::new_from_element(Graphic::Color(Color::WHITE))));
|
||||||
let heap = {
|
let heap = {
|
||||||
let Some(Graphic::Vector(vector)) = paint.element(0) else { panic!("the paint carries a vector") };
|
let Some(Graphic::Vector(vector)) = paint.element(0) else { panic!("the paint carries a vector") };
|
||||||
vector.point_domain.positions().as_ptr()
|
vector.point_domain.positions().as_ptr()
|
||||||
@@ -777,15 +777,15 @@ mod run_tests {
|
|||||||
let mut paint = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(2., 2.))));
|
let mut paint = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(2., 2.))));
|
||||||
// SAFETY: the erased native list serves only while `source` is live; the
|
// SAFETY: the erased native list serves only while `source` is live; the
|
||||||
// deep glue under test replaces its borrows at the copy-out seam.
|
// deep glue under test replaces its borrows at the copy-out seam.
|
||||||
paint.set_attribute::<Option<List<Graphic>>>(Stroke::NAME, 0, Some(unsafe { core_types::record::erase_static(native) }));
|
paint.set_attribute::<Option<List<Graphic>>>("probe:paint", 0, Some(unsafe { core_types::record::erase_static(native) }));
|
||||||
|
|
||||||
let vector = unit_square_at(DVec2::new(4., 4.));
|
let vector = unit_square_at(DVec2::new(4., 4.));
|
||||||
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<EditorMergedLayers>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vector.clone()).unwrap();
|
let lane = builder.push(vector.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let layout = item.layout().clone();
|
let layout = item.layout().clone();
|
||||||
let offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
let offset = layout.offset_of(EditorMergedLayers::NAME, 0).unwrap();
|
||||||
// SAFETY: the item's lane is a live record of `layout`.
|
// SAFETY: the item's lane is a live record of `layout`.
|
||||||
let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) };
|
let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) };
|
||||||
drop(item);
|
drop(item);
|
||||||
@@ -800,7 +800,7 @@ mod run_tests {
|
|||||||
let value = unsafe { slot.finish() };
|
let value = unsafe { slot.finish() };
|
||||||
// SAFETY: the replay wrote a record of `layout`.
|
// SAFETY: the replay wrote a record of `layout`.
|
||||||
let served = unsafe { layout.rec(&value).read::<Option<&List<Graphic>>>(offset) }.expect("the fill replays present");
|
let served = unsafe { layout.rec(&value).read::<Option<&List<Graphic>>>(offset) }.expect("the fill replays present");
|
||||||
let held = served.attribute::<Option<List<Graphic>>>(Stroke::NAME, 0).expect("the stroke attribute rides the replayed list");
|
let held = served.attribute::<Option<List<Graphic>>>("probe:paint", 0).expect("the stroke attribute rides the replayed list");
|
||||||
let held = held.as_ref().expect("the stroke is present");
|
let held = held.as_ref().expect("the stroke is present");
|
||||||
assert_eq!(map_groups_to_legacy(held.element(0).unwrap()), expected, "the attribute-held group replayed into the serving arena");
|
assert_eq!(map_groups_to_legacy(held.element(0).unwrap()), expected, "the attribute-held group replayed into the serving arena");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
//! The legacy bridge: record-backed groups rebuilt as owned legacy lists.
|
//! The legacy bridge: record-backed groups rebuilt as owned legacy lists.
|
||||||
|
|
||||||
use super::walk::push_lane_paint_into_interiors;
|
|
||||||
use super::{Graphic, detable_items};
|
use super::{Graphic, detable_items};
|
||||||
use crate::markers::{ATTR_FILL, ATTR_STROKE};
|
use crate::appearance::Appearance;
|
||||||
|
use crate::markers::{ATTR_APPEARANCE, ATTR_EDITOR_MERGED_LAYERS, ATTR_PAINT};
|
||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
use core_types::list::{Item, List};
|
use core_types::list::{Item, List};
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
@@ -15,11 +15,19 @@ pub fn run_to_list<T: Clone + Send + Sync + dyn_any::StaticTypeSized>(item: &cor
|
|||||||
core_types::record::run_to_owned_list(item)
|
core_types::record::run_to_owned_list(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the group content of the list's paint attribute values to legacy
|
/// Converts the group content the list's attribute values reach to legacy
|
||||||
/// form, so a legacy product owns everything its attributes reach.
|
/// form, so a legacy product owns everything its attributes hold: the paint
|
||||||
|
/// cells inside each appearance, and the merged-layers snapshot.
|
||||||
pub fn map_paint_attrs_to_legacy<T>(list: &mut List<T>) {
|
pub fn map_paint_attrs_to_legacy<T>(list: &mut List<T>) {
|
||||||
for key in [ATTR_FILL, ATTR_STROKE, crate::markers::ATTR_EDITOR_MERGED_LAYERS] {
|
if let Some(appearances) = list.iter_attribute_values_mut::<Appearance>(ATTR_APPEARANCE) {
|
||||||
let Some(values) = list.iter_attribute_values_mut::<Option<List<Graphic>>>(key) else { continue };
|
for appearance in appearances {
|
||||||
|
let Some(cells) = appearance.0.iter_attribute_values_mut::<Graphic>(ATTR_PAINT) else { continue };
|
||||||
|
for cell in cells {
|
||||||
|
*cell = map_groups_to_legacy(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(values) = list.iter_attribute_values_mut::<Option<List<Graphic>>>(ATTR_EDITOR_MERGED_LAYERS) {
|
||||||
for value in values.flatten() {
|
for value in values.flatten() {
|
||||||
for element in value.iter_element_values_mut() {
|
for element in value.iter_element_values_mut() {
|
||||||
*element = map_groups_to_legacy(element);
|
*element = map_groups_to_legacy(element);
|
||||||
@@ -86,7 +94,6 @@ pub fn group_to_legacy_list(group: &core_types::record::Group) -> List<Graphic<'
|
|||||||
for element in list.iter_element_values_mut() {
|
for element in list.iter_element_values_mut() {
|
||||||
*element = map_groups_to_legacy(element);
|
*element = map_groups_to_legacy(element);
|
||||||
}
|
}
|
||||||
push_lane_paint_into_interiors(&mut list);
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
None.or_else(|| run_to_legacy_list::<Vector>(item).map(|list| detable_items(list, Graphic::Vector)))
|
None.or_else(|| run_to_legacy_list::<Vector>(item).map(|list| detable_items(list, Graphic::Vector)))
|
||||||
@@ -101,24 +108,25 @@ pub fn group_to_legacy_list(group: &core_types::record::Group) -> List<Graphic<'
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod run_tests {
|
mod run_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::appearance::Coverage;
|
||||||
use crate::graphic::test_support::{native_group_paint, unit_square_at};
|
use crate::graphic::test_support::{native_group_paint, unit_square_at};
|
||||||
use crate::markers::Fill;
|
use crate::markers::Appearance as AppearanceMarker;
|
||||||
use core_types::attribute::Attribute;
|
|
||||||
use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
|
use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_legacy_list_owns_its_paint_attr_content() {
|
fn a_legacy_list_owns_its_appearance_paint_content() {
|
||||||
let inner_vector = unit_square_at(DVec2::ZERO);
|
let inner_vector = unit_square_at(DVec2::ZERO);
|
||||||
let source = core_types::arena::Arena::new(1 << 16).unwrap();
|
let source = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
// SAFETY: the erased native list serves only while `source` is live; the
|
// SAFETY: the erased native list serves only while `source` is live; the
|
||||||
// deep glue under test replaces its borrows at the copy-out seam.
|
// deep glue under test replaces its borrows at the copy-out seam.
|
||||||
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
||||||
|
let appearance = Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(paint.clone()));
|
||||||
|
|
||||||
let vector = unit_square_at(DVec2::new(4., 4.));
|
let vector = unit_square_at(DVec2::new(4., 4.));
|
||||||
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<AppearanceMarker>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vector.clone()).unwrap();
|
let lane = builder.push(vector.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list");
|
let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list");
|
||||||
let expected = map_groups_to_legacy(paint.element(0).unwrap());
|
let expected = map_groups_to_legacy(paint.element(0).unwrap());
|
||||||
@@ -126,9 +134,10 @@ mod run_tests {
|
|||||||
drop(paint);
|
drop(paint);
|
||||||
drop(source);
|
drop(source);
|
||||||
|
|
||||||
let served = legacy.attribute::<Option<List<Graphic>>>(Fill::NAME, 0).expect("the fill attribute rides the list");
|
let served = legacy.attribute::<Appearance>(ATTR_APPEARANCE, 0).expect("the appearance rides the list");
|
||||||
let served = served.as_ref().expect("the fill is present");
|
let cell = served.paint_at(0).expect("the fill coverage keeps its paint");
|
||||||
assert_eq!(served.element(0).unwrap(), &expected);
|
let Graphic::Graphic(cell_rows) = cell else { panic!("the paint cell keeps the list form") };
|
||||||
|
assert_eq!(cell_rows.element(0).unwrap(), &expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ pub(crate) use glue::{list_contains_groups, map_attribute_groups_to_owned, map_a
|
|||||||
pub use glue::{map_groups_to_owned, map_groups_to_persistent, map_groups_to_resident};
|
pub use glue::{map_groups_to_owned, map_groups_to_persistent, map_groups_to_resident};
|
||||||
pub(crate) use legacy::run_to_legacy_list;
|
pub(crate) use legacy::run_to_legacy_list;
|
||||||
pub use legacy::{group_to_legacy_graphic, group_to_legacy_list, map_groups_to_legacy, map_paint_attrs_to_legacy, run_to_list};
|
pub use legacy::{group_to_legacy_graphic, group_to_legacy_list, map_groups_to_legacy, map_paint_attrs_to_legacy, run_to_list};
|
||||||
pub use paint::{
|
pub use paint::{PaintColumns, PaintReach, bake_paint_transforms, is_paint_present, paint_cell_rows, vector_can_reduce_to_clip_path};
|
||||||
LanePaint, PaintColumns, PaintReach, bake_paint_transforms, has_paint, is_paint_present, paint_cell_rows, paint_graphics, set_paint_attribute, set_paint_attribute_at,
|
|
||||||
vector_can_reduce_to_clip_path,
|
|
||||||
};
|
|
||||||
pub use walk::{GraphicLevel, GraphicLevelColumn, RowStep, VectorRow, direct_vector_len, flatten_vector_rows, group_is_empty, lane_attributes, run_lane_attributes, walk_vector_rows};
|
pub use walk::{GraphicLevel, GraphicLevelColumn, RowStep, VectorRow, direct_vector_len, flatten_vector_rows, group_is_empty, lane_attributes, run_lane_attributes, walk_vector_rows};
|
||||||
use walk::{group_all_clipped, group_bounding_box, group_is_fully_transparent, group_is_opaque, group_render_complexity};
|
use walk::{group_all_clipped, group_bounding_box, group_is_fully_transparent, group_is_opaque, group_render_complexity};
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
//! The paint column level: fill and stroke read as lane columns and threaded down to the elements they reach.
|
//! The appearance cascade level: the declared appearance read as a lane column and threaded down to the elements it reaches.
|
||||||
|
|
||||||
use super::{Graphic, IntoGraphicList};
|
use super::Graphic;
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::markers::{ATTR_FILL, ATTR_STROKE, Appearance as AppearanceMarker, Fill, Stroke};
|
use crate::markers::Appearance as AppearanceMarker;
|
||||||
use core_types::ATTR_TRANSFORM;
|
use core_types::ATTR_TRANSFORM;
|
||||||
use core_types::attribute::{Attribute, Opacity};
|
use core_types::attribute::Opacity;
|
||||||
use core_types::lane::{LaneColumn, LaneSource};
|
use core_types::lane::{LaneColumn, LaneSource};
|
||||||
use core_types::list::{ItemAttributeValues, List};
|
use core_types::list::{ItemAttributeValues, List};
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
@@ -16,28 +16,6 @@ pub fn is_paint_present(graphic_list: &List<Graphic>) -> bool {
|
|||||||
graphic_list.element(0).is_some_and(|graphic| !graphic.is_empty())
|
graphic_list.element(0).is_some_and(|graphic| !graphic.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the paint graphics stored under the marker `A`, in the canonical `List<Graphic>` form.
|
|
||||||
pub fn paint_graphics<'a, A, S>(source: &'a S, index: usize) -> Option<&'a List<Graphic<'static>>>
|
|
||||||
where
|
|
||||||
S: LaneSource,
|
|
||||||
A: Attribute<Value<'a> = Option<&'a List<Graphic<'static>>>>,
|
|
||||||
{
|
|
||||||
source
|
|
||||||
.attr::<A>(index)
|
|
||||||
// Treat a blank paint attribute as absent so an empty attribute doesn't count as painted
|
|
||||||
.filter(|graphic_list| is_paint_present(graphic_list))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the item carries a non-blank canonical `List<Graphic>` paint under the marker `A`,
|
|
||||||
/// checked by borrowing without cloning the renderable list.
|
|
||||||
pub fn has_paint<'a, A, S>(source: &'a S, index: usize) -> bool
|
|
||||||
where
|
|
||||||
S: LaneSource,
|
|
||||||
A: Attribute<Value<'a> = Option<&'a List<Graphic<'static>>>>,
|
|
||||||
{
|
|
||||||
paint_graphics::<A, S>(source, index).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether every lane of a vector source draws as a plain clip path: fully
|
/// Whether every lane of a vector source draws as a plain clip path: fully
|
||||||
/// opaque, fill absent or opaque, stroke invisible or fully transparent.
|
/// opaque, fill absent or opaque, stroke invisible or fully transparent.
|
||||||
pub fn vector_can_reduce_to_clip_path<S: LaneSource<Element = Vector>>(source: &S, inherited_appearance: Option<&Appearance>) -> bool {
|
pub fn vector_can_reduce_to_clip_path<S: LaneSource<Element = Vector>>(source: &S, inherited_appearance: Option<&Appearance>) -> bool {
|
||||||
@@ -65,116 +43,41 @@ pub fn vector_can_reduce_to_clip_path<S: LaneSource<Element = Vector>>(source: &
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The paint a lane carries for its interiors, in the reference form
|
/// A source's declared appearance column, resolved once for per-lane reads.
|
||||||
/// [`PaintOverlay`] threads down.
|
|
||||||
#[derive(Clone, Copy, Default)]
|
|
||||||
pub struct LanePaint<'a> {
|
|
||||||
pub fill: Option<&'a List<Graphic<'static>>>,
|
|
||||||
pub stroke: Option<&'a List<Graphic<'static>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LanePaint<'a> {
|
|
||||||
pub const NONE: Self = Self { fill: None, stroke: None };
|
|
||||||
|
|
||||||
pub fn is_present(&self) -> bool {
|
|
||||||
self.fill.is_some() || self.stroke.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A source's fill, stroke, and appearance columns, resolved once for per-lane reads.
|
|
||||||
pub struct PaintColumns<'a, S: LaneSource + 'a> {
|
pub struct PaintColumns<'a, S: LaneSource + 'a> {
|
||||||
fill: S::Column<'a, Fill>,
|
|
||||||
stroke: S::Column<'a, Stroke>,
|
|
||||||
appearance: S::Column<'a, AppearanceMarker>,
|
appearance: S::Column<'a, AppearanceMarker>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, S: LaneSource> PaintColumns<'a, S> {
|
impl<'a, S: LaneSource> PaintColumns<'a, S> {
|
||||||
pub fn new(source: &'a S) -> Self {
|
pub fn new(source: &'a S) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fill: source.column::<Fill>(),
|
|
||||||
stroke: source.column::<Stroke>(),
|
|
||||||
appearance: source.column::<AppearanceMarker>(),
|
appearance: source.column::<AppearanceMarker>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The lane's present, non-blank paint.
|
|
||||||
pub fn read(&self, lane: usize) -> LanePaint<'a> {
|
|
||||||
let present = |value: Option<Option<&'a List<Graphic<'static>>>>| value.flatten().filter(|list| is_paint_present(list));
|
|
||||||
LanePaint {
|
|
||||||
fill: present(self.fill.try_get(lane)),
|
|
||||||
stroke: present(self.stroke.try_get(lane)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The lane's own declared appearance; an absent or empty cell is undeclared.
|
/// The lane's own declared appearance; an absent or empty cell is undeclared.
|
||||||
pub fn read_appearance(&self, lane: usize) -> Option<&'a Appearance> {
|
pub fn read_appearance(&self, lane: usize) -> Option<&'a Appearance> {
|
||||||
self.appearance.try_get(lane).flatten().and_then(Appearance::declared)
|
self.appearance.try_get(lane).flatten().and_then(Appearance::declared)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How far a lane's paint reaches into the element beneath it, mirroring the
|
/// The appearance cascade threading down the graphic levels: a lane's own
|
||||||
/// legacy conversion's paint push: vector interiors directly and vector
|
/// declared appearance wins wholesale, an undeclared lane inherits the
|
||||||
/// children of a nested graphic list, one level deep.
|
/// nearest ancestor's, at any depth. Only a fresh entry (a pattern's own
|
||||||
///
|
|
||||||
/// The appearance cascade rides beside the paint push with its own rule: a
|
|
||||||
/// lane's own declared appearance wins wholesale, an undeclared lane inherits
|
|
||||||
/// the nearest ancestor's, at any depth. Only a fresh entry (a pattern's own
|
|
||||||
/// render, or any standalone render root) starts without an inherited one.
|
/// render, or any standalone render root) starts without an inherited one.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct PaintReach<'a> {
|
pub struct PaintReach<'a> {
|
||||||
pub paint: LanePaint<'a>,
|
|
||||||
/// The cascade's resolved appearance: the nearest declared one at or above this lane.
|
/// The cascade's resolved appearance: the nearest declared one at or above this lane.
|
||||||
pub appearance: Option<&'a Appearance>,
|
pub appearance: Option<&'a Appearance>,
|
||||||
hops: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PaintReach<'a> {
|
impl<'a> PaintReach<'a> {
|
||||||
pub const NONE: Self = Self {
|
pub const NONE: Self = Self { appearance: None };
|
||||||
paint: LanePaint::NONE,
|
|
||||||
appearance: None,
|
|
||||||
hops: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The lane's effective reach: an inherited paint stays authoritative
|
/// The lane's effective reach: its own declared appearance wins over the inherited one.
|
||||||
/// (lane paint below a push's origin is inert in the legacy model), an
|
|
||||||
/// absent one reads the lane's own paint. The appearance arbitrates the
|
|
||||||
/// opposite way: the lane's own declared appearance wins over the
|
|
||||||
/// inherited one.
|
|
||||||
pub fn for_lane<S: LaneSource>(self, columns: &PaintColumns<'a, S>, index: usize) -> Self {
|
pub fn for_lane<S: LaneSource>(self, columns: &PaintColumns<'a, S>, index: usize) -> Self {
|
||||||
let appearance = Appearance::cascade(columns.read_appearance(index), self.appearance);
|
|
||||||
match self.paint.is_present() {
|
|
||||||
true => Self { appearance, ..self },
|
|
||||||
false => Self {
|
|
||||||
paint: columns.read(index),
|
|
||||||
appearance,
|
|
||||||
hops: 2,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn applies(&self) -> bool {
|
|
||||||
self.hops > 0 && self.paint.is_present()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The reach one graphic nesting level further down. The appearance
|
|
||||||
/// cascade is not hop-limited, so it passes through unchanged.
|
|
||||||
pub fn nested(self) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
hops: self.hops.saturating_sub(1),
|
appearance: Appearance::cascade(columns.read_appearance(index), self.appearance),
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The reach entering a group's own graphic run: a spent or absent reach
|
|
||||||
/// resets so the group's own lane paint applies at its own boundary,
|
|
||||||
/// while the appearance cascades through the boundary.
|
|
||||||
pub fn into_group_graphics(self) -> Self {
|
|
||||||
match self.applies() {
|
|
||||||
true => self.nested(),
|
|
||||||
false => Self {
|
|
||||||
appearance: self.appearance,
|
|
||||||
..Self::NONE
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,18 +92,8 @@ pub fn paint_cell_rows<'a>(cell: &'a Graphic<'static>) -> Option<&'a List<Graphi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores a paint attribute in the paint marker's owned form, the only representation paint readers accept.
|
/// Bake the provided transform into the per-item transforms of the paint
|
||||||
pub fn set_paint_attribute(attributes: &mut ItemAttributeValues, key: &str, paint: impl IntoGraphicList) {
|
/// graphics inside the item's appearance coverage cells.
|
||||||
attributes.insert(key, Some(paint.into_graphic_list()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores a paint attribute at a list index in the paint marker's owned 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, Some(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) {
|
pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DAffine2) {
|
||||||
fn bake_graphic_paint_transform(graphics: &mut List<Graphic>, transform: DAffine2) {
|
fn bake_graphic_paint_transform(graphics: &mut List<Graphic>, transform: DAffine2) {
|
||||||
for item_transform in graphics.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
for item_transform in graphics.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||||
@@ -213,9 +106,13 @@ pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, transform: DA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for paint_key in [ATTR_FILL, ATTR_STROKE] {
|
if let Some(appearance) = attributes.get_mut::<Appearance>(crate::markers::ATTR_APPEARANCE) {
|
||||||
if let Some(Some(graphics)) = attributes.get_mut::<Option<List<Graphic>>>(paint_key) {
|
if let Some(cells) = appearance.0.iter_attribute_values_mut::<Graphic>(crate::markers::ATTR_PAINT) {
|
||||||
bake_graphic_paint_transform(graphics, transform);
|
for cell in cells {
|
||||||
|
if let Graphic::Graphic(list) = cell {
|
||||||
|
bake_graphic_paint_transform(list, transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,23 +127,24 @@ mod run_tests {
|
|||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_run_serves_the_parked_paint_reference() {
|
fn a_run_serves_the_parked_appearance_reference() {
|
||||||
let paint = List::new_from_element(Graphic::Color(Color::BLACK));
|
use crate::appearance::Coverage;
|
||||||
|
use core_types::lane::LaneSource;
|
||||||
|
|
||||||
|
let appearance = Appearance::new_single(Coverage::new_fill(), Graphic::Color(Color::BLACK));
|
||||||
let vector = unit_square_at(DVec2::ZERO);
|
let vector = unit_square_at(DVec2::ZERO);
|
||||||
|
|
||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<AppearanceMarker>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vector.clone()).unwrap();
|
let lane = builder.push(vector.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let run = RunView::<Vector>::new(&item).expect("the run holds vector elements");
|
let run = RunView::<Vector>::new(&item).expect("the run holds vector elements");
|
||||||
|
|
||||||
assert_eq!(run.attr::<Fill>(0), Some(&paint));
|
assert_eq!(run.attr::<AppearanceMarker>(0), Some(&appearance));
|
||||||
assert_eq!(paint_graphics::<Fill, _>(&run, 0), Some(&paint));
|
|
||||||
assert_eq!(paint_graphics::<Stroke, _>(&run, 0), None);
|
|
||||||
|
|
||||||
let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list");
|
let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list");
|
||||||
assert_eq!(paint_graphics::<Fill, _>(&legacy, 0), paint_graphics::<Fill, _>(&run, 0));
|
assert_eq!(legacy.attr::<AppearanceMarker>(0), Some(&appearance));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -263,32 +161,10 @@ mod run_tests {
|
|||||||
list.set_attribute(ATTR_APPEARANCE, 0, own.clone());
|
list.set_attribute(ATTR_APPEARANCE, 0, own.clone());
|
||||||
|
|
||||||
let columns = PaintColumns::new(&list);
|
let columns = PaintColumns::new(&list);
|
||||||
let ancestor = PaintReach {
|
let ancestor = PaintReach { appearance: Some(&inherited) };
|
||||||
appearance: Some(&inherited),
|
|
||||||
..PaintReach::NONE
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(ancestor.for_lane(&columns, 0).appearance, Some(&own), "a declared lane wins over the inherited appearance");
|
assert_eq!(ancestor.for_lane(&columns, 0).appearance, Some(&own), "a declared lane wins over the inherited appearance");
|
||||||
assert_eq!(ancestor.for_lane(&columns, 1).appearance, Some(&inherited), "a padded lane inherits");
|
assert_eq!(ancestor.for_lane(&columns, 1).appearance, Some(&inherited), "a padded lane inherits");
|
||||||
assert_eq!(PaintReach::NONE.for_lane(&columns, 1).appearance, None, "no ancestor leaves an undeclared lane bare");
|
assert_eq!(PaintReach::NONE.for_lane(&columns, 1).appearance, None, "no ancestor leaves an undeclared lane bare");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn reach_carries_the_appearance_through_nesting_and_group_boundaries() {
|
|
||||||
use crate::appearance::Coverage;
|
|
||||||
|
|
||||||
let inherited = Appearance::new_single(Coverage::new_fill(), Graphic::Color(Color::WHITE));
|
|
||||||
let reach = PaintReach {
|
|
||||||
appearance: Some(&inherited),
|
|
||||||
..PaintReach::NONE
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(reach.nested().appearance, Some(&inherited), "nesting does not hop-limit the cascade");
|
|
||||||
assert_eq!(
|
|
||||||
reach.into_group_graphics().appearance,
|
|
||||||
Some(&inherited),
|
|
||||||
"the cascade crosses a group boundary the paint push resets at"
|
|
||||||
);
|
|
||||||
assert_eq!(PaintReach::NONE.into_group_graphics().appearance, None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
//! The native-content walk: vector rows reached through a graphic's own storage, with no legacy conversion.
|
//! The native-content walk: vector rows reached through a graphic's own storage, with no legacy conversion.
|
||||||
|
|
||||||
use super::Graphic;
|
use super::Graphic;
|
||||||
use super::paint::{LanePaint, PaintColumns, PaintReach, is_paint_present, paint_graphics, set_paint_attribute_at};
|
use super::paint::{PaintColumns, PaintReach};
|
||||||
use crate::appearance::Appearance;
|
use crate::appearance::Appearance;
|
||||||
use crate::markers::{ATTR_APPEARANCE, ATTR_FILL, ATTR_STROKE, Fill};
|
use crate::markers::ATTR_APPEARANCE;
|
||||||
use core_types::attribute::{Attribute, ClippingMask, EditorLayerPath, Opacity, OpacityFill, Transform};
|
use core_types::attribute::{Attribute, ClippingMask, EditorLayerPath, Opacity, OpacityFill, Transform};
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::lane::LaneSource;
|
use core_types::lane::LaneSource;
|
||||||
@@ -252,7 +252,6 @@ pub struct VectorRow<'w> {
|
|||||||
source: RowSourceRef<'w>,
|
source: RowSourceRef<'w>,
|
||||||
scale: FlattenScale,
|
scale: FlattenScale,
|
||||||
layer_path: Option<&'w [NodeId]>,
|
layer_path: Option<&'w [NodeId]>,
|
||||||
paint: LanePaint<'w>,
|
|
||||||
appearance: Option<&'w Appearance>,
|
appearance: Option<&'w Appearance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,16 +271,14 @@ impl VectorRow<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the built row will carry fill paint: the reaching lane paint,
|
/// Whether the built row will carry fill paint: a painted fill coverage on
|
||||||
/// else the row's own.
|
/// the row's resolved appearance.
|
||||||
pub fn has_fill(&self) -> bool {
|
pub fn has_fill(&self) -> bool {
|
||||||
if self.paint.fill.is_some() {
|
let own = match &self.source {
|
||||||
return true;
|
RowSourceRef::Lane(level, index) => level.attr::<crate::markers::Appearance>(*index),
|
||||||
}
|
RowSourceRef::Run(run, _, index) => LaneSource::attr::<crate::markers::Appearance>(*run, *index),
|
||||||
match &self.source {
|
};
|
||||||
RowSourceRef::Lane(level, index) => paint_graphics::<Fill, _>(level, *index).is_some(),
|
Appearance::cascade(own, self.appearance).is_some_and(|appearance| appearance.has_painted_cover(crate::appearance::Cover::Fill))
|
||||||
RowSourceRef::Run(run, _, index) => paint_graphics::<Fill, _>(*run, *index).is_some(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the row at the end of `out`, applying the reach paint and the
|
/// Builds the row at the end of `out`, applying the reach paint and the
|
||||||
@@ -298,11 +295,6 @@ impl VectorRow<'_> {
|
|||||||
out.push(Item::from_parts(vector, run_lane_attributes(item, *lane)));
|
out.push(Item::from_parts(vector, run_lane_attributes(item, *lane)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (key, slot) in [(ATTR_FILL, self.paint.fill), (ATTR_STROKE, self.paint.stroke)] {
|
|
||||||
if let Some(paint) = slot {
|
|
||||||
set_paint_attribute_at(out, index, key, paint.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if self.scale.has_transform || out.attribute::<DAffine2>(ATTR_TRANSFORM, index).is_some() {
|
if self.scale.has_transform || out.attribute::<DAffine2>(ATTR_TRANSFORM, index).is_some() {
|
||||||
let row_transform: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let row_transform: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
out.set_attribute(ATTR_TRANSFORM, index, self.scale.transform * row_transform);
|
out.set_attribute(ATTR_TRANSFORM, index, self.scale.transform * row_transform);
|
||||||
@@ -331,7 +323,6 @@ fn walk_rows_of_run(
|
|||||||
item: &core_types::record::GroupItem,
|
item: &core_types::record::GroupItem,
|
||||||
scale: FlattenScale,
|
scale: FlattenScale,
|
||||||
layer_path: Option<&[NodeId]>,
|
layer_path: Option<&[NodeId]>,
|
||||||
paint: LanePaint<'_>,
|
|
||||||
appearance: Option<&Appearance>,
|
appearance: Option<&Appearance>,
|
||||||
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
||||||
) -> RowStep {
|
) -> RowStep {
|
||||||
@@ -343,7 +334,6 @@ fn walk_rows_of_run(
|
|||||||
source: RowSourceRef::Run(&run, item, lane),
|
source: RowSourceRef::Run(&run, item, lane),
|
||||||
scale,
|
scale,
|
||||||
layer_path,
|
layer_path,
|
||||||
paint,
|
|
||||||
appearance,
|
appearance,
|
||||||
}) {
|
}) {
|
||||||
return RowStep::Stop;
|
return RowStep::Stop;
|
||||||
@@ -353,9 +343,9 @@ fn walk_rows_of_run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Walks a graphic level into its flattened vector rows, matching the legacy
|
/// Walks a graphic level into its flattened vector rows, matching the legacy
|
||||||
/// push-then-flatten lowering: lane paint threads with [`PaintReach`],
|
/// flatten lowering: the appearance cascades with [`PaintReach`], ancestor
|
||||||
/// ancestor transform, opacity and fill opacity compose down, the containing
|
/// transform, opacity and fill opacity compose down, the containing level's
|
||||||
/// level's parent layer path overwrites its rows, and non-vector content is
|
/// parent layer path overwrites its rows, and non-vector content is
|
||||||
/// discarded. A de-tabled leaf's row is its lane, attributes included.
|
/// discarded. A de-tabled leaf's row is its lane, attributes included.
|
||||||
pub fn walk_vector_rows(level: GraphicLevel<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) {
|
pub fn walk_vector_rows(level: GraphicLevel<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) {
|
||||||
walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, visit);
|
walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, visit);
|
||||||
@@ -371,48 +361,27 @@ fn walk_vector_rows_impl<'a>(
|
|||||||
if let GraphicLevel::Run(item) = level {
|
if let GraphicLevel::Run(item) = level {
|
||||||
// A vector-typed run is already its rows.
|
// A vector-typed run is already its rows.
|
||||||
if item.typed_lanes::<Vector>().is_some() {
|
if item.typed_lanes::<Vector>().is_some() {
|
||||||
let paint = match inherited.applies() {
|
return walk_rows_of_run(item, scale, parent_layer_path, inherited.appearance, visit);
|
||||||
true => inherited.paint,
|
|
||||||
false => LanePaint::NONE,
|
|
||||||
};
|
|
||||||
return walk_rows_of_run(item, scale, parent_layer_path, paint, inherited.appearance, visit);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let columns = PaintColumns::new(&level);
|
let columns = PaintColumns::new(&level);
|
||||||
for index in 0..level.lane_count() {
|
for index in 0..level.lane_count() {
|
||||||
let Some(element) = level.element(index) else { continue };
|
let Some(element) = level.element(index) else { continue };
|
||||||
let reach = inherited.for_lane(&columns, index);
|
let reach = inherited.for_lane(&columns, index);
|
||||||
let row_paint = match reach.applies() {
|
|
||||||
true => reach.paint,
|
|
||||||
false => LanePaint::NONE,
|
|
||||||
};
|
|
||||||
let step = match element {
|
let step = match element {
|
||||||
Graphic::Vector(_) => visit(VectorRow {
|
Graphic::Vector(_) => visit(VectorRow {
|
||||||
source: RowSourceRef::Lane(level, index),
|
source: RowSourceRef::Lane(level, index),
|
||||||
scale,
|
scale,
|
||||||
layer_path: parent_layer_path,
|
layer_path: parent_layer_path,
|
||||||
paint: row_paint,
|
|
||||||
appearance: reach.appearance,
|
appearance: reach.appearance,
|
||||||
}),
|
}),
|
||||||
Graphic::Graphic(children) => walk_vector_rows_impl(
|
Graphic::Graphic(children) => walk_vector_rows_impl(GraphicLevel::Legacy(children), scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), reach, visit),
|
||||||
GraphicLevel::Legacy(children),
|
|
||||||
scale.composed(&level, index),
|
|
||||||
level.try_attr::<EditorLayerPath>(index),
|
|
||||||
reach.nested(),
|
|
||||||
visit,
|
|
||||||
),
|
|
||||||
Graphic::Group(group) => {
|
Graphic::Group(group) => {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if item.typed_lanes::<Vector>().is_some() {
|
if item.typed_lanes::<Vector>().is_some() {
|
||||||
walk_rows_of_run(item, scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), row_paint, reach.appearance, visit)
|
walk_rows_of_run(item, scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), reach.appearance, visit)
|
||||||
} else if item.typed_lanes::<Graphic>().is_some() {
|
} else if item.typed_lanes::<Graphic>().is_some() {
|
||||||
walk_vector_rows_impl(
|
walk_vector_rows_impl(GraphicLevel::Run(item), scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), reach, visit)
|
||||||
GraphicLevel::Run(item),
|
|
||||||
scale.composed(&level, index),
|
|
||||||
level.try_attr::<EditorLayerPath>(index),
|
|
||||||
reach.into_group_graphics(),
|
|
||||||
visit,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
RowStep::Continue
|
RowStep::Continue
|
||||||
}
|
}
|
||||||
@@ -437,26 +406,6 @@ pub fn flatten_vector_rows(level: GraphicLevel<'_>) -> List<Vector> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The transitional paint placement: a lane-level fill or stroke paint
|
|
||||||
/// attribute moves onto the vector interiors the legacy paint readers
|
|
||||||
/// inspect, reaching as far as the pre-flip broadcast did.
|
|
||||||
pub(in crate::graphic) fn push_lane_paint_into_interiors(list: &mut List<Graphic>) {
|
|
||||||
for index in 0..list.len() {
|
|
||||||
for key in [ATTR_FILL, ATTR_STROKE] {
|
|
||||||
let stored = list.attribute::<Option<List<Graphic>>>(key, index).and_then(|optional| optional.as_ref());
|
|
||||||
let Some(paint) = stored.filter(|paint| is_paint_present(paint)).cloned() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(Graphic::Graphic(children)) = list.element_mut(index) else { continue };
|
|
||||||
for child in 0..children.len() {
|
|
||||||
if matches!(children.element(child), Some(Graphic::Vector(_))) {
|
|
||||||
set_paint_attribute_at(children, child, key, paint.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The count [`map_groups_to_legacy`] would expose through [`Graphic::as_vector`],
|
/// The count [`map_groups_to_legacy`] would expose through [`Graphic::as_vector`],
|
||||||
/// read from the run's lanes instead of materializing the legacy list. Mirrors
|
/// read from the run's lanes instead of materializing the legacy list. Mirrors
|
||||||
/// [`group_to_legacy_graphic`]'s typed-run path, where `Vector` is tried first.
|
/// [`group_to_legacy_graphic`]'s typed-run path, where `Vector` is tried first.
|
||||||
@@ -496,6 +445,10 @@ mod run_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_vector_row_walk_matches_the_legacy_flatten() {
|
fn the_vector_row_walk_matches_the_legacy_flatten() {
|
||||||
|
use crate::appearance::Coverage;
|
||||||
|
|
||||||
|
let single = |color: Color| Appearance::new_single(Coverage::new_fill(), Graphic::Color(color));
|
||||||
|
|
||||||
let inner_vector = unit_square_at(DVec2::ZERO);
|
let inner_vector = unit_square_at(DVec2::ZERO);
|
||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[], 1).unwrap();
|
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[], 1).unwrap();
|
||||||
@@ -507,7 +460,7 @@ mod run_tests {
|
|||||||
painted.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ONE))));
|
painted.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ONE))));
|
||||||
painted.set_attribute(core_types::ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(1., 0.)));
|
painted.set_attribute(core_types::ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(1., 0.)));
|
||||||
painted.set_attribute(core_types::ATTR_TRANSFORM, 1, DAffine2::from_translation(DVec2::new(0., 1.)));
|
painted.set_attribute(core_types::ATTR_TRANSFORM, 1, DAffine2::from_translation(DVec2::new(0., 1.)));
|
||||||
set_paint_attribute_at(&mut painted, 1, ATTR_FILL, List::new_from_element(Graphic::Color(Color::WHITE)));
|
painted.set_attribute(ATTR_APPEARANCE, 1, single(Color::WHITE));
|
||||||
|
|
||||||
let mut nested = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(2., 2.))));
|
let mut nested = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(2., 2.))));
|
||||||
nested.set_attribute(core_types::ATTR_TRANSFORM, 0, DAffine2::from_scale(DVec2::splat(2.)));
|
nested.set_attribute(core_types::ATTR_TRANSFORM, 0, DAffine2::from_scale(DVec2::splat(2.)));
|
||||||
@@ -520,12 +473,12 @@ mod run_tests {
|
|||||||
top.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(6., 0.)))));
|
top.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(6., 0.)))));
|
||||||
top.set_attribute(core_types::ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(5., 5.)));
|
top.set_attribute(core_types::ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(5., 5.)));
|
||||||
top.set_attribute(core_types::ATTR_EDITOR_LAYER_PATH, 0, vec![core_types::uuid::NodeId(7)]);
|
top.set_attribute(core_types::ATTR_EDITOR_LAYER_PATH, 0, vec![core_types::uuid::NodeId(7)]);
|
||||||
set_paint_attribute_at(&mut top, 0, ATTR_FILL, List::new_from_element(Graphic::Color(Color::BLACK)));
|
top.set_attribute(ATTR_APPEARANCE, 0, single(Color::BLACK));
|
||||||
top.set_attribute(core_types::ATTR_OPACITY, 1, 0.5);
|
top.set_attribute(core_types::ATTR_OPACITY, 1, 0.5);
|
||||||
top.set_attribute(core_types::ATTR_TRANSFORM, 2, DAffine2::from_scale(DVec2::splat(3.)));
|
top.set_attribute(core_types::ATTR_TRANSFORM, 2, DAffine2::from_scale(DVec2::splat(3.)));
|
||||||
top.set_attribute(core_types::ATTR_TRANSFORM, 4, DAffine2::from_translation(DVec2::new(0., 7.)));
|
top.set_attribute(core_types::ATTR_TRANSFORM, 4, DAffine2::from_translation(DVec2::new(0., 7.)));
|
||||||
top.set_attribute(core_types::ATTR_EDITOR_LAYER_PATH, 4, vec![core_types::uuid::NodeId(9)]);
|
top.set_attribute(core_types::ATTR_EDITOR_LAYER_PATH, 4, vec![core_types::uuid::NodeId(9)]);
|
||||||
set_paint_attribute_at(&mut top, 4, ATTR_FILL, List::new_from_element(Graphic::Color(Color::WHITE)));
|
top.set_attribute(ATTR_APPEARANCE, 4, single(Color::WHITE));
|
||||||
|
|
||||||
let legacy = {
|
let legacy = {
|
||||||
let mut list = List::new();
|
let mut list = List::new();
|
||||||
@@ -533,7 +486,6 @@ mod run_tests {
|
|||||||
let (element, attributes) = item.into_parts();
|
let (element, attributes) = item.into_parts();
|
||||||
list.push(Item::from_parts(map_groups_to_legacy(&element), attributes));
|
list.push(Item::from_parts(map_groups_to_legacy(&element), attributes));
|
||||||
}
|
}
|
||||||
push_lane_paint_into_interiors(&mut list);
|
|
||||||
list.into_flattened_list::<Vector>()
|
list.into_flattened_list::<Vector>()
|
||||||
};
|
};
|
||||||
let native = flatten_vector_rows(GraphicLevel::Legacy(&top));
|
let native = flatten_vector_rows(GraphicLevel::Legacy(&top));
|
||||||
@@ -555,9 +507,9 @@ mod run_tests {
|
|||||||
"layer path, row {row}"
|
"layer path, row {row}"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
native.attribute::<Option<List<Graphic>>>(ATTR_FILL, row),
|
native.attribute::<Appearance>(ATTR_APPEARANCE, row),
|
||||||
legacy.attribute::<Option<List<Graphic>>>(ATTR_FILL, row),
|
legacy.attribute::<Appearance>(ATTR_APPEARANCE, row),
|
||||||
"fill, row {row}"
|
"appearance, row {row}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(native, legacy);
|
assert_eq!(native, legacy);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub use vector_types;
|
|||||||
pub use appearance::{Appearance, Cover, CoverPlacement, Coverage, FillAndStroke, stamp_coverage};
|
pub use appearance::{Appearance, Cover, CoverPlacement, Coverage, FillAndStroke, stamp_coverage};
|
||||||
pub use artboard::Artboard;
|
pub use artboard::Artboard;
|
||||||
pub use graphic::{Graphic, IntoGraphicList, TryFromGraphic, Vector};
|
pub use graphic::{Graphic, IntoGraphicList, TryFromGraphic, Vector};
|
||||||
pub use markers::{ATTR_APPEARANCE, ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_PAINT, ATTR_STROKE};
|
pub use markers::{ATTR_APPEARANCE, ATTR_EDITOR_MERGED_LAYERS, ATTR_PAINT};
|
||||||
|
|
||||||
pub mod migrations {
|
pub mod migrations {
|
||||||
use crate::Vector;
|
use crate::Vector;
|
||||||
|
|||||||
@@ -1,21 +1,15 @@
|
|||||||
//! Attribute markers whose value types live in this crate, with their name
|
//! Attribute markers whose value types live in this crate, with their name
|
||||||
//! constants for the string-keyed legacy readers and writers.
|
//! constants for the string-keyed legacy readers and writers.
|
||||||
//!
|
//!
|
||||||
//! The list-valued markers may carry native [`Graphic::Group`] content: the
|
//! The markers may carry native [`Graphic::Group`] content: the registered
|
||||||
//! registered deep field glue owns it across persistence seams, and legacy
|
//! deep field glue owns it across persistence seams, and legacy products
|
||||||
//! products convert it through [`crate::graphic::map_paint_attrs_to_legacy`].
|
//! convert it through [`crate::graphic::map_paint_attrs_to_legacy`].
|
||||||
|
|
||||||
use crate::Graphic;
|
use crate::Graphic;
|
||||||
use core_types::attribute::Attribute;
|
use core_types::attribute::Attribute;
|
||||||
use core_types::list::List;
|
use core_types::list::List;
|
||||||
|
|
||||||
core_types::attribute! {
|
core_types::attribute! {
|
||||||
/// Vector graphics object's filled area paint, a graphic list in the canonical paint form.
|
|
||||||
/// An absent value means no fill.
|
|
||||||
pub Fill("fill"): Option<&List<Graphic<'static>>>;
|
|
||||||
/// Vector graphics object's stroke paint, a graphic list in the canonical paint form.
|
|
||||||
/// An absent value means no stroke paint.
|
|
||||||
pub Stroke("stroke"): Option<&List<Graphic<'static>>>;
|
|
||||||
/// Snapshot of the upstream content that fed into a destructive merge (Boolean Operation,
|
/// Snapshot of the upstream content that fed into a destructive merge (Boolean Operation,
|
||||||
/// Rasterize, etc.), so the editor can still surface click targets for the original child
|
/// Rasterize, etc.), so the editor can still surface click targets for the original child
|
||||||
/// layers after their content has been collapsed.
|
/// layers after their content has been collapsed.
|
||||||
@@ -102,8 +96,6 @@ unsafe impl Attribute for Paint {
|
|||||||
|
|
||||||
core_types::attribute!(@register Paint);
|
core_types::attribute!(@register Paint);
|
||||||
|
|
||||||
pub const ATTR_FILL: &str = Fill::NAME;
|
|
||||||
pub const ATTR_STROKE: &str = Stroke::NAME;
|
|
||||||
pub const ATTR_EDITOR_MERGED_LAYERS: &str = EditorMergedLayers::NAME;
|
pub const ATTR_EDITOR_MERGED_LAYERS: &str = EditorMergedLayers::NAME;
|
||||||
pub const ATTR_APPEARANCE: &str = Appearance::NAME;
|
pub const ATTR_APPEARANCE: &str = Appearance::NAME;
|
||||||
pub const ATTR_PAINT: &str = Paint::NAME;
|
pub const ATTR_PAINT: &str = Paint::NAME;
|
||||||
@@ -116,27 +108,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_census_carries_this_crates_names() {
|
fn the_census_carries_this_crates_names() {
|
||||||
for name in ["fill", "stroke", "editor:merged_layers"] {
|
assert_eq!(info("editor:merged_layers").unwrap().value_type, TypeId::of::<Option<&'static List<Graphic>>>());
|
||||||
assert_eq!(info(name).unwrap().value_type, TypeId::of::<Option<&'static List<Graphic>>>());
|
|
||||||
}
|
|
||||||
assert_eq!(info("appearance").unwrap().value_type, TypeId::of::<Option<&'static crate::appearance::Appearance>>());
|
assert_eq!(info("appearance").unwrap().value_type, TypeId::of::<Option<&'static crate::appearance::Appearance>>());
|
||||||
assert_eq!(info("paint").unwrap().value_type, TypeId::of::<Option<&'static Graphic>>());
|
assert_eq!(info("paint").unwrap().value_type, TypeId::of::<Option<&'static Graphic>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_absent_paint_defaults_to_none() {
|
fn an_absent_paint_defaults_to_none() {
|
||||||
assert_eq!(<Fill as Attribute>::default(), None);
|
assert_eq!(<Paint as Attribute>::default(), None);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_paint_marker_reads_back_what_the_paint_writer_stored() {
|
|
||||||
use core_types::lane::LaneSource;
|
|
||||||
|
|
||||||
let paint = List::new_from_element(Graphic::default());
|
|
||||||
let mut list = List::new_from_element(Graphic::default());
|
|
||||||
crate::graphic::set_paint_attribute_at(&mut list, 0, ATTR_FILL, paint.clone());
|
|
||||||
|
|
||||||
assert_eq!(list.attr::<Fill>(0), Some(&paint));
|
|
||||||
assert_eq!(list.attr::<Stroke>(0), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ use glam::{DAffine2, DMat2, DVec2};
|
|||||||
use graphene_hash::CacheHashWrapper;
|
use graphene_hash::CacheHashWrapper;
|
||||||
use graphene_resource::Resource;
|
use graphene_resource::Resource;
|
||||||
use graphic_types::appearance::{Appearance, Coverage};
|
use graphic_types::appearance::{Appearance, Coverage};
|
||||||
use graphic_types::graphic::{PaintColumns, PaintReach, is_paint_present, paint_cell_rows, set_paint_attribute, vector_can_reduce_to_clip_path};
|
use graphic_types::graphic::{PaintColumns, PaintReach, is_paint_present, paint_cell_rows, vector_can_reduce_to_clip_path};
|
||||||
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers};
|
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers};
|
||||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
||||||
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
||||||
@@ -32,7 +32,7 @@ use graphic_types::vector_types::markers::{GradientType as GradientTypeAttr, Spr
|
|||||||
use graphic_types::vector_types::subpath::Subpath;
|
use graphic_types::vector_types::subpath::Subpath;
|
||||||
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
||||||
use graphic_types::vector_types::vector::style::{RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphic_types::vector_types::vector::style::{RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphic_types::{ATTR_FILL, Artboard, Graphic, Vector};
|
use graphic_types::{Artboard, Graphic, Vector};
|
||||||
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
|
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
||||||
@@ -540,10 +540,10 @@ pub struct RenderMetadata {
|
|||||||
pub text_frames: HashMap<NodeId, DAffine2>,
|
pub text_frames: HashMap<NodeId, DAffine2>,
|
||||||
pub clip_targets: HashSet<NodeId>,
|
pub clip_targets: HashSet<NodeId>,
|
||||||
pub vector_data: HashMap<NodeId, Arc<Vector>>,
|
pub vector_data: HashMap<NodeId, Arc<Vector>>,
|
||||||
/// Per-layer `ATTR_FILL` row attribute, exposed so message handlers can read it.
|
/// Per-layer fill paint snapshot from the resolved appearance, exposed so message handlers can read it.
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub fill_attributes: HashMap<NodeId, Arc<List<Graphic<'static>>>>,
|
pub fill_attributes: HashMap<NodeId, Arc<List<Graphic<'static>>>>,
|
||||||
/// Per-layer `ATTR_STROKE` row attribute, exposed so message handlers can read it.
|
/// Per-layer stroke paint snapshot from the resolved appearance, exposed so message handlers can read it.
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pub stroke_attributes: HashMap<NodeId, Arc<List<Graphic<'static>>>>,
|
pub stroke_attributes: HashMap<NodeId, Arc<List<Graphic<'static>>>>,
|
||||||
pub backgrounds: Vec<Background>,
|
pub backgrounds: Vec<Background>,
|
||||||
@@ -684,7 +684,7 @@ impl Render for Graphic<'_> {
|
|||||||
fn render_element_svg<'a>(element: &'a Graphic, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) {
|
fn render_element_svg<'a>(element: &'a Graphic, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
match element {
|
match element {
|
||||||
Graphic::Vector(vector) => render_vector_svg(&Single(vector), reach.appearance, render, render_params),
|
Graphic::Vector(vector) => render_vector_svg(&Single(vector), reach.appearance, render, render_params),
|
||||||
Graphic::Graphic(inner) => render_graphic_svg_with(inner, reach.nested(), render, render_params),
|
Graphic::Graphic(inner) => render_graphic_svg_with(inner, reach, render, render_params),
|
||||||
Graphic::Group(group) => render_group_svg(group, reach, render, render_params),
|
Graphic::Group(group) => render_group_svg(group, reach, render, render_params),
|
||||||
_ => element.render_svg(render, render_params),
|
_ => element.render_svg(render, render_params),
|
||||||
}
|
}
|
||||||
@@ -693,7 +693,7 @@ fn render_element_svg<'a>(element: &'a Graphic, reach: PaintReach<'a>, render: &
|
|||||||
fn render_element_vello<'a>(element: &'a Graphic, reach: PaintReach<'a>, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_element_vello<'a>(element: &'a Graphic, reach: PaintReach<'a>, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
match element {
|
match element {
|
||||||
Graphic::Vector(vector) => render_vector_vello(&Single(vector), reach.appearance, scene, transform, context, render_params),
|
Graphic::Vector(vector) => render_vector_vello(&Single(vector), reach.appearance, scene, transform, context, render_params),
|
||||||
Graphic::Graphic(inner) => render_graphic_vello_with(inner, reach.nested(), scene, transform, context, render_params),
|
Graphic::Graphic(inner) => render_graphic_vello_with(inner, reach, scene, transform, context, render_params),
|
||||||
Graphic::Group(group) => render_group_vello(group, reach, scene, transform, context, render_params),
|
Graphic::Group(group) => render_group_vello(group, reach, scene, transform, context, render_params),
|
||||||
_ => element.render_to_vello(scene, transform, context, render_params),
|
_ => element.render_to_vello(scene, transform, context, render_params),
|
||||||
}
|
}
|
||||||
@@ -736,7 +736,7 @@ fn collect_element_metadata<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
Graphic::Graphic(list) => collect_graphic_metadata_with(list, reach.nested(), metadata, footprint, element_id),
|
Graphic::Graphic(list) => collect_graphic_metadata_with(list, reach, metadata, footprint, element_id),
|
||||||
Graphic::Vector(vector) => collect_vector_metadata(&Single(vector), reach.appearance, metadata, footprint, element_id),
|
Graphic::Vector(vector) => collect_vector_metadata(&Single(vector), reach.appearance, metadata, footprint, element_id),
|
||||||
Graphic::RasterCPU(raster) => collect_raster_metadata(&Single(raster), metadata, footprint, element_id),
|
Graphic::RasterCPU(raster) => collect_raster_metadata(&Single(raster), metadata, footprint, element_id),
|
||||||
Graphic::RasterGPU(raster) => collect_raster_metadata(&Single(raster), metadata, footprint, element_id),
|
Graphic::RasterGPU(raster) => collect_raster_metadata(&Single(raster), metadata, footprint, element_id),
|
||||||
@@ -778,7 +778,7 @@ fn collect_group_row_metadata(group: &Group, metadata: &mut RenderMetadata, elem
|
|||||||
|
|
||||||
fn add_element_upstream_click_targets<'a>(element: &'a Graphic, reach: PaintReach<'a>, click_targets: &mut Vec<ClickTarget>) {
|
fn add_element_upstream_click_targets<'a>(element: &'a Graphic, reach: PaintReach<'a>, click_targets: &mut Vec<ClickTarget>) {
|
||||||
match element {
|
match element {
|
||||||
Graphic::Graphic(list) => add_graphic_upstream_click_targets_with(list, reach.nested(), click_targets),
|
Graphic::Graphic(list) => add_graphic_upstream_click_targets_with(list, reach, click_targets),
|
||||||
Graphic::Vector(vector) => add_vector_upstream_click_targets(&Single(vector), reach.appearance, click_targets),
|
Graphic::Vector(vector) => add_vector_upstream_click_targets(&Single(vector), reach.appearance, click_targets),
|
||||||
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) => add_raster_upstream_click_targets(click_targets),
|
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) => add_raster_upstream_click_targets(click_targets),
|
||||||
Graphic::Color(_) | Graphic::Gradient(_) => {}
|
Graphic::Color(_) | Graphic::Gradient(_) => {}
|
||||||
@@ -789,7 +789,7 @@ fn add_element_upstream_click_targets<'a>(element: &'a Graphic, reach: PaintReac
|
|||||||
|
|
||||||
fn add_element_upstream_outline_targets<'a>(element: &'a Graphic, reach: PaintReach<'a>, outlines: &mut Vec<ClickTarget>) {
|
fn add_element_upstream_outline_targets<'a>(element: &'a Graphic, reach: PaintReach<'a>, outlines: &mut Vec<ClickTarget>) {
|
||||||
match element {
|
match element {
|
||||||
Graphic::Graphic(list) => add_graphic_upstream_outline_targets_with(list, reach.nested(), outlines),
|
Graphic::Graphic(list) => add_graphic_upstream_outline_targets_with(list, reach, outlines),
|
||||||
Graphic::Vector(vector) => add_vector_upstream_outline_targets(&Single(vector), reach.appearance, outlines),
|
Graphic::Vector(vector) => add_vector_upstream_outline_targets(&Single(vector), reach.appearance, outlines),
|
||||||
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) => add_raster_upstream_click_targets(outlines),
|
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) => add_raster_upstream_click_targets(outlines),
|
||||||
Graphic::Color(_) | Graphic::Gradient(_) => {}
|
Graphic::Color(_) | Graphic::Gradient(_) => {}
|
||||||
@@ -803,7 +803,7 @@ fn add_element_upstream_outline_targets<'a>(element: &'a Graphic, reach: PaintRe
|
|||||||
fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) {
|
fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if let Some(run) = RunView::<Graphic>::new(item) {
|
if let Some(run) = RunView::<Graphic>::new(item) {
|
||||||
render_graphic_svg_with(&run, reach.into_group_graphics(), render, render_params)
|
render_graphic_svg_with(&run, reach, render, render_params)
|
||||||
} else if let Some(run) = RunView::<Vector>::new(item) {
|
} else if let Some(run) = RunView::<Vector>::new(item) {
|
||||||
render_vector_svg(&run, reach.appearance, render, render_params)
|
render_vector_svg(&run, reach.appearance, render, render_params)
|
||||||
} else if let Some(run) = RunView::<Raster<CPU>>::new(item) {
|
} else if let Some(run) = RunView::<Raster<CPU>>::new(item) {
|
||||||
@@ -821,7 +821,7 @@ fn render_group_svg<'a>(group: &'a Group, reach: PaintReach<'a>, render: &mut Sv
|
|||||||
fn render_group_vello<'a>(group: &'a Group, reach: PaintReach<'a>, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_group_vello<'a>(group: &'a Group, reach: PaintReach<'a>, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if let Some(run) = RunView::<Graphic>::new(item) {
|
if let Some(run) = RunView::<Graphic>::new(item) {
|
||||||
render_graphic_vello_with(&run, reach.into_group_graphics(), scene, transform, context, render_params)
|
render_graphic_vello_with(&run, reach, scene, transform, context, render_params)
|
||||||
} else if let Some(run) = RunView::<Vector>::new(item) {
|
} else if let Some(run) = RunView::<Vector>::new(item) {
|
||||||
render_vector_vello(&run, reach.appearance, scene, transform, context, render_params)
|
render_vector_vello(&run, reach.appearance, scene, transform, context, render_params)
|
||||||
} else if let Some(run) = RunView::<Raster<CPU>>::new(item) {
|
} else if let Some(run) = RunView::<Raster<CPU>>::new(item) {
|
||||||
@@ -843,7 +843,7 @@ fn render_group_vello<'a>(group: &'a Group, reach: PaintReach<'a>, scene: &mut S
|
|||||||
fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if let Some(run) = RunView::<Graphic>::new(item) {
|
if let Some(run) = RunView::<Graphic>::new(item) {
|
||||||
collect_graphic_metadata_with(&run, reach.into_group_graphics(), metadata, footprint, element_id)
|
collect_graphic_metadata_with(&run, reach, metadata, footprint, element_id)
|
||||||
} else if let Some(run) = RunView::<Vector>::new(item) {
|
} else if let Some(run) = RunView::<Vector>::new(item) {
|
||||||
collect_vector_metadata(&run, reach.appearance, metadata, footprint, element_id)
|
collect_vector_metadata(&run, reach.appearance, metadata, footprint, element_id)
|
||||||
} else if let Some(run) = RunView::<Raster<CPU>>::new(item) {
|
} else if let Some(run) = RunView::<Raster<CPU>>::new(item) {
|
||||||
@@ -859,7 +859,7 @@ fn collect_group_metadata<'a>(group: &'a Group, reach: PaintReach<'a>, metadata:
|
|||||||
fn add_group_upstream_click_targets<'a>(group: &'a Group, reach: PaintReach<'a>, click_targets: &mut Vec<ClickTarget>) {
|
fn add_group_upstream_click_targets<'a>(group: &'a Group, reach: PaintReach<'a>, click_targets: &mut Vec<ClickTarget>) {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if let Some(run) = RunView::<Graphic>::new(item) {
|
if let Some(run) = RunView::<Graphic>::new(item) {
|
||||||
add_graphic_upstream_click_targets_with(&run, reach.into_group_graphics(), click_targets)
|
add_graphic_upstream_click_targets_with(&run, reach, click_targets)
|
||||||
} else if let Some(run) = RunView::<Vector>::new(item) {
|
} else if let Some(run) = RunView::<Vector>::new(item) {
|
||||||
add_vector_upstream_click_targets(&run, reach.appearance, click_targets)
|
add_vector_upstream_click_targets(&run, reach.appearance, click_targets)
|
||||||
} else if item.typed_lanes::<Raster<CPU>>().is_some() || item.typed_lanes::<Raster<GPU>>().is_some() {
|
} else if item.typed_lanes::<Raster<CPU>>().is_some() || item.typed_lanes::<Raster<GPU>>().is_some() {
|
||||||
@@ -872,7 +872,7 @@ fn add_group_upstream_click_targets<'a>(group: &'a Group, reach: PaintReach<'a>,
|
|||||||
fn add_group_upstream_outline_targets<'a>(group: &'a Group, reach: PaintReach<'a>, outlines: &mut Vec<ClickTarget>) {
|
fn add_group_upstream_outline_targets<'a>(group: &'a Group, reach: PaintReach<'a>, outlines: &mut Vec<ClickTarget>) {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
if let Some(run) = RunView::<Graphic>::new(item) {
|
if let Some(run) = RunView::<Graphic>::new(item) {
|
||||||
add_graphic_upstream_outline_targets_with(&run, reach.into_group_graphics(), outlines)
|
add_graphic_upstream_outline_targets_with(&run, reach, outlines)
|
||||||
} else if let Some(run) = RunView::<Vector>::new(item) {
|
} else if let Some(run) = RunView::<Vector>::new(item) {
|
||||||
add_vector_upstream_outline_targets(&run, reach.appearance, outlines)
|
add_vector_upstream_outline_targets(&run, reach.appearance, outlines)
|
||||||
} else if item.typed_lanes::<Raster<CPU>>().is_some() || item.typed_lanes::<Raster<GPU>>().is_some() {
|
} else if item.typed_lanes::<Raster<CPU>>().is_some() || item.typed_lanes::<Raster<GPU>>().is_some() {
|
||||||
@@ -1410,7 +1410,6 @@ fn render_vector_svg<S: LaneSource<Element = Vector>>(source: &S, inherited_appe
|
|||||||
// The mask must draw at full alpha so the SVG `<mask>`/`<clipPath>` fully zeroes the path interior.
|
// 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.
|
// 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);
|
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));
|
|
||||||
mask_item.set_attribute(graphic_types::ATTR_APPEARANCE, black_fill_appearance());
|
mask_item.set_attribute(graphic_types::ATTR_APPEARANCE, black_fill_appearance());
|
||||||
let vector_item = List::new_from_item(mask_item);
|
let vector_item = List::new_from_item(mask_item);
|
||||||
|
|
||||||
@@ -1766,7 +1765,6 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(
|
|||||||
// The mask must draw at full alpha so `SrcOut` fully zeroes the path interior.
|
// 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.
|
// 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);
|
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));
|
|
||||||
mask_item.set_attribute(graphic_types::ATTR_APPEARANCE, black_fill_appearance());
|
mask_item.set_attribute(graphic_types::ATTR_APPEARANCE, black_fill_appearance());
|
||||||
let vector_list = List::new_from_item(mask_item);
|
let vector_list = List::new_from_item(mask_item);
|
||||||
|
|
||||||
@@ -3129,7 +3127,6 @@ impl SvgRenderAttrs<'_> {
|
|||||||
mod group_walk_tests {
|
mod group_walk_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
|
use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
|
||||||
use graphic_types::markers::Fill;
|
|
||||||
use graphic_types::vector_types::vector::PointId;
|
use graphic_types::vector_types::vector::PointId;
|
||||||
|
|
||||||
fn unit_square_at(corner: DVec2) -> Vector {
|
fn unit_square_at(corner: DVec2) -> Vector {
|
||||||
@@ -3140,7 +3137,7 @@ mod group_walk_tests {
|
|||||||
List::new_from_element(Graphic::Color(Color::from_rgbaf32(0.8, 0.2, 0.33, 1.).unwrap()))
|
List::new_from_element(Graphic::Color(Color::from_rgbaf32(0.8, 0.2, 0.33, 1.).unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appearance the fill node stamps beside the legacy fill marker, so test content mirrors node output.
|
/// The appearance the fill node stamps, so test content mirrors node output.
|
||||||
fn fill_appearance(paint: &List<Graphic<'static>>) -> Appearance {
|
fn fill_appearance(paint: &List<Graphic<'static>>) -> Appearance {
|
||||||
Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(paint.clone()))
|
Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(paint.clone()))
|
||||||
}
|
}
|
||||||
@@ -3158,9 +3155,8 @@ mod group_walk_tests {
|
|||||||
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(3., 1.))];
|
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(3., 1.))];
|
||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let appearance = fill_appearance(&paint);
|
let appearance = fill_appearance(&paint);
|
||||||
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0), FieldWrite::of::<AppearanceMarker>(0)], 2).unwrap();
|
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<AppearanceMarker>(0)], 2).unwrap();
|
||||||
let lane = builder.push(vectors[0].clone()).unwrap();
|
let lane = builder.push(vectors[0].clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
|
||||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||||
builder.push(vectors[1].clone()).unwrap();
|
builder.push(vectors[1].clone()).unwrap();
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
@@ -3179,9 +3175,8 @@ mod group_walk_tests {
|
|||||||
let inner = Graphic::Vector(unit_square_at(DVec2::ZERO));
|
let inner = Graphic::Vector(unit_square_at(DVec2::ZERO));
|
||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let appearance = fill_appearance(&paint);
|
let appearance = fill_appearance(&paint);
|
||||||
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Graphic>(), &[FieldWrite::of::<Fill>(0), FieldWrite::of::<AppearanceMarker>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Graphic>(), &[FieldWrite::of::<AppearanceMarker>(0)], 1).unwrap();
|
||||||
let lane = builder.push(inner.clone()).unwrap();
|
let lane = builder.push(inner.clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
|
||||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let group = Group { row: None, content: item };
|
let group = Group { row: None, content: item };
|
||||||
@@ -3200,9 +3195,8 @@ mod group_walk_tests {
|
|||||||
let vectors = [unit_square_at(DVec2::ZERO)];
|
let vectors = [unit_square_at(DVec2::ZERO)];
|
||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let appearance = fill_appearance(&paint);
|
let appearance = fill_appearance(&paint);
|
||||||
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0), FieldWrite::of::<AppearanceMarker>(0)], 1).unwrap();
|
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<AppearanceMarker>(0)], 1).unwrap();
|
||||||
let lane = builder.push(vectors[0].clone()).unwrap();
|
let lane = builder.push(vectors[0].clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
|
||||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
let group = Group { row: None, content: item };
|
let group = Group { row: None, content: item };
|
||||||
@@ -3227,9 +3221,8 @@ mod group_walk_tests {
|
|||||||
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(2., 2.))];
|
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(2., 2.))];
|
||||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||||
let appearance = fill_appearance(&paint);
|
let appearance = fill_appearance(&paint);
|
||||||
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0), FieldWrite::of::<AppearanceMarker>(0)], 2).unwrap();
|
let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<AppearanceMarker>(0)], 2).unwrap();
|
||||||
let lane = builder.push(vectors[0].clone()).unwrap();
|
let lane = builder.push(vectors[0].clone()).unwrap();
|
||||||
builder.attr::<Fill>(lane, Some(&paint));
|
|
||||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||||
builder.push(vectors[1].clone()).unwrap();
|
builder.push(vectors[1].clone()).unwrap();
|
||||||
let item = builder.finish();
|
let item = builder.finish();
|
||||||
|
|||||||
@@ -181,8 +181,6 @@ fn mirror_lane<'e, T: Clone + Default + Send + Sync + 'static>(
|
|||||||
(
|
(
|
||||||
T,
|
T,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, graphic_types::markers::Fill>,
|
|
||||||
Attr<'e, graphic_types::markers::Stroke>,
|
|
||||||
Attr<'e, graphic_types::markers::Appearance>,
|
Attr<'e, graphic_types::markers::Appearance>,
|
||||||
Attr<'e, core_types::attribute::BlendMode>,
|
Attr<'e, core_types::attribute::BlendMode>,
|
||||||
Attr<'e, core_types::attribute::Opacity>,
|
Attr<'e, core_types::attribute::Opacity>,
|
||||||
@@ -214,20 +212,11 @@ where
|
|||||||
trace: Vec::new(),
|
trace: Vec::new(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let park_paint = |paint: Option<List<Graphic<'static>>>| -> Result<Option<&'e List<Graphic<'static>>>, Interrupt> {
|
|
||||||
match paint {
|
|
||||||
Some(paint) => Ok(Some(arena.alloc_sized_keyed(paint, 0).ok_or_else(exhausted)?.0)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let element = legacy.element(source).cloned().unwrap_or_default();
|
let element = legacy.element(source).cloned().unwrap_or_default();
|
||||||
let mut transform: DAffine2 = legacy.attribute_cloned_or_default(ATTR_TRANSFORM, source);
|
let mut transform: DAffine2 = legacy.attribute_cloned_or_default(ATTR_TRANSFORM, source);
|
||||||
if mirrored {
|
if mirrored {
|
||||||
transform = reflected_transform.expect("a mirrored lane exists only under a reflection") * transform;
|
transform = reflected_transform.expect("a mirrored lane exists only under a reflection") * transform;
|
||||||
}
|
}
|
||||||
let fill = park_paint(legacy.attribute::<Option<List<Graphic>>>(graphic_types::ATTR_FILL, source).cloned().flatten())?;
|
|
||||||
let stroke = park_paint(legacy.attribute::<Option<List<Graphic>>>(graphic_types::ATTR_STROKE, source).cloned().flatten())?;
|
|
||||||
let appearance = match legacy.attribute::<graphic_types::Appearance>(graphic_types::ATTR_APPEARANCE, source).cloned() {
|
let appearance = match legacy.attribute::<graphic_types::Appearance>(graphic_types::ATTR_APPEARANCE, source).cloned() {
|
||||||
Some(appearance) => Some(&*arena.alloc_sized_keyed(appearance, 0).ok_or_else(exhausted)?.0),
|
Some(appearance) => Some(&*arena.alloc_sized_keyed(appearance, 0).ok_or_else(exhausted)?.0),
|
||||||
None => None,
|
None => None,
|
||||||
@@ -238,8 +227,6 @@ where
|
|||||||
Ok((
|
Ok((
|
||||||
element,
|
element,
|
||||||
Attr(transform),
|
Attr(transform),
|
||||||
Attr(fill),
|
|
||||||
Attr(stroke),
|
|
||||||
Attr(appearance),
|
Attr(appearance),
|
||||||
Attr(legacy.attribute_cloned_or_default(core_types::ATTR_BLEND_MODE, source)),
|
Attr(legacy.attribute_cloned_or_default(core_types::ATTR_BLEND_MODE, source)),
|
||||||
Attr(legacy.attribute_cloned_or(core_types::ATTR_OPACITY, source, 1.)),
|
Attr(legacy.attribute_cloned_or(core_types::ATTR_OPACITY, source, 1.)),
|
||||||
@@ -272,8 +259,6 @@ fn mirror<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Graphic<'static>,
|
Graphic<'static>,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, graphic_types::markers::Fill>,
|
|
||||||
Attr<'e, graphic_types::markers::Stroke>,
|
|
||||||
Attr<'e, graphic_types::markers::Appearance>,
|
Attr<'e, graphic_types::markers::Appearance>,
|
||||||
Attr<'e, core_types::attribute::BlendMode>,
|
Attr<'e, core_types::attribute::BlendMode>,
|
||||||
Attr<'e, core_types::attribute::Opacity>,
|
Attr<'e, core_types::attribute::Opacity>,
|
||||||
@@ -323,8 +308,6 @@ fn mirror_vector<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, graphic_types::markers::Fill>,
|
|
||||||
Attr<'e, graphic_types::markers::Stroke>,
|
|
||||||
Attr<'e, graphic_types::markers::Appearance>,
|
Attr<'e, graphic_types::markers::Appearance>,
|
||||||
Attr<'e, core_types::attribute::BlendMode>,
|
Attr<'e, core_types::attribute::BlendMode>,
|
||||||
Attr<'e, core_types::attribute::Opacity>,
|
Attr<'e, core_types::attribute::Opacity>,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use core_types::uuid::NodeId;
|
|||||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::appearance::{Appearance, Coverage};
|
use graphic_types::appearance::{Appearance, Coverage};
|
||||||
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, is_paint_present, set_paint_attribute, set_paint_attribute_at};
|
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms};
|
||||||
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers, Fill, Stroke};
|
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers};
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
use graphic_types::vector_types::GradientStops;
|
use graphic_types::vector_types::GradientStops;
|
||||||
use graphic_types::vector_types::gradient::{GradientSpreadMethod, GradientType};
|
use graphic_types::vector_types::gradient::{GradientSpreadMethod, GradientType};
|
||||||
@@ -13,7 +13,7 @@ use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath};
|
|||||||
use graphic_types::vector_types::vector::PointId;
|
use graphic_types::vector_types::vector::PointId;
|
||||||
use graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
use graphic_types::vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||||
use graphic_types::vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
use graphic_types::vector_types::{ATTR_GRADIENT_TYPE, ATTR_SPREAD_METHOD};
|
||||||
use graphic_types::{ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList, Vector};
|
use graphic_types::{Graphic, IntoGraphicList, Vector};
|
||||||
use linesweeper::topology::Topology;
|
use linesweeper::topology::Topology;
|
||||||
use linesweeper::{BinaryOp, FillRule, binary_op};
|
use linesweeper::{BinaryOp, FillRule, binary_op};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
@@ -34,8 +34,6 @@ fn boolean_core<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, Stroke>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -69,17 +67,8 @@ fn boolean_core<'e>(
|
|||||||
trace: Vec::new(),
|
trace: Vec::new(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let park_paint = |paint: Option<List<Graphic<'static>>>| -> Result<Option<&'e List<Graphic>>, core_types::gpoll::Interrupt> {
|
|
||||||
match paint {
|
|
||||||
Some(list) => Ok(Some(arena.alloc_sized_keyed(list, 0).ok_or_else(exhausted)?.0)),
|
|
||||||
None => Ok(None),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let element = result_vector_list.element(0).cloned().unwrap_or_default();
|
let element = result_vector_list.element(0).cloned().unwrap_or_default();
|
||||||
use core_types::lane::LaneSource;
|
use core_types::lane::LaneSource;
|
||||||
let fill = park_paint(result_vector_list.attr::<Fill>(0).filter(|paint| is_paint_present(paint)).cloned())?;
|
|
||||||
let stroke = park_paint(result_vector_list.attr::<Stroke>(0).filter(|paint| is_paint_present(paint)).cloned())?;
|
|
||||||
let appearance = match result_vector_list.attr::<AppearanceMarker>(0).cloned() {
|
let appearance = match result_vector_list.attr::<AppearanceMarker>(0).cloned() {
|
||||||
Some(appearance) => Some(&*arena.alloc_sized_keyed(appearance, 0).ok_or_else(exhausted)?.0),
|
Some(appearance) => Some(&*arena.alloc_sized_keyed(appearance, 0).ok_or_else(exhausted)?.0),
|
||||||
None => None,
|
None => None,
|
||||||
@@ -93,8 +82,6 @@ fn boolean_core<'e>(
|
|||||||
Ok((
|
Ok((
|
||||||
element,
|
element,
|
||||||
Attr(result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0)),
|
Attr(result_vector_list.attribute_cloned_or_default(ATTR_TRANSFORM, 0)),
|
||||||
Attr(fill),
|
|
||||||
Attr(stroke),
|
|
||||||
Attr(appearance),
|
Attr(appearance),
|
||||||
Attr(result_vector_list.attribute_cloned_or_default(ATTR_BLEND_MODE, 0)),
|
Attr(result_vector_list.attribute_cloned_or_default(ATTR_BLEND_MODE, 0)),
|
||||||
Attr(result_vector_list.attribute_cloned_or(ATTR_OPACITY, 0, 1.)),
|
Attr(result_vector_list.attribute_cloned_or(ATTR_OPACITY, 0, 1.)),
|
||||||
@@ -122,8 +109,6 @@ fn boolean_operation<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, Stroke>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -150,8 +135,6 @@ fn boolean_operation_vector<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, Stroke>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -309,14 +292,13 @@ fn raster_stand_in_rows<S: core_types::lane::LaneSource>(image: &S, parent_trans
|
|||||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
||||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
|
||||||
item.set_attribute(graphic_types::ATTR_APPEARANCE, fill_appearance(List::new_from_element(Graphic::Color(Color::BLACK))));
|
item.set_attribute(graphic_types::ATTR_APPEARANCE, fill_appearance(List::new_from_element(Graphic::Color(Color::BLACK))));
|
||||||
item
|
item
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The single-fill appearance a built row paints with, beside its legacy fill marker.
|
/// The single-fill appearance a built row paints with.
|
||||||
fn fill_appearance(paint: List<Graphic<'static>>) -> Appearance {
|
fn fill_appearance(paint: List<Graphic<'static>>) -> Appearance {
|
||||||
Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(paint))
|
Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(paint))
|
||||||
}
|
}
|
||||||
@@ -324,7 +306,6 @@ fn fill_appearance(paint: List<Graphic<'static>>) -> Appearance {
|
|||||||
/// A color row: an empty vector carrying the color as its fill paint over the
|
/// A color row: an empty vector carrying the color as its fill paint over the
|
||||||
/// lane's attributes.
|
/// lane's attributes.
|
||||||
fn color_paint_row(color: Color, mut attributes: core_types::list::ItemAttributeValues) -> Item<Vector> {
|
fn color_paint_row(color: Color, mut attributes: core_types::list::ItemAttributeValues) -> Item<Vector> {
|
||||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
|
||||||
attributes.insert(graphic_types::ATTR_APPEARANCE, fill_appearance(List::new_from_element(Graphic::Color(color))));
|
attributes.insert(graphic_types::ATTR_APPEARANCE, fill_appearance(List::new_from_element(Graphic::Color(color))));
|
||||||
|
|
||||||
let mut element = Vector::default();
|
let mut element = Vector::default();
|
||||||
@@ -346,7 +327,6 @@ fn gradient_paint_row(stops: GradientStops, mut attributes: core_types::list::It
|
|||||||
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
||||||
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
|
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
|
||||||
}
|
}
|
||||||
attributes.insert(ATTR_FILL, Some(gradient_paint.clone()));
|
|
||||||
attributes.insert(graphic_types::ATTR_APPEARANCE, fill_appearance(gradient_paint));
|
attributes.insert(graphic_types::ATTR_APPEARANCE, fill_appearance(gradient_paint));
|
||||||
|
|
||||||
let mut element = Vector::default();
|
let mut element = Vector::default();
|
||||||
@@ -378,13 +358,6 @@ fn push_rows(out: &mut List<Vector>, rows: Vec<Item<Vector>>) {
|
|||||||
fn push_leaf_vector_row(out: &mut List<Vector>, level: GraphicLevel<'_>, index: usize, vector: &Vector, ancestors: DAffine2, reach: PaintReach<'_>) {
|
fn push_leaf_vector_row(out: &mut List<Vector>, level: GraphicLevel<'_>, index: usize, vector: &Vector, ancestors: DAffine2, reach: PaintReach<'_>) {
|
||||||
let out_index = out.len();
|
let out_index = out.len();
|
||||||
out.push(Item::from_parts(vector.clone(), graphic_types::graphic::lane_attributes(level, index)));
|
out.push(Item::from_parts(vector.clone(), graphic_types::graphic::lane_attributes(level, index)));
|
||||||
if reach.applies() {
|
|
||||||
for (key, slot) in [(ATTR_FILL, reach.paint.fill), (ATTR_STROKE, reach.paint.stroke)] {
|
|
||||||
if let Some(paint) = slot {
|
|
||||||
set_paint_attribute_at(out, out_index, key, paint.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stamp_inherited_appearance(out, out_index, reach.appearance);
|
stamp_inherited_appearance(out, out_index, reach.appearance);
|
||||||
let current: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, out_index);
|
let current: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, out_index);
|
||||||
out.set_attribute(ATTR_TRANSFORM, out_index, ancestors * current);
|
out.set_attribute(ATTR_TRANSFORM, out_index, ancestors * current);
|
||||||
@@ -395,13 +368,6 @@ fn push_vector_rows(out: &mut List<Vector>, rows: &List<Vector>, composed: DAffi
|
|||||||
let Some(item) = rows.clone_item(row) else { continue };
|
let Some(item) = rows.clone_item(row) else { continue };
|
||||||
let index = out.len();
|
let index = out.len();
|
||||||
out.push(item);
|
out.push(item);
|
||||||
if reach.applies() {
|
|
||||||
for (key, slot) in [(ATTR_FILL, reach.paint.fill), (ATTR_STROKE, reach.paint.stroke)] {
|
|
||||||
if let Some(paint) = slot {
|
|
||||||
set_paint_attribute_at(out, index, key, paint.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stamp_inherited_appearance(out, index, reach.appearance);
|
stamp_inherited_appearance(out, index, reach.appearance);
|
||||||
let current: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
let current: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
out.set_attribute(ATTR_TRANSFORM, index, composed * current);
|
out.set_attribute(ATTR_TRANSFORM, index, composed * current);
|
||||||
@@ -441,7 +407,7 @@ fn flatten_vector_run_into<'a>(out: &mut List<Vector>, level: GraphicLevel<'a>,
|
|||||||
let composed = transform * level.attr::<TransformAttr>(index);
|
let composed = transform * level.attr::<TransformAttr>(index);
|
||||||
match element {
|
match element {
|
||||||
Graphic::Vector(vector) => push_leaf_vector_row(out, level, index, vector, transform, reach),
|
Graphic::Vector(vector) => push_leaf_vector_row(out, level, index, vector, transform, reach),
|
||||||
Graphic::Graphic(children) => push_union(out, flatten_vector_run(GraphicLevel::Legacy(children), composed, reach.nested())),
|
Graphic::Graphic(children) => push_union(out, flatten_vector_run(GraphicLevel::Legacy(children), composed, reach)),
|
||||||
Graphic::Group(group) => flatten_group(out, group, composed, reach),
|
Graphic::Group(group) => flatten_group(out, group, composed, reach),
|
||||||
Graphic::RasterCPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
Graphic::RasterCPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
||||||
Graphic::RasterGPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
Graphic::RasterGPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
||||||
@@ -463,7 +429,7 @@ fn flatten_group(out: &mut List<Vector>, group: &core_types::record::Group, comp
|
|||||||
if let Some(rows) = graphic_types::graphic::run_to_list::<Vector>(item) {
|
if let Some(rows) = graphic_types::graphic::run_to_list::<Vector>(item) {
|
||||||
push_vector_rows(out, &rows, composed, reach);
|
push_vector_rows(out, &rows, composed, reach);
|
||||||
} else if core_types::record::RunView::<Graphic>::new(item).is_some() {
|
} else if core_types::record::RunView::<Graphic>::new(item).is_some() {
|
||||||
push_union(out, flatten_vector_run(GraphicLevel::Run(item), composed, reach.into_group_graphics()));
|
push_union(out, flatten_vector_run(GraphicLevel::Run(item), composed, reach));
|
||||||
} else if let Some(image) = graphic_types::graphic::run_to_list::<Raster<CPU>>(item) {
|
} else if let Some(image) = graphic_types::graphic::run_to_list::<Raster<CPU>>(item) {
|
||||||
push_rows(out, raster_stand_in_rows(&image, composed));
|
push_rows(out, raster_stand_in_rows(&image, composed));
|
||||||
} else if let Some(image) = graphic_types::graphic::run_to_list::<Raster<GPU>>(item) {
|
} else if let Some(image) = graphic_types::graphic::run_to_list::<Raster<GPU>>(item) {
|
||||||
@@ -598,22 +564,28 @@ mod tests {
|
|||||||
top.push(Item::new_from_element(Graphic::Color(Color::BLACK)));
|
top.push(Item::new_from_element(Graphic::Color(Color::BLACK)));
|
||||||
top.push(Item::new_from_element(Graphic::Group(Group { row: None, content: inner_item })));
|
top.push(Item::new_from_element(Graphic::Group(Group { row: None, content: inner_item })));
|
||||||
top.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(5., 5.)));
|
top.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(5., 5.)));
|
||||||
set_paint_attribute_at(&mut top, 0, ATTR_FILL, black_paint());
|
top.set_attribute(graphic_types::ATTR_APPEARANCE, 0, fill_appearance(black_paint()));
|
||||||
top.set_attribute(ATTR_OPACITY, 1, 0.5);
|
top.set_attribute(ATTR_OPACITY, 1, 0.5);
|
||||||
top.set_attribute(ATTR_TRANSFORM, 2, DAffine2::from_scale(DVec2::splat(3.)));
|
top.set_attribute(ATTR_TRANSFORM, 2, DAffine2::from_scale(DVec2::splat(3.)));
|
||||||
|
|
||||||
let rows = flatten_vector_run(GraphicLevel::Legacy(&top), DAffine2::IDENTITY, PaintReach::NONE);
|
let rows = flatten_vector_run(GraphicLevel::Legacy(&top), DAffine2::IDENTITY, PaintReach::NONE);
|
||||||
assert_eq!(rows.len(), 3);
|
assert_eq!(rows.len(), 3);
|
||||||
|
|
||||||
|
let fill_of = |index: usize| {
|
||||||
|
rows.attribute::<Appearance>(graphic_types::ATTR_APPEARANCE, index)
|
||||||
|
.and_then(|appearance| appearance.first_paint_of(graphic_types::appearance::Cover::Fill))
|
||||||
|
.and_then(graphic_types::graphic::paint_cell_rows)
|
||||||
|
};
|
||||||
|
|
||||||
// Lane 0: the leaf row keeps its lane attributes, with the lane fill
|
// Lane 0: the leaf row keeps its lane attributes, with the lane fill
|
||||||
// present and the ancestor composition the identity.
|
// present and the ancestor composition the identity.
|
||||||
assert_eq!(rows.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0), DAffine2::from_translation(DVec2::new(5., 5.)));
|
assert_eq!(rows.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0), DAffine2::from_translation(DVec2::new(5., 5.)));
|
||||||
assert!(graphic_types::graphic::paint_graphics::<Fill, _>(&rows, 0).is_some());
|
assert!(fill_of(0).is_some());
|
||||||
|
|
||||||
// Lane 1: the color stand-in carries the lane opacity and the color as
|
// Lane 1: the color stand-in carries the lane opacity and the color as
|
||||||
// its fill.
|
// its fill.
|
||||||
assert_eq!(rows.attribute_cloned_or::<f64>(ATTR_OPACITY, 1, 1.), 0.5);
|
assert_eq!(rows.attribute_cloned_or::<f64>(ATTR_OPACITY, 1, 1.), 0.5);
|
||||||
let fill = graphic_types::graphic::paint_graphics::<Fill, _>(&rows, 1).expect("the color row carries its fill");
|
let fill = fill_of(1).expect("the color row carries its fill");
|
||||||
assert!(matches!(fill.element(0), Some(Graphic::Color(color)) if *color == Color::BLACK));
|
assert!(matches!(fill.element(0), Some(Graphic::Color(color)) if *color == Color::BLACK));
|
||||||
|
|
||||||
// Lane 2: the group's vector run serves its row under the lane
|
// Lane 2: the group's vector run serves its row under the lane
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ use core_types::uuid::NodeId;
|
|||||||
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
|
use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, CacheHash, Color, Ctx, DeriveCtx, ExtractIndex, InjectIndex};
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphic_types::appearance::{Appearance, Cover, CoverPlacement, Coverage};
|
use graphic_types::appearance::{Appearance, Cover, CoverPlacement, Coverage};
|
||||||
use graphic_types::graphic::{bake_paint_transforms, has_paint, is_paint_present, set_paint_attribute_at};
|
use graphic_types::graphic::{bake_paint_transforms, is_paint_present};
|
||||||
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers, Fill, Stroke as StrokeAttr};
|
use graphic_types::markers::{Appearance as AppearanceMarker, EditorMergedLayers};
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_FILL, ATTR_STROKE, Graphic, IntoGraphicList};
|
use graphic_types::{ATTR_EDITOR_MERGED_LAYERS, Graphic, IntoGraphicList};
|
||||||
use graphic_types::{Artboard, Vector};
|
use graphic_types::{Artboard, Vector};
|
||||||
use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
|
use kurbo::simplify::{SimplifyOptions, simplify_bezpath};
|
||||||
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape};
|
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveArclen, PathEl, PathSeg, Shape};
|
||||||
@@ -101,33 +101,19 @@ fn assign_colors<'e>(
|
|||||||
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
|
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
|
||||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
|
||||||
repeat_every: u32,
|
repeat_every: u32,
|
||||||
) -> Result<
|
) -> Result<IList<(Vector, Attr<'e, TransformAttr>, Attr<'e, AppearanceMarker>, Attr<'e, EditorLayerPath>)>, Interrupt> {
|
||||||
IList<(
|
|
||||||
Vector,
|
|
||||||
Attr<'e, TransformAttr>,
|
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
|
||||||
Attr<'e, EditorLayerPath>,
|
|
||||||
)>,
|
|
||||||
Interrupt,
|
|
||||||
> {
|
|
||||||
let lane = ctx.index() as usize;
|
let lane = ctx.index() as usize;
|
||||||
if lane >= content.len() {
|
if lane >= content.len() {
|
||||||
return Err(GraphError::past_end().into());
|
return Err(GraphError::past_end().into());
|
||||||
}
|
}
|
||||||
let element = content.element_ref(lane).clone();
|
let element = content.element_ref(lane).clone();
|
||||||
let park_existing = |paint: Option<&List<Graphic<'static>>>| -> Result<Option<&'e List<Graphic>>, Interrupt> { paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose() };
|
|
||||||
let existing_fill = park_existing(content.lane(lane).attr::<Fill>())?;
|
|
||||||
let existing_stroke = park_existing(content.lane(lane).attr::<StrokeAttr>())?;
|
|
||||||
let existing_appearance = content.lane(lane).attr::<AppearanceMarker>().cloned();
|
let existing_appearance = content.lane(lane).attr::<AppearanceMarker>().cloned();
|
||||||
let park_appearance_attr = |appearance: Option<Appearance>| -> Result<Option<&'e Appearance>, Interrupt> { appearance.map(|appearance| park_appearance(ctx.arena(), appearance)).transpose() };
|
let park_appearance_attr = |appearance: Option<Appearance>| -> Result<Option<&'e Appearance>, Interrupt> { appearance.map(|appearance| park_appearance(ctx.arena(), appearance)).transpose() };
|
||||||
let carried = carried_lane_attrs(ctx.arena(), *content.lane(lane))?;
|
let (transform, layer_path) = carried_lane_attrs(ctx.arena(), *content.lane(lane))?;
|
||||||
let (transform, layer_path) = carried;
|
|
||||||
|
|
||||||
if gradient.is_empty() {
|
if gradient.is_empty() {
|
||||||
let parked_appearance = park_appearance_attr(existing_appearance)?;
|
let parked_appearance = park_appearance_attr(existing_appearance)?;
|
||||||
return Ok((element, transform, Attr(existing_fill), Attr(existing_stroke), Attr(parked_appearance), layer_path));
|
return Ok((element, transform, Attr(parked_appearance), layer_path));
|
||||||
}
|
}
|
||||||
let gradient_element = gradient.element_ref(0);
|
let gradient_element = gradient.element_ref(0);
|
||||||
let reversed;
|
let reversed;
|
||||||
@@ -141,20 +127,10 @@ fn assign_colors<'e>(
|
|||||||
|
|
||||||
let color = assign_color_at(gradient_element, lane, content.len(), randomize, seed, repeat_every);
|
let color = assign_color_at(gradient_element, lane, content.len(), randomize, seed, repeat_every);
|
||||||
let paint = List::new_from_element(color).into_graphic_list();
|
let paint = List::new_from_element(color).into_graphic_list();
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
|
||||||
|
|
||||||
let fill_attr = match fill {
|
// The recolor lands on the appearance's coverage paints
|
||||||
true => Some(parked),
|
|
||||||
false => existing_fill,
|
|
||||||
};
|
|
||||||
let stroke_attr = match stroke && element.stroke.is_some() {
|
|
||||||
true => Some(parked),
|
|
||||||
false => existing_stroke,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The same recolor lands on the appearance's coverage paints
|
|
||||||
let mut appearance = existing_appearance.unwrap_or_default();
|
let mut appearance = existing_appearance.unwrap_or_default();
|
||||||
let paint_cell = Graphic::Graphic(parked.clone());
|
let paint_cell = Graphic::Graphic(paint);
|
||||||
if fill && !appearance.set_paint_of(Cover::Fill, paint_cell.clone()) {
|
if fill && !appearance.set_paint_of(Cover::Fill, paint_cell.clone()) {
|
||||||
appearance.replace_or_insert(Coverage::new_fill(), paint_cell.clone(), CoverPlacement::Above);
|
appearance.replace_or_insert(Coverage::new_fill(), paint_cell.clone(), CoverPlacement::Above);
|
||||||
}
|
}
|
||||||
@@ -163,7 +139,7 @@ fn assign_colors<'e>(
|
|||||||
}
|
}
|
||||||
let parked_appearance = park_appearance_attr(Some(appearance))?;
|
let parked_appearance = park_appearance_attr(Some(appearance))?;
|
||||||
|
|
||||||
Ok((element, transform, Attr(fill_attr), Attr(stroke_attr), Attr(parked_appearance), layer_path))
|
Ok((element, transform, Attr(parked_appearance), layer_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -257,16 +233,10 @@ fn assign_colors_graphic<'e>(
|
|||||||
let row_stroke = rows.element(row).and_then(|vector| vector.stroke.clone());
|
let row_stroke = rows.element(row).and_then(|vector| vector.stroke.clone());
|
||||||
let color = assign_color_at(gradient_element, position + row, length, randomize, seed, repeat_every);
|
let color = assign_color_at(gradient_element, position + row, length, randomize, seed, repeat_every);
|
||||||
let paint = List::new_from_element(color).into_graphic_list();
|
let paint = List::new_from_element(color).into_graphic_list();
|
||||||
if fill {
|
|
||||||
set_paint_attribute_at(&mut rows, row, ATTR_FILL, paint.clone());
|
|
||||||
}
|
|
||||||
if stroke && row_stroke.is_some() {
|
|
||||||
set_paint_attribute_at(&mut rows, row, ATTR_STROKE, paint.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// The same recolor lands on the row's appearance coverage paints
|
// The recolor lands on the row's appearance coverage paints
|
||||||
let mut appearance = rows.attribute_cloned_or_default::<Appearance>(graphic_types::ATTR_APPEARANCE, row);
|
let mut appearance = rows.attribute_cloned_or_default::<Appearance>(graphic_types::ATTR_APPEARANCE, row);
|
||||||
let paint_cell = Graphic::Graphic(paint.clone());
|
let paint_cell = Graphic::Graphic(paint);
|
||||||
if fill && !appearance.set_paint_of(Cover::Fill, paint_cell.clone()) {
|
if fill && !appearance.set_paint_of(Cover::Fill, paint_cell.clone()) {
|
||||||
appearance.replace_or_insert(Coverage::new_fill(), paint_cell.clone(), CoverPlacement::Above);
|
appearance.replace_or_insert(Coverage::new_fill(), paint_cell.clone(), CoverPlacement::Above);
|
||||||
}
|
}
|
||||||
@@ -313,17 +283,8 @@ fn assign_colors_graphic_extent(
|
|||||||
|
|
||||||
pub use _assign_colors_graphic_mod::assign_colors_graphic_entries;
|
pub use _assign_colors_graphic_mod::assign_colors_graphic_entries;
|
||||||
|
|
||||||
/// Keyed, so a group-free paint's promote moves this header rather than
|
/// Keyed, so a group-free appearance's promote moves this header rather than
|
||||||
/// cloning the content it owns.
|
/// cloning the content it owns.
|
||||||
fn park_paint<'e>(arena: &'e core_types::arena::Arena, paint: List<Graphic<'static>>) -> Result<&'e List<Graphic<'static>>, Interrupt> {
|
|
||||||
let (parked, _) = arena.alloc_sized_keyed(paint, 0).ok_or(GraphError {
|
|
||||||
kind: core_types::gpoll::ErrorKind::ArenaExhausted,
|
|
||||||
trace: Vec::new(),
|
|
||||||
})?;
|
|
||||||
Ok(parked)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Keyed, as [`park_paint`] is, so a group-free appearance's promote moves this header.
|
|
||||||
fn park_appearance<'e>(arena: &'e core_types::arena::Arena, appearance: Appearance) -> Result<&'e Appearance, Interrupt> {
|
fn park_appearance<'e>(arena: &'e core_types::arena::Arena, appearance: Appearance) -> Result<&'e Appearance, Interrupt> {
|
||||||
let (parked, _) = arena.alloc_sized_keyed(appearance, 0).ok_or(GraphError {
|
let (parked, _) = arena.alloc_sized_keyed(appearance, 0).ok_or(GraphError {
|
||||||
kind: core_types::gpoll::ErrorKind::ArenaExhausted,
|
kind: core_types::gpoll::ErrorKind::ArenaExhausted,
|
||||||
@@ -397,13 +358,12 @@ fn fill<'e>(
|
|||||||
_spread_method: GradientSpreadMethod,
|
_spread_method: GradientSpreadMethod,
|
||||||
_has_transform: HasTransform,
|
_has_transform: HasTransform,
|
||||||
_transform: DAffine2,
|
_transform: DAffine2,
|
||||||
) -> Result<(Vector, Attr<'e, Fill>, Attr<'e, AppearanceMarker>), Interrupt> {
|
) -> Result<(Vector, Attr<'e, AppearanceMarker>), Interrupt> {
|
||||||
let mut paint = paint_table(fill);
|
let mut paint = paint_table(fill);
|
||||||
default_gradient_paint(&mut paint, element.bounding_box(), _gradient_type, _spread_method, _has_transform.0.then_some(_transform));
|
default_gradient_paint(&mut paint, element.bounding_box(), _gradient_type, _spread_method, _has_transform.0.then_some(_transform));
|
||||||
let appearance = stamped_appearance(*content_appearance, Coverage::new_fill(), &paint, CoverPlacement::Above);
|
let appearance = stamped_appearance(*content_appearance, Coverage::new_fill(), &paint, CoverPlacement::Above);
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
|
||||||
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
||||||
Ok((element, Attr(Some(parked)), Attr(Some(parked_appearance))))
|
Ok((element, Attr(Some(parked_appearance))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The fill over graphic lanes: the marker parks on the lane and the render
|
/// The fill over graphic lanes: the marker parks on the lane and the render
|
||||||
@@ -420,7 +380,7 @@ fn fill_graphic_leveled<'e>(
|
|||||||
_spread_method: GradientSpreadMethod,
|
_spread_method: GradientSpreadMethod,
|
||||||
_has_transform: HasTransform,
|
_has_transform: HasTransform,
|
||||||
_transform: DAffine2,
|
_transform: DAffine2,
|
||||||
) -> Result<(Graphic<'static>, Attr<'e, Fill>, Attr<'e, AppearanceMarker>), Interrupt> {
|
) -> Result<(Graphic<'static>, Attr<'e, AppearanceMarker>), Interrupt> {
|
||||||
let bounds = match BoundingBox::bounding_box(&element, DAffine2::IDENTITY, false) {
|
let bounds = match BoundingBox::bounding_box(&element, DAffine2::IDENTITY, false) {
|
||||||
RenderBoundingBox::Rectangle(bounds) => Some(bounds),
|
RenderBoundingBox::Rectangle(bounds) => Some(bounds),
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -428,9 +388,8 @@ fn fill_graphic_leveled<'e>(
|
|||||||
let mut paint = paint_table(fill);
|
let mut paint = paint_table(fill);
|
||||||
default_gradient_paint(&mut paint, bounds, _gradient_type, _spread_method, _has_transform.0.then_some(_transform));
|
default_gradient_paint(&mut paint, bounds, _gradient_type, _spread_method, _has_transform.0.then_some(_transform));
|
||||||
let appearance = stamped_appearance(*content_appearance, Coverage::new_fill(), &paint, CoverPlacement::Above);
|
let appearance = stamped_appearance(*content_appearance, Coverage::new_fill(), &paint, CoverPlacement::Above);
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
|
||||||
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
||||||
Ok((element, Attr(Some(parked)), Attr(Some(parked_appearance))))
|
Ok((element, Attr(Some(parked_appearance))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
||||||
@@ -460,7 +419,7 @@ fn stroke<'e>(
|
|||||||
/// The phase offset distance from the starting point of the dash pattern.
|
/// The phase offset distance from the starting point of the dash pattern.
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
dash_offset: f64,
|
dash_offset: f64,
|
||||||
) -> Result<(Vector, Attr<TransformAttr>, Attr<'e, StrokeAttr>, Attr<'e, AppearanceMarker>), Interrupt> {
|
) -> Result<(Vector, Attr<TransformAttr>, Attr<'e, AppearanceMarker>), Interrupt> {
|
||||||
let dash_lengths: Vec<f64> = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
let dash_lengths: Vec<f64> = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
||||||
let mut stroke = Stroke {
|
let mut stroke = Stroke {
|
||||||
weight,
|
weight,
|
||||||
@@ -487,9 +446,8 @@ fn stroke<'e>(
|
|||||||
// The paint order is the coverage row order: appending above follows the painter's algorithm, and a
|
// The paint order is the coverage row order: appending above follows the painter's algorithm, and a
|
||||||
// below stroke is expressed by the chain running the stroke node before the fill
|
// below stroke is expressed by the chain running the stroke node before the fill
|
||||||
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, CoverPlacement::Above);
|
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, CoverPlacement::Above);
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
|
||||||
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
||||||
Ok((element, Attr(*content_transform), Attr(Some(parked)), Attr(Some(parked_appearance))))
|
Ok((element, Attr(*content_transform), Attr(Some(parked_appearance))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The vector items of a graphic lane's interior, one wrap level deep, the
|
/// The vector items of a graphic lane's interior, one wrap level deep, the
|
||||||
@@ -526,7 +484,7 @@ fn stroke_graphic_leveled<'e>(
|
|||||||
#[default(4.)] miter_limit: f64,
|
#[default(4.)] miter_limit: f64,
|
||||||
dash_lengths: IList<f64>,
|
dash_lengths: IList<f64>,
|
||||||
#[unit(" px")] dash_offset: f64,
|
#[unit(" px")] dash_offset: f64,
|
||||||
) -> Result<(Graphic<'static>, Attr<TransformAttr>, Attr<'e, StrokeAttr>, Attr<'e, AppearanceMarker>), Interrupt> {
|
) -> Result<(Graphic<'static>, Attr<TransformAttr>, Attr<'e, AppearanceMarker>), Interrupt> {
|
||||||
let dash_lengths: Vec<f64> = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
let dash_lengths: Vec<f64> = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect();
|
||||||
let stroke = Stroke {
|
let stroke = Stroke {
|
||||||
weight,
|
weight,
|
||||||
@@ -554,9 +512,8 @@ fn stroke_graphic_leveled<'e>(
|
|||||||
// The paint order is the coverage row order: appending above follows the painter's algorithm, and a
|
// The paint order is the coverage row order: appending above follows the painter's algorithm, and a
|
||||||
// below stroke is expressed by the chain running the stroke node before the fill
|
// below stroke is expressed by the chain running the stroke node before the fill
|
||||||
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, CoverPlacement::Above);
|
let appearance = stamped_appearance(*content_appearance, Coverage::new_stroke(&coverage_stroke), &paint, CoverPlacement::Above);
|
||||||
let parked = park_paint(ctx.arena(), paint)?;
|
|
||||||
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
let parked_appearance = park_appearance(ctx.arena(), appearance)?;
|
||||||
Ok((element, Attr(*content_transform), Attr(Some(parked)), Attr(Some(parked_appearance))))
|
Ok((element, Attr(*content_transform), Attr(Some(parked_appearance))))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use _fill_graphic_leveled_mod::fill_graphic_leveled_entries;
|
pub use _fill_graphic_leveled_mod::fill_graphic_leveled_entries;
|
||||||
@@ -1438,8 +1395,14 @@ fn offset_path(_: impl Ctx, (vector, lane_transform): (Vector, Attr<TransformAtt
|
|||||||
fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
||||||
// TODO: Make this node support stroke align, which it currently ignores
|
// TODO: Make this node support stroke align, which it currently ignores
|
||||||
|
|
||||||
// A fill exists when the canonical attribute carries paint
|
// A fill exists when the row's appearance carries a painted fill coverage
|
||||||
let has_fills: Vec<bool> = (0..flattened.len()).map(|index| has_paint::<Fill, _>(&flattened, index)).collect();
|
let has_fills: Vec<bool> = (0..flattened.len())
|
||||||
|
.map(|index| {
|
||||||
|
flattened
|
||||||
|
.attribute::<Appearance>(graphic_types::ATTR_APPEARANCE, index)
|
||||||
|
.is_some_and(|appearance| appearance.has_painted_cover(Cover::Fill))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let output: List<Vector> = flattened
|
let output: List<Vector> = flattened
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -1503,7 +1466,6 @@ fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
|||||||
vector.stroke = None;
|
vector.stroke = None;
|
||||||
let mut fill_attributes = attributes.clone();
|
let mut fill_attributes = attributes.clone();
|
||||||
// No stroke remains on the fill row
|
// No stroke remains on the fill row
|
||||||
fill_attributes.remove::<Option<List<Graphic>>>(ATTR_STROKE);
|
|
||||||
if let Some(appearance) = fill_attributes.get_mut::<Appearance>(graphic_types::ATTR_APPEARANCE) {
|
if let Some(appearance) = fill_attributes.get_mut::<Appearance>(graphic_types::ATTR_APPEARANCE) {
|
||||||
appearance.retain_cover(Cover::Fill);
|
appearance.retain_cover(Cover::Fill);
|
||||||
}
|
}
|
||||||
@@ -1512,8 +1474,6 @@ fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
|||||||
|
|
||||||
let mut stroke_attributes = attributes;
|
let mut stroke_attributes = attributes;
|
||||||
// Drop the original fill and use the stroke paint to fill the outlined stroke
|
// Drop the original fill and use the stroke paint to fill the outlined stroke
|
||||||
stroke_attributes.remove::<Option<List<Graphic>>>(ATTR_FILL);
|
|
||||||
stroke_attributes.rename(ATTR_STROKE, ATTR_FILL);
|
|
||||||
if let Some(appearance) = stroke_attributes.get_mut::<Appearance>(graphic_types::ATTR_APPEARANCE) {
|
if let Some(appearance) = stroke_attributes.get_mut::<Appearance>(graphic_types::ATTR_APPEARANCE) {
|
||||||
// The outlined stroke is filled with the stroke coverage's paint
|
// The outlined stroke is filled with the stroke coverage's paint
|
||||||
let stroke_paint = appearance.first_paint_of(Cover::Stroke).cloned();
|
let stroke_paint = appearance.first_paint_of(Cover::Stroke).cloned();
|
||||||
@@ -1549,8 +1509,6 @@ fn solidify_native_lane<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -1610,8 +1568,6 @@ fn emit_legacy_lane<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -1633,16 +1589,6 @@ fn emit_legacy_lane<'e>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let element = output.element(lane).cloned().unwrap_or_default();
|
let element = output.element(lane).cloned().unwrap_or_default();
|
||||||
let fill = output
|
|
||||||
.attribute::<Option<List<Graphic>>>(ATTR_FILL, lane)
|
|
||||||
.and_then(|paint| paint.as_ref())
|
|
||||||
.map(|paint| park_paint(arena, paint.clone()))
|
|
||||||
.transpose()?;
|
|
||||||
let stroke = output
|
|
||||||
.attribute::<Option<List<Graphic>>>(ATTR_STROKE, lane)
|
|
||||||
.and_then(|paint| paint.as_ref())
|
|
||||||
.map(|paint| park_paint(arena, paint.clone()))
|
|
||||||
.transpose()?;
|
|
||||||
let appearance = output
|
let appearance = output
|
||||||
.attribute::<Appearance>(graphic_types::ATTR_APPEARANCE, lane)
|
.attribute::<Appearance>(graphic_types::ATTR_APPEARANCE, lane)
|
||||||
.cloned()
|
.cloned()
|
||||||
@@ -1659,8 +1605,6 @@ fn emit_legacy_lane<'e>(
|
|||||||
Ok((
|
Ok((
|
||||||
element,
|
element,
|
||||||
Attr(output.attribute_cloned_or_default(ATTR_TRANSFORM, lane)),
|
Attr(output.attribute_cloned_or_default(ATTR_TRANSFORM, lane)),
|
||||||
Attr(fill),
|
|
||||||
Attr(stroke),
|
|
||||||
Attr(appearance),
|
Attr(appearance),
|
||||||
Attr(output.attribute_cloned_or_default(ATTR_BLEND_MODE, lane)),
|
Attr(output.attribute_cloned_or_default(ATTR_BLEND_MODE, lane)),
|
||||||
Attr(output.attribute_cloned_or(ATTR_OPACITY, lane, 1.)),
|
Attr(output.attribute_cloned_or(ATTR_OPACITY, lane, 1.)),
|
||||||
@@ -1707,8 +1651,6 @@ fn solidify_stroke<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -1748,8 +1690,6 @@ fn solidify_stroke_vector<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -1821,8 +1761,6 @@ fn separate_subpaths<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -1878,8 +1816,6 @@ fn map_points<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -1916,18 +1852,7 @@ fn flatten_path_core<'e>(
|
|||||||
arena: &'e core_types::arena::Arena,
|
arena: &'e core_types::arena::Arena,
|
||||||
flattened: List<Vector>,
|
flattened: List<Vector>,
|
||||||
snapshot: List<Graphic<'static>>,
|
snapshot: List<Graphic<'static>>,
|
||||||
) -> Result<
|
) -> Result<(Vector, Attr<'e, TransformAttr>, Attr<'e, AppearanceMarker>, Attr<'e, EditorLayerPath>, Attr<'e, EditorMergedLayers>), Interrupt> {
|
||||||
(
|
|
||||||
Vector,
|
|
||||||
Attr<'e, TransformAttr>,
|
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
|
||||||
Attr<'e, EditorLayerPath>,
|
|
||||||
Attr<'e, EditorMergedLayers>,
|
|
||||||
),
|
|
||||||
Interrupt,
|
|
||||||
> {
|
|
||||||
let mut output = Vector::default();
|
let mut output = Vector::default();
|
||||||
let mut primary_source = None;
|
let mut primary_source = None;
|
||||||
|
|
||||||
@@ -1951,28 +1876,20 @@ fn flatten_path_core<'e>(
|
|||||||
primary_source = Some((index, source_transform));
|
primary_source = Some((index, source_transform));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fill = None;
|
let mut fill_cell = None;
|
||||||
let mut stroke = None;
|
let mut stroke_cell = None;
|
||||||
let mut layer_path = Vec::new();
|
let mut layer_path = Vec::new();
|
||||||
if let Some((primary, source_transform)) = primary_source {
|
if let Some((primary, source_transform)) = primary_source {
|
||||||
let source_attributes = flattened.clone_item_attributes(primary);
|
let source_attributes = flattened.clone_item_attributes(primary);
|
||||||
let mut attributes = ItemAttributeValues::new();
|
let mut attributes = ItemAttributeValues::new();
|
||||||
|
|
||||||
attributes.insert_cloned_from(&source_attributes, ATTR_FILL);
|
attributes.insert_cloned_from(&source_attributes, graphic_types::ATTR_APPEARANCE);
|
||||||
attributes.insert_cloned_from(&source_attributes, ATTR_STROKE);
|
|
||||||
bake_paint_transforms(&mut attributes, source_transform);
|
bake_paint_transforms(&mut attributes, source_transform);
|
||||||
|
|
||||||
let carrier = List::new_from_item(Item::from_parts(Vector::default(), attributes));
|
if let Some(appearance) = attributes.get::<Appearance>(graphic_types::ATTR_APPEARANCE) {
|
||||||
fill = carrier
|
fill_cell = appearance.first_paint_of(Cover::Fill).filter(|cell| !cell.is_empty()).cloned();
|
||||||
.attribute::<Option<List<Graphic>>>(ATTR_FILL, 0)
|
stroke_cell = appearance.first_paint_of(Cover::Stroke).filter(|cell| !cell.is_empty()).cloned();
|
||||||
.and_then(|paint| paint.as_ref())
|
}
|
||||||
.map(|paint| park_paint(arena, paint.clone()))
|
|
||||||
.transpose()?;
|
|
||||||
stroke = carrier
|
|
||||||
.attribute::<Option<List<Graphic>>>(ATTR_STROKE, 0)
|
|
||||||
.and_then(|paint| paint.as_ref())
|
|
||||||
.map(|paint| park_paint(arena, paint.clone()))
|
|
||||||
.transpose()?;
|
|
||||||
|
|
||||||
// Adopt the last input item's layer so the editor can also bucket clicks under a contributing child layer
|
// Adopt the last input item's layer so the editor can also bucket clicks under a contributing child layer
|
||||||
layer_path = flattened.attribute_cloned_or_default::<Vec<NodeId>>(ATTR_EDITOR_LAYER_PATH, primary);
|
layer_path = flattened.attribute_cloned_or_default::<Vec<NodeId>>(ATTR_EDITOR_LAYER_PATH, primary);
|
||||||
@@ -1988,15 +1905,15 @@ fn flatten_path_core<'e>(
|
|||||||
// editor click-target preservation, as the boolean operation does.
|
// editor click-target preservation, as the boolean operation does.
|
||||||
let merged_layers = arena.alloc_sized_keyed(snapshot, 0).ok_or_else(exhausted)?.0;
|
let merged_layers = arena.alloc_sized_keyed(snapshot, 0).ok_or_else(exhausted)?.0;
|
||||||
|
|
||||||
// The carried paints land on the appearance too, the stroke coverage recording the carried stroke's parameters
|
// The carried paints land on the appearance, the stroke coverage recording the carried stroke's parameters
|
||||||
let appearance = {
|
let appearance = {
|
||||||
let mut appearance = Appearance::default();
|
let mut appearance = Appearance::default();
|
||||||
if let Some(fill_paint) = fill {
|
if let Some(cell) = fill_cell {
|
||||||
appearance.replace_or_insert(Coverage::new_fill(), Graphic::Graphic(fill_paint.clone()), CoverPlacement::Above);
|
appearance.replace_or_insert(Coverage::new_fill(), cell, CoverPlacement::Above);
|
||||||
}
|
}
|
||||||
if let Some(stroke_paint) = stroke {
|
if let Some(cell) = stroke_cell {
|
||||||
let coverage = Coverage::new_stroke(&output.stroke.clone().unwrap_or_default());
|
let coverage = Coverage::new_stroke(&output.stroke.clone().unwrap_or_default());
|
||||||
appearance.replace_or_insert(coverage, Graphic::Graphic(stroke_paint.clone()), CoverPlacement::Above);
|
appearance.replace_or_insert(coverage, cell, CoverPlacement::Above);
|
||||||
}
|
}
|
||||||
match appearance.declared().is_some() {
|
match appearance.declared().is_some() {
|
||||||
true => Some(park_appearance(arena, appearance)?),
|
true => Some(park_appearance(arena, appearance)?),
|
||||||
@@ -2004,15 +1921,7 @@ fn flatten_path_core<'e>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((output, Attr(DAffine2::IDENTITY), Attr(appearance), Attr(layer_path.as_slice()), Attr(Some(merged_layers))))
|
||||||
output,
|
|
||||||
Attr(DAffine2::IDENTITY),
|
|
||||||
Attr(fill),
|
|
||||||
Attr(stroke),
|
|
||||||
Attr(appearance),
|
|
||||||
Attr(layer_path.as_slice()),
|
|
||||||
Attr(Some(merged_layers)),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.
|
// TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.
|
||||||
@@ -2020,18 +1929,7 @@ fn flatten_path_core<'e>(
|
|||||||
pub fn flatten_path<'e>(
|
pub fn flatten_path<'e>(
|
||||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||||
content: IList<Graphic<'static>>,
|
content: IList<Graphic<'static>>,
|
||||||
) -> Result<
|
) -> Result<(Vector, Attr<'e, TransformAttr>, Attr<'e, AppearanceMarker>, Attr<'e, EditorLayerPath>, Attr<'e, EditorMergedLayers>), Interrupt> {
|
||||||
(
|
|
||||||
Vector,
|
|
||||||
Attr<'e, TransformAttr>,
|
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
|
||||||
Attr<'e, EditorLayerPath>,
|
|
||||||
Attr<'e, EditorMergedLayers>,
|
|
||||||
),
|
|
||||||
Interrupt,
|
|
||||||
> {
|
|
||||||
let item = content.as_group_item();
|
let item = content.as_group_item();
|
||||||
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
|
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
|
||||||
let snapshot = graphic_types::graphic::run_to_list::<Graphic>(&item).expect("the run holds the row's element type");
|
let snapshot = graphic_types::graphic::run_to_list::<Graphic>(&item).expect("the run holds the row's element type");
|
||||||
@@ -2044,18 +1942,7 @@ pub fn flatten_path<'e>(
|
|||||||
pub fn flatten_path_vector<'e>(
|
pub fn flatten_path_vector<'e>(
|
||||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||||
content: IList<Vector>,
|
content: IList<Vector>,
|
||||||
) -> Result<
|
) -> Result<(Vector, Attr<'e, TransformAttr>, Attr<'e, AppearanceMarker>, Attr<'e, EditorLayerPath>, Attr<'e, EditorMergedLayers>), Interrupt> {
|
||||||
(
|
|
||||||
Vector,
|
|
||||||
Attr<'e, TransformAttr>,
|
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
|
||||||
Attr<'e, EditorLayerPath>,
|
|
||||||
Attr<'e, EditorMergedLayers>,
|
|
||||||
),
|
|
||||||
Interrupt,
|
|
||||||
> {
|
|
||||||
let wrapper = wrap_vector_level(content);
|
let wrapper = wrap_vector_level(content);
|
||||||
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Legacy(&wrapper));
|
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Legacy(&wrapper));
|
||||||
let snapshot = legacy_graphic_list_of(content);
|
let snapshot = legacy_graphic_list_of(content);
|
||||||
@@ -2374,8 +2261,6 @@ fn cut_path<'e>(
|
|||||||
IList<(
|
IList<(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -2755,7 +2640,6 @@ fn offset_points(
|
|||||||
///
|
///
|
||||||
/// *Progression* morphs through all objects. Interpolation is linear unless *Path* geometry is provided to control the trajectory between key objects. The **Origins to Polyline** node may be used to create a path with anchor points corresponding to each object. Other nodes can modify its path segments.
|
/// *Progression* morphs through all objects. Interpolation is linear unless *Path* geometry is provided to control the trajectory between key objects. The **Origins to Polyline** node may be used to create a path with anchor points corresponding to each object. Other nodes can modify its path segments.
|
||||||
fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progression: f64, reverse: bool, distribution: InterpolationDistribution, path: List<Vector>) -> List<Vector> {
|
fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progression: f64, reverse: bool, distribution: InterpolationDistribution, path: List<Vector>) -> List<Vector> {
|
||||||
use core_types::lane::LaneSource;
|
|
||||||
/// Promotes a segment's handle pair to cubic-equivalent Bézier control points.
|
/// Promotes a segment's handle pair to cubic-equivalent Bézier control points.
|
||||||
/// For linear segments (both None), handles are placed at their respective anchors (zero-length)
|
/// For linear segments (both None), handles are placed at their respective anchors (zero-length)
|
||||||
/// so that interpolation against another zero-length cubic doesn't introduce unwanted curvature.
|
/// so that interpolation against another zero-length cubic doesn't introduce unwanted curvature.
|
||||||
@@ -3193,16 +3077,14 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progres
|
|||||||
};
|
};
|
||||||
let mut vector = Vector { stroke, ..Default::default() };
|
let mut vector = Vector { stroke, ..Default::default() };
|
||||||
|
|
||||||
let fill_paint = {
|
let coverage_paint = |index: usize, cover: Cover| {
|
||||||
let source = content.attr::<Fill>(source_index).filter(|paint| is_paint_present(paint));
|
content
|
||||||
let target = content.attr::<Fill>(target_index).filter(|paint| is_paint_present(paint));
|
.attribute::<Appearance>(graphic_types::ATTR_APPEARANCE, index)
|
||||||
lerp_graphic(source, target, time)
|
.and_then(|appearance| appearance.first_paint_of(cover))
|
||||||
};
|
.and_then(graphic_types::graphic::paint_cell_rows)
|
||||||
let stroke_paint = {
|
|
||||||
let source = content.attr::<StrokeAttr>(source_index).filter(|paint| is_paint_present(paint));
|
|
||||||
let target = content.attr::<StrokeAttr>(target_index).filter(|paint| is_paint_present(paint));
|
|
||||||
lerp_graphic(source, target, time)
|
|
||||||
};
|
};
|
||||||
|
let fill_paint = lerp_graphic(coverage_paint(source_index, Cover::Fill), coverage_paint(target_index, Cover::Fill), time);
|
||||||
|
let stroke_paint = lerp_graphic(coverage_paint(source_index, Cover::Stroke), coverage_paint(target_index, Cover::Stroke), time);
|
||||||
|
|
||||||
// Work directly with manipulator groups, bypassing the BezPath intermediate representation.
|
// Work directly with manipulator groups, bypassing the BezPath intermediate representation.
|
||||||
// This avoids the full Vector → BezPath → interpolate → BezPath → Vector roundtrip each frame.
|
// This avoids the full Vector → BezPath → interpolate → BezPath → Vector roundtrip each frame.
|
||||||
@@ -3361,14 +3243,12 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic<'static>>, progres
|
|||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
|
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path)
|
||||||
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, Some(graphic_list_content));
|
.with_attribute(ATTR_EDITOR_MERGED_LAYERS, Some(graphic_list_content));
|
||||||
|
|
||||||
// The lerped paints land on the appearance too, the stroke coverage recording the lerped stroke's parameters
|
// The lerped paints land on the appearance, the stroke coverage recording the lerped stroke's parameters
|
||||||
let mut appearance = Appearance::default();
|
let mut appearance = Appearance::default();
|
||||||
if let Some(fill) = fill_paint {
|
if let Some(fill) = fill_paint {
|
||||||
item.set_attribute(ATTR_FILL, Some(fill.clone()));
|
|
||||||
appearance.replace_or_insert(Coverage::new_fill(), Graphic::Graphic(fill), CoverPlacement::Above);
|
appearance.replace_or_insert(Coverage::new_fill(), Graphic::Graphic(fill), CoverPlacement::Above);
|
||||||
}
|
}
|
||||||
if let Some(stroke) = stroke_paint {
|
if let Some(stroke) = stroke_paint {
|
||||||
item.set_attribute(ATTR_STROKE, Some(stroke.clone()));
|
|
||||||
let coverage = Coverage::new_stroke(&item.element().stroke.clone().unwrap_or_default());
|
let coverage = Coverage::new_stroke(&item.element().stroke.clone().unwrap_or_default());
|
||||||
appearance.replace_or_insert(coverage, Graphic::Graphic(stroke), CoverPlacement::Above);
|
appearance.replace_or_insert(coverage, Graphic::Graphic(stroke), CoverPlacement::Above);
|
||||||
}
|
}
|
||||||
@@ -3394,8 +3274,6 @@ fn morph_lane<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -3435,8 +3313,6 @@ fn morph<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -3468,8 +3344,6 @@ fn morph_vector<'e>(
|
|||||||
(
|
(
|
||||||
Vector,
|
Vector,
|
||||||
Attr<'e, TransformAttr>,
|
Attr<'e, TransformAttr>,
|
||||||
Attr<'e, Fill>,
|
|
||||||
Attr<'e, StrokeAttr>,
|
|
||||||
Attr<'e, AppearanceMarker>,
|
Attr<'e, AppearanceMarker>,
|
||||||
Attr<'e, BlendModeAttr>,
|
Attr<'e, BlendModeAttr>,
|
||||||
Attr<'e, Opacity>,
|
Attr<'e, Opacity>,
|
||||||
@@ -3916,7 +3790,6 @@ fn centroid(_: impl Ctx, vector: IList<Vector>, centroid_type: CentroidType) ->
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use core_types::transform::Footprint;
|
use core_types::transform::Footprint;
|
||||||
use graphic_types::graphic::paint_graphics;
|
|
||||||
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
use kurbo::{CubicBez, Ellipse, Point, Rect};
|
||||||
use vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg};
|
use vector_types::vector::algorithms::bezpath_algorithms::{TValue, trim_pathseg};
|
||||||
use vector_types::vector::misc::pathseg_abs_diff_eq;
|
use vector_types::vector::misc::pathseg_abs_diff_eq;
|
||||||
@@ -4072,12 +3945,13 @@ mod test {
|
|||||||
v
|
v
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fill_appearance = |color: Color| Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(List::new_from_element(color).into_graphic_list()));
|
||||||
let item_a = Item::new_from_element(rect())
|
let item_a = Item::new_from_element(rect())
|
||||||
.with_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY)
|
.with_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY)
|
||||||
.with_attribute(ATTR_FILL, Some(List::new_from_element(Color::RED).into_graphic_list()));
|
.with_attribute(graphic_types::ATTR_APPEARANCE, fill_appearance(Color::RED));
|
||||||
let item_b = Item::new_from_element(rect())
|
let item_b = Item::new_from_element(rect())
|
||||||
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation((-100., -100.).into()))
|
.with_attribute(ATTR_TRANSFORM, DAffine2::from_translation((-100., -100.).into()))
|
||||||
.with_attribute(ATTR_FILL, Some(List::new_from_element(Color::BLUE).into_graphic_list()));
|
.with_attribute(graphic_types::ATTR_APPEARANCE, fill_appearance(Color::BLUE));
|
||||||
|
|
||||||
let mut content = List::new_from_item(item_a);
|
let mut content = List::new_from_item(item_a);
|
||||||
content.push(item_b);
|
content.push(item_b);
|
||||||
@@ -4085,7 +3959,13 @@ mod test {
|
|||||||
let snapshot = content.into_graphic_list();
|
let snapshot = content.into_graphic_list();
|
||||||
let morphed = super::morph_core(snapshot.clone().into_flattened_list(), snapshot, 0.5, false, InterpolationDistribution::default(), List::default());
|
let morphed = super::morph_core(snapshot.clone().into_flattened_list(), snapshot, 0.5, false, InterpolationDistribution::default(), List::default());
|
||||||
|
|
||||||
let fill = paint_graphics::<Fill, _>(&morphed, 0).expect("Morph should keep the fill paint at the midpoint");
|
let appearance = morphed
|
||||||
|
.attribute::<Appearance>(graphic_types::ATTR_APPEARANCE, 0)
|
||||||
|
.expect("Morph should keep the appearance at the midpoint");
|
||||||
|
let fill = appearance
|
||||||
|
.first_paint_of(Cover::Fill)
|
||||||
|
.and_then(graphic_types::graphic::paint_cell_rows)
|
||||||
|
.expect("Morph should keep the fill paint at the midpoint");
|
||||||
|
|
||||||
// Interpolated color between red and blue should have >0 value on both R and B
|
// Interpolated color between red and blue should have >0 value on both R and B
|
||||||
let Some(Graphic::Color(color)) = fill.element(0) else {
|
let Some(Graphic::Color(color)) = fill.element(0) else {
|
||||||
|
|||||||
Reference in New Issue
Block a user