Migrate fonts to be stored as resources (#4165)

* Good start

* Impl

* allow responses.add(async { Message::NoOp });

* Fix

* Cache font blob in text node

* Refactor font handling to use Font struct direcly

* Fix fmt

* Embedd Font Resources by default

* Review

* Review

* Cache font info based on ResourceHash
This commit is contained in:
Timon
2026-06-05 13:27:25 -04:00
committed by Keavon Chambers
parent 7b9c480a8d
commit cd8ea1f554
46 changed files with 838 additions and 703 deletions
@@ -431,15 +431,21 @@ pub fn get_grid_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))
}
/// Gets properties from the Text node
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {
/// Gets properties from the Text node. Resolves the font selection by reading the resource id and lookup via the fonts message handler.
pub fn get_text<'a>(
layer: LayerNodeIdentifier,
network_interface: &'a NodeNetworkInterface,
fonts: &FontsMessageHandler,
resources: &ResourceMessageHandler,
) -> Option<(&'a String, Font, TypesettingConfig, bool)> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?;
let Some(TaggedValue::String(text)) = &inputs[graphene_std::text::text::TextInput::INDEX].as_value() else {
return None;
};
let Some(TaggedValue::Font(font)) = &inputs[graphene_std::text::text::FontInput::INDEX].as_value() else {
return None;
let font = match &inputs[graphene_std::text::text::FontInput::INDEX].as_value() {
Some(TaggedValue::Resource(resource_id)) => fonts.id_font(resources, *resource_id).unwrap_or_default(),
_ => Font::default(),
};
let Some(&TaggedValue::F64(font_size)) = inputs[graphene_std::text::text::SizeInput::INDEX].as_value() else {
return None;
@@ -16,7 +16,6 @@ use graph_craft::document::value::TaggedValue;
use graphene_std::list::List;
use graphene_std::renderer::Quad;
use graphene_std::subpath::{Bezier, BezierHandles};
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};
@@ -63,17 +62,18 @@ where
}
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) -> Quad {
// Use the `editor:text_frame` attribute if available (handles multi-item glyphs and the 'Index Elements' node)
if let Some(&frame) = document.metadata().text_frames.get(&layer) {
return frame * Quad::from_box([DVec2::ZERO, DVec2::ONE]);
}
// Fallback: recompute from text content (e.g. layer hasn't rendered yet)
let Some((text, font, typesetting, _)) = get_text(layer, &document.network_interface) else {
let Some((text, font, typesetting, _)) = get_text(layer, &document.network_interface, fonts, &document.resources) else {
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
};
let far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);
let font = fonts.get_resource_or_queue_load(&font, responses);
let far = graphene_std::text::bounding_box(text, &font, typesetting, false);
Quad::from_box([DVec2::ZERO, far])
}
@@ -4,7 +4,6 @@ use super::utility_types::{ToolActionMessageContext, ToolFsmState, tool_message_
use crate::application::generate_uuid;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
use crate::messages::portfolio::utility_types::CachedData;
use crate::messages::prelude::*;
use crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext;
use crate::messages::tool::utility_types::{HintData, ToolType};
@@ -19,7 +18,7 @@ pub struct ToolMessageContext<'a> {
pub document_id: DocumentId,
pub document: &'a mut DocumentMessageHandler,
pub input: &'a InputPreprocessorMessageHandler,
pub cached_data: &'a CachedData,
pub fonts: &'a FontsMessageHandler,
pub node_graph: &'a NodeGraphExecutor,
pub preferences: &'a PreferencesMessageHandler,
pub viewport: &'a ViewportMessageHandler,
@@ -40,7 +39,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
document_id,
document,
input,
cached_data,
fonts,
node_graph,
preferences,
viewport,
@@ -127,7 +126,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
node_graph,
preferences,
viewport,
cached_data,
fonts,
};
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
@@ -218,7 +217,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
tool_data.tools.get(active_tool).unwrap().activate(responses);
// Register initial properties
tool_data.tools.get(active_tool).unwrap().refresh_options(responses, cached_data);
tool_data.tools.get(active_tool).unwrap().refresh_options(responses);
// Notify the frontend about the initial active tool
tool_data.send_layout(responses, LayoutTarget::ToolShelf, preferences.brush_tool);
@@ -235,7 +234,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
node_graph,
preferences,
viewport,
cached_data,
fonts,
};
// Set initial hints and cursor
@@ -259,7 +258,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
ToolMessage::RefreshToolOptions => {
let tool_data = &mut self.tool_state.tool_data;
tool_data.tools.get(&tool_data.active_tool_type).unwrap().refresh_options(responses, cached_data);
tool_data.tools.get(&tool_data.active_tool_type).unwrap().refresh_options(responses);
}
ToolMessage::RefreshToolShelf => {
let tool_data = &mut self.tool_state.tool_data;
@@ -347,7 +346,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
node_graph,
preferences,
viewport,
cached_data,
fonts,
};
if matches!(tool_message, ToolMessage::UpdateHints) {
if graph_view_overlay_open {
@@ -720,13 +720,7 @@ impl Fsm for SelectToolFsmState {
type ToolOptions = ();
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionMessageContext {
document,
input,
viewport,
cached_data,
..
} = tool_action_data;
let ToolActionMessageContext { document, input, viewport, fonts, .. } = tool_action_data;
let ToolMessage::Select(event) = event else { return self };
match (self, event) {
@@ -751,7 +745,7 @@ impl Fsm for SelectToolFsmState {
if document.metadata().is_text_layer(layer) {
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, &cached_data.font_cache);
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, fonts, responses);
overlay_context.dashed_quad(transformed_quad, None, None, Some(7.), Some(5.), None);
}
}
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
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;
use crate::messages::portfolio::utility_types::{CachedData, FontCatalog, FontCatalogStyle};
use crate::messages::portfolio::fonts::utility_types::{FontCatalog, FontCatalogStyle};
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{
ToolColorOptions, apply_fill_only_color_pick, apply_fill_only_enabled, refresh_slot_working_color, selection_changed_since_last_sync, solid, sync_fill_only,
@@ -17,12 +17,13 @@ use crate::messages::tool::common_functionality::snapping::{self, SnapCandidateP
use crate::messages::tool::common_functionality::transformation_cage::*;
use crate::messages::tool::common_functionality::utility_functions::text_bounding_box;
use crate::messages::tool::utility_types::ToolRefreshOptions;
use graph_craft::application_io::resource::ResourceId;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graphene_std::choice_type::ChoiceTypeStatic;
use graphene_std::color::SRGBA8;
use graphene_std::renderer::Quad;
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping};
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI};
use graphene_std::{Color, NodeInputDecleration};
@@ -105,31 +106,30 @@ impl ToolMetadata for TextTool {
}
fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &DocumentMessageHandler) -> Vec<WidgetInstance> {
// If a single text layer is selected, the control bar's font/style menus drive that layer's text node directly, going through the
// same code path as the Properties panel (LoadFontData + SetInputValue, with closest_style and font_style_to_restore bookkeeping).
// Otherwise the menus only update the control bar option for the next created text.
let text_node_id = can_edit_selected(document).and_then(|layer| graph_modification_utils::get_text_id(layer, &document.network_interface));
let font_input_index = graphene_std::text::text::FontInput::INDEX;
let apply_font = move |new_font: Font| -> Message {
let apply_font = move |font: Font| -> Message {
match text_node_id {
Some(node_id) => NodeGraphMessage::SetInputValue {
node_id,
input_index: font_input_index,
value: TaggedValue::Font(new_font),
Some(node_id) => {
let resource_id = ResourceId::new();
Message::Batched {
messages: Box::new([
DocumentMessage::Resource(ResourceMessage::AddFont { resource_id, font }).into(),
NodeGraphMessage::SetInputValue {
node_id,
input_index: graphene_std::text::text::FontInput::INDEX,
value: TaggedValue::Resource(resource_id),
}
.into(),
]),
}
}
.into(),
None => TextToolMessage::UpdateOptions {
options: TextOptionsUpdate::Font { font: new_font },
options: TextOptionsUpdate::Font { font },
}
.into(),
}
};
let preview_font = move |new_font: Font| -> Message {
Message::Batched {
messages: Box::new([PortfolioMessage::LoadFontData { font: new_font.clone() }.into(), apply_font(new_font)]),
}
};
let commit_font = move |new_font: Font| -> Message {
match text_node_id {
Some(_) => DeferMessage::AfterGraphRun {
@@ -142,35 +142,27 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
let font = DropdownInput::new(vec![
font_catalog
.0
.iter()
.map(|family| {
let current_font = &tool.options.font;
let mut new_font = Font::new(family.name.clone(), current_font.font_style_to_restore.clone().unwrap_or_else(|| current_font.font_style.clone()));
new_font.font_style_to_restore = current_font.font_style_to_restore.clone().or_else(|| Some(new_font.font_style.clone()));
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&new_font.font_style, "");
new_font.font_style = family.closest_style(weight, italic).to_named_style();
// Intentionally drop `font_style_to_restore` on commit so the committed style becomes the new basis for
// subsequent family switches. Preserving the original style intent is hover-only behavior (handled by `new_font`).
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&current_font.font_style, "");
let commit_only_font = Font::new(family.name.clone(), family.closest_style(weight, italic).to_named_style());
let new_font = Font::new(family.name.clone(), family.closest_style(weight, italic).to_named_style());
let commit_only_font = new_font.clone();
MenuListEntry::new(family.name.clone())
.label(family.name.clone())
.font(family.closest_style(400, false).preview_url(&family.name))
.on_update(move |_| preview_font(new_font.clone()))
.on_update(move |_| apply_font(new_font.clone()))
.on_commit(move |_| commit_font(commit_only_font.clone()))
})
.collect::<Vec<_>>(),
])
.selected_index(font_catalog.0.iter().position(|family| family.name == tool.options.font.font_family).map(|i| i as u32))
.selected_index(font_catalog.iter().position(|family| family.name == tool.options.font.font_family).map(|i| i as u32))
.virtual_scrolling(true)
.widget_instance();
let style = DropdownInput::new({
font_catalog
.0
.iter()
.find(|family| family.name == tool.options.font.font_family)
.map(|family| {
@@ -182,7 +174,7 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
MenuListEntry::new(font_style.clone())
.label(font_style)
.on_update(move |_| preview_font(new_font.clone()))
.on_update(move |_| apply_font(new_font.clone()))
.on_commit(move |_| commit_font(new_font_for_commit.clone()))
};
@@ -196,7 +188,6 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
})
.selected_index(
font_catalog
.0
.iter()
.find(|family| family.name == tool.options.font.font_family)
.and_then(|family| {
@@ -253,7 +244,7 @@ fn create_text_widgets(tool: &TextTool, font_catalog: &FontCatalog, document: &D
}
impl ToolRefreshOptions for TextTool {
fn refresh_options(&self, responses: &mut VecDeque<Message>, _cached_data: &CachedData) {
fn refresh_options(&self, responses: &mut VecDeque<Message>) {
// Defer to the SelectionChanged handler which has document context, required for the font/style
// dropdowns to bind to the selected text layer's node graph inputs
responses.add(TextToolMessage::SelectionChanged);
@@ -303,7 +294,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
ToolMessage::Text(TextToolMessage::UpdateOptions { options }) => options,
ToolMessage::Text(TextToolMessage::SelectionChanged) => {
if let Some(layer) = can_edit_selected(context.document)
&& let Some((_, font, typesetting, _)) = graph_modification_utils::get_text(layer, &context.document.network_interface)
&& let Some((_, font, typesetting, _)) = graph_modification_utils::get_text(layer, &context.document.network_interface, context.fonts, &context.document.resources)
{
self.options.align = typesetting.align;
self.options.font_size = typesetting.font_size;
@@ -311,7 +302,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
if let Some(editing_text) = self.tool_data.editing_text.as_mut() {
editing_text.typesetting.align = typesetting.align;
editing_text.typesetting.font_size = typesetting.font_size;
editing_text.font = font.clone();
editing_text.font = font;
}
}
@@ -326,7 +317,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
// Text tool has no fill checkbox; keep enabled so new text never starts with `None`
self.options.fill.enabled = Some(true);
self.send_layout(responses, LayoutTarget::ToolOptions, &context.cached_data.font_catalog, context.document);
self.send_layout(responses, LayoutTarget::ToolOptions, &context.fonts.font_catalog, context.document);
return;
}
_ => {
@@ -385,7 +376,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
}
}
self.send_layout(responses, LayoutTarget::ToolOptions, &context.cached_data.font_catalog, context.document);
self.send_layout(responses, LayoutTarget::ToolOptions, &context.fonts.font_catalog, context.document);
}
fn actions(&self) -> ActionList {
@@ -476,9 +467,9 @@ struct TextToolData {
}
impl TextToolData {
fn delete_empty_layer(&mut self, font_cache: &FontCache, responses: &mut VecDeque<Message>) -> TextToolFsmState {
fn delete_empty_layer(&mut self, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) -> TextToolFsmState {
// Remove the editable textbox UI first
self.set_editing(false, font_cache, responses);
self.set_editing(false, fonts, responses);
// Delete the empty text layer and update the graph
responses.add(NodeGraphMessage::DeleteNodes {
@@ -491,15 +482,16 @@ impl TextToolData {
}
/// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
fn set_editing(&self, editable: bool, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
let (align, align_last) = editing_text.typesetting.align.css();
let font_data = fonts.get_resource_or_queue_load(&editing_text.font, responses).as_ref().to_vec().into();
responses.add(FrontendMessage::DisplayEditableTextbox {
text: editing_text.text.clone(),
line_height_ratio: editing_text.typesetting.line_height_ratio,
font_size: editing_text.typesetting.font_size,
color: editing_text.color.map_or("#000000".to_string(), |color| SRGBA8::from(color).to_css_hex()),
font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
font_data,
transform: editing_text.transform.to_cols_array(),
max_width: editing_text.typesetting.max_width,
max_height: editing_text.typesetting.max_height,
@@ -517,13 +509,13 @@ impl TextToolData {
}
}
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> Option<()> {
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler, fonts: &FontsMessageHandler) -> Option<()> {
let transform = document.metadata().transform_to_viewport(self.layer);
let color = graph_modification_utils::get_fill_color(self.layer, &document.network_interface).unwrap_or(Color::BLACK);
let (text, font, typesetting, _) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
let (text, font, typesetting, _) = graph_modification_utils::get_text(self.layer, &document.network_interface, fonts, &document.resources)?;
self.editing_text = Some(EditingText {
text: text.clone(),
font: font.clone(),
font,
typesetting,
color: Some(color),
transform,
@@ -532,20 +524,20 @@ impl TextToolData {
Some(())
}
fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot edit ROOT_PARENT in TextTooLData")
}
if tool_state == TextToolFsmState::Editing {
self.set_editing(false, font_cache, responses);
self.set_editing(false, fonts, responses);
}
self.layer = layer;
if self.load_layer_text_node(document).is_some() {
if self.load_layer_text_node(document, fonts).is_some() {
responses.add(DocumentMessage::AddTransaction);
self.set_editing(true, font_cache, responses);
self.set_editing(true, fonts, responses);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
// Make the rendered text invisible while editing
@@ -557,14 +549,16 @@ impl TextToolData {
};
}
fn new_text(&mut self, document: &DocumentMessageHandler, editing_text: EditingText, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
// Create new text
fn new_text(&mut self, document: &DocumentMessageHandler, editing_text: EditingText, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
self.new_text = String::new();
responses.add(DocumentMessage::AddTransaction);
self.layer = LayerNodeIdentifier::new_unchecked(NodeId::new());
responses.add(PortfolioMessage::LoadFontData { font: editing_text.font.clone() });
responses.add(FontsMessage::Load {
font: editing_text.font.clone(),
response: Box::new(NodeGraphMessage::RunDocumentGraph.into()),
});
responses.add(GraphOperationMessage::NewTextLayer {
id: self.layer.to_node(),
text: String::new(),
@@ -580,7 +574,7 @@ impl TextToolData {
let transform = editing_text.transform;
self.editing_text = Some(editing_text);
self.set_editing(true, font_cache, responses);
self.set_editing(true, fonts, responses);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
@@ -602,20 +596,23 @@ impl TextToolData {
});
}
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {
document.metadata().all_layers().filter(|&layer| document.metadata().is_text_layer(layer)).find(|&layer| {
let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, font_cache);
let mouse = DVec2::new(input.mouse.position.x, input.mouse.position.y);
transformed_quad.contains(mouse)
})
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) -> Option<LayerNodeIdentifier> {
let mouse = DVec2::new(input.mouse.position.x, input.mouse.position.y);
let layers: Vec<LayerNodeIdentifier> = document.metadata().all_layers().filter(|&layer| document.metadata().is_text_layer(layer)).collect();
for layer in layers {
let transformed_quad = document.metadata().transform_to_viewport(layer) * text_bounding_box(layer, document, fonts, responses);
if transformed_quad.contains(mouse) {
return Some(layer);
}
}
None
}
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, font_cache: &FontCache) {
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, fonts: &FontsMessageHandler, responses: &mut VecDeque<Message>) {
self.snap_candidates.clear();
if let Some(ResizingLayer { id, .. }) = self.layer_dragging {
let quad = document.metadata().transform_to_document(id) * text_bounding_box(id, document, font_cache);
let quad = document.metadata().transform_to_document(id) * text_bounding_box(id, document, fonts, responses);
snapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document);
}
}
@@ -651,14 +648,7 @@ impl Fsm for TextToolFsmState {
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document,
input,
cached_data,
viewport,
..
} = transition_data;
let font_cache = &cached_data.font_cache;
let ToolActionMessageContext { document, input, fonts, viewport, .. } = transition_data;
let fill_color = COLOR_OVERLAY_BLUE_05;
let ToolMessage::Text(event) = event else { return self };
@@ -667,7 +657,8 @@ impl Fsm for TextToolFsmState {
let transform = document.metadata().transform_to_viewport(tool_data.layer).to_cols_array();
responses.add(FrontendMessage::DisplayEditableTextboxTransform { transform });
if let Some(editing_text) = tool_data.editing_text.as_mut() {
let far = graphene_std::text::bounding_box(&tool_data.new_text, &editing_text.font, font_cache, editing_text.typesetting, false);
let font_resource = fonts.get_resource_or_queue_load(&editing_text.font, responses);
let far = graphene_std::text::bounding_box(&tool_data.new_text, &font_resource, 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;
@@ -694,7 +685,7 @@ impl Fsm for TextToolFsmState {
let selected = document.network_interface.selected_nodes();
let mut all_layers = selected.selected_visible_and_unlocked_layers(&document.network_interface);
let layer = all_layers.find(|&layer| document.metadata().is_text_layer(layer));
let bounds = layer.map(|layer| text_bounding_box(layer, document, font_cache));
let bounds = layer.map(|layer| text_bounding_box(layer, document, fonts, responses));
let layer_transform = layer.map(|layer| document.metadata().transform_to_viewport(layer)).unwrap_or(DAffine2::IDENTITY);
if layer.is_none() || bounds.is_none() || layer_transform.matrix2.determinant() == 0. {
@@ -710,10 +701,11 @@ impl Fsm for TextToolFsmState {
bounding_box_manager.render_quad(&mut overlay_context);
// 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)
&& 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.));
if let Some((text, font, typesetting, _)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface, fonts, &document.resources) {
let font_resource = fonts.get_resource_or_queue_load(&font, responses);
if lines_clipping(text.as_str(), &font_resource, typesetting) {
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
}
}
bounding_box_manager.render_overlays(&mut overlay_context, false);
@@ -728,7 +720,7 @@ impl Fsm for TextToolFsmState {
}
(state, TextToolMessage::EditSelected) => {
if let Some(layer) = can_edit_selected(document) {
tool_data.start_editing_layer(layer, state, document, font_cache, responses);
tool_data.start_editing_layer(layer, state, document, fonts, responses);
return TextToolFsmState::Editing;
}
@@ -769,10 +761,10 @@ impl Fsm for TextToolFsmState {
bounds.original_bound_transform = bounds.transform;
bounds.center_of_transformation = bounds.transform.transform_point2((bounds.bounds[0] + bounds.bounds[1]) / 2.);
}
tool_data.get_snap_candidates(document, font_cache);
tool_data.get_snap_candidates(document, fonts, responses);
return TextToolFsmState::ResizingBounds;
} else if let Some(clicked_layer) = TextToolData::check_click(document, input, font_cache) {
} else if let Some(clicked_layer) = TextToolData::check_click(document, input, fonts, responses) {
responses.add(DocumentMessage::StartTransaction);
if selected != Some(clicked_layer) {
@@ -784,7 +776,7 @@ impl Fsm for TextToolFsmState {
id: clicked_layer,
original_transform,
});
tool_data.get_snap_candidates(document, font_cache);
tool_data.get_snap_candidates(document, fonts, responses);
return TextToolFsmState::Dragging;
}
TextToolFsmState::Placing
@@ -967,8 +959,8 @@ impl Fsm for TextToolFsmState {
let has_dragged = (start - end).length_squared() > DRAG_THRESHOLD * DRAG_THRESHOLD;
// Check if the user has clicked (no dragging) on some existing text
if !has_dragged && let Some(clicked_text_layer_path) = TextToolData::check_click(document, input, font_cache) {
tool_data.start_editing_layer(clicked_text_layer_path, self, document, font_cache, responses);
if !has_dragged && let Some(clicked_text_layer_path) = TextToolData::check_click(document, input, fonts, responses) {
tool_data.start_editing_layer(clicked_text_layer_path, self, document, fonts, responses);
return TextToolFsmState::Editing;
}
@@ -989,7 +981,7 @@ impl Fsm for TextToolFsmState {
font: Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone()),
color: tool_options.fill.active_color(),
};
tool_data.new_text(document, editing_text, font_cache, responses);
tool_data.new_text(document, editing_text, fonts, responses);
TextToolFsmState::Editing
}
(TextToolFsmState::Dragging, TextToolMessage::DragStop) => {
@@ -1004,7 +996,7 @@ impl Fsm for TextToolFsmState {
}
if drag_too_small && let Some(layer_info) = &tool_data.layer_dragging {
tool_data.start_editing_layer(layer_info.id, self, document, font_cache, responses);
tool_data.start_editing_layer(layer_info.id, self, document, fonts, responses);
return TextToolFsmState::Editing;
}
tool_data.layer_dragging.take();
@@ -1013,8 +1005,9 @@ impl Fsm for TextToolFsmState {
}
(TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => {
let font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone());
let font_resource = fonts.get_resource_or_queue_load(&font, responses);
responses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData {
font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
font_data: font_resource.as_ref().to_vec().into(),
});
TextToolFsmState::Editing
@@ -1023,7 +1016,7 @@ impl Fsm for TextToolFsmState {
tool_data.new_text = new_text;
if !is_left_or_right_click {
tool_data.set_editing(false, font_cache, responses);
tool_data.set_editing(false, fonts, responses);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
@@ -1034,7 +1027,7 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Ready
} else {
if tool_data.new_text.is_empty() {
return tool_data.delete_empty_layer(font_cache, responses);
return tool_data.delete_empty_layer(fonts, responses);
}
responses.add(FrontendMessage::TriggerTextCommit);
@@ -1055,7 +1048,7 @@ impl Fsm for TextToolFsmState {
}
(TextToolFsmState::Editing, TextToolMessage::Abort) => {
if tool_data.new_text.is_empty() {
return tool_data.delete_empty_layer(font_cache, responses);
return tool_data.delete_empty_layer(fonts, responses);
}
responses.add(FrontendMessage::TriggerTextCommit);
+3 -4
View File
@@ -9,7 +9,6 @@ use crate::messages::input_mapper::utility_types::macros::action_shortcut;
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
use crate::messages::portfolio::utility_types::CachedData;
use crate::messages::preferences::PreferencesMessageHandler;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType;
@@ -25,7 +24,7 @@ pub struct ToolActionMessageContext<'a> {
pub document_id: DocumentId,
pub global_tool_data: &'a DocumentToolData,
pub input: &'a InputPreprocessorMessageHandler,
pub cached_data: &'a CachedData,
pub fonts: &'a FontsMessageHandler,
pub shape_editor: &'a mut ShapeState,
pub node_graph: &'a NodeGraphExecutor,
pub preferences: &'a PreferencesMessageHandler,
@@ -38,11 +37,11 @@ impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mu
type Tool = dyn ToolCommon + Send + Sync;
pub trait ToolRefreshOptions {
fn refresh_options(&self, responses: &mut VecDeque<Message>, _cached_data: &CachedData);
fn refresh_options(&self, responses: &mut VecDeque<Message>);
}
impl<T: LayoutHolder> ToolRefreshOptions for T {
fn refresh_options(&self, responses: &mut VecDeque<Message>, _cached_data: &CachedData) {
fn refresh_options(&self, responses: &mut VecDeque<Message>) {
self.send_layout(responses, LayoutTarget::ToolOptions);
}
}