mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-07 10:18:11 +08:00
De-table the graphic leaf variants
This commit is contained in:
@@ -38,6 +38,99 @@ pub trait LaneSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A bare element as a one-lane source: no columns, so every marker reads its
|
||||||
|
/// census default. The read surface for a de-tabled leaf, whose attributes
|
||||||
|
/// ride the containing lane.
|
||||||
|
pub struct Single<'a, T>(pub &'a T);
|
||||||
|
|
||||||
|
/// The column of a [`Single`]: always absent, so reads fall to the census
|
||||||
|
/// default.
|
||||||
|
pub struct NoColumn;
|
||||||
|
|
||||||
|
impl<'a, A: Attribute> LaneColumn<'a, A> for NoColumn {
|
||||||
|
fn try_get(&self, _lane: usize) -> Option<A::Value<'a>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LaneSource for Single<'_, T> {
|
||||||
|
type Element = T;
|
||||||
|
type Column<'a, A: Attribute>
|
||||||
|
= NoColumn
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn lane_count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn element(&self, lane: usize) -> Option<&T> {
|
||||||
|
(lane == 0).then_some(self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column<A: Attribute>(&self) -> NoColumn {
|
||||||
|
NoColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: crate::bounds::BoundingBox> crate::bounds::BoundingBox for Single<'_, T> {
|
||||||
|
fn bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox {
|
||||||
|
self.0.bounding_box(transform, include_stroke)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn thumbnail_bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox {
|
||||||
|
self.0.thumbnail_bounding_box(transform, include_stroke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One lane of a source re-based as a one-lane source of a leaf element: the
|
||||||
|
/// de-tabled leaf read with its containing lane's attributes.
|
||||||
|
pub struct LeafLane<'a, S, T> {
|
||||||
|
source: &'a S,
|
||||||
|
index: usize,
|
||||||
|
element: &'a T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S, T> LeafLane<'a, S, T> {
|
||||||
|
pub fn new(source: &'a S, index: usize, element: &'a T) -> Self {
|
||||||
|
Self { source, index, element }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LaneColumnAt<'a, S: LaneSource + 'a, A: Attribute> {
|
||||||
|
inner: S::Column<'a, A>,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: LaneSource, A: Attribute> LaneColumn<'a, A> for LaneColumnAt<'a, S, A> {
|
||||||
|
fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
|
||||||
|
(lane == 0).then(|| self.inner.try_get(self.index)).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: LaneSource, T> LaneSource for LeafLane<'_, S, T> {
|
||||||
|
type Element = T;
|
||||||
|
type Column<'a, A: Attribute>
|
||||||
|
= LaneColumnAt<'a, S, A>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn lane_count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn element(&self, lane: usize) -> Option<&T> {
|
||||||
|
(lane == 0).then_some(self.element)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column<A: Attribute>(&self) -> LaneColumnAt<'_, S, A> {
|
||||||
|
LaneColumnAt {
|
||||||
|
inner: self.source.column::<A>(),
|
||||||
|
index: self.index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::LaneSource;
|
use super::LaneSource;
|
||||||
|
|||||||
@@ -1966,6 +1966,17 @@ pub struct RunColumn<'a, A: crate::attribute::Attribute> {
|
|||||||
marker: std::marker::PhantomData<A>,
|
marker: std::marker::PhantomData<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, A: crate::attribute::Attribute> RunColumn<'a, A> {
|
||||||
|
/// The marker's column on an item, its offset resolved once.
|
||||||
|
pub fn of(item: &'a GroupItem) -> Self {
|
||||||
|
Self {
|
||||||
|
item,
|
||||||
|
offset: item.layout().offset_of(A::NAME, 0),
|
||||||
|
marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for RunColumn<'a, A> {
|
impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for RunColumn<'a, A> {
|
||||||
fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
|
fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
|
||||||
// SAFETY: the offset comes from the item's own layout, whose field at
|
// SAFETY: the offset comes from the item's own layout, whose field at
|
||||||
@@ -1990,11 +2001,7 @@ impl<'a, T: 'static> crate::lane::LaneSource for RunView<'a, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn column<A: crate::attribute::Attribute>(&self) -> RunColumn<'_, A> {
|
fn column<A: crate::attribute::Attribute>(&self) -> RunColumn<'_, A> {
|
||||||
RunColumn {
|
RunColumn::of(self.item)
|
||||||
item: self.item,
|
|
||||||
offset: self.item.layout().offset_of(A::NAME, 0),
|
|
||||||
marker: std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,18 @@ use vector_types::GradientStops;
|
|||||||
pub use vector_types::Vector;
|
pub use vector_types::Vector;
|
||||||
|
|
||||||
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
|
||||||
|
/// A leaf holds its element directly; its attributes ride the containing
|
||||||
|
/// lane. Multi-element content is a [`core_types::record::Group`] run, or
|
||||||
|
/// transitionally the legacy `Graphic` list.
|
||||||
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
#[derive(Clone, Debug, CacheHash, PartialEq, DynAny)]
|
||||||
pub enum Graphic {
|
pub enum Graphic {
|
||||||
Graphic(List<Graphic>),
|
Graphic(List<Graphic>),
|
||||||
Vector(List<Vector>),
|
Vector(Vector),
|
||||||
RasterCPU(List<Raster<CPU>>),
|
RasterCPU(Raster<CPU>),
|
||||||
RasterGPU(List<Raster<GPU>>),
|
RasterGPU(Raster<GPU>),
|
||||||
Color(List<Color>),
|
Color(Color),
|
||||||
Gradient(List<GradientStops>),
|
Gradient(GradientStops),
|
||||||
Text(List<String>),
|
Text(String),
|
||||||
Group(core_types::record::Group),
|
Group(core_types::record::Group),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,15 +43,26 @@ impl From<List<Graphic>> for Graphic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A typed legacy list as a legacy graphic list: each item de-tables to a
|
||||||
|
/// leaf element, keeping its attributes on the containing lane.
|
||||||
|
fn detable_items<T: Clone + Send + Sync + 'static>(list: List<T>, leaf: fn(T) -> Graphic) -> List<Graphic> {
|
||||||
|
let mut out = List::new();
|
||||||
|
for item in list.into_iter() {
|
||||||
|
let (element, attributes) = item.into_parts();
|
||||||
|
out.push(Item::from_parts(leaf(element), attributes));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
// Vector
|
// Vector
|
||||||
impl From<Vector> for Graphic {
|
impl From<Vector> for Graphic {
|
||||||
fn from(vector: Vector) -> Self {
|
fn from(vector: Vector) -> Self {
|
||||||
Graphic::Vector(List::new_from_element(vector))
|
Graphic::Vector(vector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<List<Vector>> for Graphic {
|
impl From<List<Vector>> for Graphic {
|
||||||
fn from(vector: List<Vector>) -> Self {
|
fn from(vector: List<Vector>) -> Self {
|
||||||
Graphic::Vector(vector)
|
Graphic::Graphic(detable_items(vector, Graphic::Vector))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,12 +71,12 @@ impl From<List<Vector>> for Graphic {
|
|||||||
// Raster<CPU>
|
// Raster<CPU>
|
||||||
impl From<Raster<CPU>> for Graphic {
|
impl From<Raster<CPU>> for Graphic {
|
||||||
fn from(raster: Raster<CPU>) -> Self {
|
fn from(raster: Raster<CPU>) -> Self {
|
||||||
Graphic::RasterCPU(List::new_from_element(raster))
|
Graphic::RasterCPU(raster)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<List<Raster<CPU>>> for Graphic {
|
impl From<List<Raster<CPU>>> for Graphic {
|
||||||
fn from(raster: List<Raster<CPU>>) -> Self {
|
fn from(raster: List<Raster<CPU>>) -> Self {
|
||||||
Graphic::RasterCPU(raster)
|
Graphic::Graphic(detable_items(raster, Graphic::RasterCPU))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Note: List conversions handled by blanket impl in gcore
|
// Note: List conversions handled by blanket impl in gcore
|
||||||
@@ -70,12 +84,12 @@ impl From<List<Raster<CPU>>> for Graphic {
|
|||||||
// Raster<GPU>
|
// Raster<GPU>
|
||||||
impl From<Raster<GPU>> for Graphic {
|
impl From<Raster<GPU>> for Graphic {
|
||||||
fn from(raster: Raster<GPU>) -> Self {
|
fn from(raster: Raster<GPU>) -> Self {
|
||||||
Graphic::RasterGPU(List::new_from_element(raster))
|
Graphic::RasterGPU(raster)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<List<Raster<GPU>>> for Graphic {
|
impl From<List<Raster<GPU>>> for Graphic {
|
||||||
fn from(raster: List<Raster<GPU>>) -> Self {
|
fn from(raster: List<Raster<GPU>>) -> Self {
|
||||||
Graphic::RasterGPU(raster)
|
Graphic::Graphic(detable_items(raster, Graphic::RasterGPU))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Note: List conversions handled by blanket impl in gcore
|
// Note: List conversions handled by blanket impl in gcore
|
||||||
@@ -83,12 +97,12 @@ impl From<List<Raster<GPU>>> for Graphic {
|
|||||||
// Color
|
// Color
|
||||||
impl From<Color> for Graphic {
|
impl From<Color> for Graphic {
|
||||||
fn from(color: Color) -> Self {
|
fn from(color: Color) -> Self {
|
||||||
Graphic::Color(List::new_from_element(color))
|
Graphic::Color(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<List<Color>> for Graphic {
|
impl From<List<Color>> for Graphic {
|
||||||
fn from(color: List<Color>) -> Self {
|
fn from(color: List<Color>) -> Self {
|
||||||
Graphic::Color(color)
|
Graphic::Graphic(detable_items(color, Graphic::Color))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Note: List conversions handled by blanket impl in gcore
|
// Note: List conversions handled by blanket impl in gcore
|
||||||
@@ -97,31 +111,31 @@ impl From<List<Color>> for Graphic {
|
|||||||
// GradientStops
|
// GradientStops
|
||||||
impl From<GradientStops> for Graphic {
|
impl From<GradientStops> for Graphic {
|
||||||
fn from(gradient: GradientStops) -> Self {
|
fn from(gradient: GradientStops) -> Self {
|
||||||
Graphic::Gradient(List::new_from_element(gradient))
|
Graphic::Gradient(gradient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<List<GradientStops>> for Graphic {
|
impl From<List<GradientStops>> for Graphic {
|
||||||
fn from(gradient: List<GradientStops>) -> Self {
|
fn from(gradient: List<GradientStops>) -> Self {
|
||||||
Graphic::Gradient(gradient)
|
Graphic::Graphic(detable_items(gradient, Graphic::Gradient))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String
|
// String
|
||||||
impl From<String> for Graphic {
|
impl From<String> for Graphic {
|
||||||
fn from(text: String) -> Self {
|
fn from(text: String) -> Self {
|
||||||
Graphic::Text(List::new_from_element(text))
|
Graphic::Text(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<List<String>> for Graphic {
|
impl From<List<String>> for Graphic {
|
||||||
fn from(text: List<String>) -> Self {
|
fn from(text: List<String>) -> Self {
|
||||||
Graphic::Text(text)
|
Graphic::Graphic(detable_items(text, Graphic::Text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deeply flattens a `List<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
|
/// Deeply flattens a `List<Graphic>`, collecting only elements matching a specific variant (extracted by `extract_variant`)
|
||||||
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`List`s composes transforms and opacity.
|
/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`List`s composes transforms and opacity.
|
||||||
fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) -> List<T> {
|
fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) -> List<T> {
|
||||||
fn flatten_recursive<T>(output: &mut List<T>, current_graphic_list: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>) {
|
fn flatten_recursive<T>(output: &mut List<T>, current_graphic_list: List<Graphic>, extract_variant: fn(Graphic) -> Option<List<T>>, parent_layer_path: Option<&[NodeId]>) {
|
||||||
for current_graphic_item in current_graphic_list.into_iter() {
|
for current_graphic_item in current_graphic_list.into_iter() {
|
||||||
// Whether the parent carries each attribute: a structural fact (column presence), never a value comparison.
|
// Whether the parent carries each attribute: a structural fact (column presence), never a value comparison.
|
||||||
// Flattening composes a parent attribute onto its children only when the parent has it,
|
// Flattening composes a parent attribute onto its children only when the parent has it,
|
||||||
@@ -129,14 +143,14 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
|||||||
let parent_has_transform = current_graphic_item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some();
|
let parent_has_transform = current_graphic_item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some();
|
||||||
let parent_has_opacity = current_graphic_item.attribute::<f64>(ATTR_OPACITY).is_some();
|
let parent_has_opacity = current_graphic_item.attribute::<f64>(ATTR_OPACITY).is_some();
|
||||||
let parent_has_fill = current_graphic_item.attribute::<f64>(ATTR_OPACITY_FILL).is_some();
|
let parent_has_fill = current_graphic_item.attribute::<f64>(ATTR_OPACITY_FILL).is_some();
|
||||||
let parent_has_layer_path = current_graphic_item.attribute::<Vec<NodeId>>(ATTR_EDITOR_LAYER_PATH).is_some();
|
|
||||||
|
|
||||||
let layer_path: Vec<NodeId> = current_graphic_item.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
|
|
||||||
let current_transform: DAffine2 = current_graphic_item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
let current_transform: DAffine2 = current_graphic_item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
||||||
let current_opacity: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
let current_opacity: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
||||||
let current_fill: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
let current_fill: f64 = current_graphic_item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
||||||
|
let lane_layer_path: Option<Vec<NodeId>> = current_graphic_item.attribute::<Vec<NodeId>>(ATTR_EDITOR_LAYER_PATH).cloned();
|
||||||
|
|
||||||
match current_graphic_item.into_element() {
|
let (element, attributes) = current_graphic_item.into_parts();
|
||||||
|
match element {
|
||||||
// Compose the parent's transform/opacity/fill onto each child, but only for attributes the parent carries.
|
// Compose the parent's transform/opacity/fill onto each child, but only for attributes the parent carries.
|
||||||
// A child lacking one is padded with the composition identity (`1.` for opacity/fill, identity for transform), so composing through it is a no-op.
|
// A child lacking one is padded with the composition identity (`1.` for opacity/fill, identity for transform), so composing through it is a no-op.
|
||||||
Graphic::Graphic(mut sub_list) => {
|
Graphic::Graphic(mut sub_list) => {
|
||||||
@@ -156,31 +170,18 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flatten_recursive(output, sub_list, extract_variant);
|
flatten_recursive(output, sub_list, extract_variant, lane_layer_path.as_deref());
|
||||||
}
|
}
|
||||||
// Extract the target variant and push its items, composing the parent's attributes onto each
|
// A de-tabled leaf is one attr-less element; the extracted row rides with its containing lane's full attributes, paint included.
|
||||||
|
// The enclosing group lane's own layer path overrides, one hop only, matching the native walk.
|
||||||
other => {
|
other => {
|
||||||
if let Some(typed_list) = extract_variant(other) {
|
if let Some(typed_list) = extract_variant(other) {
|
||||||
for mut item in typed_list.into_iter() {
|
for item in typed_list.into_iter() {
|
||||||
// Each `|| item.attribute(...)` keeps an attribute the item itself carries
|
let mut row = Item::from_parts(item.into_element(), attributes.clone());
|
||||||
// (recomposed with the parent's identity value) even when the parent lacks it
|
if let Some(layer_path) = parent_layer_path {
|
||||||
if parent_has_transform || item.attribute::<DAffine2>(ATTR_TRANSFORM).is_some() {
|
row.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.to_vec());
|
||||||
let item_transform: DAffine2 = item.attribute_cloned_or_default(ATTR_TRANSFORM);
|
|
||||||
item.set_attribute(ATTR_TRANSFORM, current_transform * item_transform);
|
|
||||||
}
|
}
|
||||||
if parent_has_opacity || item.attribute::<f64>(ATTR_OPACITY).is_some() {
|
output.push(row);
|
||||||
let item_opacity: f64 = item.attribute_cloned_or(ATTR_OPACITY, 1.);
|
|
||||||
item.set_attribute(ATTR_OPACITY, current_opacity * item_opacity);
|
|
||||||
}
|
|
||||||
if parent_has_fill || item.attribute::<f64>(ATTR_OPACITY_FILL).is_some() {
|
|
||||||
let item_fill: f64 = item.attribute_cloned_or(ATTR_OPACITY_FILL, 1.);
|
|
||||||
item.set_attribute(ATTR_OPACITY_FILL, current_fill * item_fill);
|
|
||||||
}
|
|
||||||
if parent_has_layer_path {
|
|
||||||
item.set_attribute(ATTR_EDITOR_LAYER_PATH, layer_path.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +190,7 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut output = List::new();
|
let mut output = List::new();
|
||||||
flatten_recursive(&mut output, content, extract_variant);
|
flatten_recursive(&mut output, content, extract_variant, None);
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,22 +399,13 @@ pub fn set_paint_attribute_at<T>(list: &mut List<T>, index: usize, key: &str, pa
|
|||||||
/// Bake the provided transform into the per-item transforms of the paint graphics stored under the
|
/// Bake the provided transform into the per-item transforms of the paint graphics stored under the
|
||||||
/// canonical `List<Graphic>` fill and stroke attributes.
|
/// 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_list_transform<T>(list: &mut List<T>, transform: DAffine2) {
|
fn bake_graphic_paint_transform(graphics: &mut List<Graphic>, transform: DAffine2) {
|
||||||
for item_transform in list.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
for item_transform in graphics.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
||||||
*item_transform = transform * *item_transform;
|
*item_transform = transform * *item_transform;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn bake_graphic_paint_transform(graphics: &mut List<Graphic>, transform: DAffine2) {
|
|
||||||
for graphic in graphics.iter_element_values_mut() {
|
for graphic in graphics.iter_element_values_mut() {
|
||||||
match graphic {
|
if let Graphic::Graphic(list) = graphic {
|
||||||
Graphic::Graphic(list) => bake_list_transform(list, transform),
|
bake_graphic_paint_transform(list, transform);
|
||||||
Graphic::Vector(list) => bake_list_transform(list, transform),
|
|
||||||
Graphic::RasterCPU(list) => bake_list_transform(list, transform),
|
|
||||||
Graphic::RasterGPU(list) => bake_list_transform(list, transform),
|
|
||||||
Graphic::Gradient(list) => bake_list_transform(list, transform),
|
|
||||||
Graphic::Text(list) => bake_list_transform(list, transform),
|
|
||||||
Graphic::Color(_) | Graphic::Group(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,31 +425,31 @@ pub trait TryFromGraphic: Clone + Sized {
|
|||||||
|
|
||||||
impl TryFromGraphic for Vector {
|
impl TryFromGraphic for Vector {
|
||||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||||
if let Graphic::Vector(t) = graphic { Some(t) } else { None }
|
if let Graphic::Vector(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFromGraphic for Raster<CPU> {
|
impl TryFromGraphic for Raster<CPU> {
|
||||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||||
if let Graphic::RasterCPU(t) = graphic { Some(t) } else { None }
|
if let Graphic::RasterCPU(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFromGraphic for Color {
|
impl TryFromGraphic for Color {
|
||||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||||
if let Graphic::Color(t) = graphic { Some(t) } else { None }
|
if let Graphic::Color(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFromGraphic for GradientStops {
|
impl TryFromGraphic for GradientStops {
|
||||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||||
if let Graphic::Gradient(t) = graphic { Some(t) } else { None }
|
if let Graphic::Gradient(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFromGraphic for String {
|
impl TryFromGraphic for String {
|
||||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||||
if let Graphic::Text(t) = graphic { Some(t) } else { None }
|
if let Graphic::Text(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,49 +474,37 @@ impl IntoGraphicList for List<Graphic> {
|
|||||||
|
|
||||||
impl IntoGraphicList for List<Vector> {
|
impl IntoGraphicList for List<Vector> {
|
||||||
fn into_graphic_list(self) -> List<Graphic> {
|
fn into_graphic_list(self) -> List<Graphic> {
|
||||||
// Propagate `editor:layer_path` from item 0 onto the wrapper Graphic item so a subsequent
|
detable_items(self, Graphic::Vector)
|
||||||
// `flatten_graphic_list` doesn't overwrite the inner Vector's stamp with an empty value
|
|
||||||
let layer_path: Vec<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
|
|
||||||
let mut graphic_list = List::new_from_element(Graphic::Vector(self));
|
|
||||||
if !layer_path.is_empty() {
|
|
||||||
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
|
|
||||||
}
|
|
||||||
graphic_list
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoGraphicList for List<Raster<CPU>> {
|
impl IntoGraphicList for List<Raster<CPU>> {
|
||||||
fn into_graphic_list(self) -> List<Graphic> {
|
fn into_graphic_list(self) -> List<Graphic> {
|
||||||
List::new_from_element(Graphic::RasterCPU(self))
|
detable_items(self, Graphic::RasterCPU)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoGraphicList for List<Raster<GPU>> {
|
impl IntoGraphicList for List<Raster<GPU>> {
|
||||||
fn into_graphic_list(self) -> List<Graphic> {
|
fn into_graphic_list(self) -> List<Graphic> {
|
||||||
List::new_from_element(Graphic::RasterGPU(self))
|
detable_items(self, Graphic::RasterGPU)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoGraphicList for List<Color> {
|
impl IntoGraphicList for List<Color> {
|
||||||
fn into_graphic_list(self) -> List<Graphic> {
|
fn into_graphic_list(self) -> List<Graphic> {
|
||||||
List::new_from_element(Graphic::Color(self))
|
detable_items(self, Graphic::Color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoGraphicList for List<GradientStops> {
|
impl IntoGraphicList for List<GradientStops> {
|
||||||
fn into_graphic_list(self) -> List<Graphic> {
|
fn into_graphic_list(self) -> List<Graphic> {
|
||||||
List::new_from_element(Graphic::Gradient(self))
|
detable_items(self, Graphic::Gradient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoGraphicList for List<String> {
|
impl IntoGraphicList for List<String> {
|
||||||
fn into_graphic_list(self) -> List<Graphic> {
|
fn into_graphic_list(self) -> List<Graphic> {
|
||||||
let layer_path: Vec<NodeId> = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0);
|
detable_items(self, Graphic::Text)
|
||||||
let mut graphic_list = List::new_from_element(Graphic::Text(self));
|
|
||||||
if !layer_path.is_empty() {
|
|
||||||
graphic_list.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path);
|
|
||||||
}
|
|
||||||
graphic_list
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +524,7 @@ impl From<DAffine2> for Graphic {
|
|||||||
// DVec2
|
// DVec2
|
||||||
impl From<DVec2> for Graphic {
|
impl From<DVec2> for Graphic {
|
||||||
fn from(position: DVec2) -> Self {
|
fn from(position: DVec2) -> Self {
|
||||||
Graphic::Vector(List::new_from_element(Vector::from_anchor_position(position)))
|
Graphic::Vector(Vector::from_anchor_position(position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Note: List conversions handled by blanket impl in gcore
|
// Note: List conversions handled by blanket impl in gcore
|
||||||
@@ -564,54 +544,50 @@ impl Graphic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_vector(&self) -> Option<&List<Vector>> {
|
pub fn as_vector(&self) -> Option<&Vector> {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(vector) => Some(vector),
|
Graphic::Vector(vector) => Some(vector),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_vector_mut(&mut self) -> Option<&mut List<Vector>> {
|
pub fn as_vector_mut(&mut self) -> Option<&mut Vector> {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(vector) => Some(vector),
|
Graphic::Vector(vector) => Some(vector),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_raster(&self) -> Option<&List<Raster<CPU>>> {
|
pub fn as_raster(&self) -> Option<&Raster<CPU>> {
|
||||||
match self {
|
match self {
|
||||||
Graphic::RasterCPU(raster) => Some(raster),
|
Graphic::RasterCPU(raster) => Some(raster),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_raster_mut(&mut self) -> Option<&mut List<Raster<CPU>>> {
|
pub fn as_raster_mut(&mut self) -> Option<&mut Raster<CPU>> {
|
||||||
match self {
|
match self {
|
||||||
Graphic::RasterCPU(raster) => Some(raster),
|
Graphic::RasterCPU(raster) => Some(raster),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A leaf carries no clipping attribute, which rides its containing lane.
|
||||||
pub fn had_clip_enabled(&self) -> bool {
|
pub fn had_clip_enabled(&self) -> bool {
|
||||||
fn all_clipped<T>(list: &List<T>) -> bool {
|
fn all_clipped<T>(list: &List<T>) -> bool {
|
||||||
list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
|
list.iter_attribute_values_or_default::<bool>(ATTR_CLIPPING_MASK).all(|clip| clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(list) => all_clipped(list),
|
|
||||||
Graphic::Graphic(list) => all_clipped(list),
|
Graphic::Graphic(list) => all_clipped(list),
|
||||||
Graphic::RasterCPU(list) => all_clipped(list),
|
|
||||||
Graphic::RasterGPU(list) => all_clipped(list),
|
|
||||||
Graphic::Color(list) => all_clipped(list),
|
|
||||||
Graphic::Gradient(list) => all_clipped(list),
|
|
||||||
Graphic::Text(list) => all_clipped(list),
|
|
||||||
Graphic::Group(group) => group_all_clipped(group),
|
Graphic::Group(group) => group_all_clipped(group),
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_reduce_to_clip_path(&self) -> bool {
|
pub fn can_reduce_to_clip_path(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(vector) => vector_can_reduce_to_clip_path(vector),
|
Graphic::Vector(vector) => vector_can_reduce_to_clip_path(&core_types::lane::Single(vector)),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -619,23 +595,11 @@ impl Graphic {
|
|||||||
pub fn is_opaque(&self) -> bool {
|
pub fn is_opaque(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => !list.is_empty() && list.iter_element_values().all(Graphic::is_opaque),
|
Graphic::Graphic(list) => !list.is_empty() && list.iter_element_values().all(Graphic::is_opaque),
|
||||||
Graphic::Vector(list) => {
|
// A bare leaf carries no paint attribute, which rides its lane, so
|
||||||
fn is_paint_opaque_at<'a, A: Attribute<Value<'a> = Option<&'a List<Graphic>>>>(list: &'a List<Vector>, index: usize) -> bool {
|
// nothing here claims opacity.
|
||||||
paint_graphics::<A, _>(list, index).is_some_and(|graphic_list| graphic_list.element(0).is_some_and(|graphic| graphic.is_opaque()))
|
Graphic::Vector(_) => false,
|
||||||
}
|
Graphic::Color(color) => color.is_opaque(),
|
||||||
|
Graphic::Gradient(stops) => stops.iter().all(|stop| stop.color.is_opaque()),
|
||||||
!list.is_empty()
|
|
||||||
&& (0..list.len()).all(|i| {
|
|
||||||
let Some(vector) = list.element(i) else { return false };
|
|
||||||
let opacity: f64 = list.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
|
||||||
let opacity_fill: f64 = list.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
|
||||||
let fill_opaque = opacity_fill >= 1. - f64::EPSILON && is_paint_opaque_at::<Fill>(list, i);
|
|
||||||
let stroke_opaque_or_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_opaque_at::<Stroke>(list, i);
|
|
||||||
opacity >= 1. - f64::EPSILON && fill_opaque && stroke_opaque_or_invisible
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Graphic::Color(list) => list.element(0).is_some_and(|color| color.is_opaque()),
|
|
||||||
Graphic::Gradient(list) => list.element(0).is_some_and(|stops| stops.iter().all(|stop| stop.color.is_opaque())),
|
|
||||||
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Text(_) => false,
|
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Text(_) => false,
|
||||||
Graphic::Group(group) => group_is_opaque(group),
|
Graphic::Group(group) => group_is_opaque(group),
|
||||||
}
|
}
|
||||||
@@ -644,23 +608,11 @@ impl Graphic {
|
|||||||
pub fn is_fully_transparent(&self) -> bool {
|
pub fn is_fully_transparent(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => list.iter_element_values().all(Graphic::is_fully_transparent),
|
Graphic::Graphic(list) => list.iter_element_values().all(Graphic::is_fully_transparent),
|
||||||
Graphic::Vector(list) => (0..list.len()).all(|i| {
|
// A bare leaf carries no paint attribute, so only an unstroked
|
||||||
let Some(vector) = list.element(i) else { return false };
|
// vector is invisible on its own.
|
||||||
fn is_paint_fully_transparent_at<'a, A: Attribute<Value<'a> = Option<&'a List<Graphic>>>>(list: &'a List<Vector>, index: usize) -> bool {
|
Graphic::Vector(vector) => vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()),
|
||||||
paint_graphics::<A, _>(list, index).is_none_or(|graphic_list| graphic_list.element(0).is_none_or(|graphic| graphic.is_fully_transparent()))
|
Graphic::Color(color) => color.a() == 0.,
|
||||||
}
|
Graphic::Gradient(stops) => stops.iter().all(|stop| stop.color.a() == 0.),
|
||||||
|
|
||||||
let opacity: f64 = list.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
|
||||||
if opacity <= f64::EPSILON {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let opacity_fill: f64 = list.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
|
||||||
let fill_invisible = opacity_fill <= f64::EPSILON || is_paint_fully_transparent_at::<Fill>(list, i);
|
|
||||||
let stroke_invisible = vector.stroke.as_ref().is_none_or(|stroke| !stroke.has_renderable_stroke()) || is_paint_fully_transparent_at::<Stroke>(list, i);
|
|
||||||
fill_invisible && stroke_invisible
|
|
||||||
}),
|
|
||||||
Graphic::Color(list) => list.iter_element_values().all(|color| color.a() == 0.),
|
|
||||||
Graphic::Gradient(list) => list.iter_element_values().all(|stops| stops.iter().all(|stop| stop.color.a() == 0.)),
|
|
||||||
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Text(_) => false,
|
Graphic::RasterCPU(_) | Graphic::RasterGPU(_) | Graphic::Text(_) => false,
|
||||||
Graphic::Group(group) => group_is_fully_transparent(group),
|
Graphic::Group(group) => group_is_fully_transparent(group),
|
||||||
}
|
}
|
||||||
@@ -672,17 +624,12 @@ impl Graphic {
|
|||||||
matches!(self, Graphic::Color(_) | Graphic::Gradient(_)) && self.is_opaque()
|
matches!(self, Graphic::Color(_) | Graphic::Gradient(_)) && self.is_opaque()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this graphic's inner list is empty.
|
/// Whether the graphic holds no content: a leaf always holds its element.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => list.is_empty(),
|
Graphic::Graphic(list) => list.is_empty(),
|
||||||
Graphic::Vector(list) => list.is_empty(),
|
|
||||||
Graphic::Color(list) => list.is_empty(),
|
|
||||||
Graphic::Gradient(list) => list.is_empty(),
|
|
||||||
Graphic::RasterCPU(list) => list.is_empty(),
|
|
||||||
Graphic::RasterGPU(list) => list.is_empty(),
|
|
||||||
Graphic::Text(list) => list.is_empty(),
|
|
||||||
Graphic::Group(group) => group_is_empty(group),
|
Graphic::Group(group) => group_is_empty(group),
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -881,6 +828,83 @@ impl FlattenScale {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A graphic level in either of its two storages, as one lane source.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum GraphicLevel<'a> {
|
||||||
|
Legacy(&'a List<Graphic>),
|
||||||
|
Run(&'a core_types::record::GroupItem),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum GraphicLevelColumn<'a, A: Attribute> {
|
||||||
|
Legacy(core_types::list::ListColumn<'a, A>),
|
||||||
|
Run(core_types::record::RunColumn<'a, A>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A: Attribute> core_types::lane::LaneColumn<'a, A> for GraphicLevelColumn<'a, A> {
|
||||||
|
fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
|
||||||
|
match self {
|
||||||
|
GraphicLevelColumn::Legacy(column) => column.try_get(lane),
|
||||||
|
GraphicLevelColumn::Run(column) => column.try_get(lane),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LaneSource for GraphicLevel<'a> {
|
||||||
|
type Element = Graphic;
|
||||||
|
type Column<'b, A: Attribute>
|
||||||
|
= GraphicLevelColumn<'b, A>
|
||||||
|
where
|
||||||
|
Self: 'b;
|
||||||
|
|
||||||
|
fn lane_count(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
GraphicLevel::Legacy(list) => list.len(),
|
||||||
|
GraphicLevel::Run(item) => item.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn element(&self, lane: usize) -> Option<&Graphic> {
|
||||||
|
match self {
|
||||||
|
GraphicLevel::Legacy(list) => list.element(lane),
|
||||||
|
GraphicLevel::Run(item) => {
|
||||||
|
let lanes = item.typed_lanes::<Graphic>()?;
|
||||||
|
if lane >= lanes.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: the layout records the element type, and a parked
|
||||||
|
// element stores its reference at offset 0.
|
||||||
|
Some(unsafe { core_types::record::borrow_element::<Graphic>(item.lanes().get(lane).rec()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column<A: Attribute>(&self) -> GraphicLevelColumn<'_, A> {
|
||||||
|
match self {
|
||||||
|
GraphicLevel::Legacy(list) => GraphicLevelColumn::Legacy(list.column::<A>()),
|
||||||
|
GraphicLevel::Run(item) => GraphicLevelColumn::Run(core_types::record::RunColumn::of(item)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The lane's attributes as an owned set, read through the erased glue.
|
||||||
|
pub fn run_lane_attributes(item: &core_types::record::GroupItem, lane: usize) -> ItemAttributeValues {
|
||||||
|
let mut scratch: List<Vector> = List::new_from_element(Vector::default());
|
||||||
|
for field in &item.layout().fields {
|
||||||
|
// SAFETY: the offset comes from the item's own layout.
|
||||||
|
let value = unsafe { (field.read_erased)(item.lanes().get(lane).rec().ptr().add(field.offset)) };
|
||||||
|
scratch.set_attribute_value_dyn(field.name, 0, AttributeValueDyn(value));
|
||||||
|
}
|
||||||
|
scratch.clone_item_attributes(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The lane's attributes as an owned set, from either level storage.
|
||||||
|
pub fn lane_attributes(level: GraphicLevel<'_>, lane: usize) -> ItemAttributeValues {
|
||||||
|
match level {
|
||||||
|
GraphicLevel::Legacy(list) => list.clone_item_attributes(lane),
|
||||||
|
GraphicLevel::Run(item) => run_lane_attributes(item, lane),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// One flattened vector row served by [`walk_vector_rows`]: cheap probes
|
/// One flattened vector row served by [`walk_vector_rows`]: cheap probes
|
||||||
/// first, the full row built on demand.
|
/// first, the full row built on demand.
|
||||||
pub struct VectorRow<'w> {
|
pub struct VectorRow<'w> {
|
||||||
@@ -891,7 +915,9 @@ pub struct VectorRow<'w> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum RowSourceRef<'w> {
|
enum RowSourceRef<'w> {
|
||||||
Legacy(&'w List<Vector>, usize),
|
/// A de-tabled vector leaf on a graphic lane: the lane is the row.
|
||||||
|
Lane(GraphicLevel<'w>, usize),
|
||||||
|
/// A lane of a vector run.
|
||||||
Run(&'w core_types::record::RunView<'w, Vector>, &'w core_types::record::GroupItem, usize),
|
Run(&'w core_types::record::RunView<'w, Vector>, &'w core_types::record::GroupItem, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -899,7 +925,7 @@ impl VectorRow<'_> {
|
|||||||
/// The row's vector, borrowed.
|
/// The row's vector, borrowed.
|
||||||
pub fn element(&self) -> &Vector {
|
pub fn element(&self) -> &Vector {
|
||||||
match &self.source {
|
match &self.source {
|
||||||
RowSourceRef::Legacy(list, index) => list.element(*index).expect("the walk visits held rows"),
|
RowSourceRef::Lane(level, index) => level.element(*index).and_then(Graphic::as_vector).expect("the walk visits vector lanes"),
|
||||||
RowSourceRef::Run(run, _, index) => LaneSource::element(*run, *index).expect("the walk visits held lanes"),
|
RowSourceRef::Run(run, _, index) => LaneSource::element(*run, *index).expect("the walk visits held lanes"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -911,10 +937,7 @@ impl VectorRow<'_> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
match &self.source {
|
match &self.source {
|
||||||
RowSourceRef::Legacy(list, index) => list
|
RowSourceRef::Lane(level, index) => paint_graphics::<Fill, _>(level, *index).is_some(),
|
||||||
.attribute::<Option<List<Graphic>>>(ATTR_FILL, *index)
|
|
||||||
.and_then(|paint| paint.as_ref())
|
|
||||||
.is_some_and(is_paint_present),
|
|
||||||
RowSourceRef::Run(run, _, index) => paint_graphics::<Fill, _>(*run, *index).is_some(),
|
RowSourceRef::Run(run, _, index) => paint_graphics::<Fill, _>(*run, *index).is_some(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -924,16 +947,13 @@ impl VectorRow<'_> {
|
|||||||
pub fn build_into(&self, out: &mut List<Vector>) {
|
pub fn build_into(&self, out: &mut List<Vector>) {
|
||||||
let index = out.len();
|
let index = out.len();
|
||||||
match &self.source {
|
match &self.source {
|
||||||
RowSourceRef::Legacy(list, row) => {
|
RowSourceRef::Lane(level, lane) => {
|
||||||
out.push(list.clone_item(*row).expect("the walk visits held rows"));
|
let vector = self.element().clone();
|
||||||
|
out.push(Item::from_parts(vector, lane_attributes(*level, *lane)));
|
||||||
}
|
}
|
||||||
RowSourceRef::Run(run, item, lane) => {
|
RowSourceRef::Run(run, item, lane) => {
|
||||||
out.push(Item::new_from_element(LaneSource::element(*run, *lane).expect("the walk visits held lanes").clone()));
|
let vector = LaneSource::element(*run, *lane).expect("the walk visits held lanes").clone();
|
||||||
for field in &item.layout().fields {
|
out.push(Item::from_parts(vector, run_lane_attributes(item, *lane)));
|
||||||
// SAFETY: the offset comes from the item's own layout.
|
|
||||||
let value = unsafe { (field.read_erased)(item.lanes().get(*lane).rec().ptr().add(field.offset)) };
|
|
||||||
out.set_attribute_value_dyn(field.name, index, AttributeValueDyn(value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (key, slot) in [(ATTR_FILL, self.paint.fill), (ATTR_STROKE, self.paint.stroke)] {
|
for (key, slot) in [(ATTR_FILL, self.paint.fill), (ATTR_STROKE, self.paint.stroke)] {
|
||||||
@@ -959,20 +979,6 @@ impl VectorRow<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_rows_of_list(list: &List<Vector>, scale: FlattenScale, layer_path: Option<&[NodeId]>, paint: LanePaint<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) -> RowStep {
|
|
||||||
for row in 0..list.len() {
|
|
||||||
if let RowStep::Stop = visit(VectorRow {
|
|
||||||
source: RowSourceRef::Legacy(list, row),
|
|
||||||
scale,
|
|
||||||
layer_path,
|
|
||||||
paint,
|
|
||||||
}) {
|
|
||||||
return RowStep::Stop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowStep::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fn walk_rows_of_run(
|
fn walk_rows_of_run(
|
||||||
item: &core_types::record::GroupItem,
|
item: &core_types::record::GroupItem,
|
||||||
scale: FlattenScale,
|
scale: FlattenScale,
|
||||||
@@ -998,36 +1004,68 @@ 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`],
|
/// push-then-flatten lowering: lane paint threads with [`PaintReach`],
|
||||||
/// ancestor transform, opacity and fill opacity compose down, the immediate
|
/// ancestor transform, opacity and fill opacity compose down, the containing
|
||||||
/// parent's layer path overwrites its rows, and non-vector content is
|
/// level's parent layer path overwrites its rows, and non-vector content is
|
||||||
/// discarded.
|
/// discarded. A de-tabled leaf's row is its lane, attributes included.
|
||||||
pub fn walk_vector_rows<S: LaneSource<Element = Graphic>>(source: &S, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) {
|
pub fn walk_vector_rows(level: GraphicLevel<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) {
|
||||||
walk_vector_rows_impl(source, FlattenScale::ROOT, PaintReach::NONE, visit);
|
walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, visit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_vector_rows_impl<'a, S: LaneSource<Element = Graphic>>(
|
fn walk_vector_rows_impl<'a>(
|
||||||
source: &'a S,
|
level: GraphicLevel<'a>,
|
||||||
scale: FlattenScale,
|
scale: FlattenScale,
|
||||||
|
parent_layer_path: Option<&'a [NodeId]>,
|
||||||
inherited: PaintReach<'a>,
|
inherited: PaintReach<'a>,
|
||||||
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep,
|
||||||
) -> RowStep {
|
) -> RowStep {
|
||||||
let columns = PaintColumns::new(source);
|
if let GraphicLevel::Run(item) = level {
|
||||||
for index in 0..source.lane_count() {
|
// A vector-typed run is already its rows.
|
||||||
let Some(element) = source.element(index) else { continue };
|
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, 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 reach = inherited.for_lane(&columns, index);
|
||||||
let lane_scale = scale.composed(source, index);
|
|
||||||
let layer_path = source.try_attr::<EditorLayerPath>(index);
|
|
||||||
let row_paint = match reach.applies() {
|
let row_paint = match reach.applies() {
|
||||||
true => reach.paint,
|
true => reach.paint,
|
||||||
false => LanePaint::NONE,
|
false => LanePaint::NONE,
|
||||||
};
|
};
|
||||||
let step = match element {
|
let step = match element {
|
||||||
Graphic::Vector(inner) => walk_rows_of_list(inner, lane_scale, layer_path, row_paint, visit),
|
Graphic::Vector(_) => visit(VectorRow {
|
||||||
Graphic::Graphic(children) => walk_vector_rows_impl(children, lane_scale, reach.nested(), visit),
|
source: RowSourceRef::Lane(level, index),
|
||||||
Graphic::Group(group) => match core_types::record::RunView::<Graphic>::new(&group.content) {
|
scale,
|
||||||
Some(run) => walk_vector_rows_impl(&run, lane_scale, reach.into_group_graphics(), visit),
|
layer_path: parent_layer_path,
|
||||||
None => walk_rows_of_run(&group.content, lane_scale, layer_path, row_paint, visit),
|
paint: row_paint,
|
||||||
},
|
}),
|
||||||
|
Graphic::Graphic(children) => walk_vector_rows_impl(
|
||||||
|
GraphicLevel::Legacy(children),
|
||||||
|
scale.composed(&level, index),
|
||||||
|
level.try_attr::<EditorLayerPath>(index),
|
||||||
|
reach.nested(),
|
||||||
|
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, 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,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RowStep::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => RowStep::Continue,
|
_ => RowStep::Continue,
|
||||||
};
|
};
|
||||||
if let RowStep::Stop = step {
|
if let RowStep::Stop = step {
|
||||||
@@ -1039,9 +1077,9 @@ fn walk_vector_rows_impl<'a, S: LaneSource<Element = Graphic>>(
|
|||||||
|
|
||||||
/// The level's flattened vector rows as one owned list, the walk's collect
|
/// The level's flattened vector rows as one owned list, the walk's collect
|
||||||
/// form.
|
/// form.
|
||||||
pub fn flatten_vector_rows<S: LaneSource<Element = Graphic>>(source: &S) -> List<Vector> {
|
pub fn flatten_vector_rows(level: GraphicLevel<'_>) -> List<Vector> {
|
||||||
let mut out = List::new();
|
let mut out = List::new();
|
||||||
walk_vector_rows(source, &mut |row| {
|
walk_vector_rows(level, &mut |row| {
|
||||||
row.build_into(&mut out);
|
row.build_into(&mut out);
|
||||||
RowStep::Continue
|
RowStep::Continue
|
||||||
});
|
});
|
||||||
@@ -1071,22 +1109,11 @@ fn push_lane_paint_into_interiors(list: &mut List<Graphic>) {
|
|||||||
let Some(paint) = stored.filter(|paint| is_paint_present(paint)).cloned() else {
|
let Some(paint) = stored.filter(|paint| is_paint_present(paint)).cloned() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(element) = list.element_mut(index) else { continue };
|
let Some(Graphic::Graphic(children)) = list.element_mut(index) else { continue };
|
||||||
let fill_list = |inner: &mut List<Vector>| {
|
for child in 0..children.len() {
|
||||||
for item in 0..inner.len() {
|
if matches!(children.element(child), Some(Graphic::Vector(_))) {
|
||||||
set_paint_attribute_at(inner, item, key, paint.clone());
|
set_paint_attribute_at(children, child, key, paint.clone());
|
||||||
}
|
}
|
||||||
};
|
|
||||||
match element {
|
|
||||||
Graphic::Vector(inner) => fill_list(inner),
|
|
||||||
Graphic::Graphic(children) => {
|
|
||||||
for child in children.iter_element_values_mut() {
|
|
||||||
if let Some(inner) = child.as_vector_mut() {
|
|
||||||
fill_list(inner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1213,7 +1240,7 @@ const _: () = {
|
|||||||
/// [`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.
|
||||||
pub fn direct_vector_len(graphic: &Graphic) -> usize {
|
pub fn direct_vector_len(graphic: &Graphic) -> usize {
|
||||||
match graphic {
|
match graphic {
|
||||||
Graphic::Vector(list) => list.len(),
|
Graphic::Vector(_) => 1,
|
||||||
Graphic::Group(group) => match &group.row {
|
Graphic::Group(group) => match &group.row {
|
||||||
None => group.content.typed_lanes::<Vector>().map_or(0, |lanes| lanes.len()),
|
None => group.content.typed_lanes::<Vector>().map_or(0, |lanes| lanes.len()),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
@@ -1244,14 +1271,14 @@ pub fn group_to_legacy_graphic(group: &core_types::record::Group) -> Graphic {
|
|||||||
if group.row.is_none() {
|
if group.row.is_none() {
|
||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
let typed = None
|
let typed = None
|
||||||
.or_else(|| run_to_legacy_list::<Vector>(item).map(Graphic::Vector))
|
.or_else(|| run_to_legacy_list::<Vector>(item).map(|list| detable_items(list, Graphic::Vector)))
|
||||||
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(Graphic::RasterCPU))
|
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(|list| detable_items(list, Graphic::RasterCPU)))
|
||||||
.or_else(|| run_to_legacy_list::<Raster<GPU>>(item).map(Graphic::RasterGPU))
|
.or_else(|| run_to_legacy_list::<Raster<GPU>>(item).map(|list| detable_items(list, Graphic::RasterGPU)))
|
||||||
.or_else(|| run_to_legacy_list::<Color>(item).map(Graphic::Color))
|
.or_else(|| run_to_legacy_list::<Color>(item).map(|list| detable_items(list, Graphic::Color)))
|
||||||
.or_else(|| run_to_legacy_list::<GradientStops>(item).map(Graphic::Gradient))
|
.or_else(|| run_to_legacy_list::<GradientStops>(item).map(|list| detable_items(list, Graphic::Gradient)))
|
||||||
.or_else(|| run_to_legacy_list::<String>(item).map(Graphic::Text));
|
.or_else(|| run_to_legacy_list::<String>(item).map(|list| detable_items(list, Graphic::Text)));
|
||||||
if let Some(typed) = typed {
|
if let Some(typed) = typed {
|
||||||
return typed;
|
return Graphic::Graphic(typed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Graphic::Graphic(group_to_legacy_list(group))
|
Graphic::Graphic(group_to_legacy_list(group))
|
||||||
@@ -1268,17 +1295,13 @@ pub fn group_to_legacy_list(group: &core_types::record::Group) -> List<Graphic>
|
|||||||
push_lane_paint_into_interiors(&mut list);
|
push_lane_paint_into_interiors(&mut list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
let element = None
|
None.or_else(|| run_to_legacy_list::<Vector>(item).map(|list| detable_items(list, Graphic::Vector)))
|
||||||
.or_else(|| run_to_legacy_list::<Vector>(item).map(Graphic::Vector))
|
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(|list| detable_items(list, Graphic::RasterCPU)))
|
||||||
.or_else(|| run_to_legacy_list::<Raster<CPU>>(item).map(Graphic::RasterCPU))
|
.or_else(|| run_to_legacy_list::<Raster<GPU>>(item).map(|list| detable_items(list, Graphic::RasterGPU)))
|
||||||
.or_else(|| run_to_legacy_list::<Raster<GPU>>(item).map(Graphic::RasterGPU))
|
.or_else(|| run_to_legacy_list::<Color>(item).map(|list| detable_items(list, Graphic::Color)))
|
||||||
.or_else(|| run_to_legacy_list::<Color>(item).map(Graphic::Color))
|
.or_else(|| run_to_legacy_list::<GradientStops>(item).map(|list| detable_items(list, Graphic::Gradient)))
|
||||||
.or_else(|| run_to_legacy_list::<GradientStops>(item).map(Graphic::Gradient))
|
.or_else(|| run_to_legacy_list::<String>(item).map(|list| detable_items(list, Graphic::Text)))
|
||||||
.or_else(|| run_to_legacy_list::<String>(item).map(Graphic::Text));
|
.unwrap_or_default()
|
||||||
match element {
|
|
||||||
Some(element) => List::new_from_element(element),
|
|
||||||
None => List::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group_render_complexity(group: &core_types::record::Group) -> usize {
|
fn group_render_complexity(group: &core_types::record::Group) -> usize {
|
||||||
@@ -1300,13 +1323,13 @@ fn group_render_complexity(group: &core_types::record::Group) -> usize {
|
|||||||
impl BoundingBox for Graphic {
|
impl BoundingBox for Graphic {
|
||||||
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Vector(list) => list.bounding_box(transform, include_stroke),
|
Graphic::Vector(vector) => BoundingBox::bounding_box(vector, transform, include_stroke),
|
||||||
Graphic::RasterCPU(list) => list.bounding_box(transform, include_stroke),
|
Graphic::RasterCPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||||
Graphic::RasterGPU(list) => list.bounding_box(transform, include_stroke),
|
Graphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke),
|
||||||
Graphic::Graphic(list) => list.bounding_box(transform, include_stroke),
|
Graphic::Graphic(list) => list.bounding_box(transform, include_stroke),
|
||||||
Graphic::Color(list) => list.bounding_box(transform, include_stroke),
|
Graphic::Color(color) => color.bounding_box(transform, include_stroke),
|
||||||
Graphic::Gradient(list) => list.bounding_box(transform, include_stroke),
|
Graphic::Gradient(gradient) => gradient.bounding_box(transform, include_stroke),
|
||||||
Graphic::Text(list) => list.bounding_box(transform, include_stroke),
|
Graphic::Text(text) => text.bounding_box(transform, include_stroke),
|
||||||
Graphic::Group(group) => group_bounding_box(group, transform, include_stroke, false),
|
Graphic::Group(group) => group_bounding_box(group, transform, include_stroke, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1327,17 +1350,17 @@ impl BoundingBox for Graphic {
|
|||||||
|
|
||||||
impl ListConvert<Graphic> for Vector {
|
impl ListConvert<Graphic> for Vector {
|
||||||
fn convert_item(self) -> Graphic {
|
fn convert_item(self) -> Graphic {
|
||||||
Graphic::Vector(List::new_from_element(self))
|
Graphic::Vector(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ListConvert<Graphic> for Raster<CPU> {
|
impl ListConvert<Graphic> for Raster<CPU> {
|
||||||
fn convert_item(self) -> Graphic {
|
fn convert_item(self) -> Graphic {
|
||||||
Graphic::RasterCPU(List::new_from_element(self))
|
Graphic::RasterCPU(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ListConvert<Graphic> for Raster<GPU> {
|
impl ListConvert<Graphic> for Raster<GPU> {
|
||||||
fn convert_item(self) -> Graphic {
|
fn convert_item(self) -> Graphic {
|
||||||
Graphic::RasterGPU(List::new_from_element(self))
|
Graphic::RasterGPU(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1433,7 +1456,7 @@ mod tests {
|
|||||||
use core_types::list::List;
|
use core_types::list::List;
|
||||||
|
|
||||||
fn vector_graphic() -> Graphic {
|
fn vector_graphic() -> Graphic {
|
||||||
Graphic::Vector(List::new_from_element(Vector::default()))
|
Graphic::Vector(Vector::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flattening must not invent attribute columns that neither the parent graphic nor the child carried
|
// Flattening must not invent attribute columns that neither the parent graphic nor the child carried
|
||||||
@@ -1479,7 +1502,7 @@ mod run_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_run_serves_the_parked_paint_reference() {
|
fn a_run_serves_the_parked_paint_reference() {
|
||||||
let paint = List::new_from_element(Graphic::Color(List::new_from_element(Color::BLACK)));
|
let paint = List::new_from_element(Graphic::Color(Color::BLACK));
|
||||||
let vector = unit_square_at(DVec2::ZERO);
|
let vector = unit_square_at(DVec2::ZERO);
|
||||||
|
|
||||||
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
||||||
@@ -1505,7 +1528,7 @@ mod run_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_owned_group_replays_content_equal_after_the_source_dies() {
|
fn an_owned_group_replays_content_equal_after_the_source_dies() {
|
||||||
let paint = List::new_from_element(Graphic::Color(List::new_from_element(Color::BLACK)));
|
let paint = List::new_from_element(Graphic::Color(Color::BLACK));
|
||||||
let vector = unit_square_at(DVec2::ZERO);
|
let vector = unit_square_at(DVec2::ZERO);
|
||||||
|
|
||||||
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
||||||
@@ -1711,30 +1734,32 @@ mod run_tests {
|
|||||||
let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) };
|
let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) };
|
||||||
|
|
||||||
let mut painted = List::new();
|
let mut painted = List::new();
|
||||||
painted.push(Item::new_from_element(unit_square_at(DVec2::ZERO)));
|
painted.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ZERO))));
|
||||||
painted.push(Item::new_from_element(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(List::new_from_element(Color::WHITE))));
|
set_paint_attribute_at(&mut painted, 1, ATTR_FILL, List::new_from_element(Graphic::Color(Color::WHITE)));
|
||||||
|
|
||||||
let mut nested_child = List::new();
|
let mut nested = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(2., 2.))));
|
||||||
nested_child.push(Item::new_from_element(unit_square_at(DVec2::new(2., 2.))));
|
|
||||||
let mut nested = List::new_from_element(Graphic::Vector(nested_child));
|
|
||||||
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.)));
|
||||||
|
|
||||||
let mut top = List::new();
|
let mut top = List::new();
|
||||||
top.push(Item::new_from_element(Graphic::Vector(painted)));
|
top.push(Item::new_from_element(Graphic::Graphic(painted)));
|
||||||
top.push(Item::new_from_element(Graphic::Graphic(nested)));
|
top.push(Item::new_from_element(Graphic::Graphic(nested)));
|
||||||
top.push(Item::new_from_element(Graphic::Group(core_types::record::Group {
|
top.push(Item::new_from_element(Graphic::Group(core_types::record::Group {
|
||||||
row: None,
|
row: None,
|
||||||
content: inner_item,
|
content: inner_item,
|
||||||
})));
|
})));
|
||||||
top.push(Item::new_from_element(Graphic::Color(List::new_from_element(Color::BLACK))));
|
top.push(Item::new_from_element(Graphic::Color(Color::BLACK)));
|
||||||
|
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(List::new_from_element(Color::BLACK))));
|
set_paint_attribute_at(&mut top, 0, ATTR_FILL, List::new_from_element(Graphic::Color(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_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)));
|
||||||
|
|
||||||
let legacy = {
|
let legacy = {
|
||||||
let mut list = top.clone();
|
let mut list = top.clone();
|
||||||
@@ -1744,7 +1769,19 @@ mod run_tests {
|
|||||||
push_lane_paint_into_interiors(&mut list);
|
push_lane_paint_into_interiors(&mut list);
|
||||||
list.into_flattened_list::<Vector>()
|
list.into_flattened_list::<Vector>()
|
||||||
};
|
};
|
||||||
assert_eq!(flatten_vector_rows(&top), legacy);
|
let native = flatten_vector_rows(GraphicLevel::Legacy(&top));
|
||||||
|
assert_eq!(native.len(), legacy.len());
|
||||||
|
for row in 0..native.len() {
|
||||||
|
assert_eq!(native.attribute::<DAffine2>(core_types::ATTR_TRANSFORM, row), legacy.attribute::<DAffine2>(core_types::ATTR_TRANSFORM, row), "transform, row {row}");
|
||||||
|
assert_eq!(native.attribute::<f64>(core_types::ATTR_OPACITY, row), legacy.attribute::<f64>(core_types::ATTR_OPACITY, row), "opacity, row {row}");
|
||||||
|
assert_eq!(
|
||||||
|
native.attribute::<Vec<core_types::uuid::NodeId>>(core_types::ATTR_EDITOR_LAYER_PATH, row),
|
||||||
|
legacy.attribute::<Vec<core_types::uuid::NodeId>>(core_types::ATTR_EDITOR_LAYER_PATH, row),
|
||||||
|
"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}");
|
||||||
|
}
|
||||||
|
assert_eq!(native, legacy);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1780,19 +1817,17 @@ mod run_tests {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod graphic_is_opaque_tests {
|
mod graphic_is_opaque_tests {
|
||||||
use vector_types::{ATTR_SPREAD_METHOD, GradientSpreadMethod, GradientStop};
|
use vector_types::GradientStop;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn color_graphic(alpha: f64) -> Graphic {
|
fn color_graphic(alpha: f64) -> Graphic {
|
||||||
let color = Color::from_rgbaf32(1., 0., 0., alpha as f32).unwrap();
|
let color = Color::from_rgbaf32(1., 0., 0., alpha as f32).unwrap();
|
||||||
Graphic::Color(List::new_from_element(color))
|
Graphic::Color(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gradient_graphic(gradient: GradientStops) -> Graphic {
|
fn gradient_graphic(gradient: GradientStops) -> Graphic {
|
||||||
let mut gradient_list = List::new_from_element(gradient);
|
Graphic::Gradient(gradient)
|
||||||
gradient_list.set_attribute(ATTR_SPREAD_METHOD, 0, GradientSpreadMethod::Pad);
|
|
||||||
Graphic::Gradient(gradient_list)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1809,7 +1844,7 @@ mod graphic_is_opaque_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vector_is_not_opaque() {
|
fn vector_is_not_opaque() {
|
||||||
let g = Graphic::Vector(List::default());
|
let g = Graphic::Vector(Vector::default());
|
||||||
assert!(!g.is_opaque());
|
assert!(!g.is_opaque());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::{Render, RenderSvgSegmentList, SvgRender};
|
|||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
use core_types::attribute::Transform;
|
use core_types::attribute::Transform;
|
||||||
use core_types::color::SRGBA8;
|
use core_types::color::SRGBA8;
|
||||||
use core_types::lane::LaneSource;
|
|
||||||
use core_types::list::List;
|
use core_types::list::List;
|
||||||
use core_types::uuid::generate_uuid;
|
use core_types::uuid::generate_uuid;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
@@ -53,6 +52,20 @@ pub trait RenderExt {
|
|||||||
) -> Self::Output;
|
) -> Self::Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The color paint attribute over any color lane source.
|
||||||
|
pub fn render_color_paint<S: core_types::lane::LaneSource<Element = Color>>(source: &S, target: PaintTarget) -> String {
|
||||||
|
let Some(color) = source.element(0) else {
|
||||||
|
return format!(r#" {}="none""#, target.paint_attr());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = format!(r##" {}="#{}""##, target.paint_attr(), SRGBA8::from(*color).to_rgb_hex());
|
||||||
|
if color.a() < 1. {
|
||||||
|
let _ = write!(result, r#" {}="{}""#, target.opacity_attr(), (color.a() * 1000.).round() / 1000.);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderExt for List<Color> {
|
impl RenderExt for List<Color> {
|
||||||
type Output = String;
|
type Output = String;
|
||||||
|
|
||||||
@@ -66,16 +79,7 @@ impl RenderExt for List<Color> {
|
|||||||
_render_params: &RenderParams,
|
_render_params: &RenderParams,
|
||||||
target: PaintTarget,
|
target: PaintTarget,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let Some(color) = self.element(0) else {
|
render_color_paint(self, target)
|
||||||
return format!(r#" {}="none""#, target.paint_attr());
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = format!(r##" {}="#{}""##, target.paint_attr(), SRGBA8::from(*color).to_rgb_hex());
|
|
||||||
if color.a() < 1. {
|
|
||||||
let _ = write!(result, r#" {}="{}""#, target.opacity_attr(), (color.a() * 1000.).round() / 1000.);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,12 +97,20 @@ impl RenderExt for List<GradientStops> {
|
|||||||
_render_params: &RenderParams,
|
_render_params: &RenderParams,
|
||||||
_target: PaintTarget,
|
_target: PaintTarget,
|
||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let mut stop = String::new();
|
render_gradient_paint(self, svg_defs, item_transform, element_transform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let Some(stops) = self.element(0) else { return 0 };
|
/// Adds the gradient def through mutating `svg_defs`, returning the gradient
|
||||||
let gradient_type: GradientType = self.attr::<GradientTypeAttr>(0);
|
/// ID, over any gradient lane source.
|
||||||
let local_gradient_transform: DAffine2 = self.attr::<Transform>(0);
|
pub fn render_gradient_paint<S: core_types::lane::LaneSource<Element = GradientStops>>(source: &S, svg_defs: &mut String, item_transform: DAffine2, element_transform: DAffine2) -> u64 {
|
||||||
let spread_method: GradientSpreadMethod = self.attr::<SpreadMethod>(0);
|
let mut stop = String::new();
|
||||||
|
|
||||||
|
{
|
||||||
|
let Some(stops) = source.element(0) else { return 0 };
|
||||||
|
let gradient_type: GradientType = source.attr::<GradientTypeAttr>(0);
|
||||||
|
let local_gradient_transform: DAffine2 = source.attr::<Transform>(0);
|
||||||
|
let spread_method: GradientSpreadMethod = source.attr::<SpreadMethod>(0);
|
||||||
|
|
||||||
for (position, color, original_midpoint) in stops.interpolated_samples() {
|
for (position, color, original_midpoint) in stops.interpolated_samples() {
|
||||||
stop.push_str("<stop");
|
stop.push_str("<stop");
|
||||||
@@ -239,9 +251,9 @@ impl RenderExt for List<Graphic> {
|
|||||||
let paint_attr = target.paint_attr();
|
let paint_attr = target.paint_attr();
|
||||||
|
|
||||||
match fill_graphic {
|
match fill_graphic {
|
||||||
Some(Graphic::Color(color_list)) => color_list.render(svg_defs, item_transform, element_transform, stroke_transform, bounds, render_params, target),
|
Some(Graphic::Color(color)) => render_color_paint(&core_types::lane::LeafLane::new(self, 0, color), target),
|
||||||
Some(Graphic::Gradient(gradient_list)) => {
|
Some(Graphic::Gradient(gradient)) => {
|
||||||
let gradient_id = gradient_list.render(svg_defs, item_transform, element_transform, stroke_transform, bounds, render_params, target);
|
let gradient_id = render_gradient_paint(&core_types::lane::LeafLane::new(self, 0, gradient), svg_defs, item_transform, element_transform);
|
||||||
format!(r##" {paint_attr}="url(#{gradient_id})""##)
|
format!(r##" {paint_attr}="url(#{gradient_id})""##)
|
||||||
}
|
}
|
||||||
Some(Graphic::Vector(_)) | Some(Graphic::RasterCPU(_)) | Some(Graphic::RasterGPU(_)) | Some(Graphic::Graphic(_)) | Some(Graphic::Text(_)) | Some(Graphic::Group(_)) => {
|
Some(Graphic::Vector(_)) | Some(Graphic::RasterCPU(_)) | Some(Graphic::RasterGPU(_)) | Some(Graphic::Graphic(_)) | Some(Graphic::Text(_)) | Some(Graphic::Group(_)) => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use core_types::color::Color;
|
|||||||
use core_types::color::SRGBA8;
|
use core_types::color::SRGBA8;
|
||||||
use core_types::lane::LaneSource;
|
use core_types::lane::LaneSource;
|
||||||
use core_types::list::{Item, List};
|
use core_types::list::{Item, List};
|
||||||
|
use core_types::lane::{LeafLane, Single};
|
||||||
use core_types::record::{Group, RunView};
|
use core_types::record::{Group, RunView};
|
||||||
use core_types::math::quad::Quad;
|
use core_types::math::quad::Quad;
|
||||||
use core_types::render_complexity::RenderComplexity;
|
use core_types::render_complexity::RenderComplexity;
|
||||||
@@ -399,7 +400,7 @@ pub(crate) fn gradient_placement(transform: DAffine2, gradient_type: GradientTyp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_peniko_gradient_brush(gradient_list: &List<GradientStops>, multiplied_transform: &DAffine2) -> Option<(peniko::Brush, DAffine2)> {
|
fn create_peniko_gradient_brush<S: LaneSource<Element = GradientStops>>(gradient_list: &S, multiplied_transform: &DAffine2) -> Option<(peniko::Brush, DAffine2)> {
|
||||||
let stops = gradient_list.element(0)?;
|
let stops = gradient_list.element(0)?;
|
||||||
|
|
||||||
let gradient_type: GradientType = gradient_list.attr::<GradientTypeAttr>(0);
|
let gradient_type: GradientType = gradient_list.attr::<GradientTypeAttr>(0);
|
||||||
@@ -552,12 +553,12 @@ impl Render for Graphic {
|
|||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => list.render_svg(render, render_params),
|
Graphic::Graphic(list) => list.render_svg(render, render_params),
|
||||||
Graphic::Vector(list) => list.render_svg(render, render_params),
|
Graphic::Vector(vector) => render_vector_svg(&Single(vector), render, render_params),
|
||||||
Graphic::RasterCPU(list) => list.render_svg(render, render_params),
|
Graphic::RasterCPU(raster) => render_raster_cpu_svg(&Single(raster), render, render_params),
|
||||||
Graphic::RasterGPU(_) => (),
|
Graphic::RasterGPU(_) => (),
|
||||||
Graphic::Color(list) => list.render_svg(render, render_params),
|
Graphic::Color(color) => render_color_svg(&Single(color), render, render_params),
|
||||||
Graphic::Gradient(list) => list.render_svg(render, render_params),
|
Graphic::Gradient(gradient) => render_gradient_svg(&Single(gradient), render, render_params),
|
||||||
Graphic::Text(list) => list.render_svg(render, render_params),
|
Graphic::Text(text) => render_text_svg(&Single(text), render, render_params),
|
||||||
Graphic::Group(group) => render_group_svg(group, PaintReach::NONE, render, render_params),
|
Graphic::Group(group) => render_group_svg(group, PaintReach::NONE, render, render_params),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,18 +566,18 @@ impl Render for Graphic {
|
|||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::Graphic(list) => list.render_to_vello(scene, transform, context, render_params),
|
||||||
Graphic::Vector(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::Vector(vector) => render_vector_vello(&Single(vector), scene, transform, context, render_params),
|
||||||
Graphic::RasterCPU(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::RasterCPU(raster) => render_raster_cpu_vello(&Single(raster), scene, transform, render_params),
|
||||||
Graphic::RasterGPU(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::RasterGPU(raster) => render_raster_gpu_vello(&Single(raster), scene, transform, context, render_params),
|
||||||
Graphic::Color(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::Color(color) => render_color_vello(&Single(color), scene, render_params),
|
||||||
Graphic::Gradient(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::Gradient(gradient) => render_gradient_vello(&Single(gradient), scene, transform, render_params),
|
||||||
Graphic::Text(list) => list.render_to_vello(scene, transform, context, render_params),
|
Graphic::Text(text) => render_text_vello(&Single(text), scene, transform, render_params),
|
||||||
Graphic::Group(group) => render_group_vello(group, PaintReach::NONE, scene, transform, context, render_params),
|
Graphic::Group(group) => render_group_vello(group, PaintReach::NONE, scene, transform, context, render_params),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
collect_element_metadata(self, PaintReach::NONE, metadata, footprint, element_id)
|
collect_element_metadata(self, PaintReach::NONE, DAffine2::IDENTITY, None, metadata, footprint, element_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
@@ -590,33 +591,22 @@ impl Render for Graphic {
|
|||||||
fn contains_artboard(&self) -> bool {
|
fn contains_artboard(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => list.contains_artboard(),
|
Graphic::Graphic(list) => list.contains_artboard(),
|
||||||
Graphic::Vector(list) => list.contains_artboard(),
|
_ => false,
|
||||||
Graphic::RasterCPU(list) => list.contains_artboard(),
|
|
||||||
Graphic::RasterGPU(list) => list.contains_artboard(),
|
|
||||||
Graphic::Color(list) => list.contains_artboard(),
|
|
||||||
Graphic::Gradient(list) => list.contains_artboard(),
|
|
||||||
Graphic::Text(list) => list.contains_artboard(),
|
|
||||||
Graphic::Group(_) => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||||
match self {
|
match self {
|
||||||
Graphic::Graphic(list) => list.new_ids_from_hash(reference),
|
Graphic::Graphic(list) => list.new_ids_from_hash(reference),
|
||||||
Graphic::Vector(list) => list.new_ids_from_hash(reference),
|
Graphic::Vector(vector) => vector.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default()),
|
||||||
Graphic::RasterCPU(_) => (),
|
_ => (),
|
||||||
Graphic::RasterGPU(_) => (),
|
|
||||||
Graphic::Color(_) => (),
|
|
||||||
Graphic::Gradient(_) => (),
|
|
||||||
Graphic::Text(_) => (),
|
|
||||||
Graphic::Group(_) => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(inner) if reach.applies() => render_vector_svg(&PaintOverlay::new(inner, reach.paint), render, render_params),
|
Graphic::Vector(vector) if reach.applies() => render_vector_svg(&PaintOverlay::new(&Single(vector), reach.paint), 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.nested(), 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),
|
||||||
@@ -625,7 +615,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(inner) if reach.applies() => render_vector_vello(&PaintOverlay::new(inner, reach.paint), scene, transform, context, render_params),
|
Graphic::Vector(vector) if reach.applies() => render_vector_vello(&PaintOverlay::new(&Single(vector), reach.paint), 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.nested(), 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),
|
||||||
@@ -634,7 +624,7 @@ fn render_element_vello<'a>(element: &'a Graphic, reach: PaintReach<'a>, scene:
|
|||||||
|
|
||||||
fn element_can_reduce_to_clip_path<'a>(element: &'a Graphic, reach: PaintReach<'a>) -> bool {
|
fn element_can_reduce_to_clip_path<'a>(element: &'a Graphic, reach: PaintReach<'a>) -> bool {
|
||||||
match element {
|
match element {
|
||||||
Graphic::Vector(inner) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(inner, reach.paint)),
|
Graphic::Vector(vector) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(&Single(vector), reach.paint)),
|
||||||
Graphic::Group(group) => match RunView::<Vector>::new(&group.content) {
|
Graphic::Group(group) => match RunView::<Vector>::new(&group.content) {
|
||||||
Some(run) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(&run, reach.paint)),
|
Some(run) if reach.applies() => vector_can_reduce_to_clip_path(&PaintOverlay::new(&run, reach.paint)),
|
||||||
Some(run) => vector_can_reduce_to_clip_path(&run),
|
Some(run) => vector_can_reduce_to_clip_path(&run),
|
||||||
@@ -644,80 +634,40 @@ fn element_can_reduce_to_clip_path<'a>(element: &'a Graphic, reach: PaintReach<'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_element_metadata<'a>(element: &'a Graphic, reach: PaintReach<'a>, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
fn collect_element_metadata<'a>(
|
||||||
|
element: &'a Graphic,
|
||||||
|
reach: PaintReach<'a>,
|
||||||
|
lane_transform: DAffine2,
|
||||||
|
lane_source: Option<NodeId>,
|
||||||
|
metadata: &mut RenderMetadata,
|
||||||
|
footprint: Footprint,
|
||||||
|
element_id: Option<NodeId>,
|
||||||
|
) {
|
||||||
if let Some(element_id) = element_id {
|
if let Some(element_id) = element_id {
|
||||||
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
match element {
|
match element {
|
||||||
Graphic::Group(group) => {
|
Graphic::Group(group) => collect_group_row_metadata(group, metadata, element_id),
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
Graphic::Graphic(_) => {}
|
||||||
collect_group_row_metadata(group, metadata, element_id);
|
// A leaf's layer identity and transform ride its containing lane.
|
||||||
|
Graphic::Vector(_) => {
|
||||||
|
metadata.first_element_source_id.insert(element_id, lane_source);
|
||||||
|
metadata.local_transforms.insert(element_id, lane_transform);
|
||||||
}
|
}
|
||||||
Graphic::Graphic(_) => {
|
_ => {
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.local_transforms.insert(element_id, lane_transform);
|
||||||
}
|
|
||||||
Graphic::Vector(list) => {
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
// TODO: Find a way to handle more than the first item
|
|
||||||
if !list.is_empty() {
|
|
||||||
let layer_path: &[NodeId] = list.attr::<EditorLayerPath>(0);
|
|
||||||
let layer = layer_path.last().copied();
|
|
||||||
let transform: DAffine2 = list.attr::<Transform>(0);
|
|
||||||
|
|
||||||
metadata.first_element_source_id.insert(element_id, layer);
|
|
||||||
metadata.local_transforms.insert(element_id, transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Graphic::RasterCPU(list) => {
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first item
|
|
||||||
if !list.is_empty() {
|
|
||||||
metadata.local_transforms.insert(element_id, list.attr::<Transform>(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Graphic::RasterGPU(list) => {
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first item
|
|
||||||
if !list.is_empty() {
|
|
||||||
metadata.local_transforms.insert(element_id, list.attr::<Transform>(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Graphic::Color(list) => {
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first item
|
|
||||||
if !list.is_empty() {
|
|
||||||
metadata.local_transforms.insert(element_id, list.attr::<Transform>(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Graphic::Gradient(list) => {
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first item
|
|
||||||
if !list.is_empty() {
|
|
||||||
metadata.local_transforms.insert(element_id, list.attr::<Transform>(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Graphic::Text(list) => {
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
|
|
||||||
// TODO: Find a way to handle more than the first item
|
|
||||||
if !list.is_empty() {
|
|
||||||
metadata.local_transforms.insert(element_id, list.attr::<Transform>(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.nested(), metadata, footprint, element_id),
|
||||||
Graphic::Vector(list) if reach.applies() => collect_vector_metadata(&PaintOverlay::new(list, reach.paint), metadata, footprint, element_id),
|
Graphic::Vector(vector) if reach.applies() => collect_vector_metadata(&PaintOverlay::new(&Single(vector), reach.paint), metadata, footprint, element_id),
|
||||||
Graphic::Vector(list) => collect_vector_metadata(list, metadata, footprint, element_id),
|
Graphic::Vector(vector) => collect_vector_metadata(&Single(vector), metadata, footprint, element_id),
|
||||||
Graphic::RasterCPU(list) => collect_raster_metadata(list, metadata, footprint, element_id),
|
Graphic::RasterCPU(raster) => collect_raster_metadata(&Single(raster), metadata, footprint, element_id),
|
||||||
Graphic::RasterGPU(list) => collect_raster_metadata(list, metadata, footprint, element_id),
|
Graphic::RasterGPU(raster) => collect_raster_metadata(&Single(raster), metadata, footprint, element_id),
|
||||||
Graphic::Color(_) => {}
|
Graphic::Color(_) => {}
|
||||||
Graphic::Gradient(_) => {}
|
Graphic::Gradient(_) => {}
|
||||||
Graphic::Text(list) => collect_text_metadata(list, metadata, footprint, element_id),
|
Graphic::Text(text) => collect_text_metadata(&Single(text), metadata, footprint, element_id),
|
||||||
Graphic::Group(group) => collect_group_metadata(group, reach, metadata, footprint, element_id),
|
Graphic::Group(group) => collect_group_metadata(group, reach, metadata, footprint, element_id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -754,11 +704,11 @@ 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.nested(), click_targets),
|
||||||
Graphic::Vector(list) if reach.applies() => add_vector_upstream_click_targets(&PaintOverlay::new(list, reach.paint), click_targets),
|
Graphic::Vector(vector) if reach.applies() => add_vector_upstream_click_targets(&PaintOverlay::new(&Single(vector), reach.paint), click_targets),
|
||||||
Graphic::Vector(list) => add_vector_upstream_click_targets(list, click_targets),
|
Graphic::Vector(vector) => add_vector_upstream_click_targets(&Single(vector), 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(_) => {}
|
||||||
Graphic::Text(list) => add_text_upstream_click_targets(list, click_targets),
|
Graphic::Text(text) => add_text_upstream_click_targets(&Single(text), click_targets),
|
||||||
Graphic::Group(group) => add_group_upstream_click_targets(group, reach, click_targets),
|
Graphic::Group(group) => add_group_upstream_click_targets(group, reach, click_targets),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -766,11 +716,11 @@ 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.nested(), outlines),
|
||||||
Graphic::Vector(list) if reach.applies() => add_vector_upstream_outline_targets(&PaintOverlay::new(list, reach.paint), outlines),
|
Graphic::Vector(vector) if reach.applies() => add_vector_upstream_outline_targets(&PaintOverlay::new(&Single(vector), reach.paint), outlines),
|
||||||
Graphic::Vector(list) => add_vector_upstream_outline_targets(list, outlines),
|
Graphic::Vector(vector) => add_vector_upstream_outline_targets(&Single(vector), 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(_) => {}
|
||||||
Graphic::Text(list) => add_text_upstream_click_targets(list, outlines),
|
Graphic::Text(text) => add_text_upstream_click_targets(&Single(text), outlines),
|
||||||
Graphic::Group(group) => add_group_upstream_outline_targets(group, reach, outlines),
|
Graphic::Group(group) => add_group_upstream_outline_targets(group, reach, outlines),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1190,10 +1140,10 @@ fn collect_graphic_metadata_with<'a, S: LaneSource<Element = Graphic>>(source: &
|
|||||||
footprint.transform *= item_transform;
|
footprint.transform *= item_transform;
|
||||||
|
|
||||||
if let Some(element_id) = layer {
|
if let Some(element_id) = layer {
|
||||||
collect_element_metadata(element, reach, metadata, footprint, Some(element_id));
|
collect_element_metadata(element, reach, item_transform, layer, metadata, footprint, Some(element_id));
|
||||||
} else {
|
} else {
|
||||||
// Recurse through anonymous wrapper items to reach nested content with editor:layer_path tags
|
// Recurse through anonymous wrapper items to reach nested content with editor:layer_path tags
|
||||||
collect_element_metadata(element, reach, metadata, footprint, None);
|
collect_element_metadata(element, reach, item_transform, layer, metadata, footprint, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1601,14 +1551,12 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(source: &S, scene: &mut
|
|||||||
for paint_index in 0..fill_graphic.len() {
|
for paint_index in 0..fill_graphic.len() {
|
||||||
let Some(paint) = fill_graphic.element(paint_index) else { continue };
|
let Some(paint) = fill_graphic.element(paint_index) else { continue };
|
||||||
match paint {
|
match paint {
|
||||||
Graphic::Color(list) => {
|
Graphic::Color(color) => {
|
||||||
let Some(color) = list.element(0) else { continue };
|
|
||||||
|
|
||||||
let fill = peniko::Brush::Solid(SRGBA8::from(*color).to_peniko_color());
|
let fill = peniko::Brush::Solid(SRGBA8::from(*color).to_peniko_color());
|
||||||
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
|
scene.fill(fill_rule, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
|
||||||
}
|
}
|
||||||
Graphic::Gradient(list) => {
|
Graphic::Gradient(gradient) => {
|
||||||
let Some((brush, gradient_to_device)) = create_peniko_gradient_brush(list, &multiplied_transform) else {
|
let Some((brush, gradient_to_device)) = create_peniko_gradient_brush(&LeafLane::new(fill_graphic, paint_index, gradient), &multiplied_transform) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1683,14 +1631,13 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(source: &S, scene: &mut
|
|||||||
};
|
};
|
||||||
|
|
||||||
match stroke_graphic {
|
match stroke_graphic {
|
||||||
Graphic::Color(list) => {
|
Graphic::Color(color) => {
|
||||||
let Some(color) = list.element(0) else { continue };
|
|
||||||
let brush = peniko::Brush::Solid(SRGBA8::from(*color).to_peniko_color());
|
let brush = peniko::Brush::Solid(SRGBA8::from(*color).to_peniko_color());
|
||||||
|
|
||||||
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), &brush, None, &path);
|
scene.stroke(&stroke, kurbo::Affine::new(element_transform.to_cols_array()), &brush, None, &path);
|
||||||
}
|
}
|
||||||
Graphic::Gradient(list) => {
|
Graphic::Gradient(gradient) => {
|
||||||
let Some((brush, gradient_to_device)) = create_peniko_gradient_brush(list, &multiplied_transform) else {
|
let Some((brush, gradient_to_device)) = create_peniko_gradient_brush(&LeafLane::new(stroke_graphic_list, paint_index, gradient), &multiplied_transform) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let inverse_element_transform = if transform_is_invertible(element_transform) {
|
let inverse_element_transform = if transform_is_invertible(element_transform) {
|
||||||
@@ -2655,7 +2602,7 @@ pub fn graphic_list_bounding_box<S: LaneSource<Element = Graphic>>(source: &S, t
|
|||||||
let item_transform = transform * source.attr::<Transform>(index);
|
let item_transform = transform * source.attr::<Transform>(index);
|
||||||
let Some(graphic) = source.element(index) else { continue };
|
let Some(graphic) = source.element(index) else { continue };
|
||||||
let bounds = match graphic {
|
let bounds = match graphic {
|
||||||
Graphic::Text(text_list) => text_list_bounding_box(text_list, item_transform),
|
Graphic::Text(text) => text_list_bounding_box(&Single(text), item_transform),
|
||||||
Graphic::Graphic(sub_list) => graphic_list_bounding_box(sub_list, item_transform),
|
Graphic::Graphic(sub_list) => graphic_list_bounding_box(sub_list, item_transform),
|
||||||
other => other.thumbnail_bounding_box(item_transform, true),
|
other => other.thumbnail_bounding_box(item_transform, true),
|
||||||
};
|
};
|
||||||
@@ -3081,7 +3028,6 @@ mod group_walk_tests {
|
|||||||
use core_types::attribute::Attribute;
|
use core_types::attribute::Attribute;
|
||||||
use core_types::node::RecordBatch;
|
use core_types::node::RecordBatch;
|
||||||
use core_types::record::{FieldWrite, GroupItem, Layout, element_write_hashed};
|
use core_types::record::{FieldWrite, GroupItem, Layout, element_write_hashed};
|
||||||
use graphic_types::graphic::group_to_legacy_graphic;
|
|
||||||
use graphic_types::markers::Fill;
|
use graphic_types::markers::Fill;
|
||||||
use graphic_types::vector_types::vector::PointId;
|
use graphic_types::vector_types::vector::PointId;
|
||||||
|
|
||||||
@@ -3090,7 +3036,7 @@ mod group_walk_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn color_paint() -> List<Graphic> {
|
fn color_paint() -> List<Graphic> {
|
||||||
List::new_from_element(Graphic::Color(List::new_from_element(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()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rendered_svg(render: impl FnOnce(&mut SvgRender)) -> (String, String) {
|
fn rendered_svg(render: impl FnOnce(&mut SvgRender)) -> (String, String) {
|
||||||
@@ -3117,7 +3063,7 @@ mod group_walk_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_vector_run_group_renders_like_its_legacy_form() {
|
fn a_vector_run_group_renders_its_rows_without_layer_wrappers() {
|
||||||
let paint = color_paint();
|
let paint = color_paint();
|
||||||
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 layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
||||||
@@ -3129,16 +3075,15 @@ mod group_walk_tests {
|
|||||||
|
|
||||||
let params = RenderParams::default();
|
let params = RenderParams::default();
|
||||||
let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, ¶ms));
|
let native = rendered_svg(|render| Graphic::Group(group.clone()).render_svg(render, ¶ms));
|
||||||
let legacy = rendered_svg(|render| group_to_legacy_graphic(&group).render_svg(render, ¶ms));
|
|
||||||
|
|
||||||
assert!(native.0.contains(r##"fill="#"##), "the run's fill paint must render: {}", native.0);
|
let expected = "\n<path d=\"M0,0 L1,0 L1,1 L0,1 L0,0 Z\" fill=\"#e77c9b\"/>\n<path d=\"M3,1 L4,1 L4,2 L3,2 L3,1 Z\" fill=\"none\"/>";
|
||||||
assert_eq!(native, legacy);
|
assert_eq!(native, (expected.to_string(), String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lane_paint_on_a_graphic_run_reaches_vector_interiors() {
|
fn lane_paint_on_a_graphic_run_reaches_vector_interiors() {
|
||||||
let paint = color_paint();
|
let paint = color_paint();
|
||||||
let inner = Graphic::Vector(List::new_from_element(unit_square_at(DVec2::ZERO)));
|
let inner = Graphic::Vector(unit_square_at(DVec2::ZERO));
|
||||||
let layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[FieldWrite::of::<Fill>(0)]);
|
let layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[FieldWrite::of::<Fill>(0)]);
|
||||||
// SAFETY: the layout carries a parked graphic element and the fill field.
|
// SAFETY: the layout carries a parked graphic element and the fill field.
|
||||||
let bytes = unsafe { write_lanes::<Graphic>(&layout, &[&inner], &[Some(&paint)]) };
|
let bytes = unsafe { write_lanes::<Graphic>(&layout, &[&inner], &[Some(&paint)]) };
|
||||||
@@ -3155,7 +3100,7 @@ mod group_walk_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_group_collects_like_its_legacy_lowering() {
|
fn a_group_collects_its_lane_metadata_for_the_caller() {
|
||||||
let paint = color_paint();
|
let paint = color_paint();
|
||||||
let vectors = [unit_square_at(DVec2::ZERO)];
|
let vectors = [unit_square_at(DVec2::ZERO)];
|
||||||
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]);
|
||||||
@@ -3171,12 +3116,12 @@ mod group_walk_tests {
|
|||||||
let mut native = RenderMetadata::default();
|
let mut native = RenderMetadata::default();
|
||||||
Graphic::Group(group.clone()).collect_metadata(&mut native, footprint, Some(caller));
|
Graphic::Group(group.clone()).collect_metadata(&mut native, footprint, Some(caller));
|
||||||
|
|
||||||
let mut legacy = RenderMetadata::default();
|
|
||||||
group_to_legacy_graphic(&group).collect_metadata(&mut legacy, footprint, Some(caller));
|
|
||||||
|
|
||||||
assert!(native.click_targets.get(&caller).is_some_and(|targets| !targets.is_empty()));
|
assert!(native.click_targets.get(&caller).is_some_and(|targets| !targets.is_empty()));
|
||||||
|
assert!(native.outlines.get(&caller).is_some_and(|targets| !targets.is_empty()));
|
||||||
assert!(native.local_transforms.contains_key(&caller));
|
assert!(native.local_transforms.contains_key(&caller));
|
||||||
assert_eq!(native, legacy);
|
assert!(native.upstream_footprints.contains_key(&caller));
|
||||||
|
assert_eq!(native.vector_data.get(&caller).map(|vector| vector.as_ref()), Some(&vectors[0]));
|
||||||
|
assert!(native.fill_attributes.get(&caller).is_some_and(|fill| matches!(fill.element(0), Some(Graphic::Color(_)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn text(label: &str) -> Graphic {
|
fn text(label: &str) -> Graphic {
|
||||||
Graphic::Text(List::new_from_element(label.to_string()))
|
Graphic::Text(label.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group(children: Vec<(Graphic, DAffine2)>) -> Graphic {
|
fn group(children: Vec<(Graphic, DAffine2)>) -> Graphic {
|
||||||
@@ -313,10 +313,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn text_of(graphic: &Graphic) -> &str {
|
fn text_of(graphic: &Graphic) -> &str {
|
||||||
let Graphic::Text(list) = graphic else {
|
let Graphic::Text(text) = graphic else {
|
||||||
panic!("expected a text leaf, got {graphic:?}");
|
panic!("expected a text leaf, got {graphic:?}");
|
||||||
};
|
};
|
||||||
list.element(0).expect("a text leaf holds its string")
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translation(x: f64) -> DAffine2 {
|
fn translation(x: f64) -> DAffine2 {
|
||||||
@@ -367,7 +367,7 @@ mod tests {
|
|||||||
let arg = core_types::ExtractVarArgs::vararg(input, 0).ok()?;
|
let arg = core_types::ExtractVarArgs::vararg(input, 0).ok()?;
|
||||||
let list = arg.downcast_ref::<core_types::list::List<Graphic>>()?;
|
let list = arg.downcast_ref::<core_types::list::List<Graphic>>()?;
|
||||||
let Graphic::Text(text) = list.element(0)? else { return None };
|
let Graphic::Text(text) = list.element(0)? else { return None };
|
||||||
Some(text.element(0)?.clone())
|
Some(text.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'e> Node<ContextImpl<'e>> for PerRowSource {
|
impl<'e> Node<ContextImpl<'e>> for PerRowSource {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use core_types::list::{Item, List};
|
|||||||
use core_types::uuid::NodeId;
|
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::graphic::{PaintColumns, PaintReach, bake_paint_transforms, set_paint_attribute, set_paint_attribute_at};
|
use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, set_paint_attribute, set_paint_attribute_at};
|
||||||
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::markers::{EditorMergedLayers, Fill, Stroke};
|
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
|
||||||
@@ -127,8 +127,7 @@ fn boolean_operation<'e>(
|
|||||||
> {
|
> {
|
||||||
// SAFETY: a materialized input's frames are arena-resident.
|
// SAFETY: a materialized input's frames are arena-resident.
|
||||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
let flattened = flatten_vector_run(GraphicLevel::Run(&item), DAffine2::IDENTITY, PaintReach::NONE);
|
||||||
let flattened = flatten_vector_run(&run, DAffine2::IDENTITY, PaintReach::NONE);
|
|
||||||
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item)
|
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item)
|
||||||
.expect("the run holds the row's element type")
|
.expect("the run holds the row's element type")
|
||||||
.into_graphic_list();
|
.into_graphic_list();
|
||||||
@@ -282,85 +281,69 @@ fn boolean_operation_on_vector_list(vector: &List<Vector>, boolean_operation: Bo
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A raster stand-in row: the image's unit rectangle under its transform,
|
/// A raster stand-in row per lane: the image's unit rectangle under its
|
||||||
/// black-filled, keeping the layer routing and blending attributes.
|
/// transform, black-filled, keeping the layer routing and blending
|
||||||
fn raster_stand_in_rows<T>(image: &List<T>, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
/// attributes.
|
||||||
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
fn raster_stand_in_rows<S: core_types::lane::LaneSource>(image: &S, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
(0..image.lane_count())
|
||||||
subpath.apply_transform(transform);
|
|
||||||
|
|
||||||
let element = Vector::from_subpath(subpath);
|
|
||||||
|
|
||||||
let mut item = Item::new_from_element(element)
|
|
||||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
|
||||||
.with_attribute(ATTR_OPACITY, opacity)
|
|
||||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
|
||||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
|
||||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
|
||||||
item
|
|
||||||
};
|
|
||||||
|
|
||||||
(0..image.len())
|
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
let row_transform: DAffine2 = image.attr::<TransformAttr>(i);
|
||||||
let layer: Vec<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
let layer: Vec<NodeId> = image.attr::<EditorLayerPath>(i).to_vec();
|
||||||
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
let blend_mode: BlendMode = image.attr::<BlendModeAttr>(i);
|
||||||
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
let opacity: f64 = image.attr::<Opacity>(i);
|
||||||
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
let fill: f64 = image.attr::<OpacityFill>(i);
|
||||||
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
let clip: bool = image.attr::<ClippingMask>(i);
|
||||||
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
|
||||||
|
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
|
subpath.apply_transform(parent_transform * row_transform);
|
||||||
|
|
||||||
|
let element = Vector::from_subpath(subpath);
|
||||||
|
|
||||||
|
let mut item = Item::new_from_element(element)
|
||||||
|
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
||||||
|
.with_attribute(ATTR_OPACITY, opacity)
|
||||||
|
.with_attribute(ATTR_OPACITY_FILL, fill)
|
||||||
|
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
||||||
|
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
||||||
|
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
||||||
|
item
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A color row: an empty vector carrying the color as its fill paint.
|
/// A color row: an empty vector carrying the color as its fill paint over the
|
||||||
fn color_paint_rows(color: &List<Color>) -> Vec<Item<Vector>> {
|
/// lane's attributes.
|
||||||
color
|
fn color_paint_row(color: Color, mut attributes: core_types::list::ItemAttributeValues) -> Item<Vector> {
|
||||||
.clone()
|
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
||||||
.into_iter()
|
|
||||||
.map(|row| {
|
|
||||||
let (color, mut attributes) = row.into_parts();
|
|
||||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
|
||||||
|
|
||||||
let mut element = Vector::default();
|
let mut element = Vector::default();
|
||||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
|
|
||||||
Item::from_parts(element, attributes)
|
Item::from_parts(element, attributes)
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A gradient row: an empty vector carrying the stops as its fill paint, the
|
/// A gradient row: an empty vector carrying the stops as its fill paint, the
|
||||||
/// gradient keys moved onto the paint.
|
/// gradient keys moved onto the paint.
|
||||||
fn gradient_paint_rows(gradient: &List<GradientStops>) -> Vec<Item<Vector>> {
|
fn gradient_paint_row(stops: GradientStops, mut attributes: core_types::list::ItemAttributeValues) -> Item<Vector> {
|
||||||
gradient
|
let mut gradient_paint = List::new_from_element(Graphic::Gradient(stops));
|
||||||
.clone()
|
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
||||||
.into_iter()
|
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||||
.map(|row| {
|
}
|
||||||
let (stops, mut attributes) = row.into_parts();
|
if let Some(gradient_type) = attributes.remove::<GradientType>(ATTR_GRADIENT_TYPE) {
|
||||||
|
gradient_paint.set_attribute(ATTR_GRADIENT_TYPE, 0, gradient_type);
|
||||||
|
}
|
||||||
|
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
||||||
|
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
|
||||||
|
}
|
||||||
|
attributes.insert(ATTR_FILL, Some(gradient_paint));
|
||||||
|
|
||||||
let mut gradient_paint = List::new_from_element(stops);
|
let mut element = Vector::default();
|
||||||
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
element.set_stroke_transform(DAffine2::IDENTITY);
|
||||||
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
|
||||||
}
|
|
||||||
if let Some(gradient_type) = attributes.remove::<GradientType>(ATTR_GRADIENT_TYPE) {
|
|
||||||
gradient_paint.set_attribute(ATTR_GRADIENT_TYPE, 0, gradient_type);
|
|
||||||
}
|
|
||||||
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
|
||||||
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
|
|
||||||
}
|
|
||||||
set_paint_attribute(&mut attributes, ATTR_FILL, gradient_paint);
|
|
||||||
|
|
||||||
let mut element = Vector::default();
|
Item::from_parts(element, attributes)
|
||||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
|
||||||
|
|
||||||
Item::from_parts(element, attributes)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A text row: the shaped glyph vectors under the composed transform.
|
/// A text lane's rows: the shaped glyph vectors under the composed transform.
|
||||||
fn text_rows(text: &List<String>, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
fn text_rows(text: &List<String>, parent_transform: DAffine2) -> Vec<Item<Vector>> {
|
||||||
text_nodes::shape_text_list(text, false)
|
text_nodes::shape_text_list(text, false)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -378,9 +361,25 @@ fn push_rows(out: &mut List<Vector>, rows: Vec<Item<Vector>>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_vector_rows(out: &mut List<Vector>, inner: &List<Vector>, composed: DAffine2, reach: PaintReach<'_>) {
|
/// A de-tabled vector leaf as one row: the lane's attributes with the reach
|
||||||
for row in 0..inner.len() {
|
/// paint and the ancestor transform composed.
|
||||||
let Some(item) = inner.clone_item(row) else { continue };
|
fn push_leaf_vector_row(out: &mut List<Vector>, level: GraphicLevel<'_>, index: usize, vector: &Vector, ancestors: DAffine2, reach: PaintReach<'_>) {
|
||||||
|
let out_index = out.len();
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let current: DAffine2 = out.attribute_cloned_or_default(ATTR_TRANSFORM, out_index);
|
||||||
|
out.set_attribute(ATTR_TRANSFORM, out_index, ancestors * current);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_vector_rows(out: &mut List<Vector>, rows: &List<Vector>, composed: DAffine2, reach: PaintReach<'_>) {
|
||||||
|
for row in 0..rows.len() {
|
||||||
|
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() {
|
if reach.applies() {
|
||||||
@@ -401,30 +400,34 @@ fn push_union(out: &mut List<Vector>, flattened: List<Vector>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The native flatten over a graphic level: the legacy flatten's arms over a
|
/// The native flatten over a graphic level: the legacy flatten's arms over
|
||||||
/// lane source, with lane paint threaded by [`PaintReach`] in place of the
|
/// either level storage, with lane paint threaded by [`PaintReach`], leaf
|
||||||
/// legacy pre-push, and native group runs walked directly.
|
/// attributes read from their lanes, and native group runs walked directly.
|
||||||
fn flatten_vector_run<'a, S: core_types::lane::LaneSource<Element = Graphic>>(source: &'a S, transform: DAffine2, inherited: PaintReach<'a>) -> List<Vector> {
|
fn flatten_vector_run(level: GraphicLevel<'_>, transform: DAffine2, inherited: PaintReach<'_>) -> List<Vector> {
|
||||||
let mut out = List::new();
|
let mut out = List::new();
|
||||||
flatten_vector_run_into(&mut out, source, transform, inherited);
|
flatten_vector_run_into(&mut out, level, transform, inherited);
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_vector_run_into<'a, S: core_types::lane::LaneSource<Element = Graphic>>(out: &mut List<Vector>, source: &'a S, transform: DAffine2, inherited: PaintReach<'a>) {
|
fn flatten_vector_run_into<'a>(out: &mut List<Vector>, level: GraphicLevel<'a>, transform: DAffine2, inherited: PaintReach<'a>) {
|
||||||
let columns = PaintColumns::new(source);
|
use core_types::lane::{LaneSource, LeafLane};
|
||||||
for index in 0..source.lane_count() {
|
let columns = PaintColumns::new(&level);
|
||||||
let Some(element) = source.element(index) else { continue };
|
for index in 0..level.lane_count() {
|
||||||
|
let Some(element) = level.element(index) else { continue };
|
||||||
let reach = inherited.for_lane(&columns, index);
|
let reach = inherited.for_lane(&columns, index);
|
||||||
let composed = transform * source.attr::<TransformAttr>(index);
|
let composed = transform * level.attr::<TransformAttr>(index);
|
||||||
match element {
|
match element {
|
||||||
Graphic::Vector(inner) => push_vector_rows(out, inner, composed, reach),
|
Graphic::Vector(vector) => push_leaf_vector_row(out, level, index, vector, transform, reach),
|
||||||
Graphic::Graphic(children) => push_union(out, flatten_vector_run(children, composed, reach.nested())),
|
Graphic::Graphic(children) => push_union(out, flatten_vector_run(GraphicLevel::Legacy(children), composed, reach.nested())),
|
||||||
Graphic::Group(group) => flatten_group(out, group, composed, reach),
|
Graphic::Group(group) => flatten_group(out, group, composed, reach),
|
||||||
Graphic::RasterCPU(image) => push_rows(out, raster_stand_in_rows(image, composed)),
|
Graphic::RasterCPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
||||||
Graphic::RasterGPU(image) => push_rows(out, raster_stand_in_rows(image, composed)),
|
Graphic::RasterGPU(raster) => push_rows(out, raster_stand_in_rows(&LeafLane::new(&level, index, raster), transform)),
|
||||||
Graphic::Color(color) => push_rows(out, color_paint_rows(color)),
|
Graphic::Color(color) => push_rows(out, vec![color_paint_row(*color, graphic_types::graphic::lane_attributes(level, index))]),
|
||||||
Graphic::Gradient(gradient) => push_rows(out, gradient_paint_rows(gradient)),
|
Graphic::Gradient(gradient) => push_rows(out, vec![gradient_paint_row(gradient.clone(), graphic_types::graphic::lane_attributes(level, index))]),
|
||||||
Graphic::Text(text) => push_rows(out, text_rows(text, composed)),
|
Graphic::Text(text) => {
|
||||||
|
let one = List::new_from_item(Item::from_parts(text.clone(), graphic_types::graphic::lane_attributes(level, index)));
|
||||||
|
push_rows(out, text_rows(&one, composed));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,172 +439,26 @@ fn flatten_group(out: &mut List<Vector>, group: &core_types::record::Group, comp
|
|||||||
let item = &group.content;
|
let item = &group.content;
|
||||||
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 let Some(run) = core_types::record::RunView::<Graphic>::new(item) {
|
} else if core_types::record::RunView::<Graphic>::new(item).is_some() {
|
||||||
push_union(out, flatten_vector_run(&run, composed, reach.into_group_graphics()));
|
push_union(out, flatten_vector_run(GraphicLevel::Run(item), composed, reach.into_group_graphics()));
|
||||||
} 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) {
|
||||||
push_rows(out, raster_stand_in_rows(&image, composed));
|
push_rows(out, raster_stand_in_rows(&image, composed));
|
||||||
} else if let Some(color) = graphic_types::graphic::run_to_list::<Color>(item) {
|
} else if let Some(color) = graphic_types::graphic::run_to_list::<Color>(item) {
|
||||||
push_rows(out, color_paint_rows(&color));
|
push_rows(out, (0..color.len()).filter_map(|i| Some(color_paint_row(*color.element(i)?, color.clone_item_attributes(i)))).collect());
|
||||||
} else if let Some(gradient) = graphic_types::graphic::run_to_list::<GradientStops>(item) {
|
} else if let Some(gradient) = graphic_types::graphic::run_to_list::<GradientStops>(item) {
|
||||||
push_rows(out, gradient_paint_rows(&gradient));
|
push_rows(
|
||||||
|
out,
|
||||||
|
(0..gradient.len())
|
||||||
|
.filter_map(|i| Some(gradient_paint_row(gradient.element(i)?.clone(), gradient.clone_item_attributes(i))))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
} else if let Some(text) = graphic_types::graphic::run_to_list::<String>(item) {
|
} else if let Some(text) = graphic_types::graphic::run_to_list::<String>(item) {
|
||||||
push_rows(out, text_rows(&text, composed));
|
push_rows(out, text_rows(&text, composed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The legacy baseline the flatten law compares against.
|
|
||||||
#[cfg(test)]
|
|
||||||
fn flatten_vector(graphic_list: &List<Graphic>) -> List<Vector> {
|
|
||||||
(0..graphic_list.len())
|
|
||||||
.flat_map(|index| {
|
|
||||||
let graphic = graphic_list.element(index).unwrap();
|
|
||||||
match graphic.clone() {
|
|
||||||
Graphic::Group(_) => Vec::new(),
|
|
||||||
Graphic::Vector(vector) => {
|
|
||||||
// Apply the parent graphic's transform to each element of the `List<Vector>`
|
|
||||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
|
||||||
vector
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut sub_vector| {
|
|
||||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
|
||||||
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
|
|
||||||
sub_vector
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
Graphic::RasterCPU(image) => {
|
|
||||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
|
||||||
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
|
||||||
subpath.apply_transform(transform);
|
|
||||||
|
|
||||||
let element = Vector::from_subpath(subpath);
|
|
||||||
|
|
||||||
let mut item = Item::new_from_element(element)
|
|
||||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
|
||||||
.with_attribute(ATTR_OPACITY, opacity)
|
|
||||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
|
||||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
|
||||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
|
||||||
item
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply the parent graphic's transform to each raster element, preserving each item's layer
|
|
||||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
|
||||||
// back to the originating raster layer
|
|
||||||
(0..image.len())
|
|
||||||
.map(|i| {
|
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
|
||||||
let layer: Vec<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
|
||||||
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
|
||||||
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
|
||||||
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
|
||||||
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
|
||||||
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
Graphic::RasterGPU(image) => {
|
|
||||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
|
||||||
let make_item = |transform, layer, blend_mode: BlendMode, opacity: f64, fill: f64, clip: bool| {
|
|
||||||
let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
|
||||||
subpath.apply_transform(transform);
|
|
||||||
|
|
||||||
let element = Vector::from_subpath(subpath);
|
|
||||||
|
|
||||||
let mut item = Item::new_from_element(element)
|
|
||||||
.with_attribute(ATTR_BLEND_MODE, blend_mode)
|
|
||||||
.with_attribute(ATTR_OPACITY, opacity)
|
|
||||||
.with_attribute(ATTR_OPACITY_FILL, fill)
|
|
||||||
.with_attribute(ATTR_CLIPPING_MASK, clip)
|
|
||||||
.with_attribute(ATTR_EDITOR_LAYER_PATH, layer);
|
|
||||||
set_paint_attribute(item.attributes_mut(), ATTR_FILL, List::new_from_element(Color::BLACK));
|
|
||||||
item
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply the parent graphic's transform to each raster element, preserving each item's layer
|
|
||||||
// and alpha_blending so the boolean op downstream can route clicks (and inherit blending state)
|
|
||||||
// back to the originating raster layer
|
|
||||||
(0..image.len())
|
|
||||||
.map(|i| {
|
|
||||||
let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i);
|
|
||||||
let layer: Vec<NodeId> = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i);
|
|
||||||
let blend_mode: BlendMode = image.attribute_cloned_or_default(ATTR_BLEND_MODE, i);
|
|
||||||
let opacity: f64 = image.attribute_cloned_or(ATTR_OPACITY, i, 1.);
|
|
||||||
let fill: f64 = image.attribute_cloned_or(ATTR_OPACITY_FILL, i, 1.);
|
|
||||||
let clip: bool = image.attribute_cloned_or_default(ATTR_CLIPPING_MASK, i);
|
|
||||||
make_item(parent_transform * row_transform, layer, blend_mode, opacity, fill, clip)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
Graphic::Graphic(mut graphic) => {
|
|
||||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
|
||||||
// Apply the parent graphic's transform to each element of the inner `List`
|
|
||||||
for transform in graphic.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
|
||||||
*transform = parent_transform * *transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively flatten the inner `List` into the output `List<Vector>`
|
|
||||||
let flattened = flatten_vector(&graphic);
|
|
||||||
let unioned = boolean_operation_on_vector_list(&flattened, BooleanOperation::Union);
|
|
||||||
|
|
||||||
unioned.into_iter().collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
Graphic::Color(color) => color
|
|
||||||
.into_iter()
|
|
||||||
.map(|row| {
|
|
||||||
let (color, mut attributes) = row.into_parts();
|
|
||||||
set_paint_attribute(&mut attributes, ATTR_FILL, List::new_from_element(color));
|
|
||||||
|
|
||||||
let mut element = Vector::default();
|
|
||||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
|
||||||
|
|
||||||
Item::from_parts(element, attributes)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
Graphic::Gradient(gradient) => gradient
|
|
||||||
.into_iter()
|
|
||||||
.map(|row| {
|
|
||||||
let (stops, mut attributes) = row.into_parts();
|
|
||||||
|
|
||||||
let mut gradient_paint = List::new_from_element(stops);
|
|
||||||
if let Some(transform) = attributes.remove::<DAffine2>(ATTR_TRANSFORM) {
|
|
||||||
gradient_paint.set_attribute(ATTR_TRANSFORM, 0, transform);
|
|
||||||
}
|
|
||||||
if let Some(gradient_type) = attributes.remove::<GradientType>(ATTR_GRADIENT_TYPE) {
|
|
||||||
gradient_paint.set_attribute(ATTR_GRADIENT_TYPE, 0, gradient_type);
|
|
||||||
}
|
|
||||||
if let Some(spread_method) = attributes.remove::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
|
||||||
gradient_paint.set_attribute(ATTR_SPREAD_METHOD, 0, spread_method);
|
|
||||||
}
|
|
||||||
set_paint_attribute(&mut attributes, ATTR_FILL, gradient_paint);
|
|
||||||
|
|
||||||
let mut element = Vector::default();
|
|
||||||
element.set_stroke_transform(DAffine2::IDENTITY);
|
|
||||||
|
|
||||||
Item::from_parts(element, attributes)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
Graphic::Text(text) => {
|
|
||||||
// Shape the glyphs into vectors (each item's own transform is applied), then compose the parent's transform like the other arms
|
|
||||||
let parent_transform: DAffine2 = graphic_list.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
|
||||||
text_nodes::shape_text_list(&text, false)
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut sub_vector| {
|
|
||||||
let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM);
|
|
||||||
*sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform;
|
|
||||||
sub_vector
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This quantization should potentially be removed since it's not conceptually necessary,
|
// This quantization should potentially be removed since it's not conceptually necessary,
|
||||||
// but without it, the oak leaf in the Changing Seasons demo artwork is funky because
|
// but without it, the oak leaf in the Changing Seasons demo artwork is funky because
|
||||||
// quantization is needed for the top and bottom points to line up vertically.
|
// quantization is needed for the top and bottom points to line up vertically.
|
||||||
@@ -699,11 +556,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn black_paint() -> List<Graphic> {
|
fn black_paint() -> List<Graphic> {
|
||||||
List::new_from_element(Graphic::Color(List::new_from_element(Color::BLACK)))
|
List::new_from_element(Graphic::Color(Color::BLACK))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_native_flatten_matches_the_legacy_flatten() {
|
fn the_native_flatten_reads_lanes_groups_and_reach() {
|
||||||
let inner_vector = square(DVec2::ZERO);
|
let inner_vector = square(DVec2::ZERO);
|
||||||
let inner_layout = core_types::record::Layout::default().with_writes(0, core_types::record::element_write_hashed::<Vector>(), &[]);
|
let inner_layout = core_types::record::Layout::default().with_writes(0, core_types::record::element_write_hashed::<Vector>(), &[]);
|
||||||
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()];
|
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()];
|
||||||
@@ -713,43 +570,32 @@ mod tests {
|
|||||||
// SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride.
|
// SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride.
|
||||||
let inner_item = unsafe { core_types::record::GroupItem::from_resident(core_types::node::RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) };
|
let inner_item = unsafe { core_types::record::GroupItem::from_resident(core_types::node::RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) };
|
||||||
|
|
||||||
let mut painted = List::new();
|
|
||||||
painted.push(Item::new_from_element(square(DVec2::ZERO)));
|
|
||||||
painted.push(Item::new_from_element(square(DVec2::ONE)));
|
|
||||||
painted.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_translation(DVec2::new(1., 0.)));
|
|
||||||
set_paint_attribute_at(&mut painted, 1, ATTR_FILL, List::new_from_element(Graphic::Color(List::new_from_element(Color::WHITE))));
|
|
||||||
|
|
||||||
let mut nested_child = List::new();
|
|
||||||
nested_child.push(Item::new_from_element(square(DVec2::new(2., 2.))));
|
|
||||||
let mut nested = List::new_from_element(Graphic::Vector(nested_child));
|
|
||||||
nested.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale(DVec2::splat(2.)));
|
|
||||||
|
|
||||||
let mut colors = List::new_from_element(Color::BLACK);
|
|
||||||
colors.set_attribute(ATTR_OPACITY, 0, 0.5);
|
|
||||||
|
|
||||||
let mut top = List::new();
|
let mut top = List::new();
|
||||||
top.push(Item::new_from_element(Graphic::Vector(painted)));
|
top.push(Item::new_from_element(Graphic::Vector(square(DVec2::ZERO))));
|
||||||
top.push(Item::new_from_element(Graphic::Graphic(nested)));
|
top.push(Item::new_from_element(Graphic::Color(Color::BLACK)));
|
||||||
top.push(Item::new_from_element(Graphic::Color(colors)));
|
|
||||||
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());
|
set_paint_attribute_at(&mut top, 0, ATTR_FILL, black_paint());
|
||||||
top.set_attribute(ATTR_TRANSFORM, 3, DAffine2::from_scale(DVec2::splat(3.)));
|
top.set_attribute(ATTR_OPACITY, 1, 0.5);
|
||||||
|
top.set_attribute(ATTR_TRANSFORM, 2, DAffine2::from_scale(DVec2::splat(3.)));
|
||||||
|
|
||||||
let legacy = {
|
let rows = flatten_vector_run(GraphicLevel::Legacy(&top), DAffine2::IDENTITY, PaintReach::NONE);
|
||||||
let mut prepared = top.clone();
|
assert_eq!(rows.len(), 3);
|
||||||
// The legacy pre-push, written by hand: the painted lane's fill
|
|
||||||
// lands on every interior item.
|
// Lane 0: the leaf row keeps its lane attributes, with the lane fill
|
||||||
if let Some(Graphic::Vector(inner)) = prepared.element_mut(0) {
|
// present and the ancestor composition the identity.
|
||||||
for index in 0..inner.len() {
|
assert_eq!(rows.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0), DAffine2::from_translation(DVec2::new(5., 5.)));
|
||||||
set_paint_attribute_at(inner, index, ATTR_FILL, black_paint());
|
assert!(graphic_types::graphic::paint_graphics::<Fill, _>(&rows, 0).is_some());
|
||||||
}
|
|
||||||
}
|
// Lane 1: the color stand-in carries the lane opacity and the color as
|
||||||
if let Some(element) = prepared.element_mut(3) {
|
// its fill.
|
||||||
*element = graphic_types::graphic::map_groups_to_legacy(element);
|
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");
|
||||||
flatten_vector(&prepared)
|
assert!(matches!(fill.element(0), Some(Graphic::Color(color)) if *color == Color::BLACK));
|
||||||
};
|
|
||||||
assert_eq!(flatten_vector_run(&top, DAffine2::IDENTITY, PaintReach::NONE), legacy);
|
// Lane 2: the group's vector run serves its row under the lane
|
||||||
|
// transform.
|
||||||
|
assert_eq!(rows.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 2), DAffine2::from_scale(DVec2::splat(3.)));
|
||||||
|
assert_eq!(rows.element(2).unwrap(), &inner_vector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ fn assign_colors_graphic<'e>(
|
|||||||
core_types::registry::cache_key(&keyed)
|
core_types::registry::cache_key(&keyed)
|
||||||
};
|
};
|
||||||
let generation = ctx.arena().generation();
|
let generation = ctx.arena().generation();
|
||||||
let (length, mut position) = {
|
let (length, position) = {
|
||||||
let mut cached = lane_offsets.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
let mut cached = lane_offsets.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||||
if !matches!(cached.as_ref(), Some(entry) if entry.key == key && entry.generation == generation) {
|
if !matches!(cached.as_ref(), Some(entry) if entry.key == key && entry.generation == generation) {
|
||||||
let mut offsets = Vec::with_capacity(content.len() + 1);
|
let mut offsets = Vec::with_capacity(content.len() + 1);
|
||||||
@@ -218,20 +218,29 @@ fn assign_colors_graphic<'e>(
|
|||||||
(entry.offsets[content.len()], entry.offsets[lane])
|
(entry.offsets[content.len()], entry.offsets[lane])
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(vector_list) = element.as_vector_mut() {
|
// A de-tabled vector leaf carries no attributes, so the paint rides the
|
||||||
for index in 0..vector_list.len() {
|
// containing lane: a bare leaf wraps into a one-lane list, and a lowered
|
||||||
let color = assign_color_at(gradient_element, position, length, randomize, seed, repeat_every);
|
// vector run's leaves take one color per lane.
|
||||||
|
if graphic_types::graphic::direct_vector_len(content.element_ref(lane)) > 0 {
|
||||||
|
let mut children = match element {
|
||||||
|
Graphic::Graphic(children) => children,
|
||||||
|
leaf => List::new_from_element(leaf),
|
||||||
|
};
|
||||||
|
let mut consumed = 0;
|
||||||
|
for index in 0..children.len() {
|
||||||
|
let Some(Graphic::Vector(vector)) = children.element(index) else { continue };
|
||||||
|
let has_stroke = vector.stroke.is_some();
|
||||||
|
let color = assign_color_at(gradient_element, position + consumed, 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 {
|
if fill {
|
||||||
set_paint_attribute_at(vector_list, index, ATTR_FILL, paint.clone());
|
set_paint_attribute_at(&mut children, index, ATTR_FILL, paint.clone());
|
||||||
}
|
}
|
||||||
if stroke && vector_list.element(index).is_some_and(|vector| vector.stroke.is_some()) {
|
if stroke && has_stroke {
|
||||||
set_paint_attribute_at(vector_list, index, ATTR_STROKE, paint.clone());
|
set_paint_attribute_at(&mut children, index, ATTR_STROKE, paint.clone());
|
||||||
}
|
}
|
||||||
|
consumed += 1;
|
||||||
position += 1;
|
|
||||||
}
|
}
|
||||||
|
element = Graphic::Graphic(children);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((element, transform, layer_path))
|
Ok((element, transform, layer_path))
|
||||||
@@ -277,21 +286,20 @@ fn park_paint(arena: &core_types::arena::Arena, paint: List<Graphic>) -> Result<
|
|||||||
/// The gradient defaulting the legacy fill performed, applied to the nested
|
/// The gradient defaulting the legacy fill performed, applied to the nested
|
||||||
/// stops list the paint table wraps.
|
/// stops list the paint table wraps.
|
||||||
fn default_gradient_paint(paint: &mut List<Graphic>, bounds: Option<[DVec2; 2]>, gradient_type: GradientType, spread_method: GradientSpreadMethod, transform: Option<DAffine2>) {
|
fn default_gradient_paint(paint: &mut List<Graphic>, bounds: Option<[DVec2; 2]>, gradient_type: GradientType, spread_method: GradientSpreadMethod, transform: Option<DAffine2>) {
|
||||||
|
let has_type = paint.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_some();
|
||||||
|
let has_spread = paint.iter_attribute_values::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_some();
|
||||||
|
let has_transform = paint.iter_attribute_values::<DAffine2>(ATTR_TRANSFORM).is_some();
|
||||||
for index in 0..paint.len() {
|
for index in 0..paint.len() {
|
||||||
let Some(Graphic::Gradient(gradient)) = paint.element_mut(index) else { continue };
|
if !matches!(paint.element(index), Some(Graphic::Gradient(_))) {
|
||||||
if gradient.iter_attribute_values::<GradientType>(ATTR_GRADIENT_TYPE).is_none() {
|
continue;
|
||||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientType>(ATTR_GRADIENT_TYPE) {
|
|
||||||
*value = gradient_type;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if !has_type {
|
||||||
if gradient.iter_attribute_values::<GradientSpreadMethod>(ATTR_SPREAD_METHOD).is_none() {
|
paint.set_attribute(ATTR_GRADIENT_TYPE, index, gradient_type);
|
||||||
for value in gradient.iter_attribute_values_mut_or_default::<GradientSpreadMethod>(ATTR_SPREAD_METHOD) {
|
|
||||||
*value = spread_method;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if !has_spread {
|
||||||
if gradient.iter_attribute_values::<DAffine2>(ATTR_TRANSFORM).is_none() {
|
paint.set_attribute(ATTR_SPREAD_METHOD, index, spread_method);
|
||||||
|
}
|
||||||
|
if !has_transform {
|
||||||
let transform = transform.unwrap_or_else(|| {
|
let transform = transform.unwrap_or_else(|| {
|
||||||
// Nudge a degenerate axis so the gradient transform stays invertible, matching the editor's `nonzero_bounding_box`
|
// Nudge a degenerate axis so the gradient transform stays invertible, matching the editor's `nonzero_bounding_box`
|
||||||
let [min, mut max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
let [min, mut max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||||
@@ -303,10 +311,7 @@ fn default_gradient_paint(paint: &mut List<Graphic>, bounds: Option<[DVec2; 2]>,
|
|||||||
}
|
}
|
||||||
initial_gradient_transform_for_bounding_box([min, max])
|
initial_gradient_transform_for_bounding_box([min, max])
|
||||||
});
|
});
|
||||||
|
paint.set_attribute(ATTR_TRANSFORM, index, transform);
|
||||||
for value in gradient.iter_attribute_values_mut_or_default::<DAffine2>(ATTR_TRANSFORM) {
|
|
||||||
*value = transform;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,18 +425,13 @@ fn stroke<'e>(
|
|||||||
/// 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
|
||||||
/// reach of the pre-flip broadcast over a legacy list.
|
/// reach of the pre-flip broadcast over a legacy list.
|
||||||
fn for_each_interior_vector_mut(element: &mut Graphic, mut f: impl FnMut(&mut Vector, DAffine2)) {
|
fn for_each_interior_vector_mut(element: &mut Graphic, mut f: impl FnMut(&mut Vector, DAffine2)) {
|
||||||
let mut walk_list = |list: &mut List<Vector>| {
|
|
||||||
let (elements, transforms) = list.element_and_attribute_slices_mut::<DAffine2>(ATTR_TRANSFORM);
|
|
||||||
for (vector, transform) in elements.iter_mut().zip(transforms.iter()) {
|
|
||||||
f(vector, *transform);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match element {
|
match element {
|
||||||
Graphic::Vector(list) => walk_list(list),
|
Graphic::Vector(vector) => f(vector, DAffine2::IDENTITY),
|
||||||
Graphic::Graphic(children) => {
|
Graphic::Graphic(children) => {
|
||||||
for child in children.iter_element_values_mut() {
|
for index in 0..children.len() {
|
||||||
if let Some(list) = child.as_vector_mut() {
|
let transform: DAffine2 = children.attribute_cloned_or_default(ATTR_TRANSFORM, index);
|
||||||
walk_list(list);
|
if let Some(Graphic::Vector(vector)) = children.element_mut(index) {
|
||||||
|
f(vector, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1449,9 +1449,9 @@ fn solidify_rows(flattened: List<Vector>) -> List<Vector> {
|
|||||||
/// lane addresses (a fill-bearing row serves two lanes), builds and splits
|
/// lane addresses (a fill-bearing row serves two lanes), builds and splits
|
||||||
/// only that row, and lane 0 additionally carries the merged-layers snapshot.
|
/// only that row, and lane 0 additionally carries the merged-layers snapshot.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn solidify_native_lane<'e, S: core_types::lane::LaneSource<Element = Graphic>>(
|
fn solidify_native_lane<'e>(
|
||||||
arena: &'e core_types::arena::Arena,
|
arena: &'e core_types::arena::Arena,
|
||||||
source: &S,
|
level: graphic_types::graphic::GraphicLevel<'_>,
|
||||||
snapshot: impl FnOnce() -> List<Graphic>,
|
snapshot: impl FnOnce() -> List<Graphic>,
|
||||||
lane: usize,
|
lane: usize,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
@@ -1472,7 +1472,7 @@ fn solidify_native_lane<'e, S: core_types::lane::LaneSource<Element = Graphic>>(
|
|||||||
use graphic_types::graphic::RowStep;
|
use graphic_types::graphic::RowStep;
|
||||||
let mut remaining = lane;
|
let mut remaining = lane;
|
||||||
let mut located: Option<List<Vector>> = None;
|
let mut located: Option<List<Vector>> = None;
|
||||||
graphic_types::graphic::walk_vector_rows(source, &mut |row| {
|
graphic_types::graphic::walk_vector_rows(level, &mut |row| {
|
||||||
let parts = 1 + row.has_fill() as usize;
|
let parts = 1 + row.has_fill() as usize;
|
||||||
if remaining >= parts {
|
if remaining >= parts {
|
||||||
remaining -= parts;
|
remaining -= parts;
|
||||||
@@ -1622,8 +1622,7 @@ fn solidify_stroke<'e>(
|
|||||||
> {
|
> {
|
||||||
// SAFETY: a materialized input's frames are arena-resident.
|
// SAFETY: a materialized input's frames are arena-resident.
|
||||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
solidify_native_lane(ctx.arena(), graphic_types::graphic::GraphicLevel::Run(&item), || legacy_graphic_list_of(content), ctx.index() as usize)
|
||||||
solidify_native_lane(ctx.arena(), &run, || legacy_graphic_list_of(content), ctx.index() as usize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A fill-bearing row splits into a fill lane and a solidified stroke lane,
|
/// A fill-bearing row splits into a fill lane and a solidified stroke lane,
|
||||||
@@ -1663,7 +1662,7 @@ fn solidify_stroke_vector<'e>(
|
|||||||
Interrupt,
|
Interrupt,
|
||||||
> {
|
> {
|
||||||
let wrapper = wrap_vector_level(content);
|
let wrapper = wrap_vector_level(content);
|
||||||
solidify_native_lane(ctx.arena(), &wrapper, || legacy_graphic_list_of(content), ctx.index() as usize)
|
solidify_native_lane(ctx.arena(), graphic_types::graphic::GraphicLevel::Legacy(&wrapper), || legacy_graphic_list_of(content), ctx.index() as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solidify_stroke_vector_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll<Extent> {
|
fn solidify_stroke_vector_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll<Extent> {
|
||||||
@@ -1905,8 +1904,7 @@ pub fn flatten_path<'e>(
|
|||||||
> {
|
> {
|
||||||
// SAFETY: a materialized input's frames are arena-resident.
|
// SAFETY: a materialized input's frames are arena-resident.
|
||||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
|
||||||
let flattened = graphic_types::graphic::flatten_vector_rows(&run);
|
|
||||||
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item).expect("the run holds the row's element type");
|
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item).expect("the run holds the row's element type");
|
||||||
flatten_path_core(ctx.arena(), flattened, snapshot)
|
flatten_path_core(ctx.arena(), flattened, snapshot)
|
||||||
}
|
}
|
||||||
@@ -1929,7 +1927,7 @@ pub fn flatten_path_vector<'e>(
|
|||||||
Interrupt,
|
Interrupt,
|
||||||
> {
|
> {
|
||||||
let wrapper = wrap_vector_level(content);
|
let wrapper = wrap_vector_level(content);
|
||||||
let flattened = graphic_types::graphic::flatten_vector_rows(&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);
|
||||||
flatten_path_core(ctx.arena(), flattened, snapshot)
|
flatten_path_core(ctx.arena(), flattened, snapshot)
|
||||||
}
|
}
|
||||||
@@ -2724,9 +2722,9 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic>, progression: f64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lerp_gradient_transform(gradient_list_a: &List<GradientStops>, gradient_list_b: &List<GradientStops>, time: f64) -> DAffine2 {
|
fn lerp_gradient_transform(paint_a: &List<Graphic>, paint_b: &List<Graphic>, time: f64) -> DAffine2 {
|
||||||
let transform_a = gradient_list_a.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
let transform_a = paint_a.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||||
let transform_b = gradient_list_b.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
let transform_b = paint_b.attribute_cloned_or_default::<DAffine2>(ATTR_TRANSFORM, 0);
|
||||||
|
|
||||||
let start_a = transform_a.translation;
|
let start_a = transform_a.translation;
|
||||||
let end_a = transform_a.translation + transform_a.matrix2.x_axis;
|
let end_a = transform_a.translation + transform_a.matrix2.x_axis;
|
||||||
@@ -2754,47 +2752,37 @@ fn morph_core(flattened: List<Vector>, snapshot: List<Graphic>, progression: f64
|
|||||||
(Some(a), Some(b)) => (a, b),
|
(Some(a), Some(b)) => (a, b),
|
||||||
};
|
};
|
||||||
|
|
||||||
// This keeps the gradient metadata attributes
|
// This keeps the gradient metadata attributes, which ride the paint lane
|
||||||
let gradient_with_stops = |mut gradient_list: List<GradientStops>, stops: GradientStops| -> Graphic {
|
let gradient_paint = |metadata_source: &List<Graphic>, stops: GradientStops, transform: Option<DAffine2>| -> List<Graphic> {
|
||||||
if let Some(target) = gradient_list.element_mut(0) {
|
let mut out = List::new_from_item(Item::from_parts(Graphic::Gradient(stops), metadata_source.clone_item_attributes(0)));
|
||||||
*target = stops;
|
if let Some(transform) = transform {
|
||||||
} else {
|
out.set_attribute(ATTR_TRANSFORM, 0, transform);
|
||||||
gradient_list.push(Item::new_from_element(stops));
|
|
||||||
}
|
}
|
||||||
Graphic::Gradient(gradient_list)
|
out
|
||||||
};
|
};
|
||||||
|
|
||||||
let graphic = match (a.element(0), b.element(0)) {
|
match (a.element(0), b.element(0)) {
|
||||||
(Some(Graphic::Color(color_list_a)), Some(Graphic::Color(color_list_b))) => color_list_a
|
(Some(Graphic::Color(color_a)), Some(Graphic::Color(color_b))) => Some(List::new_from_element(Graphic::from(color_a.lerp(color_b, time as f32)))),
|
||||||
.element(0)
|
(Some(Graphic::Color(color_a)), Some(Graphic::Gradient(stops_b))) => {
|
||||||
.zip(color_list_b.element(0))
|
|
||||||
.map(|(color_a, color_b)| Graphic::from(color_a.lerp(color_b, time as f32))),
|
|
||||||
(Some(Graphic::Color(color_list_a)), Some(Graphic::Gradient(gradient_list_b))) => color_list_a.element(0).zip(gradient_list_b.element(0)).map(|(color_a, stops_b)| {
|
|
||||||
let mut solid_to_gradient = stops_b.clone();
|
let mut solid_to_gradient = stops_b.clone();
|
||||||
solid_to_gradient.color.iter_mut().for_each(|color| *color = *color_a);
|
solid_to_gradient.color.iter_mut().for_each(|color| *color = *color_a);
|
||||||
let stops = solid_to_gradient.lerp(stops_b, time);
|
let stops = solid_to_gradient.lerp(stops_b, time);
|
||||||
gradient_with_stops(gradient_list_b.clone(), stops)
|
Some(gradient_paint(b, stops, None))
|
||||||
}),
|
}
|
||||||
(Some(Graphic::Gradient(gradient_list_a)), Some(Graphic::Color(color_list_b))) => gradient_list_a.element(0).zip(color_list_b.element(0)).map(|(stops_a, color_b)| {
|
(Some(Graphic::Gradient(stops_a)), Some(Graphic::Color(color_b))) => {
|
||||||
let mut gradient_to_solid = stops_a.clone();
|
let mut gradient_to_solid = stops_a.clone();
|
||||||
gradient_to_solid.color.iter_mut().for_each(|color| *color = *color_b);
|
gradient_to_solid.color.iter_mut().for_each(|color| *color = *color_b);
|
||||||
let stops = stops_a.lerp(&gradient_to_solid, time);
|
let stops = stops_a.lerp(&gradient_to_solid, time);
|
||||||
gradient_with_stops(gradient_list_a.clone(), stops)
|
Some(gradient_paint(a, stops, None))
|
||||||
}),
|
}
|
||||||
(Some(Graphic::Gradient(gradient_list_a)), Some(Graphic::Gradient(gradient_list_b))) => gradient_list_a.element(0).zip(gradient_list_b.element(0)).map(|(stops_a, stops_b)| {
|
(Some(Graphic::Gradient(stops_a)), Some(Graphic::Gradient(stops_b))) => {
|
||||||
let stops = stops_a.lerp(stops_b, time);
|
let stops = stops_a.lerp(stops_b, time);
|
||||||
let metadata_source = if time < 0.5 { gradient_list_a } else { gradient_list_b };
|
let metadata_source = if time < 0.5 { a } else { b };
|
||||||
|
Some(gradient_paint(metadata_source, stops, Some(lerp_gradient_transform(a, b, time))))
|
||||||
let mut gradient_list = metadata_source.clone();
|
}
|
||||||
gradient_list.set_attribute(ATTR_TRANSFORM, 0, lerp_gradient_transform(gradient_list_a, gradient_list_b, time));
|
|
||||||
|
|
||||||
gradient_with_stops(gradient_list, stops)
|
|
||||||
}),
|
|
||||||
// Pairings beyond solid colors and gradients (raster, vector, or mixed) can't be interpolated, so step at the midpoint
|
// Pairings beyond solid colors and gradients (raster, vector, or mixed) can't be interpolated, so step at the midpoint
|
||||||
_ => return Some(if time < 0.5 { a.clone() } else { b.clone() }),
|
_ => Some(if time < 0.5 { a.clone() } else { b.clone() }),
|
||||||
};
|
}
|
||||||
|
|
||||||
graphic.map(List::new_from_element)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve the original legacy snapshot as upstream data so this group layer's nested layers can be edited by the tools.
|
// Preserve the original legacy snapshot as upstream data so this group layer's nested layers can be edited by the tools.
|
||||||
@@ -3323,8 +3311,7 @@ fn morph<'e>(
|
|||||||
let path = graphic_types::graphic::run_to_list::<Vector>(&path_item).expect("the run holds vector lanes");
|
let path = graphic_types::graphic::run_to_list::<Vector>(&path_item).expect("the run holds vector lanes");
|
||||||
// SAFETY: a materialized input's frames are arena-resident.
|
// SAFETY: a materialized input's frames are arena-resident.
|
||||||
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
|
||||||
let run = core_types::record::RunView::<Graphic>::new(&item).expect("the run holds graphic lanes");
|
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
|
||||||
let flattened = graphic_types::graphic::flatten_vector_rows(&run);
|
|
||||||
morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path)
|
morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3357,7 +3344,7 @@ fn morph_vector<'e>(
|
|||||||
let path_item = unsafe { core_types::record::GroupItem::from_resident(path.batch()) };
|
let path_item = unsafe { core_types::record::GroupItem::from_resident(path.batch()) };
|
||||||
let path = graphic_types::graphic::run_to_list::<Vector>(&path_item).expect("the run holds vector lanes");
|
let path = graphic_types::graphic::run_to_list::<Vector>(&path_item).expect("the run holds vector lanes");
|
||||||
let wrapper = wrap_vector_level(content);
|
let wrapper = wrap_vector_level(content);
|
||||||
let flattened = graphic_types::graphic::flatten_vector_rows(&wrapper);
|
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Legacy(&wrapper));
|
||||||
morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path)
|
morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3961,10 +3948,10 @@ mod test {
|
|||||||
let fill = paint_graphics::<Fill, _>(&morphed, 0).expect("Morph should keep the fill paint at the midpoint");
|
let fill = paint_graphics::<Fill, _>(&morphed, 0).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(colors)) = fill.element(0) else {
|
let Some(Graphic::Color(color)) = fill.element(0) else {
|
||||||
panic!("Expected a solid color fill, got {:?}", fill.element(0));
|
panic!("Expected a solid color fill, got {:?}", fill.element(0));
|
||||||
};
|
};
|
||||||
let color = *colors.element(0).expect("Color present");
|
let color = *color;
|
||||||
assert!(color.r() > 0. && color.b() > 0., "Fill should be a red-to-blue blend, got {color:?}");
|
assert!(color.r() > 0. && color.b() > 0., "Fill should be a red-to-blue blend, got {color:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user