@@ -644,7 +644,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
644644 // text
645645 // ================================
646646 NodeReplacement {
647- node : graphene_std :: text :: text:: IDENTIFIER ,
647+ node : ProtoNodeIdentifier :: new ( "graphene_std ::text::TextNode" ) ,
648648 aliases : & [ "graphene_core::text::text::TextNode" , "graphene_core::text::TextGeneratorNode" , "graphene_core::text::TextNode" ] ,
649649 } ,
650650 NodeReplacement {
@@ -978,6 +978,16 @@ pub fn document_migration_string_preprocessing(document_serialized_content: Stri
978978 . fold ( document_serialized_content, |document_serialized_content, ( old, new) | document_serialized_content. replace ( old, new) )
979979}
980980
981+ /// Rebuilds the old 13-input "Text" node template from the current `text` template plus the trailing `separate_glyphs` input it dropped,
982+ /// so the staged input-count migrations can still upgrade old text nodes before the split.
983+ fn legacy_text_node_template ( ) -> Option < NodeTemplate > {
984+ let mut template = resolve_document_node_type ( & DefinitionIdentifier :: ProtoNode ( graphene_std:: text:: text:: IDENTIFIER ) ) ?. default_node_template ( ) ;
985+ template. document_node . implementation = DocumentNodeImplementation :: ProtoNode ( ProtoNodeIdentifier :: new ( "graphene_std::text::TextNode" ) ) ;
986+ template. document_node . inputs . push ( NodeInput :: value ( TaggedValue :: Bool ( false ) , false ) ) ;
987+ template. persistent_node_metadata . input_metadata . push ( Default :: default ( ) ) ;
988+ Some ( template)
989+ }
990+
981991fn replace_optional_f64_null ( input : & str ) -> String {
982992 let mut result = String :: new ( ) ;
983993 let mut last_end = 0 ;
@@ -1250,6 +1260,19 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
12501260 }
12511261 }
12521262
1263+ // Record which old text nodes are chain-positioned now, before `migrate_node`'s staged input-count migrations run, since those set
1264+ // the upstream chain to absolute; the split below re-chains exactly the nodes that were originally part of a layer chain.
1265+ let text_nodes_in_chain: std:: collections:: HashSet < NodeId > = document
1266+ . network_interface
1267+ . document_network ( )
1268+ . recursive_nodes ( )
1269+ . filter_map ( |( node_id, _, path) | {
1270+ ( document. network_interface . reference ( node_id, & path) == Some ( DefinitionIdentifier :: ProtoNode ( ProtoNodeIdentifier :: new ( "graphene_std::text::TextNode" ) ) )
1271+ && document. network_interface . is_chain ( node_id, & path) )
1272+ . then_some ( * node_id)
1273+ } )
1274+ . collect ( ) ;
1275+
12531276 // Apply upgrades to each unmodified node.
12541277 let nodes = document
12551278 . network_interface
@@ -1260,6 +1283,91 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
12601283 for ( node_id, node, network_path) in & nodes {
12611284 migrate_node ( node_id, node, network_path, document, reset_node_definitions_on_open) ;
12621285 }
1286+
1287+ // The old geometry-producing "Text" node was split into the current "Text" (`String[]`) -> "Text to Vector" pair, which reuses the same
1288+ // proto identifier. Runs after `migrate_node` normalizes old text nodes to the legacy 13-input layout, distinguished from the current
1289+ // 12-input node by the trailing `separate_glyphs` input (index 12): forward inputs 0..=11 onto the new node and move it onto `text_to_vector`.
1290+ let old_text_nodes: Vec < ( NodeId , Vec < NodeId > ) > = document
1291+ . network_interface
1292+ . document_network ( )
1293+ . recursive_nodes ( )
1294+ . filter_map ( |( node_id, node, path) | {
1295+ // `separate_glyphs` is a `Bool` value or a wire feeding one; only a different value type there means a newer input, not the old node
1296+ let has_legacy_separate_glyphs = node. inputs . len ( ) == 13 && node. inputs . get ( 12 ) . is_some_and ( |input| matches ! ( input. as_value( ) , None | Some ( TaggedValue :: Bool ( _) ) ) ) ;
1297+ ( has_legacy_separate_glyphs && document. network_interface . reference ( node_id, & path) == Some ( DefinitionIdentifier :: ProtoNode ( ProtoNodeIdentifier :: new ( "graphene_std::text::TextNode" ) ) ) )
1298+ . then_some ( ( * node_id, path) )
1299+ } )
1300+ . collect ( ) ;
1301+ for ( node_id, network_path) in & old_text_nodes {
1302+ // Pre-load `outward_wires` so the splice below resolves the original downstream wiring from cache rather than a mutated state.
1303+ let _ = document. network_interface . outward_wires ( network_path) ;
1304+
1305+ // Convert the old node in place to the current `text` node (12 inputs), capturing its old inputs.
1306+ let Some ( text_definition) = resolve_document_node_type ( & DefinitionIdentifier :: ProtoNode ( graphene_std:: text:: text:: IDENTIFIER ) ) else {
1307+ continue ;
1308+ } ;
1309+ let mut text_template = text_definition. default_node_template ( ) ;
1310+ document. network_interface . replace_implementation ( node_id, network_path, & mut text_template) ;
1311+ let Some ( old_inputs) = document. network_interface . replace_inputs ( node_id, network_path, & mut text_template) else {
1312+ continue ;
1313+ } ;
1314+ // The current `text` node reorders the legacy inputs (Letter Tilt moved up to sit right after Letter Spacing), so map each new
1315+ // input index to the legacy 13-input index it sources from. Legacy order:
1316+ // [primary, text, font, size, line_height, letter_spacing, has_max_width, max_width, has_max_height, max_height, letter_tilt, align, separate_glyphs].
1317+ const LEGACY_INPUT_FOR_NEW : [ usize ; 12 ] = [ 0 , 1 , 2 , 3 , 4 , 5 , 10 , 6 , 7 , 8 , 9 , 11 ] ;
1318+ for ( new_index, & legacy_index) in LEGACY_INPUT_FOR_NEW . iter ( ) . enumerate ( ) {
1319+ if let Some ( input) = old_inputs. get ( legacy_index) {
1320+ document. network_interface . set_input ( & InputConnector :: node ( * node_id, new_index) , input. clone ( ) , network_path) ;
1321+ }
1322+ }
1323+ let separate_glyphs = old_inputs. get ( 12 ) . cloned ( ) ;
1324+
1325+ // Collect the inputs reading the old text node's output before any rewiring so the new node can be spliced onto those wires.
1326+ let downstream_consumers: Vec < InputConnector > = document
1327+ . network_interface
1328+ . outward_wires ( network_path)
1329+ . and_then ( |wires| wires. get ( & OutputConnector :: node ( * node_id, 0 ) ) )
1330+ . cloned ( )
1331+ . unwrap_or_default ( ) ;
1332+
1333+ let text_was_in_chain = text_nodes_in_chain. contains ( node_id) ;
1334+
1335+ // Insert the `text_to_vector` node that converts the `text` `String[]` output back into vector geometry.
1336+ let Some ( text_to_vector_definition) = resolve_document_node_type ( & DefinitionIdentifier :: ProtoNode ( graphene_std:: text:: text_to_vector:: IDENTIFIER ) ) else {
1337+ continue ;
1338+ } ;
1339+ let text_to_vector_id = NodeId :: new ( ) ;
1340+ document
1341+ . network_interface
1342+ . insert_node ( text_to_vector_id, text_to_vector_definition. default_node_template ( ) , network_path) ;
1343+
1344+ // Splice `text_to_vector` onto the wire(s) leaving `text` (`insert_node_between` is the pure wire-splice the editor uses for
1345+ // dropping a node on a wire), then carry the old `separate_glyphs` value onto its second input.
1346+ if let Some ( ( first_consumer, remaining_consumers) ) = downstream_consumers. split_first ( ) {
1347+ document. network_interface . insert_node_between ( & text_to_vector_id, first_consumer, 0 , network_path) ;
1348+ for consumer in remaining_consumers {
1349+ document. network_interface . set_input ( consumer, NodeInput :: node ( text_to_vector_id, 0 ) , network_path) ;
1350+ }
1351+ } else {
1352+ document
1353+ . network_interface
1354+ . set_input ( & InputConnector :: node ( text_to_vector_id, 0 ) , NodeInput :: node ( * node_id, 0 ) , network_path) ;
1355+ }
1356+ if let Some ( separate_glyphs) = separate_glyphs {
1357+ document. network_interface . set_input ( & InputConnector :: node ( text_to_vector_id, 1 ) , separate_glyphs, network_path) ;
1358+ }
1359+
1360+ // If `text` was in a layer chain, re-chain `text_to_vector` and its upstream so both lay out by distance from the layer (the splice
1361+ // broke the chain, like `move_node_to_chain_start`). Otherwise `text` is absolute, so place `text_to_vector` beside it instead of
1362+ // leaving it at the origin.
1363+ if text_was_in_chain {
1364+ document. network_interface . force_set_upstream_to_chain ( & text_to_vector_id, network_path) ;
1365+ } else if let Some ( text_position) = document. network_interface . position ( node_id, network_path) {
1366+ document
1367+ . network_interface
1368+ . shift_absolute_node_position ( & text_to_vector_id, text_position + IVec2 :: new ( 7 , 0 ) , network_path) ;
1369+ }
1370+ }
12631371}
12641372
12651373fn migrate_node ( node_id : & NodeId , node : & DocumentNode , network_path : & [ NodeId ] , document : & mut DocumentMessageHandler , reset_node_definitions_on_open : bool ) -> Option < ( ) > {
@@ -1484,8 +1592,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
14841592 }
14851593
14861594 // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
1487- if reference == DefinitionIdentifier :: ProtoNode ( graphene_std :: text :: text:: IDENTIFIER ) && inputs_count == 8 {
1488- let mut template: NodeTemplate = resolve_document_node_type ( & reference ) ? . default_node_template ( ) ;
1595+ if reference == DefinitionIdentifier :: ProtoNode ( ProtoNodeIdentifier :: new ( "graphene_std ::text::TextNode" ) ) && inputs_count == 8 {
1596+ let mut template: NodeTemplate = legacy_text_node_template ( ) ? ;
14891597 document. network_interface . replace_implementation ( node_id, network_path, & mut template) ;
14901598 let old_inputs = document. network_interface . replace_inputs ( node_id, network_path, & mut template) ?;
14911599
@@ -1507,7 +1615,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
15071615 if inputs_count == 6 {
15081616 old_inputs[ 5 ] . clone ( )
15091617 } else {
1510- NodeInput :: value ( TaggedValue :: F64 ( TypesettingConfig :: default ( ) . character_spacing ) , false )
1618+ NodeInput :: value ( TaggedValue :: F64 ( TypesettingConfig :: default ( ) . letter_spacing ) , false )
15111619 } ,
15121620 network_path,
15131621 ) ;
@@ -1534,7 +1642,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
15341642 if inputs_count >= 9 {
15351643 old_inputs[ 8 ] . clone ( )
15361644 } else {
1537- NodeInput :: value ( TaggedValue :: F64 ( TypesettingConfig :: default ( ) . tilt ) , false )
1645+ NodeInput :: value ( TaggedValue :: F64 ( TypesettingConfig :: default ( ) . letter_tilt ) , false )
15381646 } ,
15391647 network_path,
15401648 ) ;
@@ -1561,8 +1669,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
15611669
15621670 // Insert bool parameters for `has_max_width` and `has_max_height`:
15631671 // https://github.com/GraphiteEditor/Graphite/pull/3643
1564- if reference == DefinitionIdentifier :: ProtoNode ( graphene_std :: text :: text:: IDENTIFIER ) && inputs_count == 11 {
1565- let mut template: NodeTemplate = resolve_document_node_type ( & reference ) ? . default_node_template ( ) ;
1672+ if reference == DefinitionIdentifier :: ProtoNode ( ProtoNodeIdentifier :: new ( "graphene_std ::text::TextNode" ) ) && inputs_count == 11 {
1673+ let mut template: NodeTemplate = legacy_text_node_template ( ) ? ;
15661674 document. network_interface . replace_implementation ( node_id, network_path, & mut template) ;
15671675 let old_inputs = document. network_interface . replace_inputs ( node_id, network_path, & mut template) ?;
15681676
@@ -1714,7 +1822,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
17141822
17151823 // Convert text nodes from the old `editor-api` scope + `Font` input to a single font `Resource` input.
17161824 // The chosen typeface is recorded as a `DataSource::Font` in the document's resource registry and loaded on open.
1717- if reference == DefinitionIdentifier :: ProtoNode ( graphene_std :: text :: text:: IDENTIFIER ) && inputs_count == 13 && matches ! ( node. inputs. first( ) , Some ( NodeInput :: Scope ( _) ) ) {
1825+ if reference == DefinitionIdentifier :: ProtoNode ( ProtoNodeIdentifier :: new ( "graphene_std ::text::TextNode" ) ) && inputs_count == 13 && matches ! ( node. inputs. first( ) , Some ( NodeInput :: Scope ( _) ) ) {
17181826 document
17191827 . network_interface
17201828 . set_input ( & InputConnector :: node ( * node_id, 0 ) , NodeInput :: value ( TaggedValue :: None , false ) , network_path) ;
0 commit comments