mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 07:18:11 +08:00
Properly track the Text node's text frame via the attribute system to fix misalignment (#4097)
* Compensate for upstream row-0 transform absorption in viewport-space 'TransformSet' * Add 'editor:text_frame' row attribute so the Text tool's drag cage tracks multi-row text * "Separate Glyph Elements" -> "Separate Glyphs" * Improve artboard migration robustness from older documents * Code review * Make the tools visualize the text frame based on attribute not upstream node
This commit is contained in:
@@ -466,7 +466,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
||||
let Some(&TaggedValue::TextAlign(align)) = inputs[graphene_std::text::text::AlignInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
let Some(&TaggedValue::Bool(per_glyph_items)) = inputs[graphene_std::text::text::SeparateGlyphElementsInput::INDEX].as_value() else {
|
||||
let Some(&TaggedValue::Bool(per_glyph_items)) = inputs[graphene_std::text::text::SeparateGlyphsInput::INDEX].as_value() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
@@ -64,21 +64,17 @@ 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 {
|
||||
let Some((text, font, typesetting, per_glyph_items)) = get_text(layer, &document.network_interface) else {
|
||||
// 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 {
|
||||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||
};
|
||||
|
||||
let far = graphene_std::text::bounding_box(text, font, font_cache, typesetting, false);
|
||||
|
||||
// TODO: Once the `Table` refactor is complete and per_glyph_items 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.
|
||||
let vertical_offset = if per_glyph_items {
|
||||
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
|
||||
} else {
|
||||
DVec2::ZERO
|
||||
};
|
||||
|
||||
Quad::from_box([DVec2::ZERO + vertical_offset, far + vertical_offset])
|
||||
Quad::from_box([DVec2::ZERO, far])
|
||||
}
|
||||
|
||||
pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector: &Vector, prefer_handle_direction: bool) -> Option<f64> {
|
||||
|
||||
@@ -4,7 +4,6 @@ use super::tool_prelude::*;
|
||||
use crate::consts::*;
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
||||
@@ -14,7 +13,6 @@ use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::measure;
|
||||
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
|
||||
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
|
||||
@@ -631,7 +629,7 @@ impl Fsm for SelectToolFsmState {
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
overlay_context.outline(document.metadata().layer_with_free_points_outline(layer), layer_to_viewport, None);
|
||||
|
||||
if is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {
|
||||
if document.metadata().is_text_layer(layer) {
|
||||
let transformed_quad = layer_to_viewport * text_bounding_box(layer, document, &cached_data.font_cache);
|
||||
overlay_context.dashed_quad(transformed_quad, None, None, Some(7.), Some(5.), None);
|
||||
}
|
||||
@@ -1574,7 +1572,7 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
if let Some(layer) = selected_layers.next() {
|
||||
// Check that only one layer is selected
|
||||
if selected_layers.next().is_none() && is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {
|
||||
if selected_layers.next().is_none() && document.metadata().is_text_layer(layer) {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
@@ -1934,7 +1932,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
|
||||
/// Called when a double click on a layer in deep select mode.
|
||||
/// If the layer is text, the text tool is selected.
|
||||
fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {
|
||||
if is_layer_fed_by_node_of_name(layer, network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {
|
||||
if network_interface.document_metadata().is_text_layer(layer) {
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||
responses.add(TextToolMessage::EditSelected);
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{COLOR_OVERLAY_BLUE_05, COLOR_OVERLAY_RED, DRAG_THRESHOLD};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||
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::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
@@ -518,16 +517,12 @@ impl TextToolData {
|
||||
}
|
||||
|
||||
fn check_click(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, font_cache: &FontCache) -> Option<LayerNodeIdentifier> {
|
||||
document
|
||||
.metadata()
|
||||
.all_layers()
|
||||
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)))
|
||||
.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);
|
||||
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)
|
||||
})
|
||||
transformed_quad.contains(mouse)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_snap_candidates(&mut self, document: &DocumentMessageHandler, font_cache: &FontCache) {
|
||||
@@ -550,7 +545,7 @@ fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdent
|
||||
return None;
|
||||
}
|
||||
|
||||
if !is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)) {
|
||||
if !document.metadata().is_text_layer(layer) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -612,7 +607,7 @@ impl Fsm for TextToolFsmState {
|
||||
// TODO: implement bounding box for multiple layers
|
||||
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| is_layer_fed_by_node_of_name(*layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)));
|
||||
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 layer_transform = layer.map(|layer| document.metadata().transform_to_viewport(layer)).unwrap_or(DAffine2::IDENTITY);
|
||||
|
||||
@@ -673,7 +668,7 @@ impl Fsm for TextToolFsmState {
|
||||
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
let mut all_selected = selected.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let selected = all_selected.find(|layer| is_layer_fed_by_node_of_name(*layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)));
|
||||
let selected = all_selected.find(|&layer| document.metadata().is_text_layer(layer));
|
||||
|
||||
if dragging_bounds.is_some() {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
@@ -712,7 +707,7 @@ impl Fsm for TextToolFsmState {
|
||||
// This ensures the cursor only changes if a layer is selected
|
||||
let selected = document.network_interface.selected_nodes();
|
||||
let mut all_selected = selected.selected_visible_and_unlocked_layers(&document.network_interface);
|
||||
let layer = all_selected.find(|&layer| is_layer_fed_by_node_of_name(layer, &document.network_interface, &DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)));
|
||||
let layer = all_selected.find(|&layer| document.metadata().is_text_layer(layer));
|
||||
|
||||
let mut cursor = tool_data
|
||||
.bounding_box_manager
|
||||
|
||||
Reference in New Issue
Block a user