Skip to content

Commit 34c57c1

Browse files
committed
Text-decoration
1 parent 6ee0239 commit 34c57c1

16 files changed

Lines changed: 201 additions & 8 deletions

File tree

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
286286
network_interface: &mut self.network_interface,
287287
collapsed: &mut self.collapsed,
288288
node_graph: &mut self.node_graph_handler,
289+
fonts,
289290
};
290291
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
291292
graph_operation_message_handler.process_message(message, responses, context);

editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct GraphOperationMessageContext<'a> {
2121
pub network_interface: &'a mut NodeNetworkInterface,
2222
pub collapsed: &'a mut CollapsedLayers,
2323
pub node_graph: &'a mut NodeGraphMessageHandler,
24+
pub fonts: &'a FontsMessageHandler,
2425
}
2526

2627
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
@@ -423,7 +424,27 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
423424
insert_index,
424425
center,
425426
} => {
426-
let tree = match usvg::Tree::from_str(&svg, &usvg::Options::default()) {
427+
let mut options = usvg::Options::default();
428+
options.font_family = graphene_std::consts::DEFAULT_FONT_FAMILY.to_string();
429+
let mut fontdb = usvg::fontdb::Database::new();
430+
fontdb.load_system_fonts();
431+
fontdb.load_font_data(graphene_std::text::FALLBACK_FONT_RESOURCE.to_vec());
432+
for data in context.fonts.font_data().values() {
433+
fontdb.load_font_data(data.to_vec());
434+
}
435+
let fallback_family = fontdb
436+
.faces()
437+
.next()
438+
.and_then(|face| face.families.first().map(|(name, _)| name.clone()))
439+
.unwrap_or_else(|| "Source Sans Pro".to_string());
440+
fontdb.set_sans_serif_family(&fallback_family);
441+
fontdb.set_serif_family(&fallback_family);
442+
fontdb.set_monospace_family(&fallback_family);
443+
fontdb.set_cursive_family(&fallback_family);
444+
fontdb.set_fantasy_family(&fallback_family);
445+
options.fontdb = std::sync::Arc::new(fontdb);
446+
447+
let tree = match usvg::Tree::from_str(&svg, &options) {
427448
Ok(t) => t,
428449
Err(e) => {
429450
responses.add(DialogMessage::DisplayDialogError {
@@ -621,8 +642,9 @@ fn import_usvg_node(
621642
}
622643
usvg::Node::Text(text) => {
623644
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_std::consts::DEFAULT_FONT_STYLE.to_string());
624-
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, TypesettingConfig::default(), layer);
645+
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, usvg_text_typesetting(text), layer);
625646
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
647+
apply_usvg_text_transform(modify_inputs, text);
626648
}
627649
}
628650
}
@@ -673,13 +695,45 @@ fn import_usvg_node_inner(
673695
}
674696
usvg::Node::Text(text) => {
675697
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_std::consts::DEFAULT_FONT_STYLE.to_string());
676-
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, TypesettingConfig::default(), layer);
698+
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, usvg_text_typesetting(text), layer);
677699
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
700+
apply_usvg_text_transform(modify_inputs, text);
678701
0
679702
}
680703
}
681704
}
682705

706+
fn usvg_text_typesetting(text: &usvg::Text) -> TypesettingConfig {
707+
let mut typesetting = TypesettingConfig::default();
708+
709+
for span in text.chunks().iter().flat_map(|chunk| chunk.spans()) {
710+
let decoration = span.decoration();
711+
typesetting.underline |= decoration.underline().is_some();
712+
typesetting.overline |= decoration.overline().is_some();
713+
typesetting.strikethrough |= decoration.line_through().is_some();
714+
}
715+
716+
if let Some(first_span) = text.chunks().first().and_then(|chunk| chunk.spans().first()) {
717+
typesetting.font_size = first_span.font_size().get() as f64;
718+
}
719+
720+
typesetting
721+
}
722+
723+
fn apply_usvg_text_transform(modify_inputs: &mut ModifyInputsContext, text: &usvg::Text) {
724+
let elem_transform = usvg_transform(text.abs_transform());
725+
let chunk_offset = text.chunks().first().map(|c| DVec2::new(c.x().unwrap_or(0.) as f64, c.y().unwrap_or(0.) as f64)).unwrap_or_default();
726+
let text_transform = elem_transform * DAffine2::from_translation(chunk_offset);
727+
728+
if text_transform.abs_diff_eq(DAffine2::IDENTITY, 1e-6) {
729+
return;
730+
}
731+
// `insert_text` always creates a Transform node; update it in-place.
732+
if let Some(transform_node_id) = modify_inputs.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false) {
733+
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, text_transform);
734+
}
735+
}
736+
683737
/// Helper to apply path data (vector geometry, fill, stroke, transform) to a layer.
684738
fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, path: &usvg::Path, layer: LayerNodeIdentifier, graphite_gradient_stops: &HashMap<String, GradientStops>) {
685739
let subpaths = convert_usvg_path(path);

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ impl<'a> ModifyInputsContext<'a> {
266266
Some(NodeInput::value(TaggedValue::Bool(typesetting.max_height.is_some()), false)),
267267
Some(NodeInput::value(TaggedValue::F64(typesetting.max_height.unwrap_or(100.)), false)),
268268
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
269+
Some(NodeInput::value(TaggedValue::Bool(typesetting.underline), false)),
270+
Some(NodeInput::value(TaggedValue::Bool(typesetting.overline), false)),
271+
Some(NodeInput::value(TaggedValue::Bool(typesetting.strikethrough), false)),
269272
]);
270273
let text_to_vector = resolve_proto_node_type(graphene_std::text::text_to_vector::IDENTIFIER)
271274
.expect("Text to Vector node does not exist")

