4

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.

Community
  • 1
  • 1
Emanuele Sabetta
  • 1,571
  • 12
  • 34
  • Inkscape is open source, probably not in c# but the source might give you some ideas. – Crowcoder Nov 20 '16 at 13:49
  • 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 Nov 20 '16 at 17:31
  • 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 Nov 21 '16 at 06:05
  • @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 Nov 23 '16 at 03:16
  • Than that is a much smpler proposition. As it seems you have already worked out. – Paul LeBeau Nov 23 '16 at 12:21

1 Answers1

0

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.

Emanuele Sabetta
  • 1,571
  • 12
  • 34
  • If a parent has a clip-path, mask etc, then you may have to leave an intermediate transform on those parent elements. Otherwise they will be left in the wrong coordinate space. Or you could copy that intermediate transform across to the clipPath definition, and continue on. However if the clipPath is used in multiple places, then that will probably also cause breakage. – Paul LeBeau Nov 23 '16 at 12:25
  • That is a problem for my project. I need to have each element independent from all parents (because I need to detach them from the xml tree and use them in another one.. like a cut&paste). Is there a way to work around that? Maybe duplicating the clip-paths, masks, etc. from all the parents and put them inside the element node? But how can I know what elements are affecting my node? – Emanuele Sabetta Nov 24 '16 at 04:11