mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Desktop: Remove web_sys text measuring to fix node graph layer widths (#3455)
* Remove web_sys text measuring * Improve export * Fix top of layer stack
This commit is contained in:
parent
3926337b44
commit
9fc98cf03f
10 changed files with 72 additions and 112 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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] {
|
||||
|
|
@ -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;
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue