Add the SVG Preview render mode in place of the Vello option in the preferences (#3797)

* Remove Vello from preferences

* Add the Render Mode: SVG Preview radio button

* Remove SVG outline renderer

* Add a tooltip explaination when disabled in unsupported browsers

* Fix Eyedropper tool to support Outline render mode

* Use #[allow(clippy::too_many_arguments)] instead of tuple

* Rerun nodegraph when max render area is changed

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Keavon Chambers 2026-02-22 12:27:26 -08:00 committed by GitHub
parent a2d3b3f410
commit 9f2c8713ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 102 additions and 169 deletions

View File

@ -314,38 +314,6 @@ impl PreferencesDialogMessageHandler {
} }
if wgpu_available { if wgpu_available {
let vello_description = "Auto uses Vello renderer when GPU is available.";
let vello_renderer_label = vec![
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
TextLabel::new("Vello Renderer")
.tooltip_label("Vello Renderer")
.tooltip_description(vello_description)
.widget_instance(),
];
let vello_preference = RadioInput::new(vec![
RadioEntryData::new("Auto").label("Auto").on_update(move |_| {
PreferencesMessage::VelloPreference {
preference: graph_craft::wasm_application_io::VelloPreference::Auto,
}
.into()
}),
RadioEntryData::new("Disabled").label("Disabled").on_update(move |_| {
PreferencesMessage::VelloPreference {
preference: graph_craft::wasm_application_io::VelloPreference::Disabled,
}
.into()
}),
])
.selected_index(Some(preferences.vello_preference as u32))
.widget_instance();
let vello_preference = vec![
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
vello_preference,
];
rows.extend_from_slice(&[vello_renderer_label, vello_preference]);
let render_tile_resolution_description = " let render_tile_resolution_description = "
Maximum X or Y resolution per render tile. Larger tiles may improve performance but can cause flickering or missing content in complex artwork if set too high.\n\ Maximum X or Y resolution per render tile. Larger tiles may improve performance but can cause flickering or missing content in complex artwork if set too high.\n\
\n\ \n\

View File

@ -33,6 +33,7 @@ use graphene_std::math::quad::Quad;
use graphene_std::path_bool::{boolean_intersect, path_bool_lib}; use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Raster; use graphene_std::raster_types::Raster;
use graphene_std::render_node::wgpu_available;
use graphene_std::subpath::Subpath; use graphene_std::subpath::Subpath;
use graphene_std::table::Table; use graphene_std::table::Table;
use graphene_std::vector::PointId; use graphene_std::vector::PointId;
@ -2549,29 +2550,46 @@ impl DocumentMessageHandler {
.popover_min_width(Some(320)) .popover_min_width(Some(320))
.widget_instance(), .widget_instance(),
Separator::new(SeparatorStyle::Unrelated).widget_instance(), Separator::new(SeparatorStyle::Unrelated).widget_instance(),
RadioInput::new(vec![ {
RadioEntryData::new("Normal") let disabled = cfg!(target_family = "wasm") && wgpu_available() == Some(false);
.icon("RenderModeNormal")
.tooltip_label("Render Mode: Normal") let mut entries = vec![
.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Normal }.into()), RadioEntryData::new("Normal")
RadioEntryData::new("Outline") .icon("RenderModeNormal")
.icon("RenderModeOutline") .tooltip_label("Render Mode: Normal")
.tooltip_label("Render Mode: Outline") .on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Normal }.into()),
.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline }.into()), RadioEntryData::new("Outline")
// TODO: See issue #320 .icon("RenderModeOutline")
// RadioEntryData::new("PixelPreview") .tooltip_label("Render Mode: Outline")
// .icon("RenderModePixels") .on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline }.into()),
// .tooltip_label("Render Mode: Pixel Preview") // TODO: See issue #320
// .on_update(|_| todo!()), // RadioEntryData::new("PixelPreview")
// TODO: See issue #1845 // .icon("RenderModePixels")
// RadioEntryData::new("SvgPreview") // .tooltip_label("Render Mode: Pixel Preview")
// .icon("RenderModeSvg") // .on_update(|_| todo!()),
// .tooltip_label("Render Mode: SVG Preview") RadioEntryData::new("SvgPreview")
// .on_update(|_| todo!()), .icon("RenderModeSvg")
]) .tooltip_label("Render Mode: SVG Preview")
.selected_index(Some(self.render_mode as u32)) .on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::SvgPreview }.into()),
.narrow(true) ];
.widget_instance(), let mut selected_index = self.render_mode as u32;
if disabled {
for entry in &mut entries {
entry.tooltip_description = "
*Normal* and *Outline* render modes are not available in this browser. For compatibility, *SVG Preview* mode is active as a fallback.\n\
\n\
This functionality requires WebGPU support. Check webgpu.org for browser implementation status.
"
.trim()
.into();
}
selected_index = entries.iter().position(|entry| entry.value == "SvgPreview").unwrap() as u32;
}
RadioInput::new(entries).selected_index(Some(selected_index)).disabled(disabled).narrow(true).widget_instance()
},
Separator::new(SeparatorStyle::Unrelated).widget_instance(), Separator::new(SeparatorStyle::Unrelated).widget_instance(),
]; ];

