svg / svgo

⚙️ Node.js tool for optimizing SVG files
https://svgo.dev/
MIT License
20.85k stars 1.39k forks source link

Please add the option to flatten transforms #624

Open Emasoft opened 7 years ago

Emasoft commented 7 years ago

"Flatten Transforms" option is available in SVG editors like Affinity Designer (~$40 / Mac) and it is very useful. It also improve performances of SVG rendering because coordinate transforms are precalculated.

ppg1q

Please add this option to SVGO.

GreLI commented 7 years ago

What does it do? If you mean applying transforms to paths then SVGO already does that when it possible.

Emasoft commented 7 years ago

It flattens the transforms of all elements (including elliptical arcs, gradients, text and tspan) concatenating the cumulative transforms of all parents since the root node and applying the resulting matrix to the leaf elements (rects are converted to polygons to allow that). When not possible to completely eliminate the transform (for edge cases like some filters on groups), the children element is left with a transform matrix that includes all transformations of the parents, so that the matrix multiplications required to get the the element local coordinates are minimized.

GreLI commented 7 years ago

Well, SVGO does something of that.

Emasoft commented 7 years ago

It does a very little of this, actually. Transforms in the output file are still too many compared to Affinity Designer exported svg file.

GreLI commented 7 years ago

It'd be more helpful if you provide such examples. Also, there was a bunch of bugs with incorrectly moved transforms, so one need to be careful with them.

steadicat commented 7 years ago

Here's some examples of what I think @Emasoft is talking about:

Input:

<svg>
    <g transform="translate(2.240000, 10.453333)">
        <rect fill="#000" x="210.56" y="94.359" width="22.811" height="33.2" rx="3.733"/>
        <rect fill="#FFF" x="210.56" y="94.359" width="22.811" height="33.2" rx="3.733"/>
    </g>
</svg>

Output:

<svg>
    <g transform="translate(2.24 10.453)">
        <rect x="210.56" y="94.359" width="22.811" height="33.2" rx="3.733"/>
        <rect fill="#FFF" x="210.56" y="94.359" width="22.811" height="33.2" rx="3.733"/>
    </g>
</svg>

Should be:

<svg>
    <rect x="212.80" y="104.812" width="22.811" height="33.2" rx="3.733"/>
    <rect fill="#FFF" x="212.80" y="104.812" width="22.811" height="33.2" rx="3.733"/>
</svg>

Input:

<svg>
    <g transform="translate(2.240000, 10.453333)">
        <rect fill="#000" x="210.56" y="94.359" width="22.811" height="33.2" rx="3.733"/>
    </g>
</svg>

Output:

<svg>
    <rect x="210.56" y="94.359" width="22.811" height="33.2" rx="3.733" transform="translate(2.24 10.453)"/>
</svg>

Should be:

<svg>
    <rect x="212.80" y="104.812" width="22.811" height="33.2" rx="3.733"/>
</svg>
Emasoft commented 7 years ago

Any update on this? This is a much needed feature because it allows to copy paste any xml node from an svg file to inline html without having to copy all parents nodes.

GreLI commented 7 years ago

I'm planning such an operation, but no updates yet.

mattvenn commented 7 years ago

+1

Emasoft commented 7 years ago

How long before this is implemented? This is a much needed feature. Too much time wasted in doing this manually every day.

GreLI commented 7 years ago

I'm afraid not so soon. It's trivial enough, but I have no time for this.

nashwaan commented 7 years ago

@GreLI I hope you get time to get this important feature implemented very sooooooooooooon. 😊

macrozone commented 7 years ago

would also be useful to convert svg graphics to simple paths, which can easily be used in react-native-svg

irisjae commented 7 years ago

seems theres an existing package https://github.com/stadline/svg-flatten to do it, maybe someone could transform this into an svgo plugin? btw the package does not seem to handle filters, maybe we should add a clause in the recursion to flatten the transform onto the filter as well. besides filters, are there any other cases where we might need to recurse transform flattening beyond a nodes children? masks maybe?

steadicat commented 7 years ago

I wrote code to flatten transforms for my own project, in case anyone wants to port it over. It doesn't quite support all possible transforms on all shapes yet, but it's a start.

lemnis commented 6 years ago

Another day, another starting point, my fork https://github.com/lemnis/svgo has support of removing translate of a select group of properties. It also has the tests of above mentioned code written by @steadicat, currently my code fails most of the tests and I am not planning to improve my code in the near future.

nashwaan commented 6 years ago

Almost a year passed and we are still waiting. 😞

lemnis commented 6 years ago