editor/src/messages/portfolio/document/overlays/utility_functions.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ pub fn text_width(text: &str, font_size: f64) -> f64 {
233233
max_width: None,
234234
max_height: None,
235235
align: TextAlign::AlignLeft,
236+
underline: false,
237+
overline: false,
238+
strikethrough: false,
236239
};
237240

238241
let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context");

editor/src/messages/portfolio/document/overlays/utility_types_native.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,9 @@ impl OverlayContextInternal {
11141114
max_width: None,
11151115
max_height: None,
11161116
align: TextAlign::AlignLeft,
1117+
underline: false,
1118+
overline: false,
1119+
strikethrough: false,
11171120
};
11181121

11191122
// Get text dimensions directly from layout

editor/src/messages/portfolio/document_migration.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,35 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
17151715
inputs_count = 13;
17161716
}
17171717

1718+
// Insert text decoration parameters: underline, overline, and strikethrough.
1719+
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 13 {
1720+
let mut template: NodeTemplate = resolve_document_node_type(&reference)?.default_node_template();
1721+
document.network_interface.replace_implementation(node_id, network_path, &mut template);
1722+
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
1723+
1724+
#[allow(clippy::needless_range_loop)]
1725+
for i in 0..=11 {
1726+
document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i].clone(), network_path);
1727+
}
1728+
1729+
document.network_interface.set_input(
1730+
&InputConnector::node(*node_id, 12),
1731+
NodeInput::value(TaggedValue::Bool(TypesettingConfig::default().underline), false),
1732+
network_path,
1733+
);
1734+
document.network_interface.set_input(
1735+
&InputConnector::node(*node_id, 13),
1736+
NodeInput::value(TaggedValue::Bool(TypesettingConfig::default().overline), false),
1737+
network_path,
1738+
);
1739+
document.network_interface.set_input(
1740+
&InputConnector::node(*node_id, 14),
1741+
NodeInput::value(TaggedValue::Bool(TypesettingConfig::default().strikethrough), false),
1742+
network_path,
1743+
);
1744+
document.network_interface.set_input(&InputConnector::node(*node_id, 15), old_inputs[12].clone(), network_path);
1745+
}
1746+
17181747
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
17191748
if inputs_count == 1
17201749
&& (reference == DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::sine::IDENTIFIER)

editor/src/messages/portfolio/fonts/fonts_message_handler.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ impl FontsMessageHandler {
110110
self.font_hashes.values().copied().chain(self.font_data.keys().copied())
111111
}
112112

113+
pub fn font_data(&self) -> &HashMap<ResourceHash, Resource> {
114+
&self.font_data
115+
}
116+
113117
fn normalize(&self, font: Font) -> Font {
114118
self.font_catalog.normalize(font)
115119
}

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,15 @@ pub fn get_text<'a>(
571571
let Some(&TaggedValue::TextAlign(align)) = inputs.get(graphene_std::text::text::AlignInput::INDEX)?.as_value() else {
572572
return None;
573573
};
574+
let Some(&TaggedValue::Bool(underline)) = inputs.get(graphene_std::text::text::UnderlineInput::INDEX)?.as_value() else {
575+
return None;
576+
};
577+
let Some(&TaggedValue::Bool(overline)) = inputs.get(graphene_std::text::text::OverlineInput::INDEX)?.as_value() else {
578+
return None;
579+
};
580+
let Some(&TaggedValue::Bool(strikethrough)) = inputs.get(graphene_std::text::text::StrikethroughInput::INDEX)?.as_value() else {
581+
return None;
582+
};
574583

575584
let typesetting = TypesettingConfig {
576585
font_size,
@@ -580,6 +589,9 @@ pub fn get_text<'a>(
580589
max_width: has_max_width.then_some(max_width),
581590
max_height: has_max_height.then_some(max_height),
582591
align,
592+
underline,
593+
overline,
594+
strikethrough,
583595
};
584596
Some((text, font, typesetting))
585597
}

node-graph/libraries/core-types/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use graphene_hash::CacheHash;
2626
pub use list::{
2727
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
2828
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL,
29-
ATTR_SPREAD_METHOD, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
29+
ATTR_OVERLINE, ATTR_SPREAD_METHOD, ATTR_START, ATTR_STRIKETHROUGH, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE, ATTR_UNDERLINE,
3030
};
3131
pub use memo::MemoHash;
3232
pub use no_std_types::AsU32;

node-graph/libraries/core-types/src/list.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ pub const ATTR_MAX_HEIGHT: &str = "max_height";
7878
pub const ATTR_LETTER_TILT: &str = "letter_tilt";
7979
/// Text item's `TextAlign` horizontal alignment of lines within the block.
8080
pub const ATTR_TEXT_ALIGN: &str = "text_align";
81+
/// Text item's underline enabled status (`bool`, implicit default `false`).
82+
pub const ATTR_UNDERLINE: &str = "underline";
83+
/// Text item's overline enabled status (`bool`, implicit default `false`).
84+
pub const ATTR_OVERLINE: &str = "overline";
85+
/// Text item's strikethrough enabled status (`bool`, implicit default `false`).
86+
pub const ATTR_STRIKETHROUGH: &str = "strikethrough";
8187

8288
// ===========================
8389
// Implicit attribute defaults

0 commit comments

Comments
 (0)