Skip to content

Commit

Permalink
Add line height and character spacing to the Text node (#2016)
Browse files Browse the repository at this point in the history
  • Loading branch information
Keavon authored Oct 1, 2024
1 parent 904cf09 commit 2d86fb2
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ pub enum GraphOperationMessage {
text: String,
font: Font,
size: f64,
line_height_ratio: f64,
character_spacing: f64,
parent: LayerNodeIdentifier,
insert_index: usize,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
text,
font,
size,
line_height_ratio,
character_spacing,
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_text(text, font, size, layer);
modify_inputs.insert_text(text, font, size, line_height_ratio, character_spacing, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() });
responses.add(NodeGraphMessage::RunDocumentGraph);
Expand Down Expand Up @@ -284,7 +286,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
}
usvg::Node::Text(text) => {
let font = Font::new(graphene_core::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_core::consts::DEFAULT_FONT_STYLE.to_string());
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, 24., layer);
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, 24., 1.2, 1., layer);
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl<'a> ModifyInputsContext<'a> {
}
}

pub fn insert_text(&mut self, text: String, font: Font, size: f64, layer: LayerNodeIdentifier) {
pub fn insert_text(&mut self, text: String, font: Font, size: f64, line_height_ratio: f64, character_spacing: f64, layer: LayerNodeIdentifier) {
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_node_template();
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_node_template();
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
Expand All @@ -186,6 +186,8 @@ impl<'a> ModifyInputsContext<'a> {
Some(NodeInput::value(TaggedValue::String(text), false)),
Some(NodeInput::value(TaggedValue::Font(font), false)),
Some(NodeInput::value(TaggedValue::F64(size), false)),
Some(NodeInput::value(TaggedValue::F64(line_height_ratio), false)),
Some(NodeInput::value(TaggedValue::F64(character_spacing), false)),
]);

let text_id = NodeId(generate_uuid());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2056,11 +2056,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
false,
),
NodeInput::value(TaggedValue::F64(24.), false),
NodeInput::value(TaggedValue::F64(1.2), false),
NodeInput::value(TaggedValue::F64(1.), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_names: vec!["Editor API".to_string(), "Text".to_string(), "Font".to_string(), "Size".to_string()],
input_names: vec![
"Editor API".to_string(),
"Text".to_string(),
"Font".to_string(),
"Size".to_string(),
"Line Height".to_string(),
"Character Spacing".to_string(),
],
output_names: vec!["Vector".to_string()],
..Default::default()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1731,12 +1731,16 @@ pub(crate) fn text_properties(document_node: &DocumentNode, node_id: NodeId, _co
let text = text_area_widget(document_node, node_id, 1, "Text", true);
let (font, style) = font_inputs(document_node, node_id, 2, "Font", true);
let size = number_widget(document_node, node_id, 3, "Size", NumberInput::default().unit(" px").min(1.), true);
let line_height_ratio = number_widget(document_node, node_id, 4, "Line Height", NumberInput::default().min(0.).step(0.1), true);
let character_spacing = number_widget(document_node, node_id, 5, "Character Spacing", NumberInput::default().min(0.).step(0.1), true);

let mut result = vec![LayoutGroup::Row { widgets: text }, LayoutGroup::Row { widgets: font }];
if let Some(style) = style {
result.push(LayoutGroup::Row { widgets: style });
}
result.push(LayoutGroup::Row { widgets: size });
result.push(LayoutGroup::Row { widgets: line_height_ratio });
result.push(LayoutGroup::Row { widgets: character_spacing });
result
}

Expand Down
23 changes: 22 additions & 1 deletion editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
log::error!("could not get node in deserialize_document");
continue;
};
let inputs_count = node.inputs.len();

if reference == "Fill" && node.inputs.len() == 8 {
if reference == "Fill" && inputs_count == 8 {
let node_definition = resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
document.network_interface.replace_implementation(node_id, &[], document_node.implementation.clone());
Expand Down Expand Up @@ -529,6 +530,26 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
}

// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == "Text" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
document.network_interface.replace_implementation(node_id, &[], document_node.implementation.clone());

let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), &[]);

document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), &[]);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), &[]);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), &[]);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), &[]);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::F64(1.), false), &[]);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 5), NodeInput::value(TaggedValue::F64(1.), false), &[]);
}

// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946
if reference == "Merge" || reference == "Artboard" {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,16 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
}

/// Gets properties from the Text node
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, f64)> {
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, f64, f64, f64)> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?;

let Some(TaggedValue::String(text)) = &inputs[1].as_value() else { return None };
let Some(TaggedValue::Font(font)) = &inputs[2].as_value() else { return None };
let Some(&TaggedValue::F64(font_size)) = inputs[3].as_value() else { return None };
let Some(&TaggedValue::F64(line_height_ratio)) = inputs[4].as_value() else { return None };
let Some(&TaggedValue::F64(character_spacing)) = inputs[5].as_value() else { return None };

Some((text, font, font_size))
Some((text, font, font_size, line_height_ratio, character_spacing))
}

pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
Expand Down
63 changes: 53 additions & 10 deletions editor/src/messages/tool/tool_messages/text_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub struct TextTool {
}