@nashwaan Be grateful that other people are willing to use their precious time to create and extend code that everybody can use. Otherwise, you have 2 options, pay lots of money more for all software you use or create the code yourself.

Or maybe the last solution is the best for you, keep waiting patiently. 😉

nashwaan commented 6 years ago

@lemnis Thank you for educating me and telling me I should be grateful to the open source community.

saivan commented 6 years ago

I've had the same issue, it would be really nice to be able to flatten everything.

cheshrkat commented 6 years ago

+1 this would be awesome.

morewry commented 6 years ago

I have this issue too, and I'd swear it used to work better. But currently SVGO doesn't "flatten" or precalculate even very simple transforms. I have examples where there's a transform translate of 1,1 on a group and even that isn't recalculated.

One very common source of unnecessary transformations stems from the behavior of design software for working with SVGs. They tend to be very literal. If you click the "path" tool, you get a path. If you click the "circle" tool, you get a circle. And if you draw an icon with some shapes, group them, then move it down with the arrow key 4 times and to the right 8 times, you'll end up with a group that has a transform translate of 8, 4. (This is true of every single editor I've used: Illustrator, Sketch, Inkscape, etc--though some of them offer ways to flatten the transforms. Sketch, however, [which we use] doesn't.)

As the OP mentions, this is just noise--there's no reason whatsoever to preserve a transformation of that kind and so files that preserve this are missing an obvious if relatively minor optimization.

But in my situation this leads to real issues. I have a use case where we need to convert our icons to Android's Vector Drawable format. We work with SVG as the "origin" format and convert to Vector Drawable after optimizing with SVGO. Vector Drawables are a little touchy, it seems (we had issues if we optimized away leading 0s), and there aren't many tools available to automate this conversion.

And, it turns out, these preserved translations also cause issues. They're unsupported or just ignored in the conversion process and result in the icon being mis-aligned and cut off relative to the viewbox.

Example SVGs, before and after
**Before** ```html icon/UI 24px/Vision Small (eye) Created with Sketch. ``` **After** ``` ``` ----------- **Before** ```html icon/Display 32px/Vision (eye) Created with Sketch. ``` **After** ```html ``` ----------- **Before** ```html icon/UI 24px/Pin (map marker, location) Created with Sketch. ``` **After** ```html ``` ----------- **Before** ```html icon/Display 32px/Prescription Generic (pharmacy, medicine, bottle, Rx) Created with Sketch. ``` **After** ```html ``` ----------- **Before** ```html icon/UI 24px/Search (magnifying glass, find, discover) Created with Sketch. ``` **After** ```html ```

SVGO config ```js const hash = require('string-hash'); module.exports = { full: true, multipass: true, precision: 3, // order of plugins is important to correct functionality plugins: [ { removeDoctype: true }, { removeXMLProcInst: true }, { removeComments: true }, { removeMetadata: true }, { removeXMLNS: false }, { removeEditorsNSData: true }, { cleanupAttrs: true }, { inlineStyles: true }, { minifyStyles: true }, { convertStyleToAttrs: true }, { cleanupIDs: true }, { prefixIds: { prefix: function(element, filePath) { const fileNameId = hash( filePath .split('/') .slice(-2) .join('/') ); return `i${fileNameId}`; } } }, { removeRasterImages: true }, { removeUselessDefs: true }, { cleanupNumericValues: true }, { cleanupListOfValues: true }, { convertColors: { currentColor: true } }, { removeUnknownsAndDefaults: true }, { removeNonInheritableGroupAttrs: true }, { removeUselessStrokeAndFill: true }, { removeViewBox: false }, { cleanupEnableBackground: true }, { removeHiddenElems: true }, { removeEmptyText: true }, { convertShapeToPath: true }, { moveElemsAttrsToGroup: true }, { moveGroupAttrsToElems: true }, { collapseGroups: true }, { convertPathData: true }, { convertTransform: true }, { removeEmptyAttrs: true }, { removeEmptyContainers: true }, { mergePaths: true }, { removeUnusedNS: true }, { sortAttrs: true }, { removeTitle: true }, { removeDesc: true }, { removeDimensions: false }, { removeAttrs: false }, { removeElementsByAttr: false }, { addClassesToSVGElement: false }, { removeStyleElement: true }, { removeScriptElement: true }, { addAttributesToSVGElement: { attributes: [{ display: 'block' }, { 'pointer-events': 'none' }] } } ], js2svg: { pretty: false, indent: '' } }; ```

