Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use std::path::PathBuf;
use super::utility_types::misc::{GroupFolderType, SnappingState};
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, OverlaysType};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
use crate::messages::portfolio::utility_types::PanelType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::node_graph::document_node_definitions;
use super::node_graph::utility_types::Transform;
use super::overlays::utility_types::Pivot;
use super::utility_types::error::EditorError;
use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
Expand All @@ -14,7 +13,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings, Pivot};
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};
Expand Down
13 changes: 11 additions & 2 deletions editor/src/messages/portfolio/document/overlays/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ pub mod grid_overlays;
mod overlays_message;
mod overlays_message_handler;
pub mod utility_functions;
#[cfg_attr(not(target_family = "wasm"), path = "utility_types_vello.rs")]
pub mod utility_types;
// Native (non‑wasm)
#[cfg(not(target_family = "wasm"))]
pub mod utility_types_native;
#[cfg(not(target_family = "wasm"))]
pub use utility_types_native as utility_types;

// WebAssembly
#[cfg(target_family = "wasm")]
pub mod utility_types_web;
#[cfg(target_family = "wasm")]
pub use utility_types_web as utility_types;

#[doc(inline)]
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerSta
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
use glam::{DAffine2, DVec2};
use graphene_std::subpath::{Bezier, BezierHandles};
use graphene_std::text::{Font, FontCache, TextAlign, TextContext, TypesettingConfig};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::{PointId, SegmentId, Vector};
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
use wasm_bindgen::JsCast;

pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
Expand Down Expand Up @@ -218,3 +220,35 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
}
}
}

// Global lazy initialized font cache and text context
pub static GLOBAL_FONT_CACHE: LazyLock<FontCache> = LazyLock::new(|| {
let mut font_cache = FontCache::default();
// Initialize with the hardcoded font used by overlay text
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
font_cache.insert(font, String::new(), FONT_DATA.to_vec());
font_cache
});

pub static GLOBAL_TEXT_CONTEXT: LazyLock<Mutex<TextContext>> = LazyLock::new(|| Mutex::new(TextContext::default()));

pub fn text_width(text: &str, font_size: f64) -> f64 {
let typesetting = TypesettingConfig {
font_size,
line_height_ratio: 1.2,
character_spacing: 0.0,
max_width: None,
max_height: None,
tilt: 0.0,
align: TextAlign::Left,
};

// Load Source Sans Pro font data
// TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo.
// TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size.
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context");
let bounds = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
bounds.x
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::consts::{
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
};
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::Message;
use crate::messages::prelude::ViewportMessageHandler;
Expand All @@ -13,30 +14,17 @@ use graphene_std::Color;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::{self, Subpath};
use graphene_std::table::Table;
use graphene_std::text::TextContext;
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig};
use graphene_std::text::{Font, TextAlign, TypesettingConfig};
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::point_to_dvec2;
use graphene_std::vector::{PointId, SegmentId, Vector};
use kurbo::{self, BezPath, ParamCurve};
use kurbo::{Affine, PathSeg};
use std::collections::HashMap;
use std::sync::{Arc, LazyLock, Mutex, MutexGuard};
use std::sync::{Arc, Mutex, MutexGuard};
use vello::Scene;
use vello::peniko;

// Global lazy initialized font cache and text context
static GLOBAL_FONT_CACHE: LazyLock<FontCache> = LazyLock::new(|| {
let mut font_cache = FontCache::default();
// Initialize with the hardcoded font used by overlay text
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
font_cache.insert(font, String::new(), FONT_DATA.to_vec());
font_cache
});

static GLOBAL_TEXT_CONTEXT: LazyLock<Mutex<TextContext>> = LazyLock::new(|| Mutex::new(TextContext::default()));

pub type OverlayProvider = fn(OverlayContext) -> Message;

pub fn empty_provider() -> OverlayProvider {
Expand Down Expand Up @@ -393,10 +381,6 @@ impl OverlayContext {
self.internal().fill_path_pattern(subpaths, transform, color);
}

pub fn get_width(&self, text: &str) -> f64 {
self.internal().get_width(text)
}

pub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
let mut internal = self.internal();
internal.text(text, font_color, background_color, transform, padding, pivot);
Expand Down Expand Up @@ -1034,29 +1018,6 @@ impl OverlayContextInternal {
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), &brush, None, &path);
}

fn get_width(&mut self, text: &str) -> f64 {
// Use the actual text-to-path system to get precise text width
const FONT_SIZE: f64 = 12.0;

let typesetting = TypesettingConfig {
font_size: FONT_SIZE,
line_height_ratio: 1.2,
character_spacing: 0.0,
max_width: None,
max_height: None,
tilt: 0.0,
align: TextAlign::Left,
};

// Load Source Sans Pro font data
// TODO: Grab this from the node_modules folder (either with `include_bytes!` or ideally at runtime) instead of checking the font file into the repo.
// TODO: And maybe use the WOFF2 version (if it's supported) for its smaller, compressed file size.
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context");
let bounds = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
bounds.x
}

fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
// Use the proper text-to-path system for accurate text rendering
const FONT_SIZE: f64 = 12.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,10 +962,6 @@ impl OverlayContext {
self.render_context.fill();
}

