Merge origin/master into the async record refactor

Scaffolding merge for the reconcile; the final series to master is
authored fresh. Rank plumbing resolves to our axis-IR model, the node
macro and the LaneSource render walk stay ours, master's vector
restructure and gradient vocabulary are adopted, and the paint and
appearance adoption is deliberately deferred behind our fill and stroke
markers.
This commit is contained in:
Dennis Kobert
2026-09-08 15:03:57 +00:00
385 changed files with 34669 additions and 20078 deletions

View File

@@ -12,7 +12,7 @@ pub use graphene_application_io as application_io;
pub use graphene_core;
pub use graphene_core::debug;
pub use graphic_nodes;
pub use graphic_types::{Artboard, Graphic, Vector};
pub use graphic_types::{Appearance, Artboard, Cover, CoverPlacement, Coverage, Graphic, Vector, stamp_coverage};
pub use math_nodes;
pub use path_bool_nodes;
pub use raster_nodes;
@@ -32,7 +32,7 @@ pub mod vector {
pub use vector_types::vector::algorithms;
pub use vector_types::vector::click_target;
pub use vector_types::vector::misc::HandleId;
pub use vector_types::vector::{PointId, RegionId, SegmentId, StrokeId};
pub use vector_types::vector::{PointId, SegmentId};
pub use vector_types::vector::{deserialize_hashmap, serialize_hashmap, serialize_hashmap_as_sorted_object};
// Re-export HandleExt trait and NoHashBuilder
@@ -53,12 +53,8 @@ pub mod artboard {
pub use graphic_types::artboard::*;
}
pub mod subpath {
pub use vector_types::subpath::*;
}
pub mod gradient {
pub use vector_types::{GradientStop, GradientStops};
pub use vector_types::{Gradient, GradientStop};
}
pub mod transform {
@@ -71,10 +67,11 @@ pub mod repeat {
}
pub mod math {
pub use core_types::math::float_noise;
pub use core_types::math::quad;
pub mod math_ext {
pub use vector_types::{QuadExt, RectExt};
pub use vector_types::QuadExt;
}
}

View File

@@ -33,7 +33,7 @@ use graphic_types::markers::EditorMergedLayers;
use graphic_types::raster_types::Image;
use graphic_types::raster_types::{CPU, Raster};
#[cfg(target_family = "wasm")]
use graphic_types::vector_types::gradient::GradientStops;
use graphic_types::vector_types::gradient::Gradient;
#[cfg(target_family = "wasm")]
use rendering::{Render, RenderParams, RenderSvgSegmentList, SvgRender};
use std::sync::Arc;
@@ -212,7 +212,7 @@ async fn rasterize<T: WasmNotSend + Clone>(
List<Raster<CPU>>,
List<Graphic>,
List<Color>,
List<GradientStops>,
List<Gradient>,
)]
mut data: List<T>,
footprint: Footprint,
@@ -282,12 +282,17 @@ pub fn editor_api(_: impl Ctx, #[scope("editor-api")] editor_api: Arc<PlatformEd
pub fn resource(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> SourceFuture<GPoll<Resource>> {
let application_io = editor_api.application_io.clone();
Box::pin(async move {
// An older document can name a resource whose bytes are gone, so hand back an empty one and keep the document loading
let Some(application_io) = application_io else {
return GPoll::error("ApplicationIo not available");
log::error!("Resource {hash} is unavailable because the platform's application IO is missing");
return GPoll::Final(Resource::empty());
};
match application_io.load_resource(hash).await {
Some(resource) => GPoll::Final(resource),
None => GPoll::error("resource not found"),
None => {
log::error!("Resource {hash} was not found in storage");
GPoll::Final(Resource::empty())
}
}
})
}

View File

@@ -8,8 +8,7 @@ use graph_craft::document::value::{RenderOutput, RenderOutputType};
use graphic_types::raster_types::Texture;
use rendering::{RenderParams, SvgRender, SvgRenderOutput};
use std::fmt::Write;
use wgpu::util::DeviceExt;
use wgpu_executor::{WgpuExecutor, WgpuPipeline, WgpuPipelineCache};
use wgpu_executor::{Buffer, WgpuExecutor, WgpuPipeline, WgpuPipelineCache};
#[node_macro::node(category(""))]
fn render_background(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, #[scope(composite_background_pipeline::IDENTIFIER)] pipeline: WgpuPipelineCache, data: RenderOutput) -> RenderOutput {
@@ -352,10 +351,8 @@ impl WgpuPipeline for CompositeBackground {
let foreground_view = foreground.create_view(&wgpu::TextureViewDescriptor::default());
let checker_draws = if backgrounds.is_empty() {
vec![(
3,
self.create_checker_bind_group(device, CompositeUniforms::fullscreen(viewport_size, screen_to_document, checker_size_doc)),
)]
let uniforms = CompositeUniforms::fullscreen(viewport_size, screen_to_document, checker_size_doc).create_buffer(executor);
vec![(3, self.create_checker_bind_group(device, &uniforms), uniforms)]
} else {
backgrounds
.iter()
@@ -370,8 +367,8 @@ impl WgpuPipeline for CompositeBackground {
return None;
}
let uniforms = CompositeUniforms::rect(min, max, document_to_screen, viewport_size, checker_size_doc);
Some((6, self.create_checker_bind_group(device, uniforms)))
let uniforms = CompositeUniforms::rect(min, max, document_to_screen, viewport_size, checker_size_doc).create_buffer(executor);
Some((6, self.create_checker_bind_group(device, &uniforms), uniforms))
})
.collect()
};
@@ -413,13 +410,13 @@ impl WgpuPipeline for CompositeBackground {
if backgrounds.is_empty() {
pass.set_pipeline(&self.checker_viewport_pipeline);
for (vertex_count, bind_group) in &checker_draws {
for (vertex_count, bind_group, _uniforms) in &checker_draws {
pass.set_bind_group(0, bind_group, &[]);
pass.draw(0..*vertex_count, 0..1);
}
} else {
pass.set_pipeline(&self.checker_rect_pipeline);
for (vertex_count, bind_group) in &checker_draws {
for (vertex_count, bind_group, _uniforms) in &checker_draws {
pass.set_bind_group(0, bind_group, &[]);
pass.draw(0..*vertex_count, 0..1);
}
@@ -437,19 +434,13 @@ impl WgpuPipeline for CompositeBackground {
}
impl CompositeBackground {
fn create_checker_bind_group(&self, device: &wgpu::Device, uniforms: CompositeUniforms) -> wgpu::BindGroup {
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("background_checker_uniforms"),
contents: bytemuck::bytes_of(&uniforms),
usage: wgpu::BufferUsages::UNIFORM,
});
fn create_checker_bind_group(&self, device: &wgpu::Device, uniforms: &Buffer) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("background_checker_bind_group"),
layout: &self.checker_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
resource: uniforms.as_entire_binding(),
}],
})
}
@@ -491,4 +482,12 @@ impl CompositeUniforms {
_pad: 0.,
}
}
fn create_buffer(&self, executor: &WgpuExecutor) -> Buffer {
executor.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("background_checker_uniforms"),
contents: bytemuck::bytes_of(self),
usage: wgpu::BufferUsages::UNIFORM,
})
}
}

