RazrFalcon / resvg

An SVG rendering library.
Mozilla Public License 2.0
2.74k stars 220 forks source link

Failed to rendering a text in nested SVG #740

Open yanorei32 opened 5 months ago

yanorei32 commented 5 months ago

I wrote are two SVGs and I nested those and I got a blank image unexpectedly.

Is this a bug...? (Sorry. I can't classify that problem as bug, unimplemented feature, undefined behaviour or something else, because I'm not an expert of SVG specification.)

Version: 0.41.0 OS: Windows 11 23H2 22631.3447 (x86_64) and Arch Linux.

I got a same results in --use-fonts-dir option.

Text containing SVG (child.svg)

Works expectedly if this file only. This file just needs to contain a text element. But I wrote some elements and attributes for visiblity.

Note: I choose a Noto Sans but this problem is occured too in any system fonts.

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px">
    <rect x="25" y="25" width="25" height="25" fill="red" />
    <text
        x="50" y="50" dominant-baseline="middle" text-anchor="middle"
        font-size="25" font-family="Noto Sans"
        fill="black"
    >
        Hello
    </text>
</svg>

Rendered Image (child.svg):

resvg child.svg child.png

child

Parent SVG (parent.svg)

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px">
    <image x="0" y="0" width="100" height="100" href="child.svg" />
</svg>

Rendered Image (parent.svg):

resvg parent.svg parent.png

Actually behaviour (parent.svg)

parent

and I got warning:

Warning (in usvg::text::layout:1115): No match for '"Noto Sans"' font-family.

Expected behaviour (parent.svg)

Completly same as child.svg in this case.

RazrFalcon commented 5 months ago

Yes, we're loading fonts only when the main SVG has text nodes. We do not check referenced SVGs. Will fix.

For now, simply add a single empty text element to the main SVG and it will fix the issue.

yanorei32 commented 5 months ago

Amazing! Thank you for very fast reply! I confirmed that solution is works and that completely avoid this problem. I hope this problem will be fixed in future.

LaurenzV commented 5 months ago

Yes, we're loading fonts only when the main SVG has text nodes. We do not check referenced SVGs.

Didn't I fix this in #675? Apparently I didn't...

LaurenzV commented 5 months ago

Oh... It's because resvg has it's own logic for checking if there are text nodes... So it works in tests but not in the CLI:

https://github.com/RazrFalcon/resvg/blob/ec1627fb73ecf4f3d91f5b2283cf8b89ec6c83c3/crates/resvg/src/main.rs#L84-L86

That's gonna be tricky to change though if we want to do it inside the resvg CLI...

Maybe it's best to just always initialize the fontdb?

RazrFalcon commented 5 months ago

Maybe it's best to just always initialize the fontdb?

That's pretty slow. Even on a macbook with a hot cache it's like 10-20ms. It could easily take 100-500ms on an average hardware.

The issue here is that before, text flattening was done after parsing, not during. So before we were checking for usvg::Text nodes, not text XML elements.

This is partially related to #710 , as we have to add a callback to text flattening. I haven't thought about it much yet.

The only "solution" so far is to have some public, intermediate tree representation. One between usvg::parser::svgtree and usvg::Tree. This way we wouldn't have to do everything in one go, which has it's own limitations (paint servers resolving) and complicates code quite a bit (text flattening). But it would have a small performance hit. Basically, instead of modifying usvg::Tree and passing it to resvg, we would allow modifying of this new tree, which then be converted into usvg::Tree. And we already do roxmltree -> svgtree -> usvg::Tree ...

LaurenzV commented 5 months ago

That will be a lot of trees then. :p But I've also had thoughts about whether there should be some additional intermediate representation...

RazrFalcon commented 5 months ago

While intermediate representations do reduce performance a bit, they do simplify the code and logic quite a lot. The hard part is to find the right boundaries for each representation.

My goal is to keep usvg::Tree read-only with all of the SVG complexity removed and all metadata calculated (bboxes, etc). For the caller, SVG should look like a simple list of layers. And as you already know, it's ridiculously hard to do.