| Bytes | Lang | Time | Link |
|---|---|---|---|
| nan | 250529T102406Z | Miro | |
| nan | 241022T072418Z | Hand-E-F | |
| nan | 230305T165051Z | SE - sto | |
| nan | 230305T162416Z | SE - sto | |
| 000 | Coordinates use default 0 | 230305T112748Z | SE - sto |
| nan | 230304T142420Z | ccprog | |
| nan | 230304T142202Z | ccprog | |
| nan | 230304T142126Z | ccprog | |
| nan | 230304T141931Z | ccprog | |
| nan | 230304T141851Z | ccprog | |
| nan | 230304T141812Z | ccprog | |
| nan | 230304T141733Z | ccprog | |
| nan | 230304T141656Z | ccprog | |
| nan | 230304T141621Z | ccprog | |
| nan | 230303T012348Z | Parcly T |
Use color names where applicable
Save one character by using the colour name red instead of the hex code #f00.
Some other cases where the colour name is equal to or shorter than the hex code are listed here (note that rgba(0,0,0,0) can actually be shortened to #0000, so transparent is not shorter).
Use 12-bit colour codes
Colour codes can be expressed as 12-bit colour codes. There's less resolution, but is fine for most applications.
fill="white" == fill="#FFF"
fill="yellow" == fill="#FF0"
fill="aquamarine" ~= fill="#7FC"
You have all of unicode
There's a built-in library of thousands upon thousands of graphic primitives, which cost you just a couple of bytes each, plus an up-front cost of <text></text>
In production SVGs, this is horrible practice since you will of course be at the mercy of font rendering and support, and are mixing up graphics and semantics. But good practice has never stopped code golfing!
Interesting unicode blocks:
<rect> is never the answer
The equivalent path for a rectangle is always shorter than <rect>
<rect x=5 y=5 width=2 height=1 fill=red />
<path d=M5,5h2v1h-2 fill=red />
If either the width or height is equal to 1 (or otherwise the local line width), <line> is shorter when either the x or y coordinate is 0
One coordinate is 0:
<rect x=5 width=2 height=1 fill=red />
<path d=M5,0h2v1h-2 fill=red />
<line x1=5 x2=7 stroke=red />
Both coordinates are 0:
<rect width=2 height=1 fill=red />
<path d=h2v1h-2 fill=red />
<line x2=2 stroke=red />
Coordinates: use default (0,0) positioning
Transforming the origin is often cheaper than giving explicit coordinates.
<svg viewBox=-4,-4,8,8><circle r=3 fill=red
Path data: Path fills close automatically
If a path has a fill, but no stroke, you can leave out closing the path
M0,0H5V5H0V0Z == M0,0H5V5H0V0 == M0,0H5V5H0 for stroke="none" or in a clip-path
== M0,0H5V5H0Z for stroke="<paint>"
But the automatic closing last segment is alway straight, so if you have a curve as the last command, the repetition of the last coordinate might be inevitable
M0,0H5V5Q0,5 0,0Z == M0,0H5V5Q0,5 0,0
...unless you are able to use another point as the start
M0,0H5V5Q0,5 0,0Z == M5,0V5Q0,5 0,0
Path data: Try out the A command
Grafical editors tend to approximate elliptical arcs with (sequences of) cubic Bezier curves. For example, Inkscape perfectly understands
M0,0A10,15-20 0 1 10,12
But move the handle of the end point, even if you immediately move it back to its original position, and suddenly you will have a cubic Bezier.
M 0,0 C 4.3412,1.4378 8.2913,6.1779 10,12
This also happens with quadratic Beziers, with the small difference that the result of the conversion is mathematically equal. Conversions from C to A or C to Q on the other hand are impossible most of the time.
With reasonable rounding, you might (almost) regain the original length, but you will have to test this manually
M0,0C4.3,1.4 8.3,6.2 10,12
But take note: the above arc command has extra opportunities for minification. (This is something that the available tools all miss.) The large_arc and sweep parameters are flags. As they can only be 0 or 1, no space needs to follow them
M0,0A10,15-20 0 1 10,12 == M0,0A10,15-20 0110,12
I know this looks strange, but it is really part of the grammar. Note the comma_wsp?
elliptical_arc_argument::=
number comma_wsp? number comma_wsp? number comma_wsp
flag comma_wsp? flag comma_wsp? coordinate_pair
Path data: The art of spaces
The most curious term in the BNF grammar is the production comma_wsp?. What it means is: separate numbers with commas and/or whitespace as you wish, or leave them off completely. I'll quote from the spec directly:
The processing of the EBNF must consume as much of a given EBNF production as possible, stopping at the point when a character is encountered which no longer satisfies the production. Thus, in the string
"M 100-200", the first coordinate for the "moveto" consumes the characters "100" and stops upon encountering the minus sign because the minus sign cannot follow a digit in the production of a "coordinate". The result is that the first coordinate will be "100" and the second coordinate will be "-200".Similarly, for the string
"M 0.6.5", the first coordinate of the "moveto" consumes the characters "0.6" and stops upon encountering the second decimal point because the production of a "coordinate" only allows one decimal point. The result is that the first coordinate will be "0.6" and the second coordinate will be ".5".
Implied here is that adjecent to command letters no space is needed to separate a number (and commas are forbidden).
The same rules apply to transform functions, but not to the viewBox attribute.
Another small note is about writing SVG content inline in a HTML page. It is legal to leave off the surrounding quotes for an attribute, as long as the attribute contains no whitespace. This applies also to path data, but you might be forced to replace spaces with commas
<path d="M0,0 1,1" /> == <path d=M0,0,1,1 />
Path data: Don't overestimate relative commands
Adobe Illustrator writes all path commands as relative commands, obviously assuming they will lead to shorter strings. While this is true in a lot of cases, most of the time they will lead to nothing, and sometimes even take up extra space
M10,10 15,16 == M10,10l5,6
M10,10 25,26 == M10,10l15,16
M25,26 10,10 == M25,26l-15-16
In an extra note, relative commands are relative not to the previous (control) point, but to the previous stop point, which might be a further distance away
M0,0C0,2 8,10 15,15 != M0,0c0,2 8,8 7,7 WRONG!
== M0,0c0,2 8,10 15,15
Path data: Concatenate paths
Path strings can contain multiple substrings. If they
- have the same styles,
- are defined in the same userspace coordinate system and
- do not overlap,
instead of writing two elements, you can just concatenate the path data strings.
<path d="M0,0 5,5" /><path d="M10,10 15,15" /> == <path d="M0,0 5,5M10,10 15,15" />
In this case, you could even take advantage of relative commands
<path d="M0,0 5,5m5,5 5,5" />
Path data: Mirror symetrical Bezier curves
There are special commands that mirror the Bezier control points around a stop coordinate.
M0,0Q0,1 1,1 1,2 2,2 2,3 3,3 == M0,0Q0,1 1,1T2,2 3,3
M0,0C0,1 1,2 2,2 3,2 4,3 4,4 == M0,0C0,1 1,2 2,2S4,3 4,4
This even works when mixing quadratic and cubic Beziers:
M0,0C0,1 1,2 2,2Q3,2 3,3 == M0,0C0,1 1,2 2,2T3,3
Path data: Do not repeat command letters
If there are multiple path segments with the same command, you can leave out the letters on the repetitions.
M0,0Q0,1 1,1Q1,2 2,2 == M0,0Q0,1 1,1 1,2 2,2
Path data: Use H and V instead of L
If your path contains straight lines that are horizontal or vertical, the following sequences are equal:
M0,0L1,0 == M0,0H1
M0,0L0,1 == M0,0V1
Path data: Use automatic tools
The grammar for the path data microsyntax is especially aimed at a minimal representation, but leaves room for a lot of variants.
Because of that, there are tools available that try to automatically minimize the path data. Notably Adobe Illustrator will automatically try to apply minification on SVG export, and the SVGOMG optimization tool contains a module for the same purpose. The latter one is included in a number of online tools, including for example Figma.
These tools are a good start, but still fall short in some aspects.
Know your transforms
Note: this answer talks about the implementation of transforms described in the SVG 1.1 standard, not the CSS standard.
In SVG there are five main types of affine transforms that can be used in the transform attribute of an object.
matrix(a,b,c,d,e,f)is the most general one. It transforms using the affine transformation matrix $$\begin{bmatrix}x'\\y'\end{bmatrix}\leftarrow\begin{bmatrix}a&c\\b&d\end{bmatrix}\begin{bmatrix}x\\y\end{bmatrix}+\begin{bmatrix}e\\f\end{bmatrix}$$ where the coordinate system is the usual "x right, y down" one in the frame of the deepest enclosing group of the object in question, which may be the document root. Thus (for example) groups of groups of clones can be arranged in a tree with one simple transformation per group to achieve fractal-like effects.translate(x,y)does as you would expect. Note thattranslate(x)works and is equivalent totranslate(x,0).- Ditto for
scale(x,y), but herescale(x) = scale(x,x). Negative values can be provided, and indeedscale(-1)is shorter thanrotate(180)(see below). rotate(d)means rotation byddegrees clockwise about the origin (of the current coordinate frame).rotate(d,x,y)means rotation byddegrees clockwise about(x,y).- Define
skew(a,b) = matrix(1,tan(b),tan(a),1,0,0)wherea,bare again in degrees. This is not in SVG, butskewX(a) = skew(a,0)andskewY(b) = skew(0,b)are.
My experience with compressing My Little Pony: Friendship Is Magic cutie marks has taught me that in the most general case using matrix is the best. When the transform is only a similarity (angle-preserving) using rotate(a,b,c)scale(s) (transforms are applied right-to-left) is usually better, and for yet more special cases composing two of the non-matrix primitives above is usually better, but never three.
A particularly egregious example of transform compression can be seen in Fluttershy's cutie mark from the relevant section of my Selwyn repository. (The actual file over there duplicates groups because clones don't play well with the path effects of Inkscape; I often use the Envelope Deformation path effect to slightly bend cutie marks to fit the curving flanks of ponies, as can be seen e.g. here.)
<svg viewBox=-50,-50,100,100><g id=a><path d=m15-33.8c-3.8-4,10.2-9.1-3,16.3,14.4-25.5,18.3-11.7,12.6-12.2 fill=none stroke=#69c8c3 stroke-width=.9 /><path d=m17.1-14.3c12.5,13.7-8.1,27-9.7,4-21.6,12.5-21.9-12.6-3-11.6-11.9-12.7,10-31.6,8.4,2.7,28.5-19.9,19.3,2.4,4.3,4.9z fill=#f3b5cf /><path d=m5.9-7.6c4.4-11.9,8.7-17.7,10-17.1,1.3.6-1,7.8-10,17.1z fill=#69c8c3 /></g><use href=#a transform=matrix(.83,0,0,.83,11.9,37.3) /><use href=#a transform=rotate(-65,22.3,22)scale(.94) />
Here one of the cloned butterflies is transformed using the rotate()scale() pattern, but the other clone is not because matrix(.83,0,0,.83,11.9,37.3) is shorter than translate(11.9,37.3)scale(.83).
