Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer parts of style resolution and layouting #841

Closed
Caellian opened this issue Nov 3, 2024 · 6 comments
Closed

Defer parts of style resolution and layouting #841

Caellian opened this issue Nov 3, 2024 · 6 comments

Comments

@Caellian
Copy link

Caellian commented Nov 3, 2024

Resvg currently draws SVGs without any context. While rendering SVGs in isolation works for one-shot rasterization of standalone files, it doesn't allow for integration with documents and UI.

I'm suggesting the resvg::render function signature changes into something like:

pub fn render(
    tree: &usvg::Tree,
    context: resvg::Context,
    pixmap: &mut tiny_skia::PixmapMut,
)

where resvg::Context holds the Transform as it previously did but also top-level defaults:

  • text properties
    • alignment-baseline
    • baseline-shift
    • color a.k.a. currentColor
    • direction
    • dominant-baseline
    • font-family
    • font-size
    • font-size-adjust
    • font-stretch
    • font-style
    • font-variant
    • font-weight
    • glyph-orientation-horizontal
    • glyph-orientation-vertical
    • letter-spacing
    • text-anchor
    • text-decoration
    • unicode-bidi
    • word-spacing
    • writing-mode
  • fill & stroke
    • color-interpolation
    • color-interpolation-filters
    • color-rendering
    • fill
    • fill-opacity
    • fill-rule
    • stroke
    • stroke-dasharray
    • stroke-dashoffset
    • stroke-linecap
    • stroke-linejoin
    • stroke-miterlimit
    • stroke-opacity
    • stroke-width
    • marker-end
    • marker-mid
    • marker-start
    • opacity
  • convenience
    • filter - can be applied top-level with tiny_skia
  • filters
    • flood-color
    • flood-opacity
    • image-rendering
    • lighting-color
    • mask
    • overflow
  • render options (currently static in usvg::Options)
    • shape-rendering
    • text-rendering
    • vector-effect
  • transform (set in tiny_skia; currently passed in directly)
    • also transform-origin

This is a cleaned up list. A lot of these styling attributes aren't inherited by default, but elements can explicitly set inherit for them in which case they would be.

Some of these are currently set in usvg::Options, but usvg shouldn't resolve computed values unless it's an obvious optimization (e.g. parent has explicit value and child has inherit; outer ctx doesn't matter). Output of usvg parsing an SVG document should really be a function which produces a different usvg::Tree based on the values above or some new usvg::ResolvedTree type.

usvg handles text-to-path conversion so a lot of text related options should really be passed into usvg::text::layout::layout_text and text layouting should be deferred too.

@Caellian Caellian changed the title Defer rasterization and layouting Defer parts of style resolution and layouting Nov 3, 2024
@LaurenzV
Copy link
Contributor

LaurenzV commented Nov 3, 2024

Don't take my word for it, but the goal of usvg is to turn the SVG into a representation that is as simple as possible, so that it's easy to build a renderer on top of it. Not resolving relative units during the creation of the tree would complicate the tree a lot (How would you resolve objectBoundingBox for example if you don't know the actual size in px?), which kind of goes against the philosophy of usvg. I guess that point is a bit moot if it were implemented, like you mentioned, as an additional intermediate represetnation (Tree vs ResolvedTree), but that would also come with a bit of overhead.

I don't really get the problem though, why can't you just create a new usvg tree and populate it with the corresponding values in Options (leaving aside the fact that some of the options you mentioned aren't configurable yet)? Creating a isn't that expensive, the expensive part is usually rendering it.

@Caellian
Copy link
Author

Caellian commented Nov 3, 2024

Don't take my word for it, but the goal of usvg is to turn the SVG into a representation that is as simple as possible, so that it's easy to build a renderer on top of it.

Externally, but passing Options to usvg in a separate call from parsing is an improvement.

How would you resolve objectBoundingBox for example if you don't know the actual size in px?

It depends on where I'm rendering it. If it's a dropdown menu icon then it's smaller, but if I draw the same icon in toolbar then it will be larger. Currently (hypothetically), I'd have to multiply the bounding box with the transform I'm passing to tiny_skia which is less convenient.

Creating a tree isn't that expensive, the expensive part is usually rendering it.

If I want to animate SVG colors I need to parse XML every frame - that part makes no sense to me. XML parsing is an added cost, and it's much slower than simply rendering after configuration (clone). This becomes much more apparent if you want to animate 10+ SVGs at once.

The properties I listed are just what SVGs pick up from surrounding context in browsers. The same mechanism allows a bunch of different animations via #821 or <animate> (for which it's required).

I can work around this limitation by generating a lot of animations at build time (for icons), but deferred evaluation is one of the big pieces that allow the format to be fully interactive.

@RazrFalcon
Copy link
Collaborator

In short, this is not possible and not planned. You need a separate library for that.

Also, if you want to change SVG appearance - the only correct way to do so is by CSS style injection, which we already support. Which obviously requires re-parsing.

I think you fundamentally misunderstand how SVG works, because I do not see how resvg::Context can meaningfully change the document's style.

If I want to animate SVG colors I need to parse XML every frame

Rendering SVG in real-time is a fruitless endeavor. It's not possible. SVG isn't designed for fast rendering.

@RazrFalcon RazrFalcon closed this as not planned Won't fix, can't repro, duplicate, stale Nov 4, 2024
@Caellian
Copy link
Author

Caellian commented Nov 4, 2024

I think you fundamentally misunderstand how SVG works, because I do not see how resvg::Context can meaningfully change the document's style.

I think you skimmed through this too fast because usvg maps "implementor defined" values to constants. I'll assume you understand those values are something browsers propagate from parent HTML document for inlined SVGs, and that they affect the final appearance of the document.

It's a meaningful difference if I draw a black icon on a dark gray bg, or a red one.

Rendering SVG in real-time is a fruitless endeavor. It's not possible. SVG isn't designed for fast rendering.

Same goes for OBJ - which is why engines apply skeletal animations to an in-memory format.

The problem is internal representation which destroys structural information used by browsers to animate SVGs, not some inherent limitation of the format itself. You could parse Lottie (for some given frame) into the same structure used by usvg and you'd have the same problem with having to re-parse the JSON to draw another frame.

In other words, the usvg library wasn't designed with this goal in mind, and you don't intend to do such a large redesign.

@waywardmonkeys
Copy link
Collaborator

@Caellian While usvg and friends don't currently support this sort of use case, it is something that we talk about doing in some other parts of the ecosystem, like with Vello. We don't have a good answer yet and I'm not sure where to point you for discussion other than our Zulip: http://xi.zulipchat.com

@RazrFalcon
Copy link
Collaborator

I'm well aware how a browser handles user styles. And if you look at Chrome's inspector, you will notice that you basically define a user CSS, which is exactly what style injection does in resvg.

Not only you need an ability do define which elements must be affected by a certain style override, but in many cases a style change would cause the whole tree recalculation (like for stroke width change or text font change). So you will still end up re-parsing/recalculating everything.

And no, resvg isn't designed to modify already parsed tree. Unlike browsers, the original SVG tree isn't preserved and you would need a brand new library to achieve that. Which is exactly what I wrote already.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants