4
votes

I need to write some C# code that flatten transforms programmatically of any SVG file.

This option is available in SVG editors like Affinity Designer (~$40 / Mac) or Inkscape (FOSS):

Affinity Designer SVG Export Option for Flatten Transforms

But how do you do this programmatically? Even good SVG c# libraries do not provide a method to do this.

There are many discussions open on this on SO (i.e. here, here, here and here ). In those it is suggested some tool, but no one gives the code to do it (except from some javascript snippet that is useless to me because it uses the browser API to get the transformed coordinates).

I need to do this in C# code because to manipulate the svg elements I need those to be independent of parents transforms, including elliptical arcs, gradients, text and tspan elements.

EDIT to clarify: I don't need to necessarily transform the elements. I just need to have transform matrices present only in the deepest child nodes, so I don't need to consider the parents anymore. For this I need all parent matrices concatenated in a single matrix and assigned to svg elements as a single transform attribute. I don't necessarily need to transform paths or arcs, convert ellipses to paths, for example, or resampling bitmaps. I just need to be able to compute only ONE matrix transform for each element, its own, assigned to it as a transform attribute, instead of having to consider all the parent groups transforms every time.

1
Inkscape is open source, probably not in c# but the source might give you some ideas. - Crowcoder
I've done that already. I searched the whole Inkscape source code. I searched for "concatenate", "cumulative", "recursive" and "flatten". I had no luck in finding a specific method that flattens the transforms calculating the cumulative parents transforms. Maybe is something it does implicitly when traversing the nodes, I don't know. But I can't find it. - Emanuele Sabetta
Do you realise that to implement this properly, you are going to have to implement an SVG parser? Flattening all the transforms and multiplying into the coordinates means you will have to also parse path commands accurately. Also know how to correctly apply those transforms to the coordinates in other SVG elements. Then some elements like rectangles and ellipses will need to be converted to paths. If you need to handle images, you are going to need to resample the bitmaps into other bitmaps. It is potentially a very big job - depending on how deep you need to go. - Paul LeBeau
@PaulLeBeau I don't need to necessarily transform the element. I just need to have the transform matrix only as an attribute of the last leaf child nodes, so I don't need to consider the parents anymore. Recursively I can concatenate all matrixes in a single matrix and assign that to any kind of svg element. I don't need to convert ellipses to paths, for example, or resampling bitmaps. I just need to be able to compute only ONE matrix transform for each element, instead of having to consider all the parent groups transforms. Is this possible? - Emanuele Sabetta
Than that is a much smpler proposition. As it seems you have already worked out. - Paul LeBeau

1 Answers

0
votes

This is an initial answer to my question. But it still has problems with some elements (elements with clip-path, mask or filter attributes, or using urls or defs, seems to have broken coordinates after flattening). Any further help and improvement is appreciated.

public void FlattenNodeRecursive(XElement item) {

            var children_elements = item.Elements();

            if ( IsSVGElem(item, "g") &&
                HasAttr(item,"transform") &&
                !item.IsEmpty &&
                item.Elements().Any((XElement inner) => { return IsSVGElem(inner, "path") || IsSVGElem(inner, "text") || IsSVGElem(inner, "g"); }) &&
                item.Elements().Any((XElement inner) => { return !IsSVGElem(inner, "tspan"); })
               ) {

                XAttribute parent_attribute = item.Attribute("transform");

                foreach (var inner in children_elements) {
                    if (HasAttr(inner, "transform")) {
                            inner.Attribute("transform").SetValue(ConcatenateTransforms((parent_attribute.Value.Trim() + " " + inner.Attribute("transform").Value.Trim())).Trim());
                        } else {
                            XAttribute attribute = new XAttribute("transform", parent_attribute.Value.Trim());
                            inner.Add(attribute);
                        }
                    }

                parent_attribute.Remove();

            }

            foreach(XElement xelem in children_elements){
                if(xelem.Elements() != null) {
                    FlattenNodeRecursive(xelem);
                }

            }
        }

NOTE: I didn't post the HasAttr method or the ConcatenateTransforms method source code because those are pretty straightforward, but if someone need those I will post them.