View File

@ -157,5 +157,4 @@ pub enum PortfolioMessage {
ToggleRulers, ToggleRulers,
UpdateDocumentWidgets, UpdateDocumentWidgets,
UpdateOpenDocumentsList, UpdateOpenDocumentsList,
UpdateVelloPreference,
} }

View File

@ -407,6 +407,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize) // Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)
let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2(); let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();
// TODO: Remove this when we do the SVG rendering with a separate library on desktop, thus avoiding a need for the hole punch.
// TODO: See #3796. There is a second instance of this todo comment and code block (be sure to remove both).
#[cfg(not(target_family = "wasm"))]
responses.add_front(FrontendMessage::UpdateViewportHolePunch {
active: document.render_mode != graphene_std::vector::style::RenderMode::SvgPreview,
});
if let Ok(message) = self.executor.submit_node_graph_evaluation( if let Ok(message) = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(document_id).expect("Tried to render non-existent document"), self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
*document_id, *document_id,
@ -1163,6 +1170,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize) // Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)
let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2(); let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();
// TODO: Remove this when we do the SVG rendering with a separate library on desktop, thus avoiding a need for the hole punch.
// TODO: See #3796. There is a second instance of this todo comment and code block (be sure to remove both).
#[cfg(not(target_family = "wasm"))]
responses.add_front(FrontendMessage::UpdateViewportHolePunch {
active: document.render_mode != graphene_std::vector::style::RenderMode::SvgPreview,
});
let result = self let result = self
.executor .executor
.submit_node_graph_evaluation(document, document_id, physical_resolution, scale, timing_information, node_to_inspect, ignore_hash, pointer_position); .submit_node_graph_evaluation(document, document_id, physical_resolution, scale, timing_information, node_to_inspect, ignore_hash, pointer_position);
@ -1197,7 +1211,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let result = self let result = self
.executor .executor
.submit_eyedropper_preview(document_id, preview_transform, pointer_position, resolution, scale, timing_information); .submit_eyedropper_preview(document, document_id, preview_transform, pointer_position, resolution, scale, timing_information);
match result { match result {
Err(description) => { Err(description) => {
@ -1364,13 +1378,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout); responses.add(PortfolioMessage::RequestWelcomeScreenButtonsLayout);
} }
} }
PortfolioMessage::UpdateVelloPreference => {
// TODO: Resend this message once the GPU context is initialized to avoid having the hole punch be stuck in an invalid state
let active = if cfg!(target_family = "wasm") { false } else { preferences.use_vello() };
responses.add(FrontendMessage::UpdateViewportHolePunch { active });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.persistent_data.use_vello = preferences.use_vello();
}
} }
} }

View File

@ -6,7 +6,6 @@ use graphene_std::text::{Font, FontCache};
pub struct PersistentData { pub struct PersistentData {
pub font_cache: FontCache, pub font_cache: FontCache,
pub font_catalog: FontCatalog, pub font_catalog: FontCatalog,
pub use_vello: bool,
} }
// TODO: Should this be a BTreeMap instead? // TODO: Should this be a BTreeMap instead?

