mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Restructure gcore/text module and fix memory leak (#3221)
* Restructure gcore/text module and fix memory leak * Remove unused import * Fix default font fallback causing wrong caching and rename to TextContext * Upgrade demo art
This commit is contained in:
@@ -15,6 +15,8 @@ pub struct OverlaysMessageHandler {
|
||||
canvas: Option<web_sys::HtmlCanvasElement>,
|
||||
#[cfg(target_family = "wasm")]
|
||||
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||
#[cfg(all(not(target_family = "wasm"), not(test)))]
|
||||
context: Option<super::utility_types::OverlayContext>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
@@ -80,7 +82,11 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
|
||||
let size = ipp.viewport_bounds.size();
|
||||
|
||||
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||
if self.context.is_none() {
|
||||
self.context = Some(OverlayContext::new(size, device_pixel_ratio, visibility_settings));
|
||||
}
|
||||
|
||||
let overlay_context = self.context.as_mut().unwrap();
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
|
||||
@@ -89,7 +95,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
responses.add(provider(overlay_context.clone()));
|
||||
}
|
||||
}
|
||||
responses.add(FrontendMessage::RenderOverlays { context: overlay_context });
|
||||
responses.add(FrontendMessage::RenderOverlays { context: overlay_context.clone() });
|
||||
}
|
||||
#[cfg(all(not(target_family = "wasm"), test))]
|
||||
OverlaysMessage::Draw => {
|
||||
|
||||
@@ -12,7 +12,8 @@ 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::{TextAlign, TypesettingConfig, load_font, to_path};
|
||||
use graphene_std::text::TextContext;
|
||||
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
@@ -215,7 +216,7 @@ impl OverlayContext {
|
||||
|
||||
pub fn take_scene(self) -> Scene {
|
||||
let mut internal = self.internal.lock().expect("Failed to lock internal overlay context");
|
||||
std::mem::take(&mut *internal).scene
|
||||
std::mem::take(&mut internal.scene)
|
||||
}
|
||||
|
||||
fn internal(&'_ self) -> MutexGuard<'_, OverlayContextInternal> {
|
||||
@@ -411,26 +412,31 @@ pub(super) struct OverlayContextInternal {
|
||||
size: DVec2,
|
||||
device_pixel_ratio: f64,
|
||||
visibility_settings: OverlaysVisibilitySettings,
|
||||
font_cache: FontCache,
|
||||
thread_text: TextContext,
|
||||
}
|
||||
|
||||
impl Default for OverlayContextInternal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scene: Scene::new(),
|
||||
size: DVec2::ZERO,
|
||||
device_pixel_ratio: 1.0,
|
||||
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||
}
|
||||
Self::new(DVec2::new(100., 100.), 1., OverlaysVisibilitySettings::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlayContextInternal {
|
||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
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());
|
||||
|
||||
Self {
|
||||
scene: Scene::new(),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
visibility_settings,
|
||||
font_cache,
|
||||
thread_text: TextContext::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1007,7 +1013,7 @@ impl OverlayContextInternal {
|
||||
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), &brush, None, &path);
|
||||
}
|
||||
|
||||
fn get_width(&self, text: &str) -> f64 {
|
||||
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;
|
||||
|
||||
@@ -1024,13 +1030,9 @@ impl OverlayContextInternal {
|
||||
// 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.
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font_blob = Some(load_font(FONT_DATA));
|
||||
|
||||
// Convert text to paths and calculate actual bounds
|
||||
let text_table = to_path(text, font_blob, typesetting, false);
|
||||
let text_bounds = self.calculate_text_bounds(&text_table);
|
||||
text_bounds.width()
|
||||
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
|
||||
let bounds = self.thread_text.bounding_box(text, &font, &self.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]) {
|
||||
@@ -1051,15 +1053,17 @@ impl OverlayContextInternal {
|
||||
// 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.
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font_blob = Some(load_font(FONT_DATA));
|
||||
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
|
||||
|
||||
// Convert text to vector paths using the existing text system
|
||||
let text_table = to_path(text, font_blob, typesetting, false);
|
||||
// Calculate text bounds from the generated paths
|
||||
let text_bounds = self.calculate_text_bounds(&text_table);
|
||||
let text_width = text_bounds.width();
|
||||
let text_height = text_bounds.height();
|
||||
// Get text dimensions directly from layout
|
||||
let text_size = self.thread_text.bounding_box(text, &font, &self.font_cache, typesetting, false);
|
||||
let text_width = text_size.x;
|
||||
let text_height = text_size.y;
|
||||
// Create a rect from the size (assuming text starts at origin)
|
||||
let text_bounds = kurbo::Rect::new(0.0, 0.0, text_width, text_height);
|
||||
|
||||
// Convert text to vector paths for rendering
|
||||
let text_table = self.thread_text.to_path(text, &font, &self.font_cache, typesetting, false);
|
||||
|
||||
// Calculate position based on pivot
|
||||
let mut position = DVec2::ZERO;
|
||||
@@ -1094,56 +1098,6 @@ impl OverlayContextInternal {
|
||||
self.render_text_paths(&text_table, font_color, vello_transform);
|
||||
}
|
||||
|
||||
// Calculate bounds of text from vector table
|
||||
fn calculate_text_bounds(&self, text_table: &Table<Vector>) -> kurbo::Rect {
|
||||
let mut min_x = f64::INFINITY;
|
||||
let mut min_y = f64::INFINITY;
|
||||
let mut max_x = f64::NEG_INFINITY;
|
||||
let mut max_y = f64::NEG_INFINITY;
|
||||
|
||||
for row in text_table.iter() {
|
||||
// Use the existing segment_bezier_iter to get all bezier curves
|
||||
for (_, bezier, _, _) in row.element.segment_bezier_iter() {
|
||||
let transformed_bezier = bezier.apply_transformation(|point| row.transform.transform_point2(point));
|
||||
|
||||
// Add start and end points to bounds
|
||||
let points = [transformed_bezier.start, transformed_bezier.end];
|
||||
for point in points {
|
||||
min_x = min_x.min(point.x);
|
||||
min_y = min_y.min(point.y);
|
||||
max_x = max_x.max(point.x);
|
||||
max_y = max_y.max(point.y);
|
||||
}
|
||||
|
||||
// Add handle points if they exist
|
||||
match transformed_bezier.handles {
|
||||
subpath::BezierHandles::Quadratic { handle } => {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
max_x = max_x.max(handle.x);
|
||||
max_y = max_y.max(handle.y);
|
||||
}
|
||||
subpath::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
for handle in [handle_start, handle_end] {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
max_x = max_x.max(handle.x);
|
||||
max_y = max_y.max(handle.y);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite() {
|
||||
kurbo::Rect::new(min_x, min_y, max_x, max_y)
|
||||
} else {
|
||||
// Fallback for empty text
|
||||
kurbo::Rect::new(0.0, 0.0, 0.0, 12.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Render text paths to the vello scene using existing infrastructure
|
||||
fn render_text_paths(&mut self, text_table: &Table<Vector>, font_color: &str, base_transform: kurbo::Affine) {
|
||||
let color = Self::parse_color(font_color);
|
||||
|
||||
@@ -15,7 +15,7 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
||||
@@ -74,8 +74,7 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
|
||||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||
};
|
||||
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
|
||||
let far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);
|
||||
|
||||
// TODO: Once the instance tables refactor is complete and per_glyph_instances can be removed (since it'll be the default),
|
||||
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
|
||||
|
||||
@@ -17,7 +17,7 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping, load_font};
|
||||
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping};
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
@@ -513,8 +513,7 @@ impl Fsm for TextToolFsmState {
|
||||
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
||||
});
|
||||
if let Some(editing_text) = tool_data.editing_text.as_mut() {
|
||||
let font_data = font_cache.get(&editing_text.font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, font_data, editing_text.typesetting, false);
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, &editing_text.font, font_cache, editing_text.typesetting, false);
|
||||
if far.x != 0. && far.y != 0. {
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||
@@ -562,8 +561,7 @@ impl Fsm for TextToolFsmState {
|
||||
// Draw red overlay if text is clipped
|
||||
let transformed_quad = layer_transform * bounds;
|
||||
if let Some((text, font, typesetting, _)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) {
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
if lines_clipping(text.as_str(), font_data, typesetting) {
|
||||
if lines_clipping(text.as_str(), font, font_cache, typesetting) {
|
||||
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user