Skip to content

Commit a558fb8

Browse files
committed
Fix text-on-path rendering
1 parent c921696 commit a558fb8

8 files changed

Lines changed: 164 additions & 87 deletions

File tree

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

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use graphene_std::Color;
1414
use graphene_std::renderer::Quad;
1515
use graphene_std::renderer::convert_usvg_path::{convert_tiny_skia_path, convert_usvg_path};
1616
use graphene_std::table::Table;
17-
use graphene_std::text::{Font, TextAnchor, TypesettingConfig};
17+
use graphene_std::text::{Font, TypesettingConfig};
1818
use graphene_std::vector::style::{Fill, Gradient, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
1919
#[derive(ExtractField)]
2020
pub struct GraphOperationMessageContext<'a> {
@@ -394,7 +394,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
394394
center,
395395
} => {
396396
let mut options = usvg::Options::default();
397-
options.fontdb_mut().load_font_data(include_bytes!("../../../../font.ttf").to_vec());
397+
options.fontdb_mut().load_font_data(include_bytes!("../overlays/source-sans-pro-regular.ttf").to_vec());
398398
options.font_family = "Source Sans Pro".to_string();
399399

400400
let svg = svg.replace("font-family=\"sans-serif\"", "font-family=\"Source Sans Pro\"");
@@ -552,7 +552,6 @@ fn import_usvg_node(
552552
insert_index: usize,
553553
graphite_gradient_stops: &HashMap<String, GradientStops>,
554554
) {
555-
log::error!("DIAGNOSTIC: Visiting node root: {:?}", node);
556555
let layer = modify_inputs.create_layer(id);
557556

558557
modify_inputs.network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
@@ -597,7 +596,7 @@ fn import_usvg_node(
597596
warn!("Skip image");
598597
}
599598
usvg::Node::Text(text) => {
600-
import_usvg_text(modify_inputs, text, node.abs_transform(), layer);
599+
import_usvg_text(modify_inputs, text, node.abs_transform(), layer, parent, insert_index);
601600
}
602601
}
603602
}
@@ -643,7 +642,7 @@ fn import_usvg_node_inner(
643642
0
644643
}
645644
usvg::Node::Text(text) => {
646-
import_usvg_text(modify_inputs, text, node.abs_transform(), layer);
645+
import_usvg_text(modify_inputs, text, node.abs_transform(), layer, parent, insert_index);
647646
0
648647
}
649648
usvg::Node::Path(path) => {
@@ -676,23 +675,31 @@ fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
676675
}
677676
}
678677

