mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-06 18:08:13 +08:00
Drive every render body from the lane source
This commit is contained in:
@@ -200,21 +200,6 @@ pub fn is_paint_present(graphic_list: &List<Graphic>) -> bool {
|
|||||||
graphic_list.element(0).is_some_and(|graphic| !graphic.is_empty())
|
graphic_list.element(0).is_some_and(|graphic| !graphic.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the paint graphics stored under attribute for a vector item, in the canonical `List<Graphic>` form.
|
|
||||||
pub fn graphic_list_at<'a>(list: &'a List<Vector>, index: usize, attribute: &str) -> Option<Cow<'a, List<Graphic>>> {
|
|
||||||
list.attribute::<Option<List<Graphic>>>(attribute, index)
|
|
||||||
.and_then(|optional| optional.as_ref())
|
|
||||||
.map(Cow::Borrowed)
|
|
||||||
// Treat a blank paint attribute as absent so an empty attribute doesn't count as painted
|
|
||||||
.filter(|graphic_list| is_paint_present(graphic_list))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the item carries a non-blank canonical `List<Graphic>` paint attribute,
|
|
||||||
/// checked by borrowing without cloning the renderable list.
|
|
||||||
pub fn has_paint_at(list: &List<Vector>, index: usize, attribute: &str) -> bool {
|
|
||||||
graphic_list_at(list, index, attribute).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up the paint graphics stored under the marker `A`, in the canonical `List<Graphic>` form.
|
/// Look up the paint graphics stored under the marker `A`, in the canonical `List<Graphic>` form.
|
||||||
pub fn paint_graphics<'a, A, S>(source: &'a S, index: usize) -> Option<Cow<'a, List<Graphic>>>
|
pub fn paint_graphics<'a, A, S>(source: &'a S, index: usize) -> Option<Cow<'a, List<Graphic>>>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -21,15 +21,15 @@ use dyn_any::DynAny;
|
|||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graphene_hash::CacheHashWrapper;
|
use graphene_hash::CacheHashWrapper;
|
||||||
use graphene_resource::Resource;
|
use graphene_resource::Resource;
|
||||||
use graphic_types::graphic::{graphic_list_at, has_paint_at, is_paint_present, set_paint_attribute};
|
use graphic_types::graphic::{has_paint, is_paint_present, paint_graphics, set_paint_attribute};
|
||||||
use graphic_types::markers::EditorMergedLayers;
|
use graphic_types::markers::{EditorMergedLayers, Fill, Stroke};
|
||||||
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
use graphic_types::raster_types::{BitmapMut, CPU, GPU, Image, Raster, Texture};
|
||||||
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
use graphic_types::vector_types::gradient::{GradientStops, GradientType};
|
||||||
use graphic_types::vector_types::markers::{GradientType as GradientTypeAttr, SpreadMethod};
|
use graphic_types::vector_types::markers::{GradientType as GradientTypeAttr, SpreadMethod};
|
||||||
use graphic_types::vector_types::subpath::Subpath;
|
use graphic_types::vector_types::subpath::Subpath;
|
||||||
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
||||||
use graphic_types::vector_types::vector::style::{PaintOrder, RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphic_types::vector_types::vector::style::{PaintOrder, RenderMode, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use graphic_types::{ATTR_FILL, ATTR_STROKE, Artboard, Graphic, Vector};
|
use graphic_types::{ATTR_FILL, Artboard, Graphic, Vector};
|
||||||
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
|
use kurbo::{Affine, BezPath, Cap, Join, Shape, StrokeOpts};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
||||||
@@ -703,20 +703,19 @@ impl Render for Graphic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the artboard metadata for the item at `index` from a `List<Artboard>`.
|
/// Reads the artboard metadata for the item at `index`.
|
||||||
fn read_artboard_attributes(list: &List<Artboard>, index: usize) -> (DVec2, DVec2, Color, bool) {
|
fn read_artboard_attributes<S: LaneSource>(source: &S, index: usize) -> (DVec2, DVec2, Color, bool) {
|
||||||
let location: DVec2 = list.attr::<Location>(index);
|
let location: DVec2 = source.attr::<Location>(index);
|
||||||
let dimensions: DVec2 = list.attr::<Dimensions>(index);
|
let dimensions: DVec2 = source.attr::<Dimensions>(index);
|
||||||
let background: Color = list.attr::<BackgroundAttr>(index);
|
let background: Color = source.attr::<BackgroundAttr>(index);
|
||||||
let clip: bool = list.attr::<Clip>(index);
|
let clip: bool = source.attr::<Clip>(index);
|
||||||
(location, dimensions, background, clip)
|
(location, dimensions, background, clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for List<Artboard> {
|
fn render_artboard_svg<S: LaneSource<Element = Artboard>>(source: &S, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
for index in 0..source.lane_count() {
|
||||||
for index in 0..self.len() {
|
let Some(content) = source.element(index).map(Artboard::as_graphic_list) else { continue };
|
||||||
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
|
let (location, dimensions, background, clip) = read_artboard_attributes(source, index);
|
||||||
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
|
|
||||||
|
|
||||||
let x = location.x.min(location.x + dimensions.x);
|
let x = location.x.min(location.x + dimensions.x);
|
||||||
let y = location.y.min(location.y + dimensions.y);
|
let y = location.y.min(location.y + dimensions.y);
|
||||||
@@ -769,12 +768,12 @@ impl Render for List<Artboard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_artboard_vello<S: LaneSource<Element = Artboard>>(source: &S, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
|
let Some(content) = source.element(index).map(Artboard::as_graphic_list) else { continue };
|
||||||
let (location, dimensions, background, clip) = read_artboard_attributes(self, index);
|
let (location, dimensions, background, clip) = read_artboard_attributes(source, index);
|
||||||
|
|
||||||
let [a, b] = [location, location + dimensions];
|
let [a, b] = [location, location + dimensions];
|
||||||
let rect = kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));
|
let rect = kurbo::Rect::new(a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y));
|
||||||
@@ -801,12 +800,12 @@ impl Render for List<Artboard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
fn collect_artboard_metadata<S: LaneSource<Element = Artboard>>(source: &S, metadata: &mut RenderMetadata, footprint: Footprint) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(content) = self.element(index).map(Artboard::as_graphic_list) else { continue };
|
let Some(content) = source.element(index).map(Artboard::as_graphic_list) else { continue };
|
||||||
let (location, dimensions, _background, clip) = read_artboard_attributes(self, index);
|
let (location, dimensions, _background, clip) = read_artboard_attributes(source, index);
|
||||||
|
|
||||||
let layer_path: &[NodeId] = self.attr::<EditorLayerPath>(index);
|
let layer_path: &[NodeId] = source.attr::<EditorLayerPath>(index);
|
||||||
let element_id = layer_path.last().copied();
|
let element_id = layer_path.last().copied();
|
||||||
|
|
||||||
if let Some(element_id) = element_id {
|
if let Some(element_id) = element_id {
|
||||||
@@ -827,29 +826,45 @@ impl Render for List<Artboard> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_artboard_upstream_click_targets<S: LaneSource<Element = Artboard>>(source: &S, click_targets: &mut Vec<ClickTarget>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let dimensions: DVec2 = self.attr::<Dimensions>(index);
|
let dimensions: DVec2 = source.attr::<Dimensions>(index);
|
||||||
let subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, dimensions);
|
let subpath_rectangle = Subpath::new_rectangle(DVec2::ZERO, dimensions);
|
||||||
click_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));
|
click_targets.push(ClickTarget::new_with_subpath(subpath_rectangle, 0.));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Render for List<Artboard> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
render_artboard_svg(self, render, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_artboard_vello(self, scene, transform, context, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||||
|
collect_artboard_metadata(self, metadata, footprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
|
add_artboard_upstream_click_targets(self, click_targets)
|
||||||
|
}
|
||||||
|
|
||||||
fn contains_artboard(&self) -> bool {
|
fn contains_artboard(&self) -> bool {
|
||||||
!self.is_empty()
|
!self.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for List<Graphic> {
|
fn render_graphic_svg<S: LaneSource<Element = Graphic>>(source: &S, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
|
||||||
let mut mask_state = None;
|
let mut mask_state = None;
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let element = self.element(index).unwrap();
|
let element = source.element(index).unwrap();
|
||||||
|
|
||||||
render.parent_tag(
|
render.parent_tag(
|
||||||
"g",
|
"g",
|
||||||
@@ -868,7 +883,7 @@ impl Render for List<Graphic> {
|
|||||||
attributes.push("style", blend_mode.render());
|
attributes.push("style", blend_mode.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
let next_clips = index + 1 < source.lane_count() && source.element(index + 1).unwrap().had_clip_enabled();
|
||||||
|
|
||||||
if next_clips && mask_state.is_none() {
|
if next_clips && mask_state.is_none() {
|
||||||
let uuid = generate_uuid();
|
let uuid = generate_uuid();
|
||||||
@@ -897,16 +912,16 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_graphic_vello<S: LaneSource<Element = Graphic>>(source: &S, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
let mut mask_element_and_transform = None;
|
let mut mask_element_and_transform = None;
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let transform = transform * item_transform;
|
let transform = transform * item_transform;
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let element = self.element(index).unwrap();
|
let element = source.element(index).unwrap();
|
||||||
|
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
@@ -932,7 +947,7 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_clips = index + 1 < self.len() && self.element(index + 1).unwrap().had_clip_enabled();
|
let next_clips = index + 1 < source.lane_count() && source.element(index + 1).unwrap().had_clip_enabled();
|
||||||
if next_clips && mask_element_and_transform.is_none() {
|
if next_clips && mask_element_and_transform.is_none() {
|
||||||
mask_element_and_transform = Some((element, transform));
|
mask_element_and_transform = Some((element, transform));
|
||||||
|
|
||||||
@@ -975,12 +990,12 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
fn collect_graphic_metadata<S: LaneSource<Element = Graphic>>(source: &S, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let layer_path: &[NodeId] = self.attr::<EditorLayerPath>(index);
|
let layer_path: &[NodeId] = source.attr::<EditorLayerPath>(index);
|
||||||
let layer = layer_path.last().copied();
|
let layer = layer_path.last().copied();
|
||||||
let element = self.element(index).unwrap();
|
let element = source.element(index).unwrap();
|
||||||
|
|
||||||
let mut footprint = footprint;
|
let mut footprint = footprint;
|
||||||
footprint.transform *= item_transform;
|
footprint.transform *= item_transform;
|
||||||
@@ -997,9 +1012,9 @@ impl Render for List<Graphic> {
|
|||||||
let mut all_upstream_click_targets = Vec::new();
|
let mut all_upstream_click_targets = Vec::new();
|
||||||
let mut all_upstream_outlines = Vec::new();
|
let mut all_upstream_outlines = Vec::new();
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let element = self.element(index).unwrap();
|
let element = source.element(index).unwrap();
|
||||||
|
|
||||||
let mut new_click_targets = Vec::new();
|
let mut new_click_targets = Vec::new();
|
||||||
element.add_upstream_click_targets(&mut new_click_targets);
|
element.add_upstream_click_targets(&mut new_click_targets);
|
||||||
@@ -1023,10 +1038,10 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_graphic_upstream_click_targets<S: LaneSource<Element = Graphic>>(source: &S, click_targets: &mut Vec<ClickTarget>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let element = self.element(index).unwrap();
|
let element = source.element(index).unwrap();
|
||||||
let mut new_click_targets = Vec::new();
|
let mut new_click_targets = Vec::new();
|
||||||
|
|
||||||
element.add_upstream_click_targets(&mut new_click_targets);
|
element.add_upstream_click_targets(&mut new_click_targets);
|
||||||
@@ -1039,10 +1054,10 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
|
fn add_graphic_upstream_outline_targets<S: LaneSource<Element = Graphic>>(source: &S, outlines: &mut Vec<ClickTarget>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let element = self.element(index).unwrap();
|
let element = source.element(index).unwrap();
|
||||||
let mut new_outlines = Vec::new();
|
let mut new_outlines = Vec::new();
|
||||||
|
|
||||||
element.add_upstream_outline_targets(&mut new_outlines);
|
element.add_upstream_outline_targets(&mut new_outlines);
|
||||||
@@ -1055,8 +1070,33 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn graphic_contains_artboard<S: LaneSource<Element = Graphic>>(source: &S) -> bool {
|
||||||
|
(0..source.lane_count()).any(|index| source.element(index).is_some_and(|element| element.contains_artboard()))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for List<Graphic> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
render_graphic_svg(self, render, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_graphic_vello(self, scene, transform, context, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
|
collect_graphic_metadata(self, metadata, footprint, element_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
|
add_graphic_upstream_click_targets(self, click_targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
|
||||||
|
add_graphic_upstream_outline_targets(self, outlines)
|
||||||
|
}
|
||||||
|
|
||||||
fn contains_artboard(&self) -> bool {
|
fn contains_artboard(&self) -> bool {
|
||||||
self.iter_element_values().any(|element| element.contains_artboard())
|
graphic_contains_artboard(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||||
@@ -1067,14 +1107,13 @@ impl Render for List<Graphic> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for List<Vector> {
|
fn render_vector_svg<S: LaneSource<Element = Vector>>(source: &S, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
for index in 0..source.lane_count() {
|
||||||
for index in 0..self.len() {
|
let Some(vector) = source.element(index) else { continue };
|
||||||
let Some(vector) = self.element(index) else { continue };
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
|
||||||
|
|
||||||
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
||||||
let has_real_stroke = vector.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
let has_real_stroke = vector.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||||
@@ -1103,10 +1142,10 @@ impl Render for List<Vector> {
|
|||||||
MaskType::Mask
|
MaskType::Mask
|
||||||
};
|
};
|
||||||
|
|
||||||
let fill_graphic_list = graphic_list_at(self, index, ATTR_FILL);
|
let fill_graphic_list = paint_graphics::<Fill, _>(source, index);
|
||||||
let fill_graphic = fill_graphic_list.as_ref().and_then(|l| l.element(0));
|
let fill_graphic = fill_graphic_list.as_ref().and_then(|l| l.element(0));
|
||||||
|
|
||||||
let stroke_graphic_list = graphic_list_at(self, index, ATTR_STROKE);
|
let stroke_graphic_list = paint_graphics::<Stroke, _>(source, index);
|
||||||
let stroke_graphic = stroke_graphic_list.as_ref().and_then(|l| l.element(0));
|
let stroke_graphic = stroke_graphic_list.as_ref().and_then(|l| l.element(0));
|
||||||
|
|
||||||
let path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());
|
let path_is_closed = vector.stroke_bezier_paths().all(|path| path.closed());
|
||||||
@@ -1282,15 +1321,15 @@ impl Render for List<Vector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_vector_vello<S: LaneSource<Element = Vector>>(source: &S, scene: &mut Scene, parent_transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
use graphic_types::vector_types::vector;
|
use graphic_types::vector_types::vector;
|
||||||
|
|
||||||
let Some(element) = self.element(index) else { continue };
|
let Some(element) = source.element(index) else { continue };
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let multiplied_transform = parent_transform * item_transform;
|
let multiplied_transform = parent_transform * item_transform;
|
||||||
let has_real_stroke = element.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
let has_real_stroke = element.stroke.as_ref().filter(|stroke| stroke.weight() > 0.);
|
||||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform_is_invertible(*transform));
|
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform_is_invertible(*transform));
|
||||||
@@ -1316,8 +1355,8 @@ impl Render for List<Vector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fill_graphic_list = graphic_list_at(self, index, ATTR_FILL);
|
let fill_graphic_list = paint_graphics::<Fill, _>(source, index);
|
||||||
let stroke_graphic_list = graphic_list_at(self, index, ATTR_STROKE);
|
let stroke_graphic_list = paint_graphics::<Stroke, _>(source, index);
|
||||||
|
|
||||||
// If we're using opacity or a blend mode, we need to push a layer
|
// If we're using opacity or a blend mode, we need to push a layer
|
||||||
let blend_mode = match render_params.render_mode {
|
let blend_mode = match render_params.render_mode {
|
||||||
@@ -1559,10 +1598,10 @@ impl Render for List<Vector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
fn collect_vector_metadata<S: LaneSource<Element = Vector>>(source: &S, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||||
// Aggregate all items' targets per element_id so multi-item lists (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph.
|
// Aggregate all items' targets per element_id so multi-item lists (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph.
|
||||||
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
|
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
|
||||||
let item_zero_transform: DAffine2 = if !self.is_empty() { self.attr::<Transform>(0) } else { DAffine2::IDENTITY };
|
let item_zero_transform: DAffine2 = if source.lane_count() > 0 { source.attr::<Transform>(0) } else { DAffine2::IDENTITY };
|
||||||
let item_zero_inverse = if transform_is_invertible(item_zero_transform) {
|
let item_zero_inverse = if transform_is_invertible(item_zero_transform) {
|
||||||
item_zero_transform.inverse()
|
item_zero_transform.inverse()
|
||||||
} else {
|
} else {
|
||||||
@@ -1572,10 +1611,10 @@ impl Render for List<Vector> {
|
|||||||
let mut accumulated_click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
let mut accumulated_click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
||||||
let mut accumulated_outlines: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
let mut accumulated_outlines: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(source) = self.element(index) else { continue };
|
let Some(element) = source.element(index) else { continue };
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let layer_path: &[NodeId] = self.attr::<EditorLayerPath>(index);
|
let layer_path: &[NodeId] = source.attr::<EditorLayerPath>(index);
|
||||||
let layer = layer_path.last().copied();
|
let layer = layer_path.last().copied();
|
||||||
|
|
||||||
if let Some(element_id) = caller_element_id.or(layer) {
|
if let Some(element_id) = caller_element_id.or(layer) {
|
||||||
@@ -1588,17 +1627,17 @@ impl Render for List<Vector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use click-target override if the item provides one (e.g. 'Text' node's per-glyph bboxes)
|
// Use click-target override if the item provides one (e.g. 'Text' node's per-glyph bboxes)
|
||||||
let click_target_vector = self.attr::<EditorClickTarget>(index).unwrap_or(source);
|
let click_target_vector = source.attr::<EditorClickTarget>(index).unwrap_or(element);
|
||||||
|
|
||||||
let item_relative_transform = item_zero_inverse * transform;
|
let item_relative_transform = item_zero_inverse * transform;
|
||||||
|
|
||||||
let mut click_targets_unwrapped = Vec::new();
|
let mut click_targets_unwrapped = Vec::new();
|
||||||
extend_targets_from_vector(&mut click_targets_unwrapped, self, index, click_target_vector, item_relative_transform);
|
extend_targets_from_vector(&mut click_targets_unwrapped, source, index, click_target_vector, item_relative_transform);
|
||||||
accumulated_click_targets.entry(element_id).or_default().extend(click_targets_unwrapped.into_iter().map(Arc::new));
|
accumulated_click_targets.entry(element_id).or_default().extend(click_targets_unwrapped.into_iter().map(Arc::new));
|
||||||
|
|
||||||
// Outlines always use source geometry so the visual outline reflects actual letterforms
|
// Outlines always use source geometry so the visual outline reflects actual letterforms
|
||||||
let mut outlines_unwrapped = Vec::new();
|
let mut outlines_unwrapped = Vec::new();
|
||||||
extend_targets_from_vector(&mut outlines_unwrapped, self, index, source, item_relative_transform);
|
extend_targets_from_vector(&mut outlines_unwrapped, source, index, element, item_relative_transform);
|
||||||
accumulated_outlines.entry(element_id).or_default().extend(outlines_unwrapped.into_iter().map(Arc::new));
|
accumulated_outlines.entry(element_id).or_default().extend(outlines_unwrapped.into_iter().map(Arc::new));
|
||||||
|
|
||||||
// Source geometry (not the click-target override) so editing tools work on letterforms.
|
// Source geometry (not the click-target override) so editing tools work on letterforms.
|
||||||
@@ -1606,18 +1645,18 @@ impl Render for List<Vector> {
|
|||||||
// Only item 0 is recorded since editing tools can only target a single item currently.
|
// Only item 0 is recorded since editing tools can only target a single item currently.
|
||||||
// If that row has no paint attribute, none is recorded.
|
// If that row has no paint attribute, none is recorded.
|
||||||
if let std::collections::hash_map::Entry::Vacant(e) = metadata.vector_data.entry(element_id) {
|
if let std::collections::hash_map::Entry::Vacant(e) = metadata.vector_data.entry(element_id) {
|
||||||
e.insert(Arc::new(source.clone()));
|
e.insert(Arc::new(element.clone()));
|
||||||
|
|
||||||
if let Some(fill_graphic) = graphic_list_at(self, index, ATTR_FILL) {
|
if let Some(fill_graphic) = paint_graphics::<Fill, _>(source, index) {
|
||||||
metadata.fill_attributes.insert(element_id, Arc::new(fill_graphic.into_owned()));
|
metadata.fill_attributes.insert(element_id, Arc::new(fill_graphic.into_owned()));
|
||||||
}
|
}
|
||||||
if let Some(stroke_graphic) = graphic_list_at(self, index, ATTR_STROKE) {
|
if let Some(stroke_graphic) = paint_graphics::<Stroke, _>(source, index) {
|
||||||
metadata.stroke_attributes.insert(element_id, Arc::new(stroke_graphic.into_owned()));
|
metadata.stroke_attributes.insert(element_id, Arc::new(stroke_graphic.into_owned()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Surface `editor:text_frame` for the Text tool's drag cage
|
// Surface `editor:text_frame` for the Text tool's drag cage
|
||||||
if let Some(frame) = self.try_attr::<EditorTextFrame>(index) {
|
if let Some(frame) = source.try_attr::<EditorTextFrame>(index) {
|
||||||
metadata.text_frames.entry(element_id).or_insert(frame);
|
metadata.text_frames.entry(element_id).or_insert(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1625,7 +1664,7 @@ impl Render for List<Vector> {
|
|||||||
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
|
||||||
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
|
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can
|
||||||
// surface the original child layers' click targets.
|
// surface the original child layers' click targets.
|
||||||
if let Some(upstream_nested_layers) = self.attr::<EditorMergedLayers>(index).filter(|layers| !layers.is_empty()) {
|
if let Some(upstream_nested_layers) = source.attr::<EditorMergedLayers>(index).filter(|layers| !layers.is_empty()) {
|
||||||
let mut upstream_footprint = footprint;
|
let mut upstream_footprint = footprint;
|
||||||
upstream_footprint.transform *= transform;
|
upstream_footprint.transform *= transform;
|
||||||
upstream_nested_layers.collect_metadata(metadata, upstream_footprint, None);
|
upstream_nested_layers.collect_metadata(metadata, upstream_footprint, None);
|
||||||
@@ -1641,26 +1680,47 @@ impl Render for List<Vector> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_vector_upstream_click_targets<S: LaneSource<Element = Vector>>(source: &S, click_targets: &mut Vec<ClickTarget>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(source) = self.element(index) else { continue };
|
let Some(element) = source.element(index) else { continue };
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
|
|
||||||
// Use click-target override geometry if the item provides one (e.g. 'Text' node's per-glyph bounding boxes)
|
// Use click-target override geometry if the item provides one (e.g. 'Text' node's per-glyph bounding boxes)
|
||||||
let vector = self.attr::<EditorClickTarget>(index).unwrap_or(source);
|
let vector = source.attr::<EditorClickTarget>(index).unwrap_or(element);
|
||||||
|
|
||||||
extend_targets_from_vector(click_targets, self, index, vector, transform);
|
extend_targets_from_vector(click_targets, source, index, vector, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_vector_upstream_outline_targets<S: LaneSource<Element = Vector>>(source: &S, outlines: &mut Vec<ClickTarget>) {
|
||||||
|
// Source geometry only, ignoring `editor:click_target`, so outlines reflect actual letterforms
|
||||||
|
for index in 0..source.lane_count() {
|
||||||
|
let Some(element) = source.element(index) else { continue };
|
||||||
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
|
|
||||||
|
extend_targets_from_vector(outlines, source, index, element, transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for List<Vector> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
render_vector_svg(self, render, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_vector_vello(self, scene, parent_transform, context, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||||
|
collect_vector_metadata(self, metadata, footprint, caller_element_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
|
add_vector_upstream_click_targets(self, click_targets)
|
||||||
|
}
|
||||||
|
|
||||||
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
|
fn add_upstream_outline_targets(&self, outlines: &mut Vec<ClickTarget>) {
|
||||||
// Source geometry only, ignoring `editor:click_target`, so outlines reflect actual letterforms
|
add_vector_upstream_outline_targets(self, outlines)
|
||||||
for index in 0..self.len() {
|
|
||||||
let Some(source) = self.element(index) else { continue };
|
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
|
||||||
|
|
||||||
extend_targets_from_vector(outlines, self, index, source, transform);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||||
@@ -1672,8 +1732,8 @@ impl Render for List<Vector> {
|
|||||||
|
|
||||||
/// Build one `CompoundPath` (non-zero fill rule, so holes like the inside of an "O" work
|
/// Build one `CompoundPath` (non-zero fill rule, so holes like the inside of an "O" work
|
||||||
/// correctly) plus one `FreePoint` per disconnected anchor, apply the transform, and append.
|
/// correctly) plus one `FreePoint` per disconnected anchor, apply the transform, and append.
|
||||||
fn extend_targets_from_vector(targets: &mut Vec<ClickTarget>, vector_list: &List<Vector>, index: usize, geometry: &Vector, transform: DAffine2) {
|
fn extend_targets_from_vector<S: LaneSource<Element = Vector>>(targets: &mut Vec<ClickTarget>, source: &S, index: usize, geometry: &Vector, transform: DAffine2) {
|
||||||
let filled = has_paint_at(vector_list, index, ATTR_FILL);
|
let filled = has_paint::<Fill, _>(source, index);
|
||||||
|
|
||||||
let mut subpaths: Vec<Subpath<_>> = geometry.stroke_bezier_paths().collect();
|
let mut subpaths: Vec<Subpath<_>> = geometry.stroke_bezier_paths().collect();
|
||||||
let all_subpaths_closed = subpaths.iter().all(|subpath| subpath.closed());
|
let all_subpaths_closed = subpaths.iter().all(|subpath| subpath.closed());
|
||||||
@@ -1724,15 +1784,14 @@ fn extend_free_point_targets(vector: &Vector, transform: DAffine2) -> impl Itera
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for List<Raster<CPU>> {
|
fn render_raster_cpu_svg<S: LaneSource<Element = Raster<CPU>>>(source: &S, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
for index in 0..source.lane_count() {
|
||||||
for index in 0..self.len() {
|
let Some(image) = source.element(index) else { continue };
|
||||||
let Some(image) = self.element(index) else { continue };
|
|
||||||
|
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
|
|
||||||
if image.data.is_empty() {
|
if image.data.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@@ -1809,23 +1868,23 @@ impl Render for List<Raster<CPU>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
fn render_raster_cpu_vello<S: LaneSource<Element = Raster<CPU>> + BoundingBox>(source: &S, scene: &mut Scene, transform: DAffine2, render_params: &RenderParams) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(image) = self.element(index) else { continue };
|
let Some(image) = source.element(index) else { continue };
|
||||||
if image.data.is_empty() {
|
if image.data.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let blend_mode = blend_mode_attr.to_peniko();
|
let blend_mode = blend_mode_attr.to_peniko();
|
||||||
|
|
||||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
let mut layer = false;
|
let mut layer = false;
|
||||||
|
|
||||||
if (opacity < 1. || (render_params.render_mode != RenderMode::Outline && blend_mode_attr != BlendMode::default()))
|
if (opacity < 1. || (render_params.render_mode != RenderMode::Outline && blend_mode_attr != BlendMode::default()))
|
||||||
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, false)
|
&& let RenderBoundingBox::Rectangle(bounds) = source.bounding_box(transform, false)
|
||||||
{
|
{
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||||
@@ -1833,7 +1892,7 @@ impl Render for List<Raster<CPU>> {
|
|||||||
layer = true;
|
layer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform_attribute: DAffine2 = self.attr::<Transform>(index);
|
let transform_attribute: DAffine2 = source.attr::<Transform>(index);
|
||||||
|
|
||||||
if let RenderMode::Outline = render_params.render_mode {
|
if let RenderMode::Outline = render_params.render_mode {
|
||||||
let outline_transform: DAffine2 = transform * transform_attribute;
|
let outline_transform: DAffine2 = transform * transform_attribute;
|
||||||
@@ -1865,15 +1924,15 @@ impl Render for List<Raster<CPU>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
fn collect_raster_metadata<S: LaneSource>(source: &S, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
let Some(element_id) = element_id else { return };
|
let Some(element_id) = element_id else { return };
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
|
|
||||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
metadata.upstream_footprints.insert(element_id, footprint);
|
||||||
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
||||||
if !self.is_empty() {
|
if source.lane_count() > 0 {
|
||||||
let transform: DAffine2 = self.attr::<Transform>(0);
|
let transform: DAffine2 = source.attr::<Transform>(0);
|
||||||
metadata.local_transforms.insert(element_id, transform);
|
metadata.local_transforms.insert(element_id, transform);
|
||||||
|
|
||||||
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
||||||
@@ -1882,32 +1941,44 @@ impl Render for List<Raster<CPU>> {
|
|||||||
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
||||||
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
||||||
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
||||||
if let Some(upstream_nested_layers) = self.attr::<EditorMergedLayers>(0).filter(|layers| !layers.is_empty()) {
|
if let Some(upstream_nested_layers) = source.attr::<EditorMergedLayers>(0).filter(|layers| !layers.is_empty()) {
|
||||||
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_raster_upstream_click_targets(click_targets: &mut Vec<ClickTarget>) {
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
||||||
click_targets.push(ClickTarget::new_with_subpath(subpath, 0.));
|
click_targets.push(ClickTarget::new_with_subpath(subpath, 0.));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Render for List<Raster<CPU>> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
render_raster_cpu_svg(self, render, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_raster_cpu_vello(self, scene, transform, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||||
|
collect_raster_metadata(self, metadata, footprint, element_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
|
add_raster_upstream_click_targets(click_targets)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
|
static LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
|
||||||
|
|
||||||
impl Render for List<Raster<GPU>> {
|
fn render_raster_gpu_vello<S: LaneSource<Element = Raster<GPU>> + BoundingBox>(source: &S, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
|
for index in 0..source.lane_count() {
|
||||||
log::warn!("tried to render texture as an svg");
|
let Some(raster) = source.element(index) else { continue };
|
||||||
}
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
for index in 0..self.len() {
|
let clip_attr: bool = source.attr::<ClippingMask>(index);
|
||||||
let Some(raster) = self.element(index) else { continue };
|
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
|
||||||
let clip_attr: bool = self.attr::<ClippingMask>(index);
|
|
||||||
let blend_mode = match render_params.render_mode {
|
let blend_mode = match render_params.render_mode {
|
||||||
RenderMode::Outline => peniko::Mix::Normal,
|
RenderMode::Outline => peniko::Mix::Normal,
|
||||||
_ => blend_mode_attr.to_peniko(),
|
_ => blend_mode_attr.to_peniko(),
|
||||||
@@ -1918,7 +1989,7 @@ impl Render for List<Raster<GPU>> {
|
|||||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
let any_nondefault = blend_mode_attr != BlendMode::default() || opacity < 1. || clip_attr;
|
let any_nondefault = blend_mode_attr != BlendMode::default() || opacity < 1. || clip_attr;
|
||||||
if (render_params.render_mode != RenderMode::Outline && any_nondefault)
|
if (render_params.render_mode != RenderMode::Outline && any_nondefault)
|
||||||
&& let RenderBoundingBox::Rectangle(bounds) = self.bounding_box(transform, true)
|
&& let RenderBoundingBox::Rectangle(bounds) = source.bounding_box(transform, true)
|
||||||
{
|
{
|
||||||
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
let blending = peniko::BlendMode::new(blend_mode, peniko::Compose::SrcOver);
|
||||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||||
@@ -1926,7 +1997,7 @@ impl Render for List<Raster<GPU>> {
|
|||||||
layer = true;
|
layer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform_attribute: DAffine2 = self.attr::<Transform>(index);
|
let transform_attribute: DAffine2 = source.attr::<Transform>(index);
|
||||||
|
|
||||||
if let RenderMode::Outline = render_params.render_mode {
|
if let RenderMode::Outline = render_params.render_mode {
|
||||||
let outline_transform = transform * transform_attribute;
|
let outline_transform = transform * transform_attribute;
|
||||||
@@ -1959,32 +2030,21 @@ impl Render for List<Raster<GPU>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Render for List<Raster<GPU>> {
|
||||||
|
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
|
||||||
|
log::warn!("tried to render texture as an svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_raster_gpu_vello(self, 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>) {
|
||||||
let Some(element_id) = element_id else { return };
|
collect_raster_metadata(self, metadata, footprint, element_id)
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
|
||||||
|
|
||||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]);
|
|
||||||
metadata.upstream_footprints.insert(element_id, footprint);
|
|
||||||
// TODO: Find a way to handle more than one item of the `List<Raster<...>>`
|
|
||||||
if !self.is_empty() {
|
|
||||||
let transform: DAffine2 = self.attr::<Transform>(0);
|
|
||||||
metadata.local_transforms.insert(element_id, transform);
|
|
||||||
|
|
||||||
// If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize,
|
|
||||||
// which destructively merges its inputs into pixels), recurse into that snapshot so the editor can
|
|
||||||
// surface the original child layers' click targets (the same mechanism Boolean Operation uses).
|
|
||||||
// The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization
|
|
||||||
// area, so the children are already in the coordinate space matching `footprint` here — we must NOT
|
|
||||||
// multiply in `transform` (which is the rasterization area, not a layer-stack transform).
|
|
||||||
if let Some(upstream_nested_layers) = self.attr::<EditorMergedLayers>(0).filter(|layers| !layers.is_empty()) {
|
|
||||||
upstream_nested_layers.collect_metadata(metadata, footprint, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE);
|
add_raster_upstream_click_targets(click_targets)
|
||||||
click_targets.push(ClickTarget::new_with_subpath(subpath, 0.));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2068,8 +2128,7 @@ impl Render for List<Color> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for List<GradientStops> {
|
fn render_gradient_svg<S: LaneSource<Element = GradientStops>>(source: &S, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
|
||||||
// For thumbnails the gradient fills a finite rect at the footprint's document space bounds, with a 1-unit margin to cover the `as u32` truncation of `Footprint::resolution`.
|
// For thumbnails the gradient fills a finite rect at the footprint's document space bounds, with a 1-unit margin to cover the `as u32` truncation of `Footprint::resolution`.
|
||||||
// The viewBox crops the overshoot. Canvas rendering keeps the polyline path since Chrome rejects rects larger than ~20 million.
|
// The viewBox crops the overshoot. Canvas rendering keeps the polyline path since Chrome rejects rects larger than ~20 million.
|
||||||
let thumbnail_rect = if render_params.thumbnail {
|
let thumbnail_rect = if render_params.thumbnail {
|
||||||
@@ -2080,14 +2139,14 @@ impl Render for List<GradientStops> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(gradient) = self.element(index) else { continue };
|
let Some(gradient) = source.element(index) else { continue };
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let spread_method: GradientSpreadMethod = self.attr::<SpreadMethod>(index);
|
let spread_method: GradientSpreadMethod = source.attr::<SpreadMethod>(index);
|
||||||
let gradient_type: GradientType = self.attr::<GradientTypeAttr>(index);
|
let gradient_type: GradientType = source.attr::<GradientTypeAttr>(index);
|
||||||
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
let tag = if thumbnail_rect.is_some() { "rect" } else { "polyline" };
|
||||||
render.leaf_tag(tag, |attributes| {
|
render.leaf_tag(tag, |attributes| {
|
||||||
if let Some((min, size)) = thumbnail_rect {
|
if let Some((min, size)) = thumbnail_rect {
|
||||||
@@ -2161,21 +2220,21 @@ impl Render for List<GradientStops> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_gradient_vello<S: LaneSource<Element = GradientStops>>(source: &S, scene: &mut Scene, parent_transform: DAffine2, render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
|
||||||
if let RenderMode::Outline = render_params.render_mode {
|
if let RenderMode::Outline = render_params.render_mode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(gradient) = self.element(index) else { continue };
|
let Some(gradient) = source.element(index) else { continue };
|
||||||
let spread_method: GradientSpreadMethod = self.attr::<SpreadMethod>(index);
|
let spread_method: GradientSpreadMethod = source.attr::<SpreadMethod>(index);
|
||||||
let gradient_type: GradientType = self.attr::<GradientTypeAttr>(index);
|
let gradient_type: GradientType = source.attr::<GradientTypeAttr>(index);
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let gradient_transform = parent_transform * transform;
|
let gradient_transform = parent_transform * transform;
|
||||||
|
|
||||||
let blend_mode = blend_mode_attr.to_peniko();
|
let blend_mode = blend_mode_attr.to_peniko();
|
||||||
@@ -2244,6 +2303,15 @@ impl Render for List<GradientStops> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Render for List<GradientStops> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
render_gradient_svg(self, render, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_gradient_vello(self, scene, parent_transform, render_params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `kurbo::BezPath` from a glyph outline, baking in the glyph origin (`ox`, `oy`) and faux-italic shear (`tilt_tan`).
|
/// Builds a `kurbo::BezPath` from a glyph outline, baking in the glyph origin (`ox`, `oy`) and faux-italic shear (`tilt_tan`).
|
||||||
@@ -2317,22 +2385,22 @@ fn draw_glyph_run_to_bezpaths(glyph_run: &parley::GlyphRun<'_, ()>, x_offset: f3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lays out text item `index` of a styled `List<String>` and returns its local size and transform. The `BoundingBox` trait can't do
|
/// Lays out the text item at `index` and returns its local size and transform. The `BoundingBox` trait can't do
|
||||||
/// this since a bare `String` carries no typography, so click-target and bounding-box computation share this. Falls back to an em
|
/// this since a bare `String` carries no typography, so click-target and bounding-box computation share this. Falls back to an em
|
||||||
/// square if the font isn't registered yet.
|
/// square if the font isn't registered yet.
|
||||||
fn text_item_size_and_transform(list: &List<String>, index: usize) -> Option<(DVec2, DAffine2)> {
|
fn text_item_size_and_transform<S: LaneSource<Element = String>>(source: &S, index: usize) -> Option<(DVec2, DAffine2)> {
|
||||||
let text = list.element(index)?;
|
let text = source.element(index)?;
|
||||||
let font: Resource = {
|
let font: Resource = {
|
||||||
let f = list.attr::<Font>(index);
|
let f = source.attr::<Font>(index);
|
||||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f.clone() }
|
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f.clone() }
|
||||||
};
|
};
|
||||||
let font_size: f64 = list.attr::<FontSize>(index);
|
let font_size: f64 = source.attr::<FontSize>(index);
|
||||||
let line_height: f64 = list.attr::<LineHeight>(index);
|
let line_height: f64 = source.attr::<LineHeight>(index);
|
||||||
let letter_spacing: f64 = list.attr::<LetterSpacing>(index);
|
let letter_spacing: f64 = source.attr::<LetterSpacing>(index);
|
||||||
let max_width: Option<f64> = list.attr::<MaxWidth>(index);
|
let max_width: Option<f64> = source.attr::<MaxWidth>(index);
|
||||||
let max_height: Option<f64> = list.attr::<MaxHeight>(index);
|
let max_height: Option<f64> = source.attr::<MaxHeight>(index);
|
||||||
let align: text_nodes::TextAlign = list.attr::<TextAlign>(index);
|
let align: text_nodes::TextAlign = source.attr::<TextAlign>(index);
|
||||||
let transform: DAffine2 = list.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
|
|
||||||
let typesetting = text_nodes::TypesettingConfig {
|
let typesetting = text_nodes::TypesettingConfig {
|
||||||
font_size,
|
font_size,
|
||||||
@@ -2356,12 +2424,12 @@ fn text_item_size_and_transform(list: &List<String>, index: usize) -> Option<(DV
|
|||||||
Some((DVec2::new(width, height), transform))
|
Some((DVec2::new(width, height), transform))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Union bounding box of a styled `List<String>`, laid out per item. The `BoundingBox` trait returns `None` for `List<String>`
|
/// Union bounding box of a styled text source, laid out per item. The `BoundingBox` trait returns `None` for `List<String>`
|
||||||
/// (a bare `String` has no extent), so text-layer thumbnails and bounds use this instead. Each item is laid out under `outer_transform`.
|
/// (a bare `String` has no extent), so text-layer thumbnails and bounds use this instead. Each item is laid out under `outer_transform`.
|
||||||
pub fn text_list_bounding_box(list: &List<String>, outer_transform: DAffine2) -> RenderBoundingBox {
|
pub fn text_list_bounding_box<S: LaneSource<Element = String>>(source: &S, outer_transform: DAffine2) -> RenderBoundingBox {
|
||||||
let mut bounds: Option<[DVec2; 2]> = None;
|
let mut bounds: Option<[DVec2; 2]> = None;
|
||||||
for index in 0..list.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some((size, transform)) = text_item_size_and_transform(list, index) else { continue };
|
let Some((size, transform)) = text_item_size_and_transform(source, index) else { continue };
|
||||||
let full_transform = outer_transform * transform;
|
let full_transform = outer_transform * transform;
|
||||||
for corner in [DVec2::ZERO, DVec2::new(size.x, 0.), DVec2::new(0., size.y), size] {
|
for corner in [DVec2::ZERO, DVec2::new(size.x, 0.), DVec2::new(0., size.y), size] {
|
||||||
let point = full_transform.transform_point2(corner);
|
let point = full_transform.transform_point2(corner);
|
||||||
@@ -2379,13 +2447,13 @@ pub fn text_list_bounding_box(list: &List<String>, outer_transform: DAffine2) ->
|
|||||||
|
|
||||||
/// Like `List<Graphic>::thumbnail_bounding_box`, but lays out `Graphic::Text` items, which the `BoundingBox` trait reports as `None`.
|
/// Like `List<Graphic>::thumbnail_bounding_box`, but lays out `Graphic::Text` items, which the `BoundingBox` trait reports as `None`.
|
||||||
/// Used for layer thumbnails so text layers (whose content is a `List<Graphic>` wrapping the text) frame their content.
|
/// Used for layer thumbnails so text layers (whose content is a `List<Graphic>` wrapping the text) frame their content.
|
||||||
pub fn graphic_list_bounding_box(list: &List<Graphic>, transform: DAffine2) -> RenderBoundingBox {
|
pub fn graphic_list_bounding_box<S: LaneSource<Element = Graphic>>(source: &S, transform: DAffine2) -> RenderBoundingBox {
|
||||||
let mut combined: Option<[DVec2; 2]> = None;
|
let mut combined: Option<[DVec2; 2]> = None;
|
||||||
let mut any_infinite = false;
|
let mut any_infinite = false;
|
||||||
|
|
||||||
for index in 0..list.len() {
|
for index in 0..source.lane_count() {
|
||||||
let item_transform = transform * list.attr::<Transform>(index);
|
let item_transform = transform * source.attr::<Transform>(index);
|
||||||
let Some(graphic) = list.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_list) => text_list_bounding_box(text_list, 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),
|
||||||
@@ -2410,29 +2478,28 @@ pub fn graphic_list_bounding_box(list: &List<Graphic>, transform: DAffine2) -> R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for List<String> {
|
fn render_text_svg<S: LaneSource<Element = String>>(source: &S, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
for index in 0..source.lane_count() {
|
||||||
for index in 0..self.len() {
|
let Some(text) = source.element(index) else { continue };
|
||||||
let Some(text) = self.element(index) else { continue };
|
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let transform: DAffine2 = self.attr::<Transform>(index);
|
let transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let font: Resource = {
|
let font: Resource = {
|
||||||
let f = self.attr::<Font>(index);
|
let f = source.attr::<Font>(index);
|
||||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f.clone() }
|
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f.clone() }
|
||||||
};
|
};
|
||||||
let font_size: f64 = self.attr::<FontSize>(index);
|
let font_size: f64 = source.attr::<FontSize>(index);
|
||||||
let line_height: f64 = self.attr::<LineHeight>(index);
|
let line_height: f64 = source.attr::<LineHeight>(index);
|
||||||
let letter_spacing: f64 = self.attr::<LetterSpacing>(index);
|
let letter_spacing: f64 = source.attr::<LetterSpacing>(index);
|
||||||
let max_width: Option<f64> = self.attr::<MaxWidth>(index);
|
let max_width: Option<f64> = source.attr::<MaxWidth>(index);
|
||||||
let max_height: Option<f64> = self.attr::<MaxHeight>(index);
|
let max_height: Option<f64> = source.attr::<MaxHeight>(index);
|
||||||
let letter_tilt: f64 = self.attr::<LetterTilt>(index);
|
let letter_tilt: f64 = source.attr::<LetterTilt>(index);
|
||||||
let align: text_nodes::TextAlign = self.attr::<TextAlign>(index);
|
let align: text_nodes::TextAlign = source.attr::<TextAlign>(index);
|
||||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
|
|
||||||
let typesetting = text_nodes::TypesettingConfig {
|
let typesetting = text_nodes::TypesettingConfig {
|
||||||
@@ -2496,28 +2563,28 @@ impl Render for List<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
fn render_text_vello<S: LaneSource<Element = String>>(source: &S, scene: &mut Scene, transform: DAffine2, render_params: &RenderParams) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some(text) = self.element(index) else { continue };
|
let Some(text) = source.element(index) else { continue };
|
||||||
if text.is_empty() {
|
if text.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_transform: DAffine2 = self.attr::<Transform>(index);
|
let item_transform: DAffine2 = source.attr::<Transform>(index);
|
||||||
let font: Resource = {
|
let font: Resource = {
|
||||||
let f = self.attr::<Font>(index);
|
let f = source.attr::<Font>(index);
|
||||||
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f.clone() }
|
if f.is_empty() { text_nodes::FALLBACK_FONT_RESOURCE.clone() } else { f.clone() }
|
||||||
};
|
};
|
||||||
let font_size: f64 = self.attr::<FontSize>(index);
|
let font_size: f64 = source.attr::<FontSize>(index);
|
||||||
let line_height: f64 = self.attr::<LineHeight>(index);
|
let line_height: f64 = source.attr::<LineHeight>(index);
|
||||||
let letter_spacing: f64 = self.attr::<LetterSpacing>(index);
|
let letter_spacing: f64 = source.attr::<LetterSpacing>(index);
|
||||||
let max_width: Option<f64> = self.attr::<MaxWidth>(index);
|
let max_width: Option<f64> = source.attr::<MaxWidth>(index);
|
||||||
let max_height: Option<f64> = self.attr::<MaxHeight>(index);
|
let max_height: Option<f64> = source.attr::<MaxHeight>(index);
|
||||||
let letter_tilt: f64 = self.attr::<LetterTilt>(index);
|
let letter_tilt: f64 = source.attr::<LetterTilt>(index);
|
||||||
let align: text_nodes::TextAlign = self.attr::<TextAlign>(index);
|
let align: text_nodes::TextAlign = source.attr::<TextAlign>(index);
|
||||||
let blend_mode_attr: BlendMode = self.attr::<BlendModeAttr>(index);
|
let blend_mode_attr: BlendMode = source.attr::<BlendModeAttr>(index);
|
||||||
let opacity_attr: f64 = self.attr::<Opacity>(index);
|
let opacity_attr: f64 = source.attr::<Opacity>(index);
|
||||||
let opacity_fill_attr: f64 = self.attr::<OpacityFill>(index);
|
let opacity_fill_attr: f64 = source.attr::<OpacityFill>(index);
|
||||||
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
let opacity = (opacity_attr * if render_params.for_mask { 1. } else { opacity_fill_attr }) as f32;
|
||||||
|
|
||||||
let typesetting = text_nodes::TypesettingConfig {
|
let typesetting = text_nodes::TypesettingConfig {
|
||||||
@@ -2565,9 +2632,9 @@ impl Render for List<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
fn collect_text_metadata<S: LaneSource<Element = String>>(source: &S, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||||
// Click targets are baked relative to item 0's transform, which `Graphic::collect_metadata` records as `local_transforms[element_id]`.
|
// Click targets are baked relative to item 0's transform, which `Graphic::collect_metadata` records as `local_transforms[element_id]`.
|
||||||
let item_zero_transform: DAffine2 = if !self.is_empty() { self.attr::<Transform>(0) } else { DAffine2::IDENTITY };
|
let item_zero_transform: DAffine2 = if source.lane_count() > 0 { source.attr::<Transform>(0) } else { DAffine2::IDENTITY };
|
||||||
let item_zero_inverse = if item_zero_transform.matrix2.determinant() != 0. {
|
let item_zero_inverse = if item_zero_transform.matrix2.determinant() != 0. {
|
||||||
item_zero_transform.inverse()
|
item_zero_transform.inverse()
|
||||||
} else {
|
} else {
|
||||||
@@ -2576,8 +2643,8 @@ impl Render for List<String> {
|
|||||||
|
|
||||||
let mut accumulated_click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
let mut accumulated_click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>> = HashMap::new();
|
||||||
|
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let layer_path: &[NodeId] = self.attr::<EditorLayerPath>(index);
|
let layer_path: &[NodeId] = source.attr::<EditorLayerPath>(index);
|
||||||
let layer = layer_path.last().copied();
|
let layer = layer_path.last().copied();
|
||||||
let Some(element_id) = caller_element_id.or(layer) else { continue };
|
let Some(element_id) = caller_element_id.or(layer) else { continue };
|
||||||
|
|
||||||
@@ -2587,7 +2654,7 @@ impl Render for List<String> {
|
|||||||
metadata.local_transforms.entry(element_id).or_insert(item_zero_transform);
|
metadata.local_transforms.entry(element_id).or_insert(item_zero_transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some((size, item_transform)) = text_item_size_and_transform(self, index) else { continue };
|
let Some((size, item_transform)) = text_item_size_and_transform(source, index) else { continue };
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, size);
|
let subpath = Subpath::new_rectangle(DVec2::ZERO, size);
|
||||||
let mut target = ClickTarget::new_with_subpath(subpath, 0.);
|
let mut target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||||
target.apply_transform(item_zero_inverse * item_transform);
|
target.apply_transform(item_zero_inverse * item_transform);
|
||||||
@@ -2601,15 +2668,32 @@ impl Render for List<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_text_upstream_click_targets<S: LaneSource<Element = String>>(source: &S, click_targets: &mut Vec<ClickTarget>) {
|
||||||
for index in 0..self.len() {
|
for index in 0..source.lane_count() {
|
||||||
let Some((size, transform)) = text_item_size_and_transform(self, index) else { continue };
|
let Some((size, transform)) = text_item_size_and_transform(source, index) else { continue };
|
||||||
let subpath = Subpath::new_rectangle(DVec2::ZERO, size);
|
let subpath = Subpath::new_rectangle(DVec2::ZERO, size);
|
||||||
let mut target = ClickTarget::new_with_subpath(subpath, 0.);
|
let mut target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||||
target.apply_transform(transform);
|
target.apply_transform(transform);
|
||||||
click_targets.push(target);
|
click_targets.push(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Render for List<String> {
|
||||||
|
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||||
|
render_text_svg(self, render, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) {
|
||||||
|
render_text_vello(self, scene, transform, render_params)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||||
|
collect_text_metadata(self, metadata, footprint, caller_element_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
|
add_text_upstream_click_targets(self, click_targets)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|||||||
Reference in New Issue
Block a user