Skip to content

Commit

Permalink
Add support for indirect text-decoration resolving.
Browse files Browse the repository at this point in the history
Closes #410 Closes #674
  • Loading branch information
LaurenzV authored Nov 1, 2023
1 parent 2f599e9 commit 23d689d
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 69 deletions.
14 changes: 2 additions & 12 deletions crates/resvg/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,11 @@ pub fn convert(upath: &usvg::Path, children: &mut Vec<Node>) -> Option<BBoxes> {
let anti_alias = upath.rendering_mode.use_shape_antialiasing();

let fill_path = upath.fill.as_ref().and_then(|ufill| {
convert_fill_path(
ufill,
upath.data.clone(),
upath.text_bbox,
anti_alias,
)
convert_fill_path(ufill, upath.data.clone(), upath.text_bbox, anti_alias)
});

let stroke_path = upath.stroke.as_ref().and_then(|ustroke| {
convert_stroke_path(
ustroke,
upath.data.clone(),
upath.text_bbox,
anti_alias,
)
convert_stroke_path(ustroke, upath.data.clone(), upath.text_bbox, anti_alias)
});

if fill_path.is_none() && stroke_path.is_none() {
Expand Down
3 changes: 1 addition & 2 deletions crates/resvg/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,7 @@ fn convert_group(
None => return convert_empty_group(ugroup, children),
};

let (filters, filter_bbox) =
crate::filter::convert(&ugroup.filters, bboxes.object.to_rect());
let (filters, filter_bbox) = crate::filter::convert(&ugroup.filters, bboxes.object.to_rect());

// TODO: figure out a nicer solution
// Ignore groups with filters but invalid filter bboxes.
Expand Down
2 changes: 2 additions & 0 deletions crates/resvg/tests/integration/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,7 @@ use crate::render;
#[test] fn structure_image_embedded_jpeg_as_image_jpg() { assert_eq!(render("tests/structure/image/embedded-jpeg-as-image-jpg"), 0); }
#[test] fn structure_image_embedded_jpeg_without_mime() { assert_eq!(render("tests/structure/image/embedded-jpeg-without-mime"), 0); }
#[test] fn structure_image_embedded_png() { assert_eq!(render("tests/structure/image/embedded-png"), 0); }
#[test] fn structure_image_embedded_svg_with_text() { assert_eq!(render("tests/structure/image/embedded-svg-with-text"), 0); }
#[test] fn structure_image_embedded_svg_without_mime() { assert_eq!(render("tests/structure/image/embedded-svg-without-mime"), 0); }
#[test] fn structure_image_embedded_svg() { assert_eq!(render("tests/structure/image/embedded-svg"), 0); }
#[test] fn structure_image_embedded_svgz() { assert_eq!(render("tests/structure/image/embedded-svgz"), 0); }
Expand Down Expand Up @@ -1471,6 +1472,7 @@ use crate::render;
#[test] fn text_text_decoration_all_types_inline_no_spaces() { assert_eq!(render("tests/text/text-decoration/all-types-inline-no-spaces"), 0); }
#[test] fn text_text_decoration_all_types_inline() { assert_eq!(render("tests/text/text-decoration/all-types-inline"), 0); }
#[test] fn text_text_decoration_all_types_nested() { assert_eq!(render("tests/text/text-decoration/all-types-nested"), 0); }
#[test] fn text_text_decoration_indirect_with_multiple_colors() { assert_eq!(render("tests/text/text-decoration/indirect-with-multiple-colors"), 0); }
#[test] fn text_text_decoration_indirect() { assert_eq!(render("tests/text/text-decoration/indirect"), 0); }
#[test] fn text_text_decoration_line_through() { assert_eq!(render("tests/text/text-decoration/line-through"), 0); }
#[test] fn text_text_decoration_outside_the_text_element() { assert_eq!(render("tests/text/text-decoration/outside-the-text-element"), 0); }
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified crates/resvg/tests/tests/text/text-decoration/indirect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 34 additions & 55 deletions crates/usvg-parser/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ fn collect_text_chunks_impl(
font_size,
small_caps: parent.find_attribute::<&str>(AId::FontVariant) == Some("small-caps"),
apply_kerning,
decoration: resolve_decoration(text_node, parent, state, cache),
decoration: resolve_decoration(parent, state, cache),
visibility: parent.find_attribute(AId::Visibility).unwrap_or_default(),
dominant_baseline,
alignment_baseline: parent
Expand Down Expand Up @@ -611,73 +611,52 @@ fn resolve_rotate_list(text_node: SvgNode) -> Vec<f32> {
}

/// Resolves node's `text-decoration` property.
///
/// `text` and `tspan` can point to the same node.
fn resolve_decoration(
text_node: SvgNode,
tspan: SvgNode,
state: &converter::State,
cache: &mut converter::Cache,
) -> TextDecoration {
// TODO: explain the algorithm

let text_dec = conv_text_decoration(text_node);
let tspan_dec = conv_text_decoration2(tspan);

let mut gen_style = |in_tspan: bool, in_text: bool| {
let n = if in_tspan {
tspan
} else if in_text {
text_node
// Checks if a decoration is present in a single node.
fn find_decoration(node: SvgNode, value: &str) -> bool {
if let Some(str_value) = node.attribute::<&str>(AId::TextDecoration) {
str_value.split(' ').any(|v| v == value)
} else {
false
}
}

// The algorithm is as follows: First, we check whether the given text decoration appears in ANY
// ancestor, i.e. it can also appear in ancestors outside of the <text> element. If the text
// decoration is declared somewhere, it means that this tspan will have it. However, we still
// need to find the corresponding fill/stroke for it. To do this, we iterate through all
// ancestors (i.e. tspans) until we find the text decoration declared. If not, we will
// stop at latest at the text node, and use its fill/stroke.
let mut gen_style = |text_decoration: &str| {
if !tspan.ancestors().any(|n| find_decoration(n, text_decoration)) {
return None;
};
}

let mut fill_node = None;
let mut stroke_node = None;

for node in tspan.ancestors() {
if find_decoration(node, text_decoration) || node.tag_name() == Some(EId::Text) {
fill_node = fill_node.map_or(Some(node), |n| Some(n));
stroke_node = stroke_node.map_or(Some(node), |n| Some(n));
break;
}
}

Some(TextDecorationStyle {
fill: style::resolve_fill(n, true, state, cache),
stroke: style::resolve_stroke(n, true, state, cache),
fill: fill_node.and_then(|node| style::resolve_fill(node, true, state, cache)),
stroke: stroke_node.and_then(|node| style::resolve_stroke(node, true, state, cache)),
})
};

TextDecoration {
underline: gen_style(tspan_dec.has_underline, text_dec.has_underline),
overline: gen_style(tspan_dec.has_overline, text_dec.has_overline),
line_through: gen_style(tspan_dec.has_line_through, text_dec.has_line_through),
}
}

struct TextDecorationTypes {
has_underline: bool,
has_overline: bool,
has_line_through: bool,
}

/// Resolves the `text` node's `text-decoration` property.
fn conv_text_decoration(text_node: SvgNode) -> TextDecorationTypes {
fn find_decoration(node: SvgNode, value: &str) -> bool {
node.ancestors().any(|n| {
if let Some(str_value) = n.attribute::<&str>(AId::TextDecoration) {
str_value.split(' ').any(|v| v == value)
} else {
false
}
})
}

TextDecorationTypes {
has_underline: find_decoration(text_node, "underline"),
has_overline: find_decoration(text_node, "overline"),
has_line_through: find_decoration(text_node, "line-through"),
}
}

/// Resolves the default `text-decoration` property.
fn conv_text_decoration2(tspan: SvgNode) -> TextDecorationTypes {
let s = tspan.attribute(AId::TextDecoration);
TextDecorationTypes {
has_underline: s == Some("underline"),
has_overline: s == Some("overline"),
has_line_through: s == Some("line-through"),
underline: gen_style("underline"),
overline: gen_style("overline"),
line_through: gen_style("line-through"),
}
}

Expand Down

0 comments on commit 23d689d

Please sign in to comment.