From 537dd26b3fd7bb7286efda97341dddcfe5f59c67 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl <47084093+LaurenzV@users.noreply.github.com> Date: Sun, 10 Dec 2023 13:58:43 +0100 Subject: [PATCH] Support text preserving in the usvg CLI tool. --- crates/usvg-parser/src/text.rs | 7 +- crates/usvg-tree/src/lib.rs | 6 +- crates/usvg-tree/src/text.rs | 5 + crates/usvg/src/main.rs | 7 +- crates/usvg/src/writer.rs | 312 +++++++++++++++++- .../usvg/tests/files/preserve-id-fe-image.svg | 11 + .../files/preserve-text-in-clip-path.svg | 14 + .../tests/files/preserve-text-in-mask.svg | 12 + .../preserve-text-in-pattern-expected.svg | 8 + .../tests/files/preserve-text-in-pattern.svg | 7 + ...e-text-multiple-font-families-expected.svg | 4 + .../preserve-text-multiple-font-families.svg | 4 + .../files/preserve-text-on-path-expected.svg | 7 + .../tests/files/preserve-text-on-path.svg | 15 + .../preserve-text-simple-case-expected.svg | 6 + .../tests/files/preserve-text-simple-case.svg | 4 + ...-with-complex-text-decoration-expected.svg | 4 + ...erve-text-with-complex-text-decoration.svg | 12 + .../preserve-text-with-dx-and-dy-expected.svg | 4 + .../files/preserve-text-with-dx-and-dy.svg | 6 + ...xt-with-nested-baseline-shift-expected.svg | 4 + ...eserve-text-with-nested-baseline-shift.svg | 25 ++ .../preserve-text-with-rotate-expected.svg | 4 + .../tests/files/preserve-text-with-rotate.svg | 7 + crates/usvg/tests/write.rs | 77 ++++- 25 files changed, 562 insertions(+), 10 deletions(-) create mode 100644 crates/usvg/tests/files/preserve-id-fe-image.svg create mode 100644 crates/usvg/tests/files/preserve-text-in-clip-path.svg create mode 100644 crates/usvg/tests/files/preserve-text-in-mask.svg create mode 100644 crates/usvg/tests/files/preserve-text-in-pattern-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-in-pattern.svg create mode 100644 crates/usvg/tests/files/preserve-text-multiple-font-families-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-multiple-font-families.svg create mode 100644 crates/usvg/tests/files/preserve-text-on-path-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-on-path.svg create mode 100644 crates/usvg/tests/files/preserve-text-simple-case-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-simple-case.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-complex-text-decoration-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-complex-text-decoration.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-dx-and-dy-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-dx-and-dy.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-nested-baseline-shift-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-nested-baseline-shift.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-rotate-expected.svg create mode 100644 crates/usvg/tests/files/preserve-text-with-rotate.svg diff --git a/crates/usvg-parser/src/text.rs b/crates/usvg-parser/src/text.rs index 916dedd07..7e060f8a5 100644 --- a/crates/usvg-parser/src/text.rs +++ b/crates/usvg-parser/src/text.rs @@ -366,7 +366,12 @@ fn resolve_text_flow(node: SvgNode, state: &converter::State) -> Option Font { diff --git a/crates/usvg-tree/src/lib.rs b/crates/usvg-tree/src/lib.rs index a88bcaf78..56c15169e 100644 --- a/crates/usvg-tree/src/lib.rs +++ b/crates/usvg-tree/src/lib.rs @@ -1297,7 +1297,11 @@ impl NodeExt for Node { fn calc_node_bbox(node: &Node, ts: Transform) -> Option { match *node.borrow() { - NodeKind::Path(ref path) => path.data.compute_tight_bounds()?.transform(ts).map(BBox::from), + NodeKind::Path(ref path) => path + .data + .compute_tight_bounds()? + .transform(ts) + .map(BBox::from), NodeKind::Image(ref img) => img.view_box.rect.transform(ts).map(BBox::from), NodeKind::Group(_) => { let mut bbox = BBox::default(); diff --git a/crates/usvg-tree/src/text.rs b/crates/usvg-tree/src/text.rs index 7932eaa64..069d57338 100644 --- a/crates/usvg-tree/src/text.rs +++ b/crates/usvg-tree/src/text.rs @@ -239,6 +239,11 @@ impl Default for TextAnchor { /// A path used by text-on-path. #[derive(Clone, Debug)] pub struct TextPath { + /// Element's ID. + /// + /// Taken from the SVG itself. + pub id: String, + /// A text offset in SVG coordinates. /// /// Percentage values already resolved. diff --git a/crates/usvg/src/main.rs b/crates/usvg/src/main.rs index 80287b612..1863daee5 100644 --- a/crates/usvg/src/main.rs +++ b/crates/usvg/src/main.rs @@ -91,6 +91,7 @@ OPTIONS: Refer to the explanation of the '--default-width' option. [values: 1..4294967295 (inclusive)] [default: 100] + --preserve-text Do not convert text into paths. --id-prefix Adds a prefix to each ID attribute --indent INDENT Sets the XML nodes indent [values: none, 0, 1, 2, 3, 4, tabs] [default: 4] @@ -128,6 +129,7 @@ struct Args { font_files: Vec, font_dirs: Vec, skip_system_fonts: bool, + preserve_text: bool, list_fonts: bool, default_width: u32, default_height: u32, @@ -187,6 +189,7 @@ fn collect_args() -> Result { font_files: input.values_from_str("--use-font-file")?, font_dirs: input.values_from_str("--use-fonts-dir")?, skip_system_fonts: input.contains("--skip-system-fonts"), + preserve_text: input.contains("--preserve-text"), list_fonts: input.contains("--list-fonts"), default_width: input .opt_value_from_fn("--default-width", parse_length)? @@ -426,7 +429,9 @@ fn process(args: Args) -> Result<(), String> { }?; let mut tree = usvg_tree::Tree::from_data(&input_svg, &re_opt).map_err(|e| format!("{}", e))?; - tree.convert_text(&fontdb); + if !args.preserve_text { + tree.convert_text(&fontdb); + } let xml_opt = usvg::XmlOptions { id_prefix: args.id_prefix, diff --git a/crates/usvg/src/writer.rs b/crates/usvg/src/writer.rs index c5ec992c4..2cfb53122 100644 --- a/crates/usvg/src/writer.rs +++ b/crates/usvg/src/writer.rs @@ -71,6 +71,9 @@ struct WriterContext<'a> { next_linear_gradient_index: usize, next_radial_gradient_index: usize, next_pattern_index: usize, + next_path_index: usize, + + text_path_map: HashMap, } impl WriterContext<'_> { @@ -129,6 +132,12 @@ impl WriterContext<'_> { id } + fn gen_path_id(&mut self) -> String { + let (new_index, id) = self.gen_id("path", self.next_path_index); + self.next_path_index = new_index; + id + } + fn push_defs_id(&mut self, node: &Rc, id: String) { let key = Rc::as_ptr(node) as usize; if !self.id_map.contains_key(&key) { @@ -180,6 +189,8 @@ pub(crate) fn convert(tree: &Tree, opt: &XmlOptions) -> String { next_linear_gradient_index: 0, next_radial_gradient_index: 0, next_pattern_index: 0, + next_path_index: 0, + text_path_map: HashMap::new(), }; collect_ids(tree, &mut ctx); @@ -722,6 +733,35 @@ fn conv_defs(tree: &Tree, ctx: &mut WriterContext, xml: &mut XmlWriter) { xml.end_element(); } + + if tree.has_text_nodes() { + write_text_path_paths(&tree.root, ctx, xml); + } +} + +fn write_text_path_paths(node: &Node, ctx: &mut WriterContext, xml: &mut XmlWriter) { + for node in node.descendants() { + if let NodeKind::Text(ref text) = *node.borrow() { + for chunk in &text.chunks { + if let TextFlow::Path(ref text_path) = chunk.text_flow { + let path = Path { + id: ctx.gen_path_id(), + data: text_path.path.clone(), + visibility: Visibility::default(), + fill: None, + stroke: None, + rendering_mode: ShapeRendering::default(), + paint_order: PaintOrder::default(), + }; + write_path(&path, false, Transform::default(), None, ctx, xml); + ctx.text_path_map + .insert(text_path.id.clone(), path.id.clone()); + } + } + } + + node.subroots(|subroot| write_text_path_paths(&subroot, ctx, xml)); + } } fn conv_elements(parent: &Node, is_clip_path: bool, ctx: &mut WriterContext, xml: &mut XmlWriter) { @@ -863,7 +903,115 @@ fn conv_element(node: &Node, is_clip_path: bool, ctx: &mut WriterContext, xml: & if let Some(ref flattened) = text.flattened { conv_element(flattened, is_clip_path, ctx, xml); } else { - log::warn!("Text must be flattened."); + xml.start_svg_element(EId::Text); + + if !text.id.is_empty() { + xml.write_id_attribute(&text.id, ctx); + } + + xml.write_attribute("xml:space", "preserve"); + + match text.writing_mode { + WritingMode::LeftToRight => {} + WritingMode::TopToBottom => xml.write_svg_attribute(AId::WritingMode, "tb"), + } + + match text.rendering_mode { + TextRendering::OptimizeSpeed => { + xml.write_svg_attribute(AId::TextRendering, "optimizeSpeed") + } + TextRendering::GeometricPrecision => { + xml.write_svg_attribute(AId::TextRendering, "geometricPrecision") + } + TextRendering::OptimizeLegibility => {} + } + + if text.rotate.iter().any(|r| *r != 0.0) { + xml.write_numbers(AId::Rotate, &text.rotate); + } + + if text.dx.iter().any(|dx| *dx != 0.0) { + xml.write_numbers(AId::Dx, &text.dx); + } + + if text.dy.iter().any(|dy| *dy != 0.0) { + xml.write_numbers(AId::Dy, &text.dy); + } + + xml.set_preserve_whitespaces(true); + + for chunk in &text.chunks { + if let TextFlow::Path(text_path) = &chunk.text_flow { + xml.start_svg_element(EId::TextPath); + + let prefix = ctx.opt.id_prefix.as_deref().unwrap_or_default(); + let ref_path = ctx.text_path_map.get(&text_path.id).unwrap(); + xml.write_attribute_fmt( + "xlink:href", + format_args!("#{}{}", prefix, ref_path), + ); + + if text_path.start_offset != 0.0 { + xml.write_svg_attribute(AId::StartOffset, &text_path.start_offset); + } + } + + xml.start_svg_element(EId::Tspan); + + if let Some(x) = chunk.x { + xml.write_svg_attribute(AId::X, &x); + } + + if let Some(y) = chunk.y { + xml.write_svg_attribute(AId::Y, &y); + } + + match chunk.anchor { + TextAnchor::Start => {} + TextAnchor::Middle => xml.write_svg_attribute(AId::TextAnchor, "middle"), + TextAnchor::End => xml.write_svg_attribute(AId::TextAnchor, "end"), + } + + for span in &chunk.spans { + let decorations: Vec<_> = [ + ("underline", &span.decoration.underline), + ("line-through", &span.decoration.line_through), + ("overline", &span.decoration.overline), + ] + .iter() + .filter_map(|&(key, option_value)| { + option_value.as_ref().map(|value| (key, value)) + }) + .collect(); + + // Decorations need to be dumped BEFORE we write the actual span data + // (so that for example stroke color of span doesn't affect the text + // itself while baseline shifts need to be written after (since they are + // affected by the font size) + for (deco_name, deco) in &decorations { + xml.start_svg_element(EId::Tspan); + xml.write_svg_attribute(AId::TextDecoration, deco_name); + write_fill(&deco.fill, false, ctx, xml); + write_stroke(&deco.stroke, ctx, xml); + } + + write_span(is_clip_path, ctx, xml, chunk, span); + + // End for each tspan we needed to create for decorations + for _ in &decorations { + xml.end_element(); + } + } + xml.end_element(); + + // End textPath element + if matches!(&chunk.text_flow, TextFlow::Path(_)) { + xml.end_element(); + } + } + + xml.end_element(); + xml.set_preserve_whitespaces(false); } } } @@ -1162,6 +1310,16 @@ fn has_xlink(node: &Node) -> bool { NodeKind::Image(_) => { return true; } + NodeKind::Text(ref text) => { + if text + .chunks + .iter() + .find(|t| matches!(t.text_flow, TextFlow::Path(_))) + .is_some() + { + return true; + } + } _ => {} } } @@ -1438,3 +1596,155 @@ fn write_num(num: f32, buf: &mut Vec, precision: u8) { write!(buf, "{}", v).unwrap(); } + +/// Write all of the tspan attributes except for decorations. +fn write_span( + is_clip_path: bool, + ctx: &mut WriterContext, + xml: &mut XmlWriter, + chunk: &TextChunk, + span: &TextSpan, +) { + xml.start_svg_element(EId::Tspan); + + if span.font.families.len() > 0 { + let families = if span.font.families.len() == 1 { + span.font.families[0].clone() + } else { + span.font + .families + .iter() + .map(|family| { + if ctx.opt.writer_opts.use_single_quote { + format!("\"{}\"", family) + } else { + format!("'{}'", family) + } + }) + .collect::>() + .join(", ") + }; + xml.write_svg_attribute(AId::FontFamily, &families); + } + + match span.font.style { + FontStyle::Normal => {} + FontStyle::Italic => xml.write_svg_attribute(AId::FontStyle, "italic"), + FontStyle::Oblique => xml.write_svg_attribute(AId::FontStyle, "oblique"), + } + + if span.font.weight != 400 { + xml.write_svg_attribute(AId::FontWeight, &span.font.weight); + } + + if span.font.stretch != FontStretch::Normal { + let name = match span.font.stretch { + FontStretch::Condensed => "condensed", + FontStretch::ExtraCondensed => "extra-condensed", + FontStretch::UltraCondensed => "ultra-condensed", + FontStretch::SemiCondensed => "semi-condensed", + FontStretch::Expanded => "expanded", + FontStretch::SemiExpanded => "semi-expanded", + FontStretch::ExtraExpanded => "extra-expanded", + FontStretch::UltraExpanded => "ultra-expanded", + FontStretch::Normal => unreachable!(), + }; + xml.write_svg_attribute(AId::FontStretch, name); + } + + xml.write_svg_attribute(AId::FontSize, &span.font_size); + + match span.visibility { + Visibility::Visible => {} + Visibility::Hidden => xml.write_svg_attribute(AId::Visibility, "hidden"), + Visibility::Collapse => xml.write_svg_attribute(AId::Visibility, "collapse"), + } + + if span.letter_spacing != 0.0 { + xml.write_svg_attribute(AId::LetterSpacing, &span.letter_spacing); + } + + if span.word_spacing != 0.0 { + xml.write_svg_attribute(AId::WordSpacing, &span.word_spacing); + } + + if let Some(text_length) = span.text_length { + xml.write_svg_attribute(AId::TextLength, &text_length); + } + + if span.length_adjust == LengthAdjust::SpacingAndGlyphs { + xml.write_svg_attribute(AId::LengthAdjust, "spacingAndGlyphs"); + } + + if span.small_caps { + xml.write_svg_attribute(AId::FontVariant, "small-caps"); + } + + if span.paint_order == PaintOrder::StrokeAndFill { + xml.write_svg_attribute(AId::PaintOrder, "stroke fill"); + } + + if !span.apply_kerning { + xml.write_attribute("style", "font-kerning:none") + } + + if span.dominant_baseline != DominantBaseline::Auto { + let name = match span.dominant_baseline { + DominantBaseline::UseScript => "use-script", + DominantBaseline::NoChange => "no-change", + DominantBaseline::ResetSize => "reset-size", + DominantBaseline::TextBeforeEdge => "text-before-edge", + DominantBaseline::Middle => "middle", + DominantBaseline::Central => "central", + DominantBaseline::TextAfterEdge => "text-after-edge", + DominantBaseline::Ideographic => "ideographic", + DominantBaseline::Alphabetic => "alphabetic", + DominantBaseline::Hanging => "hanging", + DominantBaseline::Mathematical => "mathematical", + DominantBaseline::Auto => unreachable!(), + }; + xml.write_svg_attribute(AId::DominantBaseline, name); + } + + if span.alignment_baseline != AlignmentBaseline::Auto { + let name = match span.alignment_baseline { + AlignmentBaseline::Baseline => "baseline", + AlignmentBaseline::BeforeEdge => "before-edge", + AlignmentBaseline::TextBeforeEdge => "text-before-edge", + AlignmentBaseline::Middle => "middle", + AlignmentBaseline::Central => "central", + AlignmentBaseline::AfterEdge => "after-edge", + AlignmentBaseline::TextAfterEdge => "text-after-edge", + AlignmentBaseline::Ideographic => "ideographic", + AlignmentBaseline::Alphabetic => "alphabetic", + AlignmentBaseline::Hanging => "hanging", + AlignmentBaseline::Mathematical => "mathematical", + AlignmentBaseline::Auto => unreachable!(), + }; + xml.write_svg_attribute(AId::AlignmentBaseline, name); + } + + write_fill(&span.fill, is_clip_path, ctx, xml); + write_stroke(&span.stroke, ctx, xml); + + for baseline_shift in &span.baseline_shift { + xml.start_svg_element(EId::Tspan); + match baseline_shift { + BaselineShift::Baseline => {} + BaselineShift::Number(num) => xml.write_svg_attribute(AId::BaselineShift, num), + BaselineShift::Subscript => xml.write_svg_attribute(AId::BaselineShift, "sub"), + BaselineShift::Superscript => xml.write_svg_attribute(AId::BaselineShift, "super"), + } + } + + let cur_text = &chunk.text[span.start..span.end]; + + xml.write_text(&cur_text.replace('&', "&")); + + // End for each tspan we needed to create for baseline_shift + for _ in &span.baseline_shift { + xml.end_element(); + } + + xml.end_element(); +} diff --git a/crates/usvg/tests/files/preserve-id-fe-image.svg b/crates/usvg/tests/files/preserve-id-fe-image.svg new file mode 100644 index 000000000..bcba824c5 --- /dev/null +++ b/crates/usvg/tests/files/preserve-id-fe-image.svg @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/crates/usvg/tests/files/preserve-text-in-clip-path.svg b/crates/usvg/tests/files/preserve-text-in-clip-path.svg new file mode 100644 index 000000000..ef7ee7a33 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-in-clip-path.svg @@ -0,0 +1,14 @@ + + + + + + + + abcdefghijklmnopqrstuvwxyz + + + + + diff --git a/crates/usvg/tests/files/preserve-text-in-mask.svg b/crates/usvg/tests/files/preserve-text-in-mask.svg new file mode 100644 index 000000000..33f6f31ab --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-in-mask.svg @@ -0,0 +1,12 @@ + + + + + + abcdefghijklmnopqrstuvwxyz + + + + + diff --git a/crates/usvg/tests/files/preserve-text-in-pattern-expected.svg b/crates/usvg/tests/files/preserve-text-in-pattern-expected.svg new file mode 100644 index 000000000..93478a3d7 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-in-pattern-expected.svg @@ -0,0 +1,8 @@ + + + + H + + + + diff --git a/crates/usvg/tests/files/preserve-text-in-pattern.svg b/crates/usvg/tests/files/preserve-text-in-pattern.svg new file mode 100644 index 000000000..54c0b2fa0 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-in-pattern.svg @@ -0,0 +1,7 @@ + + + H + + + diff --git a/crates/usvg/tests/files/preserve-text-multiple-font-families-expected.svg b/crates/usvg/tests/files/preserve-text-multiple-font-families-expected.svg new file mode 100644 index 000000000..9c053168f --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-multiple-font-families-expected.svg @@ -0,0 +1,4 @@ + + + Text + diff --git a/crates/usvg/tests/files/preserve-text-multiple-font-families.svg b/crates/usvg/tests/files/preserve-text-multiple-font-families.svg new file mode 100644 index 000000000..71fe961df --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-multiple-font-families.svg @@ -0,0 +1,4 @@ + + + Text + diff --git a/crates/usvg/tests/files/preserve-text-on-path-expected.svg b/crates/usvg/tests/files/preserve-text-on-path-expected.svg new file mode 100644 index 000000000..e8ae8c9b7 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-on-path-expected.svg @@ -0,0 +1,7 @@ + + + + + + abcdefghijklmnopqrstuvwxyz + diff --git a/crates/usvg/tests/files/preserve-text-on-path.svg b/crates/usvg/tests/files/preserve-text-on-path.svg new file mode 100644 index 000000000..5d4392ee5 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-on-path.svg @@ -0,0 +1,15 @@ + + + + + + + + + + abcdefghijklmnopqrstuvwxyz + + + diff --git a/crates/usvg/tests/files/preserve-text-simple-case-expected.svg b/crates/usvg/tests/files/preserve-text-simple-case-expected.svg new file mode 100644 index 000000000..7a7db01ae --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-simple-case-expected.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/crates/usvg/tests/files/preserve-text-simple-case.svg b/crates/usvg/tests/files/preserve-text-simple-case.svg new file mode 100644 index 000000000..573bf4224 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-simple-case.svg @@ -0,0 +1,4 @@ + + + Text + diff --git a/crates/usvg/tests/files/preserve-text-with-complex-text-decoration-expected.svg b/crates/usvg/tests/files/preserve-text-with-complex-text-decoration-expected.svg new file mode 100644 index 000000000..cf072a844 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-complex-text-decoration-expected.svg @@ -0,0 +1,4 @@ + + + Text + diff --git a/crates/usvg/tests/files/preserve-text-with-complex-text-decoration.svg b/crates/usvg/tests/files/preserve-text-with-complex-text-decoration.svg new file mode 100644 index 000000000..d1c8111fd --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-complex-text-decoration.svg @@ -0,0 +1,12 @@ + + + + + + + Text + + + + diff --git a/crates/usvg/tests/files/preserve-text-with-dx-and-dy-expected.svg b/crates/usvg/tests/files/preserve-text-with-dx-and-dy-expected.svg new file mode 100644 index 000000000..e67e8ae42 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-dx-and-dy-expected.svg @@ -0,0 +1,4 @@ + + + Text + diff --git a/crates/usvg/tests/files/preserve-text-with-dx-and-dy.svg b/crates/usvg/tests/files/preserve-text-with-dx-and-dy.svg new file mode 100644 index 000000000..4ce13d3e9 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-dx-and-dy.svg @@ -0,0 +1,6 @@ + + + Text + + diff --git a/crates/usvg/tests/files/preserve-text-with-nested-baseline-shift-expected.svg b/crates/usvg/tests/files/preserve-text-with-nested-baseline-shift-expected.svg new file mode 100644 index 000000000..9b0c8fe2c --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-nested-baseline-shift-expected.svg @@ -0,0 +1,4 @@ + + + A B C D E F + diff --git a/crates/usvg/tests/files/preserve-text-with-nested-baseline-shift.svg b/crates/usvg/tests/files/preserve-text-with-nested-baseline-shift.svg new file mode 100644 index 000000000..a8b5aefc8 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-nested-baseline-shift.svg @@ -0,0 +1,25 @@ + + + + + + + A + + B + + C + + D + + E + + F + + + + + + + diff --git a/crates/usvg/tests/files/preserve-text-with-rotate-expected.svg b/crates/usvg/tests/files/preserve-text-with-rotate-expected.svg new file mode 100644 index 000000000..1239df9e1 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-rotate-expected.svg @@ -0,0 +1,4 @@ + + + Some long Text + diff --git a/crates/usvg/tests/files/preserve-text-with-rotate.svg b/crates/usvg/tests/files/preserve-text-with-rotate.svg new file mode 100644 index 000000000..11eaa4940 --- /dev/null +++ b/crates/usvg/tests/files/preserve-text-with-rotate.svg @@ -0,0 +1,7 @@ + + + Some long + Text + + diff --git a/crates/usvg/tests/write.rs b/crates/usvg/tests/write.rs index 5e6dacdc3..73c02b799 100644 --- a/crates/usvg/tests/write.rs +++ b/crates/usvg/tests/write.rs @@ -17,21 +17,27 @@ static GLOBAL_FONTDB: Lazy> }); fn resave(name: &str) { - resave_impl(name, None); + resave_impl(name, None, false); +} + +fn resave_with_text(name: &str) { + resave_impl(name, None, true); } fn resave_with_prefix(name: &str, id_prefix: &str) { - resave_impl(name, Some(id_prefix.to_string())); + resave_impl(name, Some(id_prefix.to_string()), false); } -fn resave_impl(name: &str, id_prefix: Option) { +fn resave_impl(name: &str, id_prefix: Option, preserve_text: bool) { let input_svg = std::fs::read_to_string(format!("tests/files/{}.svg", name)).unwrap(); let tree = { let opt = usvg_parser::Options::default(); let mut tree = usvg_tree::Tree::from_str(&input_svg, &opt).unwrap(); - let fontdb = GLOBAL_FONTDB.lock().unwrap(); - tree.convert_text(&fontdb); + if !preserve_text { + let fontdb = GLOBAL_FONTDB.lock().unwrap(); + tree.convert_text(&fontdb); + } tree }; let mut xml_opt = usvg::XmlOptions::default(); @@ -40,7 +46,11 @@ fn resave_impl(name: &str, id_prefix: Option) { xml_opt.transforms_precision = 4; let output_svg = tree.to_string(&xml_opt); - // std::fs::write(format!("tests/files/{}-expected.svg", name), output_svg).unwrap(); + // std::fs::write( + // format!("tests/files/{}-expected.svg", name), + // output_svg.clone(), + // ) + // .unwrap(); let expected_svg = std::fs::read_to_string(format!("tests/files/{}-expected.svg", name)).unwrap(); @@ -68,6 +78,11 @@ fn preserve_id_filter() { resave("preserve-id-filter"); } +// #[test] +// fn preserve_id_fe_image() { +// resave("preserve-id-fe-image"); +// } + #[test] fn generate_filter_id_function_v1() { resave("generate-id-filter-function-v1"); @@ -112,3 +127,53 @@ fn clip_path_with_complex_text() { fn text_with_generated_gradients() { resave("text-with-generated-gradients"); } + +#[test] +fn preserve_text_multiple_font_families() { + resave_with_text("preserve-text-multiple-font-families"); +} + +#[test] +fn preserve_text_on_path() { + resave_with_text("preserve-text-on-path"); +} + +// #[test] +// fn preserve_text_in_clip_path() { +// resave_with_text("preserve-text-in-clip-path"); +// } + +// #[test] +// fn preserve_text_in_mask() { +// resave_with_text("preserve-text-in-mask"); +// } + +#[test] +fn preserve_text_in_pattern() { + resave_with_text("preserve-text-in-pattern"); +} + +#[test] +fn preserve_text_simple_case() { + resave("preserve-text-simple-case"); +} + +#[test] +fn preserve_text_with_dx_and_dy() { + resave_with_text("preserve-text-with-dx-and-dy"); +} + +#[test] +fn preserve_text_with_rotate() { + resave_with_text("preserve-text-with-rotate"); +} + +#[test] +fn preserve_text_with_complex_text_decoration() { + resave_with_text("preserve-text-with-complex-text-decoration"); +} + +#[test] +fn preserve_text_with_nested_baseline_shift() { + resave_with_text("preserve-text-with-nested-baseline-shift"); +}