Skip to content

Commit fc6c9e2

Browse files
committed
Fix export
1 parent 76c6f9d commit fc6c9e2

6 files changed

Lines changed: 102 additions & 39 deletions

File tree

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -619,11 +619,14 @@ fn escape_xml_attr(value: &str) -> String {
619619

620620
#[derive(Debug, Default, Clone)]
621621
struct TextPathAttrs {
622+
pub start_offset: Option<String>,
622623
pub method: Option<String>,
623624
pub spacing: Option<String>,
624625
pub side: Option<String>,
625626
pub text_length: Option<f64>,
626627
pub length_adjust: Option<String>,
628+
pub path_length: Option<f64>,
629+
pub direction: Option<String>,
627630
}
628631

629632
fn pre_parse_textpath_attrs(svg: &str) -> std::collections::HashMap<String, Vec<TextPathAttrs>> {
@@ -638,11 +641,14 @@ fn pre_parse_textpath_attrs(svg: &str) -> std::collections::HashMap<String, Vec<
638641
continue;
639642
};
640643
map.entry(path_id).or_default().push(TextPathAttrs {
644+
start_offset: node.attribute("startOffset").map(str::to_string),
641645
method: node.attribute("method").map(str::to_string),
642646
spacing: node.attribute("spacing").map(str::to_string),
643647
side: node.attribute("side").map(str::to_string),
644648
text_length: node.attribute("textLength").and_then(|v| v.parse().ok()),
645649
length_adjust: node.attribute("lengthAdjust").map(str::to_string),
650+
path_length: node.attribute("pathLength").and_then(|v| v.parse().ok()),
651+
direction: node.attribute("direction").or_else(|| node.attribute("style").and_then(|s| s.split(';').find(|p| p.trim().starts_with("direction")).and_then(|p| p.split(':').last()).map(|v| v.trim()))).map(str::to_string),
646652
});
647653
}
648654
}
@@ -802,20 +808,21 @@ fn import_usvg_text(
802808
text: &usvg::Text,
803809
transform: usvg::Transform,
804810
layer: LayerNodeIdentifier,
805-
parent: LayerNodeIdentifier,
806-
insert_index: usize,
811+
_parent: LayerNodeIdentifier,
812+
_insert_index: usize,
807813
textpath_attrs: &mut HashMap<String, Vec<TextPathAttrs>>,
808814
) {
809815
log::info!("Importing usvg text node with {} chunks", text.chunks().len());
810816

811-
for (i, chunk) in text.chunks().iter().enumerate() {
812-
let current_layer = if i == 0 {
813-
layer
814-
} else {
817+
let chunks = text.chunks();
818+
for (i, chunk) in chunks.iter().enumerate() {
819+
let current_layer = if chunks.len() > 1 {
815820
let new_id = NodeId::new();
816821
let new_layer = modify_inputs.create_layer(new_id);
817-
modify_inputs.network_interface.move_layer_to_stack_for_import(new_layer, parent, insert_index, &[]);
822+
modify_inputs.network_interface.move_layer_to_stack_for_import(new_layer, layer, i, &[]);
818823
new_layer
824+
} else {
825+
layer
819826
};
820827
modify_inputs.layer_node = Some(current_layer);
821828

@@ -834,7 +841,12 @@ fn import_usvg_text(
834841
let tp_id = text_path.id();
835842
let tp_attrs = take_textpath_attrs(textpath_attrs, tp_id);
836843
let path_subpaths = convert_tiny_skia_path(text_path.path());
837-
let start_offset = text_path.start_offset() as f64;
844+
845+
let (start_offset, start_offset_percent) = match tp_attrs.start_offset.as_deref() {
846+
Some(s) if s.ends_with('%') => (s.trim_end_matches('%').parse::<f64>().unwrap_or(0.0) / 100.0, true),
847+
Some(s) => (s.parse::<f64>().unwrap_or(0.0), false),
848+
None => (text_path.start_offset() as f64, false),
849+
};
838850

839851
modify_inputs.insert_text_on_path(
840852
chunk.text().to_string(),
@@ -843,12 +855,15 @@ fn import_usvg_text(
843855
letter_spacing,
844856
path_subpaths,
845857
start_offset,
858+
start_offset_percent,
846859
text_anchor(chunk.anchor()),
847860
text_path_side(&tp_attrs),
848861
text_path_method(&tp_attrs),
849862
text_path_spacing(&tp_attrs),
850863
tp_attrs.text_length,
851864
text_length_adjust(&tp_attrs),
865+
tp_attrs.path_length,
866+
tp_attrs.direction.as_deref() == Some("rtl"),
852867
usvg_transform(transform),
853868
current_layer,
854869
);

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,15 @@ impl<'a> ModifyInputsContext<'a> {
298298
character_spacing: f64,
299299
path_subpaths: Vec<Subpath<PointId>>,
300300
start_offset: f64,
301+
start_offset_percent: bool,
301302
text_anchor: TextAnchor,
302303
side: graphene_std::text::TextPathSide,
303304
method: graphene_std::text::TextPathMethod,
304305
spacing: graphene_std::text::TextPathSpacing,
305306
text_length: Option<f64>,
306307
length_adjust: graphene_std::text::LengthAdjust,
308+
path_length: Option<f64>,
309+
rtl: bool,
307310
transform: DAffine2,
308311
layer: LayerNodeIdentifier,
309312
) {
@@ -318,17 +321,17 @@ impl<'a> ModifyInputsContext<'a> {
318321
Some(NodeInput::value(TaggedValue::F64(font_size), false)),
319322
Some(NodeInput::value(TaggedValue::F64(character_spacing), false)),
320323
Some(NodeInput::value(TaggedValue::F64(start_offset), false)),
321-
Some(NodeInput::value(TaggedValue::Bool(false), false)),
324+
Some(NodeInput::value(TaggedValue::Bool(start_offset_percent), false)),
322325
Some(NodeInput::value(TaggedValue::TextPathSide(side), false)),
323326
Some(NodeInput::value(TaggedValue::TextAnchor(text_anchor), false)),
324327
Some(NodeInput::value(TaggedValue::TextPathMethod(method), false)),
325328
Some(NodeInput::value(TaggedValue::TextPathSpacing(spacing), false)),
326329
Some(NodeInput::value(TaggedValue::Bool(text_length.is_some()), false)),
327330
Some(NodeInput::value(TaggedValue::F64(text_length.unwrap_or(0.0)), false)),
328331
Some(NodeInput::value(TaggedValue::LengthAdjust(length_adjust), false)),
329-
Some(NodeInput::value(TaggedValue::Bool(false), false)),
330-
Some(NodeInput::value(TaggedValue::F64(0.0), false)),
331-
Some(NodeInput::value(TaggedValue::Bool(false), false)),
332+
Some(NodeInput::value(TaggedValue::Bool(path_length.is_some()), false)),
333+
Some(NodeInput::value(TaggedValue::F64(path_length.unwrap_or(0.0)), false)),
334+
Some(NodeInput::value(TaggedValue::Bool(rtl), false)),
332335
]);
333336

334337
let text_on_path_id = NodeId::new();

node-graph/libraries/rendering/src/renderer.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,9 +794,14 @@ impl Render for Table<Graphic> {
794794

795795
impl Render for Table<Vector> {
796796
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
797+
let mut text_on_path_exported = false;
797798
for row in self.iter() {
798799
if render_params.for_export {
799800
if let Some(ref meta) = row.element.text_on_path_metadata {
801+
if text_on_path_exported {
802+
continue;
803+
}
804+
text_on_path_exported = true;
800805
let path_id = format!("textpath-{}", generate_uuid());
801806
write!(&mut render.svg_defs, r#"<path id="{path_id}" d="{}" fill="none"/>"#, escape_xml_attr(&meta.path_d)).unwrap();
802807

@@ -813,9 +818,11 @@ impl Render for Table<Vector> {
813818
let anchor_style = format!("text-anchor: {};", meta.text_anchor);
814819
let method = &meta.method;
815820
let spacing = &meta.spacing;
821+
let direction_attr = if meta.rtl { r#" direction="rtl""# } else { "" };
822+
let path_length_attr = meta.path_length.map(|pl| format!(r#" pathLength="{pl}""#)).unwrap_or_default();
816823
let text = escape_xml_text(&meta.text);
817824

818-
render.leaf_node(format!(r##"<text style="{font_style_css} {anchor_style}"{transform_attr}><textPath href="#{path_id}" startOffset="{start_offset_attr}" method="{method}" spacing="{spacing}"{side_attr}{text_length_attr}>{text}</textPath></text>"##));
825+
render.leaf_node(format!(r##"<text style="{font_style_css} {anchor_style}"{transform_attr}{direction_attr}><textPath href="#{path_id}" startOffset="{start_offset_attr}" method="{method}" spacing="{spacing}"{side_attr}{text_length_attr}{path_length_attr}>{text}</textPath></text>"##));
819826
continue;
820827
}
821828
}

node-graph/libraries/vector-types/src/vector/vector_types.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub struct TextOnPathMetadata {
4040
pub text_length: Option<f64>,
4141
/// "spacing" | "spacingAndGlyphs"
4242
pub length_adjust: String,
43+
pub path_length: Option<f64>,
44+
pub rtl: bool,
4345
}
4446

4547
/// Represents vector graphics data, composed of Bézier curves in a path or mesh arrangement.
@@ -98,8 +100,18 @@ impl<Upstream> std::hash::Hash for Vector<Upstream> {
98100
metadata.text.hash(state);
99101
metadata.font_family.hash(state);
100102
metadata.font_style.hash(state);
101-
(metadata.font_size as u64).hash(state);
103+
metadata.font_size.to_bits().hash(state);
102104
metadata.path_d.hash(state);
105+
metadata.start_offset.to_bits().hash(state);
106+
metadata.start_offset_percent.hash(state);
107+
metadata.text_anchor.hash(state);
108+
metadata.side.hash(state);
109+
metadata.method.hash(state);
110+
metadata.spacing.hash(state);
111+
metadata.text_length.map(|tl| tl.to_bits()).hash(state);
112+
metadata.length_adjust.hash(state);
113+
metadata.path_length.map(|pl| pl.to_bits()).hash(state);
114+
metadata.rtl.hash(state);
103115
}
104116
}
105117
}

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct PathBuilder<Upstream: Default + 'static> {
1414
current_point: Point,
1515
is_text_on_path: bool,
1616
scale: f64,
17+
glyph_index: u64,
1718
}
1819

1920
impl<Upstream: Default + 'static> PathBuilder<Upstream> {
@@ -25,6 +26,7 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
2526
current_point: Point::ZERO,
2627
is_text_on_path,
2728
scale,
29+
glyph_index: 0,
2830
}
2931
}
3032

@@ -63,21 +65,17 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
6365
};
6466
let transform = if let Some(skew) = style_skew { transform * skew } else { transform };
6567

66-
let subpaths = std::mem::take(&mut self.glyph_subpaths);
68+
let mut vector = Vector::from_subpaths(self.glyph_subpaths.clone(), false);
69+
vector.transform(transform);
6770
if per_glyph_instances {
68-
let mut vector = Vector::from_subpaths(subpaths, false);
69-
vector.transform(transform);
7071
self.vector_table.push(TableRow::new_from_element(vector));
72+
} else if self.vector_table.is_empty() {
73+
self.vector_table = Table::new_from_element(vector);
7174
} else {
72-
let mut vector = Vector::from_subpaths(subpaths, false);
73-
vector.transform(transform);
74-
if self.vector_table.is_empty() {
75-
self.vector_table = Table::new_from_element(vector);
76-
} else {
77-
let current_vector = self.vector_table.iter_mut().next().unwrap();
78-
current_vector.element.concat(&vector, DAffine2::IDENTITY, 0);
79-
}
75+
let current_vector = self.vector_table.iter_mut().next().unwrap();
76+
current_vector.element.concat(&vector, DAffine2::IDENTITY, self.glyph_index);
8077
}
78+
self.glyph_index += 1;
8179
}
8280

8381
pub fn draw_glyph_with_mapping(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], style_skew: Option<DAffine2>, mapping_function: impl Fn(DVec2) -> DVec2) {
@@ -110,7 +108,14 @@ impl<Upstream: Default + 'static> PathBuilder<Upstream> {
110108
let run_y = glyph_run.baseline();
111109

112110
let synthesis = run.synthesis();
113-
let style_skew = synthesis.skew().map(|angle| DAffine2::from_cols_array(&[1., 0., -(angle as f64).to_radians().tan(), 1., 0., 0.]));
111+
let style_skew = synthesis.skew().map(|angle| {
112+
let skew = DAffine2::from_cols_array(&[1., 0., -(angle as f64).to_radians().tan(), 1., 0., 0.]);
113+
if per_glyph_instances || self.is_text_on_path {
114+
skew
115+
} else {
116+
DAffine2::from_translation(DVec2::new(0., run_y as f64)) * skew * DAffine2::from_translation(DVec2::new(0., -run_y as f64))
117+
}
118+
});
114119
let tilt_skew = (tilt != 0.).then(|| DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.]));
115120

116121
let font = run.font();

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

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,19 @@ fn is_glyph_hidden(mid: f64, _start_offset: f64, total_length: f64, is_closed: b
204204
mid < -1e-3 || mid > total_length + 1e-3
205205
}
206206

207-
fn resolve_startpoint(abs_offset: f64, total_advance: f64, text_anchor: TextAnchor) -> f64 {
208-
match text_anchor {
209-
TextAnchor::Start => abs_offset,
210-
TextAnchor::Middle => abs_offset - total_advance / 2.0,
211-
TextAnchor::End => abs_offset - total_advance,
207+
fn resolve_startpoint(abs_offset: f64, total_advance: f64, text_anchor: TextAnchor, rtl: bool) -> f64 {
208+
if !rtl {
209+
match text_anchor {
210+
TextAnchor::Start => abs_offset,
211+
TextAnchor::Middle => abs_offset - total_advance / 2.0,
212+
TextAnchor::End => abs_offset - total_advance,
213+
}
214+
} else {
215+
match text_anchor {
216+
TextAnchor::Start => abs_offset,
217+
TextAnchor::Middle => abs_offset + total_advance / 2.0,
218+
TextAnchor::End => abs_offset + total_advance,
219+
}
212220
}
213221
}
214222

@@ -285,10 +293,18 @@ pub fn place_text_on_path<Upstream: Default + 'static>(
285293

286294
log::info!("Placing text on path: {} (length: {})", text, lut.total_length);
287295

288-
let mut abs_offset = if start_offset_percent { start_offset * lut.total_length } else { start_offset };
289-
if let Some(author_length) = path_length.filter(|&l| l > 1e-9) {
290-
abs_offset *= lut.total_length / author_length;
291-
}
296+
let abs_offset = if let Some(pl) = path_length.filter(|&l| l > 1e-9) {
297+
let scale = lut.total_length / pl;
298+
let offset = if start_offset_percent { start_offset * lut.total_length } else { start_offset * scale };
299+
if rtl { lut.total_length - offset } else { offset }
300+
} else if start_offset_percent {
301+
let offset = start_offset * lut.total_length;
302+
if rtl { lut.total_length - offset } else { offset }
303+
} else if rtl {
304+
lut.total_length - start_offset
305+
} else {
306+
start_offset
307+
};
292308

293309
let mut path_builder = crate::path_builder::PathBuilder::new(true, layout.scale() as f64);
294310

@@ -307,7 +323,7 @@ pub fn place_text_on_path<Upstream: Default + 'static>(
307323
};
308324

309325
let effective_line_width = line_width * advance_scale + spacing_delta * glyph_count.saturating_sub(1) as f64;
310-
let line_start = resolve_startpoint(abs_offset, effective_line_width, text_anchor);
326+
let line_start = resolve_startpoint(abs_offset, effective_line_width, text_anchor, rtl);
311327

312328
let mut cumulative_offset = 0.0_f64;
313329
let mut glyph_index = 0_usize;
@@ -326,9 +342,12 @@ pub fn place_text_on_path<Upstream: Default + 'static>(
326342
glyph_run.glyphs().for_each(|glyph| {
327343
let scaled_advance = glyph.advance as f64 * advance_scale;
328344
cumulative_offset += if glyph_index > 0 { spacing_delta } else { 0.0 };
329-
let glyph_origin = line_start + (run_x as f64 - glyph_run.offset() as f64 + glyph.x as f64) * advance_scale + cumulative_offset;
330-
let mid = glyph_origin + scaled_advance / 2.0;
331-
let adjusted_mid = mid + text_path_spacing_adjustment(spacing, &lut, mid, scaled_advance);
345+
346+
let glyph_x_offset = (run_x as f64 - glyph_run.offset() as f64 + glyph.x as f64) * advance_scale + cumulative_offset;
347+
let mid = if rtl { line_start - glyph_x_offset - scaled_advance / 2.0 } else { line_start + glyph_x_offset + scaled_advance / 2.0 };
348+
349+
let spacing_adj = text_path_spacing_adjustment(spacing, &lut, mid, scaled_advance);
350+
let adjusted_mid = if rtl { mid - spacing_adj } else { mid + spacing_adj };
332351

333352
run_x += glyph.advance;
334353
glyph_index += 1;
@@ -396,6 +415,8 @@ pub fn place_text_on_path<Upstream: Default + 'static>(
396415
LengthAdjust::SpacingAndGlyphs => "spacingAndGlyphs",
397416
}
398417
.to_string(),
418+
path_length,
419+
rtl,
399420
});
400421
for row in result.iter_mut() {
401422
row.element.text_on_path_metadata = Some(Arc::clone(&metadata));

0 commit comments

Comments
 (0)