View File

@ -10,7 +10,6 @@ pub enum PreferencesMessage {
ResetToDefaults, ResetToDefaults,
// Per-preference messages // Per-preference messages
VelloPreference { preference: graph_craft::wasm_application_io::VelloPreference },
SelectionMode { selection_mode: SelectionMode }, SelectionMode { selection_mode: SelectionMode },
BrushTool { enabled: bool }, BrushTool { enabled: bool },
ModifyLayout { zoom_with_scroll: bool }, ModifyLayout { zoom_with_scroll: bool },

View File

@ -5,7 +5,6 @@ use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::ToolType; use crate::messages::tool::utility_types::ToolType;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::EditorPreferences;
use graphene_std::application_io::GetEditorPreferences;
#[derive(ExtractField)] #[derive(ExtractField)]
pub struct PreferencesMessageContext<'a> { pub struct PreferencesMessageContext<'a> {
@ -17,7 +16,6 @@ pub struct PreferencesMessageContext<'a> {
pub struct PreferencesMessageHandler { pub struct PreferencesMessageHandler {
pub selection_mode: SelectionMode, pub selection_mode: SelectionMode,
pub zoom_with_scroll: bool, pub zoom_with_scroll: bool,
pub vello_preference: graph_craft::wasm_application_io::VelloPreference,
pub brush_tool: bool, pub brush_tool: bool,
pub graph_wire_style: GraphWireStyle, pub graph_wire_style: GraphWireStyle,
pub viewport_zoom_wheel_rate: f64, pub viewport_zoom_wheel_rate: f64,
@ -37,7 +35,6 @@ impl PreferencesMessageHandler {
pub fn editor_preferences(&self) -> EditorPreferences { pub fn editor_preferences(&self) -> EditorPreferences {
EditorPreferences { EditorPreferences {
vello_preference: self.vello_preference,
max_render_region_size: self.max_render_region_size, max_render_region_size: self.max_render_region_size,
} }
} }
@ -45,10 +42,6 @@ impl PreferencesMessageHandler {
pub fn supports_wgpu(&self) -> bool { pub fn supports_wgpu(&self) -> bool {
graph_craft::wasm_application_io::wgpu_available().unwrap_or_default() graph_craft::wasm_application_io::wgpu_available().unwrap_or_default()
} }
pub fn use_vello(&self) -> bool {
self.editor_preferences().use_vello()
}
} }
impl Default for PreferencesMessageHandler { impl Default for PreferencesMessageHandler {
@ -56,7 +49,6 @@ impl Default for PreferencesMessageHandler {
Self { Self {
selection_mode: SelectionMode::Touched, selection_mode: SelectionMode::Touched,
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll), zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
vello_preference: EditorPreferences::default().vello_preference,
brush_tool: false, brush_tool: false,
graph_wire_style: GraphWireStyle::default(), graph_wire_style: GraphWireStyle::default(),
viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE, viewport_zoom_wheel_rate: VIEWPORT_ZOOM_WHEEL_RATE,
@ -78,7 +70,6 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
*self = preferences; *self = preferences;
responses.add(PortfolioMessage::EditorPreferences); responses.add(PortfolioMessage::EditorPreferences);
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PreferencesMessage::ModifyLayout { responses.add(PreferencesMessage::ModifyLayout {
zoom_with_scroll: self.zoom_with_scroll, zoom_with_scroll: self.zoom_with_scroll,
}); });
@ -90,12 +81,6 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
} }
// Per-preference messages // Per-preference messages
PreferencesMessage::VelloPreference { preference } => {
self.vello_preference = preference;
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PortfolioMessage::EditorPreferences);
responses.add(PreferencesDialogMessage::Update);
}
PreferencesMessage::BrushTool { enabled } => { PreferencesMessage::BrushTool { enabled } => {
self.brush_tool = enabled; self.brush_tool = enabled;
@ -131,8 +116,8 @@ impl MessageHandler<PreferencesMessage, PreferencesMessageContext<'_>> for Prefe
} }
PreferencesMessage::MaxRenderRegionSize { size } => { PreferencesMessage::MaxRenderRegionSize { size } => {
self.max_render_region_size = size; self.max_render_region_size = size;
responses.add(PortfolioMessage::UpdateVelloPreference);
responses.add(PortfolioMessage::EditorPreferences); responses.add(PortfolioMessage::EditorPreferences);
responses.add(NodeGraphMessage::RunDocumentGraph);
} }
} }

View File

@ -187,9 +187,11 @@ impl NodeGraphExecutor {
self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, viewport_scale, time, pointer) self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, viewport_scale, time, pointer)
} }
#[allow(clippy::too_many_arguments)]
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
pub(crate) fn submit_eyedropper_preview( pub(crate) fn submit_eyedropper_preview(
&mut self, &mut self,
document: &DocumentMessageHandler,
document_id: DocumentId, document_id: DocumentId,
transform: DAffine2, transform: DAffine2,
pointer: DVec2, pointer: DVec2,
@ -208,7 +210,7 @@ impl NodeGraphExecutor {
time, time,
pointer, pointer,
export_format: graphene_std::application_io::ExportFormat::Raster, export_format: graphene_std::application_io::ExportFormat::Raster,
render_mode: graphene_std::vector::style::RenderMode::Normal, render_mode: document.render_mode,
hide_artboards: false, hide_artboards: false,
for_export: false, for_export: false,
for_eyedropper: true, for_eyedropper: true,

View File

@ -18,6 +18,7 @@ use graphene_std::table::{Table, TableRow};
use graphene_std::text::FontCache; use graphene_std::text::FontCache;
use graphene_std::transform::RenderQuality; use graphene_std::transform::RenderQuality;
use graphene_std::vector::Vector; use graphene_std::vector::Vector;
use graphene_std::vector::style::RenderMode;
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
use graphene_std::{Artboard, Context, Graphic}; use graphene_std::{Artboard, Context, Graphic};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta}; use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
@ -243,16 +244,11 @@ impl NodeRuntime {
self.sender.send_generation_response(CompilationResponse { result, node_graph_errors }); self.sender.send_generation_response(CompilationResponse { result, node_graph_errors });
} }
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => { GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, mut render_config, .. }) => {
// There are cases where we want to export via the svg pipeline eventhough raster was requested. // We may want to render via the SVG pipeline even though raster was requested, if SVG Preview render mode is active or WebGPU/Vello is unavailable
if matches!(render_config.export_format, ExportFormat::Raster) { if render_config.export_format == ExportFormat::Raster
let vello_available = self.editor_api.application_io.as_ref().unwrap().gpu_executor().is_some(); && (render_config.render_mode == RenderMode::SvgPreview || self.editor_api.application_io.as_ref().unwrap().gpu_executor().is_none())
let use_vello = vello_available && self.editor_api.editor_preferences.use_vello(); {
render_config.export_format = ExportFormat::Svg;
// On web when the user has disabled vello rendering in the preferences or we are exporting.
// And on all platforms when vello is not supposed to be used.
if !use_vello || cfg!(target_family = "wasm") && render_config.for_export {
render_config.export_format = ExportFormat::Svg;
}
} }
let result = self.execute_network(render_config).await; let result = self.execute_network(render_config).await;

View File

@ -79,15 +79,6 @@
margin-right: 2px; margin-right: 2px;
} }
&:hover {
background: var(--color-6-lowergray);
color: var(--color-f-white);
svg {
fill: var(--color-f-white);
}
}
&.active { &.active {
background: var(--color-e-nearwhite); background: var(--color-e-nearwhite);
color: var(--color-2-mildblack); color: var(--color-2-mildblack);
@ -112,19 +103,12 @@
} }
} }
&.narrow.narrow { &:not(.disabled) button:not(.active):hover {
--widget-height: 20px; background: var(--color-6-lowergray);
height: var(--widget-height); color: var(--color-f-white);
button { svg {
height: 16px; fill: var(--color-f-white);
}
}
&.mixed {
button:not(:hover),
&.disabled button:hover {
background: var(--color-5-dullgray);
} }
} }
@ -144,5 +128,21 @@
} }
} }
} }
&.narrow.narrow {
--widget-height: 20px;
height: var(--widget-height);
button {
height: 16px;
}
}
&.mixed {
button:not(:hover),
&.disabled button:hover {
background: var(--color-5-dullgray);
}
}
} }
</style> </style>