679-
fn import_usvg_text(modify_inputs: &mut ModifyInputsContext, text: &usvg::Text, transform: usvg::Transform, layer: LayerNodeIdentifier) {
678+
fn import_usvg_text(modify_inputs: &mut ModifyInputsContext, text: &usvg::Text, transform: usvg::Transform, layer: LayerNodeIdentifier, parent: LayerNodeIdentifier, insert_index: usize) {
680679
use graphene_std::text::TextAnchor;
681-
let font_family = text
682-
.chunks()
683-
.first()
684-
.and_then(|chunk| chunk.spans().first())
685-
.and_then(|span| span.font().families().first().map(|f| f.to_string()))
686-
.unwrap_or_else(|| graphene_std::consts::DEFAULT_FONT_FAMILY.to_string());
687-
let font_style = graphene_std::consts::DEFAULT_FONT_STYLE.to_string();
688-
let font = Font::new(font_family, font_style);
689680

690-
let full_text: String = text.chunks().iter().map(|chunk| chunk.text()).collect();
681+
for (i, chunk) in text.chunks().iter().enumerate() {
682+
let current_layer = if i == 0 {
683+
layer
684+
} else {
685+
let new_id = NodeId::new();
686+
let new_layer = modify_inputs.create_layer(new_id);
687+
modify_inputs.network_interface.move_layer_to_stack_for_import(new_layer, parent, insert_index, &[]);
688+
new_layer
689+
};
690+
modify_inputs.layer_node = Some(current_layer);
691691

692-
// Check if any chunk uses text-on-path (TextFlow::Path)
693-
let text_path_chunk = text.chunks().iter().find(|chunk| matches!(chunk.text_flow(), usvg::TextFlow::Path(_)));
692+
let font_family = chunk
693+
.spans()
694+
.first()
695+
.and_then(|span| span.font().families().first().map(|f| f.to_string()))
696+
.unwrap_or_else(|| graphene_std::consts::DEFAULT_FONT_FAMILY.to_string());
697+
let font_style = graphene_std::consts::DEFAULT_FONT_STYLE.to_string();
698+
let font = Font::new(font_family, font_style);
699+
700+
let font_size = chunk.spans().first().map(|s| s.font_size().get()).unwrap_or(24.0) as f64;
701+
let letter_spacing = chunk.spans().first().map(|s| s.letter_spacing()).unwrap_or(0.0) as f64;
694702

695-
if let Some(chunk) = text_path_chunk {
696703
if let usvg::TextFlow::Path(text_path) = chunk.text_flow() {
697704
let path_subpaths = convert_tiny_skia_path(text_path.path());
698705
let start_offset = text_path.start_offset() as f64;
@@ -701,8 +708,6 @@ fn import_usvg_text(modify_inputs: &mut ModifyInputsContext, text: &usvg::Text,
701708
usvg::TextAnchor::Middle => TextAnchor::Middle,
702709
usvg::TextAnchor::End => TextAnchor::End,
703710
};
704-
let font_size = chunk.spans().first().map(|s| s.font_size().get()).unwrap_or(24.0) as f64;
705-
let letter_spacing = chunk.spans().first().map(|s| s.letter_spacing()).unwrap_or(0.0) as f64;
706711

707712
let affine = DAffine2::from_cols_array(&[
708713
transform.sx as f64,
@@ -712,15 +717,14 @@ fn import_usvg_text(modify_inputs: &mut ModifyInputsContext, text: &usvg::Text,
712717
transform.tx as f64,
713718
transform.ty as f64,
714719
]);
715-
modify_inputs.insert_text_on_path(full_text, font, font_size, letter_spacing, path_subpaths, start_offset, anchor, affine, layer);
720+
modify_inputs.insert_text_on_path(chunk.text().to_string(), font, font_size, letter_spacing, path_subpaths, start_offset, anchor, affine, current_layer);
721+
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
722+
} else {
723+
// Regular text fallback
724+
modify_inputs.insert_text(chunk.text().to_string(), font, TypesettingConfig { font_size, ..Default::default() }, current_layer);
716725
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
717-
return;
718726
}
719727
}
720-
721-
// Fallback: regular text
722-
modify_inputs.insert_text(full_text, font, TypesettingConfig::default(), layer);
723-
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
724728
}
725729

726730
/// Set correct positions for all imported layers in a single top-down O(n) pass.

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
44
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
55
use crate::messages::prelude::*;
66
use glam::{DAffine2, DVec2, IVec2};
7-
use graphene_std::transform::Transform as _;
87
use graph_craft::document::value::TaggedValue;
98
use graph_craft::document::{NodeId, NodeInput};
109
use graph_craft::{ProtoNodeIdentifier, concrete};
@@ -15,6 +14,7 @@ use graphene_std::raster_types::{CPU, Raster};
1514
use graphene_std::subpath::Subpath;
1615
use graphene_std::table::Table;
1716
use graphene_std::text::{Font, TextAnchor, TypesettingConfig};
17+
use graphene_std::transform::Transform as _;
1818
use graphene_std::vector::Vector;
1919
use graphene_std::vector::style::{Fill, Stroke};
2020
use graphene_std::vector::{PointId, VectorModificationType};
@@ -290,8 +290,18 @@ impl<'a> ModifyInputsContext<'a> {
290290
self.network_interface.move_node_to_chain_start(&fill_id, layer, &[], self.import);
291291
}
292292

293-
294-
pub fn insert_text_on_path(&mut self, text: String, font: Font, font_size: f64, character_spacing: f64, path_subpaths: Vec<Subpath<PointId>>, start_offset: f64, text_anchor: TextAnchor, transform: DAffine2, layer: LayerNodeIdentifier) {
293+
pub fn insert_text_on_path(
294+
&mut self,
295+
text: String,
296+
font: Font,
297+
font_size: f64,
298+
character_spacing: f64,
299+
path_subpaths: Vec<Subpath<PointId>>,
300+
start_offset: f64,
301+
text_anchor: TextAnchor,
302+
transform: DAffine2,
303+
layer: LayerNodeIdentifier,
304+
) {
295305
use graphene_std::text::TextPathSide;
296306

297307
// Create the path vector object

node-graph/nodes/gstd/src/text.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use core_types::{Ctx, table::Table};
22
use graph_craft::wasm_application_io::WasmEditorApi;
33
use graphic_types::Vector;
4-
pub use text_nodes::*;
54
pub use text_nodes::text_on_path::{TextAnchor, TextPathSide};
5+
pub use text_nodes::*;
66

77
/// Draws a text string as vector geometry with a choice of font and styling.
88
#[node_macro::node(category("Text"))]
@@ -80,8 +80,7 @@ fn text<'i: 'n>(
8080
#[node_macro::node(category("Text"))]
8181
fn text_on_path<'i: 'n>(
8282
_: impl Ctx,
83-
#[scope("editor-api")]
84-
editor_resources: &'i WasmEditorApi,
83+
#[scope("editor-api")] editor_resources: &'i WasmEditorApi,
8584
/// The text content to flow along the path.
8685
#[default("Lorem ipsum")]
8786
text: String,

node-graph/nodes/text/src/font_cache.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use dyn_any::DynAny;
22
use parley::fontique::Blob;
3+
use serde::Deserialize;
34
use std::collections::HashMap;
5+
use std::hash::Hash;
46
use std::sync::Arc;
5-
use serde::Deserialize;
67

78
/// A font type (storing font family and font style and an optional preview URL)
89
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
@@ -37,6 +38,21 @@ impl Font {
3738
font_style_to_restore: None,
3839
}
3940
}
41+
42+
pub fn named_weight(weight: u32) -> &'static str {
43+
match weight {
44+
100 => "Thin",
45+
200 => "Extra Light",
46+
300 => "Light",
47+
400 => "Regular",
48+
500 => "Medium",
49+
600 => "Semi Bold",
50+
700 => "Bold",
51+
800 => "Extra Bold",
52+
900 => "Black",
53+
_ => "Weight",
54+
}
55+
}
4056
}
4157

4258
impl Default for Font {
@@ -50,21 +66,32 @@ impl Default for Font {
5066
}
5167

5268
/// A cache of fonts
53-
#[derive(Debug, Default, Clone, DynAny)]
69+
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize, DynAny)]
5470
pub struct FontCache {
5571
/// Mapping of font family name to font style name to font data
5672
pub font_file_data: HashMap<Font, Arc<Vec<u8>>>,
5773
}
5874

75+
impl Hash for FontCache {
76+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
77+
self.font_file_data.len().hash(state);
78+
}
79+
}
80+
5981
impl FontCache {
6082
/// Get the font data for a font
6183
pub fn get_data(&self, font: &Font) -> Option<Arc<Vec<u8>>> {
6284
self.font_file_data.get(font).cloned()
6385
}
6486

6587
/// Insert font data for a font
66-
pub fn insert(&mut self, font: Font, data: Arc<Vec<u8>>) {
67-
self.font_file_data.insert(font, data);
88+
pub fn insert(&mut self, font: Font, data: impl Into<Arc<Vec<u8>>>) {
89+
self.font_file_data.insert(font, data.into());
90+
}
91+
92+
/// Check if the font data for a font is cached
93+
pub fn loaded_font(&self, font: &Font) -> bool {
94+
self.font_file_data.contains_key(font)
6895
}
6996

7097
/// Check if the font data for a font is cached
@@ -92,15 +119,15 @@ impl FontCache {
92119
if self.font_file_data.contains_key(font) {
93120
Some(font)
94121
} else {
95-
let fallback = self.font_file_data
122+
let fallback = self
123+
.font_file_data
96124
.keys()
97125
.find(|font| font.font_family == core_types::consts::DEFAULT_FONT_FAMILY && font.font_style == core_types::consts::DEFAULT_FONT_STYLE)
98-
.or_else(|| self.font_file_data.keys().next());
126+
.or_else(|| self.font_file_data.keys().next());
99127
fallback
100128
}
101129
}
102130

103-
104131
/// Try to get the bytes for a font
105132
pub fn get<'a>(&'a self, font: &'a Font) -> Option<(&'a Vec<u8>, &'a Font)> {
106133
let resolved = self.resolve_font(font)?;

node-graph/nodes/text/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
mod font_cache;
22
mod path_builder;
33
mod text_context;
4-
mod to_path;
54
pub mod text_on_path;
5+
mod to_path;
66

77
use dyn_any::DynAny;
88
pub use font_cache::*;

0 commit comments

Comments
 (0)