pub fn get_width(&self, text: &str) -> f64 {
self.render_context.measure_text(text).expect("Failed to measure text dimensions").width()
}

pub fn text(&self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
let metrics = self.render_context.measure_text(text).expect("Failed to measure the text dimensions");
let x = match pivot[0] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_G
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DocumentNodeDefinition, resolve_document_node_type};
use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput};
use crate::messages::portfolio::document::overlays::utility_functions::text_width;
use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::ResolvedDocumentNodeTypes;
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
use crate::messages::tool::common_functionality::graph_modification_utils;
Expand Down Expand Up @@ -1052,7 +1053,11 @@ impl NodeNetworkInterface {
log::error!("Could not get downstream_connectors in primary_output_connected_to_layer");
return false;
};
let downstream_nodes = downstream_connectors.iter().filter_map(|connector| connector.node_id()).collect::<Vec<_>>();

let downstream_nodes = downstream_connectors
.iter()
.filter_map(|connector| connector.node_id().filter(|_| connector.input_index() == 0))
.collect::<Vec<_>>();
downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path))
}

Expand Down Expand Up @@ -1314,57 +1319,6 @@ impl NodeNetworkInterface {
.any(|id| id == potentially_upstream_node)
}

#[cfg(not(target_family = "wasm"))]
fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> {
warn!("Failed to find width of {node_id:#?} in network_path {network_path:?} due to non-wasm arch");
Some(0.)
}

#[cfg(target_family = "wasm")]
fn text_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<f64> {
let document = web_sys::window().unwrap().document().unwrap();
let div = match document.create_element("div") {
Ok(div) => div,
Err(err) => {
log::error!("Error creating div: {:?}", err);
return None;
}
};

// Set the div's style to make it offscreen and single line
match div.set_attribute("style", "position: absolute; top: -9999px; left: -9999px; white-space: nowrap;") {
Err(err) => {
log::error!("Error setting attribute: {:?}", err);
return None;
}
_ => {}
};

let name = self.display_name(node_id, network_path);

div.set_text_content(Some(&name));

// Append the div to the document body
match document.body().unwrap().append_child(&div) {
Err(err) => {
log::error!("Error setting adding child to document {:?}", err);
return None;
}
_ => {}
};

// Measure the width
let text_width = div.get_bounding_client_rect().width();

// Remove the div from the document
match document.body().unwrap().remove_child(&div) {
Err(_) => log::error!("Could not remove child when rendering text"),
_ => {}
};

Some(text_width)
}

pub fn from_old_network(old_network: OldNodeNetwork) -> Self {
let mut node_network = NodeNetwork::default();
let mut network_metadata = NodeNetworkMetadata::default();
Expand Down Expand Up @@ -2121,19 +2075,19 @@ impl NodeNetworkInterface {
}

pub fn load_layer_width(&mut self, node_id: &NodeId, network_path: &[NodeId]) {
const GAP_WIDTH: f64 = 8.;
const FONT_SIZE: f64 = 14.;
let left_thumbnail_padding = GRID_SIZE as f64 / 2.;
let thumbnail_width = 3. * GRID_SIZE as f64;
let gap_width = 8.;
let text_width = self.text_width(node_id, network_path).unwrap_or_else(|| {
log::error!("Could not get text width for node {node_id}");
0.
});
let layer_text = self.display_name(node_id, network_path);

let text_width = text_width(&layer_text, FONT_SIZE);

let grip_padding = 4.;
let grip_width = 8.;
let icon_overhang_width = GRID_SIZE as f64 / 2.;

let layer_width_pixels = left_thumbnail_padding + thumbnail_width + gap_width + text_width + grip_padding + grip_width + icon_overhang_width;
let layer_width_pixels = left_thumbnail_padding + thumbnail_width + GAP_WIDTH + text_width + grip_padding + grip_width + icon_overhang_width;
let layer_width = ((layer_width_pixels / 24.).ceil() as u32).max(8);

let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::consts::{ARC_SNAP_THRESHOLD, GIZMO_HIDE_THRESHOLD};
use crate::messages::message::Message;
use crate::messages::portfolio::document::overlays::utility_functions::text_width;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
Expand Down Expand Up @@ -176,7 +177,11 @@ impl SweepAngleGizmo {
.to_degrees();

let text = format!("{}°", format_rounded(display_angle, 2));
let text_texture_width = overlay_context.get_width(&text) / 2.;
const FONT_SIZE: f64 = 12.;

let text_width = text_width(&text, FONT_SIZE);

let text_texture_width = text_width / 2.;

let transform = calculate_arc_text_transform(angle, offset_angle, center, text_texture_width);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, SLOWING_DIVISOR};
use crate::messages::input_mapper::utility_types::input_mouse::{DocumentPosition, ViewportPosition};
use crate::messages::portfolio::document::overlays::utility_functions::text_width;
use crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::PTZ;
Expand Down Expand Up @@ -288,7 +289,9 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
angle_in_degrees
};
let text = format!("{}°", format_rounded(display_angle, 2));
let text_texture_width = overlay_context.get_width(&text) / 2.;
const FONT_SIZE: f64 = 12.;

let text_texture_width = text_width(&text, FONT_SIZE) / 2.;
let text_texture_height = 12.;
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
let text_texture_position = DVec2::new(
Expand Down