Could be this works better with some configurations than others, but I'm sure we're all aware that SVGO can be a bit challenging to configure, so if anyone has some tips on that, please share, it could help in some cases. (One configuration possibility that I tried was not doubling up on `moveElemsAttrsToGroup` and `moveGroupAttrsToElems`, but enabling either option alone didn't help flatten any of these. I don't 100% guarantee I didn't make a mistake on that experiment, because all 3 ways ended up with the `transform` on the `g`roup [though other attributes did move], which strikes me as a little odd.)
upendra-web commented 5 years ago

@Emasoft, recently I made this repo, just like SVGOMG but with Vue. I added flatten svg option, whose code was provided by Timo in his gist (with some modifications). It is still in experimental stage. It worked on the transforms of most of the paths (except some text and clipaths). So can anyone check that option and give your opinions.

Thank you all.

andrewrcollins commented 5 years ago

@upendra-web Superb work with lean-SVG !

upendra-web commented 5 years ago

@upendra-web Superb work with lean-SVG !

Thank you very much @andrewrcollins

MarkJeronimus commented 4 years ago

Lean-SVG still doesn't work with rect and circle. It's been 3 years, and I can see you put an enormous amount of effort in SVGO, so why not finish it with transform applying?

maxwell8888 commented 4 years ago

If you are using Inkscape:

  1. Select everything and ungroup
  2. Save as "Optimised Svg (*.svg)"

In all cases I have tried, this has removed any transform attributes, then I can run through SVGO without having to worry about transforms. Not sure if it works for all SVG, but certainly the ones I have tried so far.

user98765446 commented 3 years ago

@maxwell8888 this works well for some elements, but not all unfortunately.

Some very tricky ones are:


I realise that the status of this is not likely to change, but I would love to know if it does (or if anyone finds a suitable workaround).

specious commented 3 years ago

I've been able to apply transforms to path elements manually using stadline/svg-flatten provided that the transform attribute was associated with the path element itself (and not a group) by running in the nodejs interpreter (inside the project directory):

var flatten = require('./')
var svg = fs.readFileSync('./logo.svg')

flatten(svg).transform().value().toString()

However, this doesn't work as far as preserving the gradients if they have been defined with gradientUnits="userSpaceOnUse". If such a gradient is specified as a fill then the gradient also needs to have the transform applied to it. I imagine this applies to patterns as well.

davidwebca commented 3 years ago

I really hope this eventually comes in SVGO, I'm relying on Lean-SVG for my day-to-day work right now, but I'd love SVGO to handle everything. I think most of the debate earlier was about the reliability of the transforms, but as long as it's clear that it's an opt-in one, I can't see why not.

ghost commented 3 years ago

Could we perhaps use Inkscape CLI to ungroup elements with a transform attribute on the group, then regroup them without the transform? That should help remove the transforms.

milesrichardson commented 2 years ago

I had an auto-generated svg with a transform="matrix(1.5,0,0,1.5,0,0)" on the outer group with only paths inside it. I was looking for a way to perform that translation on each path ahead of time so I could remove the transform, which is how I found this thread.

I was able to accomplish this manually by using either of these tools:

If you don't need an automated solution, these both offer a quick way to apply computations to path data.

GreLI commented 2 years ago

BTW, it's good starter task, if someone would like to help.

folsze commented 1 year ago

I just thought about solving this here: https://stackoverflow.com/a/75964763/20009330

I am not saying that this solves this problem for svgo. But maybe writing a quick script (like mentioned at the bottom of the answer) to edit the svg is a workaround for some of you that come here?

folsze commented 1 year ago

The solution above only works with translate transforms...

A better solution, also for matrix transforms: maybe think: how do they even appear? Maybe prevent them from entering your svg in the first place?

Inkscape: in Inkscape, if you select all elements, it'll select groups and not all single elements. Then if you scale etc., it'll likely apply a matrix transform (to the group). We do not want this!

Solution: Select all single elements without their group folders. And then apply the transform, you'll see, Inkscape will directly apply any transforms if you do the transform operation on the children elements themselves. So, how to select ALL single elements?

GPT-4 told me this: ctrl. + f to find all.

THEN: deselect group elements in advanced search options

then just search with empty input, it'll select all

Here is a screenshot: image

mattsputnikdigital commented 11 months ago

Has this been solved now?

GreLI commented 11 months ago

@mattsputnikdigital PRs are welcome

Airkro commented 8 months ago

https://github.com/svg/svgo/pull/1854 may handle this?

vadimcoder commented 3 months ago

There are multiple implementations out there, but still, it would be really nice to have in svgo as well.