View File

@ -967,7 +967,7 @@ async fn poll_node_graph_evaluation() {
if !editor::node_graph_executor::run_node_graph().await.0 { if !editor::node_graph_executor::run_node_graph().await.0 {
return; return;
}; }
editor_and_handle(|editor, handle| { editor_and_handle(|editor, handle| {
let mut messages = VecDeque::new(); let mut messages = VecDeque::new();

View File

@ -336,27 +336,13 @@ pub type WasmSurfaceHandle = SurfaceHandle<wgpu_executor::Window>;
#[cfg(feature = "wgpu")] #[cfg(feature = "wgpu")]
pub type WasmSurfaceHandleFrame = graphene_application_io::SurfaceHandleFrame<wgpu_executor::Window>; pub type WasmSurfaceHandleFrame = graphene_application_io::SurfaceHandleFrame<wgpu_executor::Window>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, specta::Type, serde::Serialize, serde::Deserialize)]
pub enum VelloPreference {
Auto,
Disabled,
}
#[derive(Clone, Debug, PartialEq, Hash, specta::Type, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, Hash, specta::Type, serde::Serialize, serde::Deserialize)]
pub struct EditorPreferences { pub struct EditorPreferences {
pub vello_preference: VelloPreference,
/// Maximum render region size in pixels along one dimension of the square area. /// Maximum render region size in pixels along one dimension of the square area.
pub max_render_region_size: u32, pub max_render_region_size: u32,
} }
impl graphene_application_io::GetEditorPreferences for EditorPreferences { impl graphene_application_io::GetEditorPreferences for EditorPreferences {
fn use_vello(&self) -> bool {
match self.vello_preference {
VelloPreference::Auto => wgpu_available().unwrap_or(false),
VelloPreference::Disabled => false,
}
}
fn max_render_region_area(&self) -> u32 { fn max_render_region_area(&self) -> u32 {
let size = self.max_render_region_size.min(u32::MAX.isqrt()); let size = self.max_render_region_size.min(u32::MAX.isqrt());
size.pow(2) size.pow(2)
@ -365,10 +351,7 @@ impl graphene_application_io::GetEditorPreferences for EditorPreferences {
impl Default for EditorPreferences { impl Default for EditorPreferences {
fn default() -> Self { fn default() -> Self {
Self { Self { max_render_region_size: 1280 }
vello_preference: VelloPreference::Auto,
max_render_region_size: 1280,
}
} }
} }

View File

@ -126,7 +126,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
let device = wgpu_executor_ref.context.device.clone(); let device = wgpu_executor_ref.context.device.clone();
let preferences = EditorPreferences { let preferences = EditorPreferences {
vello_preference: graph_craft::wasm_application_io::VelloPreference::Auto,
max_render_region_size: EditorPreferences::default().max_render_region_size, max_render_region_size: EditorPreferences::default().max_render_region_size,
}; };
let editor_api = Arc::new(WasmEditorApi { let editor_api = Arc::new(WasmEditorApi {

View File

@ -217,7 +217,6 @@ impl<T: NodeGraphUpdateSender> NodeGraphUpdateSender for std::sync::Mutex<T> {
} }
pub trait GetEditorPreferences { pub trait GetEditorPreferences {
fn use_vello(&self) -> bool;
fn max_render_region_area(&self) -> u32; fn max_render_region_area(&self) -> u32;
} }
@ -238,11 +237,11 @@ pub struct TimingInformation {
pub struct RenderConfig { pub struct RenderConfig {
pub viewport: Footprint, pub viewport: Footprint,
pub scale: f64, pub scale: f64,
pub export_format: ExportFormat,
pub time: TimingInformation, pub time: TimingInformation,
pub pointer: DVec2, pub pointer: DVec2,
#[serde(alias = "view_mode")] #[serde(alias = "view_mode")]
pub render_mode: RenderMode, pub render_mode: RenderMode,
pub export_format: ExportFormat,
pub hide_artboards: bool, pub hide_artboards: bool,
pub for_export: bool, pub for_export: bool,
pub for_eyedropper: bool, pub for_eyedropper: bool,
@ -259,10 +258,6 @@ impl NodeGraphUpdateSender for Logger {
struct DummyPreferences; struct DummyPreferences;
impl GetEditorPreferences for DummyPreferences { impl GetEditorPreferences for DummyPreferences {
fn use_vello(&self) -> bool {
false
}
fn max_render_region_area(&self) -> u32 { fn max_render_region_area(&self) -> u32 {
1024 * 1024 1024 * 1024
} }

View File

@ -1,9 +1,8 @@
use crate::renderer::{RenderParams, black_or_white_for_best_contrast, format_transform_matrix}; use crate::renderer::{RenderParams, format_transform_matrix};
use core_types::consts::LAYER_OUTLINE_STROKE_WEIGHT;
use core_types::uuid::generate_uuid; use core_types::uuid::generate_uuid;
use glam::DAffine2; use glam::DAffine2;
use graphic_types::vector_types::gradient::{Gradient, GradientType}; use graphic_types::vector_types::gradient::{Gradient, GradientType};
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, PathStyle, RenderMode, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; use graphic_types::vector_types::vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
use std::fmt::Write; use std::fmt::Write;
pub trait RenderExt { pub trait RenderExt {
@ -14,7 +13,7 @@ pub trait RenderExt {
impl RenderExt for Gradient { impl RenderExt for Gradient {
type Output = u64; type Output = u64;
// /// Adds the gradient def through mutating the first argument, returning the gradient ID. /// Adds the gradient def through mutating the first argument, returning the gradient ID.
fn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, _render_params: &RenderParams) -> Self::Output { fn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, _render_params: &RenderParams) -> Self::Output {
let mut stop = String::new(); let mut stop = String::new();
for (position, color) in self.stops.0.iter() { for (position, color) in self.stops.0.iter() {
@ -163,28 +162,12 @@ impl RenderExt for PathStyle {
/// Renders the shape's fill and stroke attributes as a string with them concatenated together. /// Renders the shape's fill and stroke attributes as a string with them concatenated together.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, render_params: &RenderParams) -> String { fn render(&self, svg_defs: &mut String, element_transform: DAffine2, stroke_transform: DAffine2, bounds: DAffine2, transformed_bounds: DAffine2, render_params: &RenderParams) -> String {
let render_mode = render_params.render_mode; let fill_attribute = self.fill.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params);
match render_mode { let stroke_attribute = self
RenderMode::Outline => { .stroke
let fill_attribute = Fill::None.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params); .as_ref()
.map(|stroke| stroke.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params))
let outline_color = black_or_white_for_best_contrast(render_params.artboard_background); .unwrap_or_default();
let mut outline_stroke = Stroke::new(Some(outline_color), LAYER_OUTLINE_STROKE_WEIGHT); format!("{fill_attribute}{stroke_attribute}")
// Outline strokes should be non-scaling by default
outline_stroke.non_scaling = true;
let stroke_attribute = outline_stroke.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params);
format!("{fill_attribute}{stroke_attribute}")
}
_ => {
let fill_attribute = self.fill.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params);
let stroke_attribute = self
.stroke
.as_ref()
.map(|stroke| stroke.render(svg_defs, element_transform, stroke_transform, bounds, transformed_bounds, render_params))
.unwrap_or_default();
format!("{fill_attribute}{stroke_attribute}")
}
}
} }
} }

View File

@ -662,6 +662,6 @@ pub enum RenderMode {
Outline, Outline,
// /// Render with normal coloration at the document resolution, showing the pixels when the current viewport resolution is higher // /// Render with normal coloration at the document resolution, showing the pixels when the current viewport resolution is higher
// PixelPreview, // PixelPreview,
// /// Render a preview of how the object would be exported as an SVG. /// Render a preview of how the object would be exported as an SVG.
// SvgPreview, SvgPreview,
} }