View File

@@ -43,7 +43,7 @@ pub struct CacheKey {
pub for_mask: bool,
pub thumbnail: bool,
pub aligned_strokes: bool,
pub override_paint_order: bool,
pub stroke_below: bool,
pub animation_time_ms: i64,
pub real_time_ms: i64,
pub pointer: [u8; 16],
@@ -60,7 +60,7 @@ impl CacheKey {
for_mask: bool,
thumbnail: bool,
aligned_strokes: bool,
override_paint_order: bool,
stroke_below: bool,
animation_time: f64,
real_time: f64,
pointer: Option<DVec2>,
@@ -85,7 +85,7 @@ impl CacheKey {
for_mask,
thumbnail,
aligned_strokes,
override_paint_order,
stroke_below,
animation_time_ms: (animation_time * 1000.).round() as i64,
real_time_ms: (real_time * 1000.).round() as i64,
pointer: pointer_bytes,
@@ -360,7 +360,7 @@ pub fn render_output_cache(
render_params.for_mask,
render_params.thumbnail,
render_params.aligned_strokes,
render_params.override_paint_order,
render_params.stroke_below,
ctx.try_animation_time().unwrap_or(0.),
ctx.try_real_time().unwrap_or(0.),
ctx.try_pointer_position(),

View File

@@ -8,7 +8,7 @@ use graphic_types::raster_types::{CPU, Raster};
use graphic_types::{Artboard, Graphic, Vector};
use rendering::{Render, RenderMetadata, RenderOutputType as RenderOutputTypeRequest, RenderParams, SvgRender, SvgRenderOutput};
use std::sync::Arc;
use vector_types::GradientStops;
use vector_types::Gradient;
use wgpu_executor::RenderContext;
#[derive(Clone, dyn_any::DynAny)]
@@ -25,7 +25,7 @@ pub struct RenderIntermediate {
fn intermediate_of<R: Render>(data: &R, render_params: &RenderParams) -> RenderIntermediate {
let footprint = Footprint::default();
let mut metadata = RenderMetadata::default();
data.collect_metadata(&mut metadata, footprint, None);
data.collect_metadata(&mut metadata, footprint, None, None);
match &render_params.render_output_type {
RenderOutputTypeRequest::Vello => {
let mut scene = vello::Scene::new();
@@ -60,7 +60,7 @@ fn render_intermediate<T: dyn_any::StaticTypeSized + 'static + Render + WasmNotS
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
Context -> List<Gradient>,
Context -> List<String>,
)]
data: impl Node<Context<'_>, Output = T>,
@@ -80,7 +80,7 @@ fn render_intermediate<T: dyn_any::StaticTypeSized + 'static + Render + WasmNotS
#[node_macro::node(category(""))]
fn render_intermediate_leveled<T: Clone + Send + Sync + core_types::CacheHash + dyn_any::StaticTypeSized + 'static>(
ctx: impl Ctx + ExtractVarArgs + ExtractIndex + InjectIndex + Copy,
#[implementations(Artboard, Graphic, Vector, Raster<CPU>, Color, GradientStops, String)] data: IList<T>,
#[implementations(Artboard, Graphic, Vector, Raster<CPU>, Color, Gradient, String)] data: IList<T>,
) -> Result<RenderIntermediate, Interrupt>
where
for<'a> core_types::record::RunView<'a, T>: Render,

View File

@@ -5,7 +5,7 @@ use graph_craft::application_io::resource::Resource;
use graphic_types::Vector;
pub use text_nodes::*;
/// Produces a styled `String[]` carrying all typographic attributes.
/// Produces a styled text string carrying all typographic attributes.
///
/// Use the **Text to Vector** node to convert this into vector geometry if desired.
#[node_macro::node(category("Text"))]
@@ -15,15 +15,15 @@ fn text(
/// The text content to be drawn.
#[widget(ParsedWidgetOverride::Custom = "text_area")]
#[default("Lorem ipsum")]
text: String,
text: Item<String>,
/// The loaded font file used to draw the text. The editor resolves the chosen typeface to these bytes via the resource system.
#[widget(ParsedWidgetOverride::Custom = "text_font")]
font: Resource,
font: Item<Resource>,
/// The font size used to draw the text.
#[unit(" px")]
#[default(24.)]
#[hard(1..)]
size: f64,
size: Item<f64>,
/// The line height ratio, relative to the font size. Each line is drawn lower than its previous line by the distance of *Size* × *Line Height*.
///
/// 0 means all lines overlap. 1 means all lines are spaced by just the font size. 1.2 is a common default for readable text. 2 means double-spaced text.
@@ -31,74 +31,87 @@ fn text(
#[hard(0..)]
#[step(0.1)]
#[default(1.2)]
line_height: f64,
line_height: Item<f64>,
/// Additional spacing, in pixels, added between each character.
#[unit(" px")]
#[step(0.1)]
letter_spacing: f64,
letter_spacing: Item<f64>,
/// The angle of faux italic slant applied to each glyph.
#[unit("°")]
#[hard(-85..85)]
letter_tilt: f64,
letter_tilt: Item<f64>,
/// Enables the maximum width constraint so lines can wrap.
#[widget(ParsedWidgetOverride::Hidden)]
has_max_width: bool,
has_max_width: Item<bool>,
/// The maximum width that the text block can occupy before wrapping to a new line. Otherwise, lines do not wrap.
#[unit(" px")]
#[hard(1..)]
#[widget(ParsedWidgetOverride::Custom = "optional_f64")]
max_width: f64,
max_width: Item<f64>,
/// Whether the *Max Height* property is enabled so that lines beyond it are not drawn.
#[widget(ParsedWidgetOverride::Hidden)]
has_max_height: bool,
has_max_height: Item<bool>,
/// The maximum height that the text block can occupy. Excess lines are not drawn.
#[unit(" px")]
#[hard(1..)]
#[widget(ParsedWidgetOverride::Custom = "optional_f64")]
max_height: f64,
max_height: Item<f64>,
/// The horizontal alignment of each line of text within its surrounding box. To have an effect on a single line of text, *Max Width* must be set.
#[widget(ParsedWidgetOverride::Custom = "text_align")]
align: TextAlign,
) -> List<String> {
let mut list = List::new_from_element(text);
align: Item<TextAlign>,
) -> Item<String> {
let text = text.into_element();
let font = font.into_element();
let (size, line_height, letter_spacing, letter_tilt) = (*size.element(), *line_height.element(), *letter_spacing.element(), *letter_tilt.element());
let (has_max_width, max_width, has_max_height, max_height) = (*has_max_width.element(), *max_width.element(), *has_max_height.element(), *max_height.element());
let align = align.into_element();
let mut item = Item::new_from_element(text);
if font != Resource::default() {
list.set_attribute(ATTR_FONT, 0, font);
item.set_attribute(ATTR_FONT, font);
}
if (size - DEFAULT_FONT_SIZE).abs() > f64::EPSILON {
list.set_attribute(ATTR_FONT_SIZE, 0, size);
item.set_attribute(ATTR_FONT_SIZE, size);
}
if (line_height - DEFAULT_LINE_HEIGHT).abs() > f64::EPSILON {
list.set_attribute(ATTR_LINE_HEIGHT, 0, line_height);
item.set_attribute(ATTR_LINE_HEIGHT, line_height);
}
if letter_spacing != 0. {
list.set_attribute(ATTR_LETTER_SPACING, 0, letter_spacing);
item.set_attribute(ATTR_LETTER_SPACING, letter_spacing);
}
if letter_tilt != 0. {
list.set_attribute(ATTR_LETTER_TILT, 0, letter_tilt);
item.set_attribute(ATTR_LETTER_TILT, letter_tilt);
}
if has_max_width {
list.set_attribute(ATTR_MAX_WIDTH, 0, Some(max_width));
item.set_attribute(ATTR_MAX_WIDTH, Some(max_width));
}
if has_max_height {
list.set_attribute(ATTR_MAX_HEIGHT, 0, Some(max_height));
item.set_attribute(ATTR_MAX_HEIGHT, Some(max_height));
}
if align != TextAlign::default() {
list.set_attribute(ATTR_TEXT_ALIGN, 0, align);
item.set_attribute(ATTR_TEXT_ALIGN, align);
}
list
item
}
/// Converts a styled `String[]` into vector geometry.
/// Converts a styled text string into a vector compound path.
#[node_macro::node(category("Text"), name("Text to Vector"))]
fn text_to_vector(
_: impl Ctx,
/// A styled list of text strings produced by the **Text** node (or any other `String[]` source).
#[implementations(List<String>)]
strings: List<String>,
/// Whether to split every letterform into its own vector item. Otherwise, a single vector compound path is produced.
separate_glyphs: bool,
) -> List<Vector> {
shape_text_list(&strings, separate_glyphs)
/// A styled text string produced by the **Text** node (or any other string source).
string: Item<String>,
) -> Item<Vector> {
shape_text_item(&string, false).into_iter().next().unwrap_or_default()
}
/// Splits a styled text string into a separate vector item for each of its glyphs (letterforms).
#[node_macro::node(category("Text"), name("Text to Vector Glyphs"))]
fn text_to_vector_glyphs(
_: impl Ctx,
/// A styled text string produced by the **Text** node (or any other string source).
string: Item<String>,
) -> List<Vector> {
shape_text_item(&string, true)
}