pub struct TextOptions {
font_size: u32,
font_size: f64,
line_height_ratio: f64,
character_spacing: f64,
font_name: String,
font_style: String,
fill: ToolColorOptions,
Expand All @@ -33,7 +35,9 @@ pub struct TextOptions {
impl Default for TextOptions {
fn default() -> Self {
Self {
font_size: 24,
font_size: 24.,
line_height_ratio: 1.2,
character_spacing: 1.,
font_name: graphene_core::consts::DEFAULT_FONT_FAMILY.into(),
font_style: graphene_core::consts::DEFAULT_FONT_STYLE.into(),
fill: ToolColorOptions::new_primary(),
Expand Down Expand Up @@ -63,7 +67,9 @@ pub enum TextOptionsUpdate {
FillColor(Option<Color>),
FillColorType(ToolColorType),
Font { family: String, style: String },
FontSize(u32),
FontSize(f64),
LineHeightRatio(f64),
CharacterSpacing(f64),
WorkingColors(Option<Color>, Option<Color>),
}

Expand Down Expand Up @@ -100,20 +106,40 @@ fn create_text_widgets(tool: &TextTool) -> Vec<WidgetHolder> {
.into()
})
.widget_holder();
let size = NumberInput::new(Some(tool.options.font_size as f64))
let size = NumberInput::new(Some(tool.options.font_size))
.unit(" px")
.label("Size")
.int()
.min(1.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into())
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap())).into())
.widget_holder();
let line_height_ratio = NumberInput::new(Some(tool.options.line_height_ratio))
.label("Line Height")
.int()
.min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.step(0.1)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::LineHeightRatio(number_input.value.unwrap())).into())
.widget_holder();
let character_spacing = NumberInput::new(Some(tool.options.character_spacing))
.label("Character Spacing")
.int()
.min(0.)
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.step(0.1)
.on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::CharacterSpacing(number_input.value.unwrap())).into())
.widget_holder();
vec![
font,
Separator::new(SeparatorType::Related).widget_holder(),
style,
Separator::new(SeparatorType::Related).widget_holder(),
size,
Separator::new(SeparatorType::Related).widget_holder(),
line_height_ratio,
Separator::new(SeparatorType::Related).widget_holder(),
character_spacing,
]
}

Expand Down Expand Up @@ -149,6 +175,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextToo
self.send_layout(responses, LayoutTarget::ToolOptions);
}
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
TextOptionsUpdate::LineHeightRatio(line_height_ratio) => self.options.line_height_ratio = line_height_ratio,
TextOptionsUpdate::CharacterSpacing(character_spacing) => self.options.character_spacing = character_spacing,
TextOptionsUpdate::FillColor(color) => {
self.options.fill.custom_color = color;
self.options.fill.color_type = ToolColorType::Custom;
Expand Down Expand Up @@ -200,6 +228,8 @@ pub struct EditingText {
text: String,
font: Font,
font_size: f64,
line_height_ratio: f64,
character_spacing: f64,
color: Option<Color>,
transform: DAffine2,
}
Expand Down Expand Up @@ -233,11 +263,13 @@ impl TextToolData {
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> 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, font_size) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
let (text, font, font_size, line_height_ratio, character_spacing) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
self.editing_text = Some(EditingText {
text: text.clone(),
font: font.clone(),
font_size,
line_height_ratio,
character_spacing,
color: Some(color),
transform,
});
Expand Down Expand Up @@ -295,6 +327,8 @@ impl TextToolData {
text: String::new(),
font: editing_text.font.clone(),
size: editing_text.font_size,
line_height_ratio: editing_text.line_height_ratio,
character_spacing: editing_text.character_spacing,
parent: document.new_layer_parent(true),
insert_index: 0,
});
Expand Down Expand Up @@ -364,7 +398,14 @@ impl Fsm for TextToolFsmState {
});
if let Some(editing_text) = tool_data.editing_text.as_ref() {
let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data));
let far = graphene_core::text::bounding_box(&tool_data.new_text, buzz_face, editing_text.font_size, None);
let far = graphene_core::text::bounding_box(
&tool_data.new_text,
buzz_face,
editing_text.font_size,
editing_text.line_height_ratio,
editing_text.character_spacing,
None,
);
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;
Expand All @@ -376,11 +417,11 @@ impl Fsm for TextToolFsmState {
}
(_, TextToolMessage::Overlays(mut overlay_context)) => {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
let Some((text, font, font_size)) = graph_modification_utils::get_text(layer, &document.network_interface) else {
let Some((text, font, font_size, line_height_ratio, character_spacing)) = graph_modification_utils::get_text(layer, &document.network_interface) else {
continue;
};
let buzz_face = font_cache.get(font).map(|data| load_face(data));
let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None);
let far = graphene_core::text::bounding_box(text, buzz_face, font_size, line_height_ratio, character_spacing, None);
let quad = Quad::from_box([DVec2::ZERO, far]);
let multiplied = document.metadata().transform_to_viewport(layer) * quad;
overlay_context.quad(multiplied, None);
Expand All @@ -392,7 +433,9 @@ impl Fsm for TextToolFsmState {
tool_data.editing_text = Some(EditingText {
text: String::new(),
transform: DAffine2::from_translation(input.mouse.position),
font_size: tool_options.font_size as f64,
font_size: tool_options.font_size,
line_height_ratio: tool_options.line_height_ratio,
character_spacing: tool_options.character_spacing,
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
color: tool_options.fill.active_color(),
});
Expand Down
Loading

0 comments on commit 2d86fb2

Please sign in to comment.