svgo
svgo copied to clipboard
feat(applyTransformsShapes): new plugin
Right now transforms can only be applied to paths. This plugin applies them to circle, ellipse, rect, and image. These transforms often come from graphical editors. Has 99% line test coverage Closes #989 Closes #594
Results
While this plugin doesn't help with any of the common test cases, it does help with some other real situations.
- https://accelerator.github.com/images/invertocat.svg -82b compared to current SVGO
-
Lawnicons, a collection of SVG icons
While I didn't do a full size comparison, I tested it on a number of SVGs, and it successfully applied the transformations. For example, this SVG
was converted to<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"><circle cx="5" cy="5" r="5" stroke-width="10" transform="matrix(1 0 0 -1 65 81)"/><circle cx="96" cy="96" r="74"/><path d="M133 77a13.998 13.998 0 0 0-28 0m-34 46s8 15 24 15 24-15 24-15m14-46c0 11-14 24-14 24s-14-14-14-24"/></svg>
(-33b)<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"><circle cx="70" cy="76" r="5" stroke-width="10"/><circle cx="96" cy="96" r="74"/><path d="M133 77a13.998 13.998 0 0 0-28 0m-34 46s8 15 24 15 24-15 24-15m14-46c0 11-14 24-14 24s-14-14-14-24"/></svg>
Work fine for me, but why APPLICABLE_SHAPES
not includes text
?
I haven't considered applying transforms to text. Would you mind providing a couple examples of places where I can apply the transform and a couple places where I can't?
<svg xmlns="http://www.w3.org/2000/svg">
<text y="7" transform="translate(1270 167)">abcdefg</text>
<!-- ↓ could be -->
<text x="1270" y="174">abcdefg</text>
</svg>
I know that there's a <tspan>
. Will I still not have to worry about it as long as I stick to the outer <text>
?
I don't know the details of <tspan>
, I have some real-world test cases where files do not include <tspan>
, they are working without bugs, so maybe there's no risk.
Maybe adding options.allowSelector
could ensure users know what they doing. Then you don't have to worry about that?
I know that there's a
<tspan>
. Will I still not have to worry about it as long as I stick to the outer<text>
?
This comes up a lot in Inkscape - it includes a bunch of redundant and seemingly useless attributes/elements. If you create 2 text elements and resize the canvas, you get something like:
<svg xmlns="http://www.w3.org/2000/svg" width="32.496" height="51.922">
<text xml:space="preserve" x="50" y="70" style="font-size:8px;font-family:Arial;fill:none;stroke:#000" transform="translate(-49.688 -63.672)">
<tspan x="50" y="70">Text One</tspan>
</text>
<text xml:space="preserve" x="50" y="115" style="font-size:8px;font-family:Arial;fill:none;stroke:#000" transform="translate(-49.688 -63.672)">
<tspan x="50" y="115">Text Two</tspan>
</text>
</svg>
The x/y on the text don't seem to do anything, it's only the tspan coordinates that matter.
It looks complicated, Maybe you guys can merge this PR first, leave The
is this feature implemented? I see that the svg transformations get flattened when I try to use SVGR
, but i'm not sure if this is is a SVGR
issue or implementation from SVGO
I noticed this file in the repo, is this the plugin? https://github.com/svg/svgo/blob/main/plugins/applyTransforms.js
SVGO already flattens transforms for paths. This PR adds a new plugin that does it for shapes, like circles or images. SVGO won't have it until this is merged.
@SethFalco any blockers on this?
No blockers, I was just prioritizing issues that fix regression tests. Sorry to keep you waiting though, in fact, I'll review this ~~tomorrow evening~~ soon for you.
So looks like the mismatches are because gradients can get messed up by changing the orientation. This would be solved by checking for gradients, but I don't want to duplicate code, and now I'm seeing that we have many places where we might want to check for references in SVGO:
- Do any of this group's attributes that can reference something reference a URL? (moveGroupAttrsToElems, applyTransforms, future applyTransformsShapes)
- In this element's computed styles, do we use anything that is sensitive to bounding boxes, namely gradient fills, filters, or strokes? (mergePaths, future removing trailing
M
)
I would normally want to simply extract it all out to a function, but the behavior is different for different cases (in fact, we don't run applyTransforms
for anything with style
right now). And arguably, we should update the existing cases so that they can cope with style
by using computedStyles
instead of just attributes. What would you suggest here?
check for references
We do already have the #findReferences
which can be used for checking for href
and url()
references. (Check if the resulting array is empty.)
There's also #includesUrlReference
to check the value of a specific attribute.
If we don't need to check for all references props, happy for us to pass a third parameter with an array of referencesProps
that defaults to the one defined in _collections.js
.
but the behavior is different for different cases
Just checking, when you note that the behavior is different for different cases, are you referring to in theory we need to handle this differently in certain plugins, or just that how we approach this now is inconsistent?
Immediately, as you say, it'd be good to extract some of the logic (or at least the common parts of it) into a function like isBoundingBoxSensitive
that accepts the node and computed styles.
And arguably, we should update the existing cases so that they can cope with style by using computedStyles instead of just attributes.
Agreed, but best for a separate issue/PR. Once this is finalized, we can refactor applyTransforms
in general to use the new helper methods too.
Just checking, when you note that the behavior is different for different cases, are you referring to in theory we need to handle this differently in certain plugins, or just that how we approach this now is inconsistent?
After a bit of thinking, I've concluded that these should be the same for the most part.
If we were to have a basis, it would be mergePaths
. The way we currently do it in applyTransforms
is oversensitive (color-profile
doesn't affect things) and doesn't check all the cases (doesn't notice <style>
, doesn't notice inline clip-path
).
Here's how we handle it now in mergePaths
:
computedStyle['marker-start'] ||
computedStyle['marker-mid'] ||
computedStyle['marker-end'] ||
computedStyle['clip-path'] ||
computedStyle['mask'] ||
computedStyle['mask-image'] ||
['fill', 'filter', 'stroke'].some((attName) =>
elementHasUrl(computedStyle, attName),
)
I believe in almost all of the cases where this is tripped, applyTransforms shouldn't run, and if it isn't tripped a path is likely safe. The only possible change we might make when using it in applyTransforms
would be to allow markers as long as there is no scaling, but it's arguable that the added complexity isn't worth it when markers are rare and this combination would be even rarer.
But more deliberation can be saved for later. Which way would you recommend moving forward with this PR?
- Make the function extracted from
mergePaths
, use it here, and apply more refactoring in a separate PR - Make the function extracted from
mergePaths
, use it here and inmergePaths
, and apply more refactoring in a separate PR - Duplicate the
applyTransforms
logic and apply more refactoring in a separate PR - Duplicate the
mergePaths
logic and apply more refactoring in a separate PR
For the most part, just whatever you're comfortable with, tbh. 👍🏽
In terms of reviewing, I think the easiest would be:
Make the function extracted from mergePaths, use it here and in mergePaths, and apply more refactoring in a separate PR
Makes sense to refactor mergePaths
as part of this PR since the helper function is ultimately pulling out code from there. Then any changes/refactors to applyTransforms
itself can be handled in another PR.