mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +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:
@@ -492,7 +492,7 @@ mod run_tests {
|
||||
use super::*;
|
||||
use crate::graphic::test_support::{native_group_paint, unit_square_at};
|
||||
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::lane::LaneSource;
|
||||
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 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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||
let group = core_types::record::Group { row: None, content: builder.finish() };
|
||||
let expected = group_to_legacy_list(&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 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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||
let item = builder.finish();
|
||||
let owned = item.copy_out();
|
||||
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 replayed = owned.replay(&arena).expect("the arena holds the replay");
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -574,12 +574,12 @@ mod run_tests {
|
||||
let paint = unsafe { core_types::record::erase_static(native_group_paint(&inner_vector, &source)) };
|
||||
|
||||
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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||
let item = builder.finish();
|
||||
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`.
|
||||
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());
|
||||
@@ -609,8 +609,8 @@ mod run_tests {
|
||||
) -> (core_types::record::Layout, core_types::record::MaterializedSpan, Vec<u64>) {
|
||||
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 offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
||||
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[FieldWrite::of::<EditorMergedLayers>(0)]);
|
||||
let offset = layout.offset_of(EditorMergedLayers::NAME, 0).unwrap();
|
||||
let stride = layout.lane_stride();
|
||||
let mut buffer = vec![0u64; (lanes * stride).div_ceil(8)];
|
||||
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.
|
||||
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");
|
||||
// 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")
|
||||
@@ -713,7 +713,7 @@ mod run_tests {
|
||||
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
|
||||
// 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 (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
||||
@@ -723,7 +723,7 @@ mod run_tests {
|
||||
|
||||
transient.reset();
|
||||
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");
|
||||
assert_eq!(
|
||||
map_groups_to_legacy(held.element(0).unwrap()),
|
||||
@@ -742,7 +742,7 @@ mod run_tests {
|
||||
// whose value holds none: neither denies the move.
|
||||
paint.set_attribute::<f64>("opacity", 0, 0.5);
|
||||
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 Some(Graphic::Vector(vector)) = paint.element(0) else { panic!("the paint carries a vector") };
|
||||
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.))));
|
||||
// SAFETY: the erased native list serves only while `source` is live; the
|
||||
// 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 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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<EditorMergedLayers>(lane, Some(&paint));
|
||||
let item = builder.finish();
|
||||
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`.
|
||||
let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) };
|
||||
drop(item);
|
||||
@@ -800,7 +800,7 @@ mod run_tests {
|
||||
let value = unsafe { slot.finish() };
|
||||
// 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 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");
|
||||
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.
|
||||
|
||||
use super::walk::push_lane_paint_into_interiors;
|
||||
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::list::{Item, List};
|
||||
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)
|
||||
}
|
||||
|
||||
/// Converts the group content of the list's paint attribute values to legacy
|
||||
/// form, so a legacy product owns everything its attributes reach.
|
||||
/// Converts the group content the list's attribute values reach to legacy
|
||||
/// 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>) {
|
||||
for key in [ATTR_FILL, ATTR_STROKE, crate::markers::ATTR_EDITOR_MERGED_LAYERS] {
|
||||
let Some(values) = list.iter_attribute_values_mut::<Option<List<Graphic>>>(key) else { continue };
|
||||
if let Some(appearances) = list.iter_attribute_values_mut::<Appearance>(ATTR_APPEARANCE) {
|
||||
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 element in value.iter_element_values_mut() {
|
||||
*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() {
|
||||
*element = map_groups_to_legacy(element);
|
||||
}
|
||||
push_lane_paint_into_interiors(&mut list);
|
||||
return list;
|
||||
}
|
||||
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)]
|
||||
mod run_tests {
|
||||
use super::*;
|
||||
use crate::appearance::Coverage;
|
||||
use crate::graphic::test_support::{native_group_paint, unit_square_at};
|
||||
use crate::markers::Fill;
|
||||
use core_types::attribute::Attribute;
|
||||
use crate::markers::Appearance as AppearanceMarker;
|
||||
use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
|
||||
use glam::DVec2;
|
||||
|
||||
#[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 source = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
// SAFETY: the erased native list serves only while `source` is live; the
|
||||
// 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 appearance = Appearance::new_single(Coverage::new_fill(), Graphic::Graphic(paint.clone()));
|
||||
|
||||
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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||
let item = builder.finish();
|
||||
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());
|
||||
@@ -126,9 +134,10 @@ mod run_tests {
|
||||
drop(paint);
|
||||
drop(source);
|
||||
|
||||
let served = legacy.attribute::<Option<List<Graphic>>>(Fill::NAME, 0).expect("the fill attribute rides the list");
|
||||
let served = served.as_ref().expect("the fill is present");
|
||||
assert_eq!(served.element(0).unwrap(), &expected);
|
||||
let served = legacy.attribute::<Appearance>(ATTR_APPEARANCE, 0).expect("the appearance rides the list");
|
||||
let cell = served.paint_at(0).expect("the fill coverage keeps its paint");
|
||||
let Graphic::Graphic(cell_rows) = cell else { panic!("the paint cell keeps the list form") };
|
||||
assert_eq!(cell_rows.element(0).unwrap(), &expected);
|
||||
}
|
||||
|
||||
#[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(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 paint::{
|
||||
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 paint::{PaintColumns, PaintReach, bake_paint_transforms, is_paint_present, paint_cell_rows, 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};
|
||||
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::markers::{ATTR_FILL, ATTR_STROKE, Appearance as AppearanceMarker, Fill, Stroke};
|
||||
use crate::markers::Appearance as AppearanceMarker;
|
||||
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::list::{ItemAttributeValues, List};
|
||||
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())
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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 {
|
||||
@@ -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
|
||||
/// [`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.
|
||||
/// A source's declared appearance column, resolved once for per-lane reads.
|
||||
pub struct PaintColumns<'a, S: LaneSource + 'a> {
|
||||
fill: S::Column<'a, Fill>,
|
||||
stroke: S::Column<'a, Stroke>,
|
||||
appearance: S::Column<'a, AppearanceMarker>,
|
||||
}
|
||||
|
||||
impl<'a, S: LaneSource> PaintColumns<'a, S> {
|
||||
pub fn new(source: &'a S) -> Self {
|
||||
Self {
|
||||
fill: source.column::<Fill>(),
|
||||
stroke: source.column::<Stroke>(),
|
||||
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.
|
||||
pub fn read_appearance(&self, lane: usize) -> Option<&'a Appearance> {
|
||||
self.appearance.try_get(lane).flatten().and_then(Appearance::declared)
|
||||
}
|
||||
}
|
||||
|
||||
/// How far a lane's paint reaches into the element beneath it, mirroring the
|
||||
/// legacy conversion's paint push: vector interiors directly and vector
|
||||
/// children of a nested graphic list, one level deep.
|
||||
///
|
||||
/// 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
|
||||
/// The appearance cascade threading down the graphic levels: 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.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PaintReach<'a> {
|
||||
pub paint: LanePaint<'a>,
|
||||
/// The cascade's resolved appearance: the nearest declared one at or above this lane.
|
||||
pub appearance: Option<&'a Appearance>,
|
||||
hops: u8,
|
||||
}
|
||||
|
||||
impl<'a> PaintReach<'a> {
|
||||
pub const NONE: Self = Self {
|
||||
paint: LanePaint::NONE,
|
||||
appearance: None,
|
||||
hops: 0,
|
||||
};
|
||||
pub const NONE: Self = Self { appearance: None };
|
||||
|
||||
/// The lane's effective reach: an inherited paint stays authoritative
|
||||
/// (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.
|
||||
/// The lane's effective reach: its own declared appearance wins over the inherited one.
|
||||
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 {
|
||||
hops: self.hops.saturating_sub(1),
|
||||
..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
|
||||
},
|
||||
appearance: Appearance::cascade(columns.read_appearance(index), self.appearance),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
pub fn set_paint_attribute(attributes: &mut ItemAttributeValues, key: &str, paint: impl IntoGraphicList) {
|
||||
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.
|
||||
/// Bake the provided transform into the per-item transforms of the paint
|
||||
/// graphics inside the item's appearance coverage cells.
|
||||
pub fn bake_paint_transforms(attributes: &mut ItemAttributeValues, 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) {
|
||||
@@ -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(Some(graphics)) = attributes.get_mut::<Option<List<Graphic>>>(paint_key) {
|
||||
bake_graphic_paint_transform(graphics, transform);
|
||||
if let Some(appearance) = attributes.get_mut::<Appearance>(crate::markers::ATTR_APPEARANCE) {
|
||||
if let Some(cells) = appearance.0.iter_attribute_values_mut::<Graphic>(crate::markers::ATTR_PAINT) {
|
||||
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;
|
||||
|
||||
#[test]
|
||||
fn a_run_serves_the_parked_paint_reference() {
|
||||
let paint = List::new_from_element(Graphic::Color(Color::BLACK));
|
||||
fn a_run_serves_the_parked_appearance_reference() {
|
||||
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 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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||
let item = builder.finish();
|
||||
let run = RunView::<Vector>::new(&item).expect("the run holds vector elements");
|
||||
|
||||
assert_eq!(run.attr::<Fill>(0), Some(&paint));
|
||||
assert_eq!(paint_graphics::<Fill, _>(&run, 0), Some(&paint));
|
||||
assert_eq!(paint_graphics::<Stroke, _>(&run, 0), None);
|
||||
assert_eq!(run.attr::<AppearanceMarker>(0), Some(&appearance));
|
||||
|
||||
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]
|
||||
@@ -263,32 +161,10 @@ mod run_tests {
|
||||
list.set_attribute(ATTR_APPEARANCE, 0, own.clone());
|
||||
|
||||
let columns = PaintColumns::new(&list);
|
||||
let ancestor = PaintReach {
|
||||
appearance: Some(&inherited),
|
||||
..PaintReach::NONE
|
||||
};
|
||||
let ancestor = PaintReach { appearance: Some(&inherited) };
|
||||
|
||||
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!(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.
|
||||
|
||||
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::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::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::lane::LaneSource;
|
||||
@@ -252,7 +252,6 @@ pub struct VectorRow<'w> {
|
||||
source: RowSourceRef<'w>,
|
||||
scale: FlattenScale,
|
||||
layer_path: Option<&'w [NodeId]>,
|
||||
paint: LanePaint<'w>,
|
||||
appearance: Option<&'w Appearance>,
|
||||
}
|
||||
|
||||
@@ -272,16 +271,14 @@ impl VectorRow<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the built row will carry fill paint: the reaching lane paint,
|
||||
/// else the row's own.
|
||||
/// Whether the built row will carry fill paint: a painted fill coverage on
|
||||
/// the row's resolved appearance.
|
||||
pub fn has_fill(&self) -> bool {
|
||||
if self.paint.fill.is_some() {
|
||||
return true;
|
||||
}
|
||||
match &self.source {
|
||||
RowSourceRef::Lane(level, index) => paint_graphics::<Fill, _>(level, *index).is_some(),
|
||||
RowSourceRef::Run(run, _, index) => paint_graphics::<Fill, _>(*run, *index).is_some(),
|
||||
}
|
||||
let own = match &self.source {
|
||||
RowSourceRef::Lane(level, index) => level.attr::<crate::markers::Appearance>(*index),
|
||||
RowSourceRef::Run(run, _, index) => LaneSource::attr::<crate::markers::Appearance>(*run, *index),
|
||||
};
|
||||
Appearance::cascade(own, self.appearance).is_some_and(|appearance| appearance.has_painted_cover(crate::appearance::Cover::Fill))
|
||||
}
|
||||
|
||||
/// 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)));
|
||||
}
|
||||
}
|
||||
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() {
|
||||
let row_transform: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||
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,
|
||||
scale: FlattenScale,
|
||||
layer_path: Option<&[NodeId]>,
|
||||
paint: LanePaint<'_>,
|
||||
appearance: Option<&Appearance>,
|
||||
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
||||
) -> RowStep {
|
||||
@@ -343,7 +334,6 @@ fn walk_rows_of_run(
|
||||
source: RowSourceRef::Run(&run, item, lane),
|
||||
scale,
|
||||
layer_path,
|
||||
paint,
|
||||
appearance,
|
||||
}) {
|
||||
return RowStep::Stop;
|
||||
@@ -353,9 +343,9 @@ fn walk_rows_of_run(
|
||||
}
|
||||
|
||||
/// Walks a graphic level into its flattened vector rows, matching the legacy
|
||||
/// push-then-flatten lowering: lane paint threads with [`PaintReach`],
|
||||
/// ancestor transform, opacity and fill opacity compose down, the containing
|
||||
/// level's parent layer path overwrites its rows, and non-vector content is
|
||||
/// flatten lowering: the appearance cascades with [`PaintReach`], ancestor
|
||||
/// transform, opacity and fill opacity compose down, the containing level's
|
||||
/// parent layer path overwrites its rows, and non-vector content is
|
||||
/// 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) {
|
||||
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 {
|
||||
// A vector-typed run is already its rows.
|
||||
if item.typed_lanes::<Vector>().is_some() {
|
||||
let paint = match inherited.applies() {
|
||||
true => inherited.paint,
|
||||
false => LanePaint::NONE,
|
||||
};
|
||||
return walk_rows_of_run(item, scale, parent_layer_path, paint, inherited.appearance, visit);
|
||||
return walk_rows_of_run(item, scale, parent_layer_path, inherited.appearance, visit);
|
||||
}
|
||||
}
|
||||
let columns = PaintColumns::new(&level);
|
||||
for index in 0..level.lane_count() {
|
||||
let Some(element) = level.element(index) else { continue };
|
||||
let reach = inherited.for_lane(&columns, index);
|
||||
let row_paint = match reach.applies() {
|
||||
true => reach.paint,
|
||||
false => LanePaint::NONE,
|
||||
};
|
||||
let step = match element {
|
||||
Graphic::Vector(_) => visit(VectorRow {
|
||||
source: RowSourceRef::Lane(level, index),
|
||||
scale,
|
||||
layer_path: parent_layer_path,
|
||||
paint: row_paint,
|
||||
appearance: reach.appearance,
|
||||
}),
|
||||
Graphic::Graphic(children) => walk_vector_rows_impl(
|
||||
GraphicLevel::Legacy(children),
|
||||
scale.composed(&level, index),
|
||||
level.try_attr::<EditorLayerPath>(index),
|
||||
reach.nested(),
|
||||
visit,
|
||||
),
|
||||
Graphic::Graphic(children) => walk_vector_rows_impl(GraphicLevel::Legacy(children), scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), reach, visit),
|
||||
Graphic::Group(group) => {
|
||||
let item = &group.content;
|
||||
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() {
|
||||
walk_vector_rows_impl(
|
||||
GraphicLevel::Run(item),
|
||||
scale.composed(&level, index),
|
||||
level.try_attr::<EditorLayerPath>(index),
|
||||
reach.into_group_graphics(),
|
||||
visit,
|
||||
)
|
||||
walk_vector_rows_impl(GraphicLevel::Run(item), scale.composed(&level, index), level.try_attr::<EditorLayerPath>(index), reach, visit)
|
||||
} else {
|
||||
RowStep::Continue
|
||||
}
|
||||
@@ -437,26 +406,6 @@ pub fn flatten_vector_rows(level: GraphicLevel<'_>) -> List<Vector> {
|
||||
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`],
|
||||
/// 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.
|
||||
@@ -496,6 +445,10 @@ mod run_tests {
|
||||
|
||||
#[test]
|
||||
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 arena = core_types::arena::Arena::new(1 << 16).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.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.)));
|
||||
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.))));
|
||||
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.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)]);
|
||||
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_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_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 mut list = List::new();
|
||||
@@ -533,7 +486,6 @@ mod run_tests {
|
||||
let (element, attributes) = item.into_parts();
|
||||
list.push(Item::from_parts(map_groups_to_legacy(&element), attributes));
|
||||
}
|
||||
push_lane_paint_into_interiors(&mut list);
|
||||
list.into_flattened_list::<Vector>()
|
||||
};
|
||||
let native = flatten_vector_rows(GraphicLevel::Legacy(&top));
|
||||
@@ -555,9 +507,9 @@ mod run_tests {
|
||||
"layer path, row {row}"
|
||||
);
|
||||
assert_eq!(
|
||||
native.attribute::<Option<List<Graphic>>>(ATTR_FILL, row),
|
||||
legacy.attribute::<Option<List<Graphic>>>(ATTR_FILL, row),
|
||||
"fill, row {row}"
|
||||
native.attribute::<Appearance>(ATTR_APPEARANCE, row),
|
||||
legacy.attribute::<Appearance>(ATTR_APPEARANCE, row),
|
||||
"appearance, row {row}"
|
||||
);
|
||||
}
|
||||
assert_eq!(native, legacy);
|
||||
|
||||
@@ -13,7 +13,7 @@ pub use vector_types;
|
||||
pub use appearance::{Appearance, Cover, CoverPlacement, Coverage, FillAndStroke, stamp_coverage};
|
||||
pub use artboard::Artboard;
|
||||
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 {
|
||||
use crate::Vector;
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
//! Attribute markers whose value types live in this crate, with their name
|
||||
//! constants for the string-keyed legacy readers and writers.
|
||||
//!
|
||||
//! The list-valued markers may carry native [`Graphic::Group`] content: the
|
||||
//! registered deep field glue owns it across persistence seams, and legacy
|
||||
//! products convert it through [`crate::graphic::map_paint_attrs_to_legacy`].
|
||||
//! The markers may carry native [`Graphic::Group`] content: the registered
|
||||
//! deep field glue owns it across persistence seams, and legacy products
|
||||
//! convert it through [`crate::graphic::map_paint_attrs_to_legacy`].
|
||||
|
||||
use crate::Graphic;
|
||||
use core_types::attribute::Attribute;
|
||||
use core_types::list::List;
|
||||
|
||||
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,
|
||||
/// Rasterize, etc.), so the editor can still surface click targets for the original child
|
||||
/// layers after their content has been collapsed.
|
||||
@@ -102,8 +96,6 @@ unsafe impl Attribute for 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_APPEARANCE: &str = Appearance::NAME;
|
||||
pub const ATTR_PAINT: &str = Paint::NAME;
|
||||
@@ -116,27 +108,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_census_carries_this_crates_names() {
|
||||
for name in ["fill", "stroke", "editor:merged_layers"] {
|
||||
assert_eq!(info(name).unwrap().value_type, TypeId::of::<Option<&'static List<Graphic>>>());
|
||||
}
|
||||
assert_eq!(info("editor:merged_layers").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("paint").unwrap().value_type, TypeId::of::<Option<&'static Graphic>>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_absent_paint_defaults_to_none() {
|
||||
assert_eq!(<Fill 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);
|
||||
assert_eq!(<Paint as Attribute>::default(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_hash::CacheHashWrapper;
|
||||
use graphene_resource::Resource;
|
||||
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::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
||||
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::vector::click_target::{ClickTarget, FreePoint};
|
||||
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 num_traits::Zero;
|
||||
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
||||
@@ -540,10 +540,10 @@ pub struct RenderMetadata {
|
||||
pub text_frames: HashMap<NodeId, DAffine2>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
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))]
|
||||
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))]
|
||||
pub stroke_attributes: HashMap<NodeId, Arc<List<Graphic<'static>>>>,
|
||||
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) {
|
||||
match element {
|
||||
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),
|
||||
_ => 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) {
|
||||
match element {
|
||||
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),
|
||||
_ => element.render_to_vello(scene, transform, context, render_params),
|
||||
}
|
||||
@@ -736,7 +736,7 @@ fn collect_element_metadata<'a>(
|
||||
}
|
||||
|
||||
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::RasterCPU(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>) {
|
||||
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::RasterCPU(_) | Graphic::RasterGPU(_) => add_raster_upstream_click_targets(click_targets),
|
||||
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>) {
|
||||
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::RasterCPU(_) | Graphic::RasterGPU(_) => add_raster_upstream_click_targets(outlines),
|
||||
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) {
|
||||
let item = &group.content;
|
||||
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) {
|
||||
render_vector_svg(&run, reach.appearance, render, render_params)
|
||||
} 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) {
|
||||
let item = &group.content;
|
||||
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) {
|
||||
render_vector_vello(&run, reach.appearance, scene, transform, context, render_params)
|
||||
} 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>) {
|
||||
let item = &group.content;
|
||||
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) {
|
||||
collect_vector_metadata(&run, reach.appearance, metadata, footprint, element_id)
|
||||
} 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>) {
|
||||
let item = &group.content;
|
||||
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) {
|
||||
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() {
|
||||
@@ -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>) {
|
||||
let item = &group.content;
|
||||
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) {
|
||||
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() {
|
||||
@@ -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 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);
|
||||
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());
|
||||
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 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);
|
||||
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());
|
||||
let vector_list = List::new_from_item(mask_item);
|
||||
|
||||
@@ -3129,7 +3127,6 @@ impl SvgRenderAttrs<'_> {
|
||||
mod group_walk_tests {
|
||||
use super::*;
|
||||
use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
|
||||
use graphic_types::markers::Fill;
|
||||
use graphic_types::vector_types::vector::PointId;
|
||||
|
||||
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()))
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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 arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||
builder.push(vectors[1].clone()).unwrap();
|
||||
let item = builder.finish();
|
||||
@@ -3179,9 +3175,8 @@ mod group_walk_tests {
|
||||
let inner = Graphic::Vector(unit_square_at(DVec2::ZERO));
|
||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||
let item = builder.finish();
|
||||
let group = Group { row: None, content: item };
|
||||
@@ -3200,9 +3195,8 @@ mod group_walk_tests {
|
||||
let vectors = [unit_square_at(DVec2::ZERO)];
|
||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||
let item = builder.finish();
|
||||
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 arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
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();
|
||||
builder.attr::<Fill>(lane, Some(&paint));
|
||||
builder.attr::<AppearanceMarker>(lane, Some(&appearance));
|
||||
builder.push(vectors[1].clone()).unwrap();
|
||||
let item = builder.finish();
|
||||
|
||||
Reference in New Issue
Block a user