= Micro SVG Document Structure
:toc:
== Intro
SVG Micro represents a strip down SVG Full 1.1 subset.
Here is the main differences between SVG Full and SVG Micro.
- No XML DTD.
- No CSS.
- `use`, `marker` and nested `svg` will be resolved.
- Simplified path notation. Only absolute MoveTo, LineTo, CurveTo
and ClosePath segments are allowed.
- No inheritable attributes.
- No `xlink:href`, except the `image` element.
- No recursive references.
- Only valid elements and attributes.
- No unused elements.
- No redundant groups.
- No units.
- No `objectBoundingBox` units.
- No `viewBox` and `preserveAspectRatio` attributes.
- No `style` attribute, except for `mix-blend-mode` and `isolation`
- Default attributes are implicit.
You can use
https://github.com/linebender/resvg/tree/main/crates/usvg[usvg]
to convert a random SVG into a SVG Micro almost losslessly.
== Elements
[[svg-element]]
=== The `svg` element
The `svg` element is the root element of the document.
It's defined only once and can't be nested, unlike by the SVG spec.
*Children:*
* <<defs-element,defs>>
* <<g-element,g>>
* <<path-element,path>>
* <<image-element,image>>
*Attributes:*
* `width` = <<positive-number-type,<positive-number> >> +
The width of the rectangular region into which the referenced document is placed.
* `height` = <<positive-number-type,<positive-number> >> +
The height of the rectangular region into which the referenced document is placed.
[[defs-element]]
=== The `defs` element
Always present. Always the first `svg` child. Can be empty.
*Children:*
* <<linearGradient-element,linearGradient>>
* <<radialGradient-element,radialGradient>>
* <<clipPath-element,clipPath>>
* <<mask-element,mask>>
* <<pattern-element,pattern>>
* <<filter-element,filter>>
* <<g-element,g>>
* <<path-element,path>>
* <<image-element,image>>
*Attributes:*
* none
[[linearGradient-element]]
=== The `linearGradient` element
Doesn't have a `xlink:href` attribute because all attributes and `stop`
children will be resolved.
*Children:*
* At least two <<stop-element,stop>>
*Attributes:*
* `id` = <<string-type,<string> >> +
The element ID. Always set. Guarantee to be unique.
* `x1` = <<number-type,<number> >>
* `y1` = <<number-type,<number> >>
* `x2` = <<number-type,<number> >>
* `y2` = <<number-type,<number> >>
* `gradientUnits` = `userSpaceOnUse`?
* `spreadMethod` = `reflect | repeat`?
* `gradientTransform` = <<transform-type,<transform> >>?
[[radialGradient-element]]
=== The `radialGradient` element
Doesn't have a `xlink:href` attribute because all attributes and `stop`
children will be resolved.
*Children:*
* At least two <<stop-element,stop>>
*Attributes:*
* `id` = <<string-type,<string> >> +
The element ID. Always set. Guarantee to be unique.
* `cx` = <<number-type,<number> >>
* `cy` = <<number-type,<number> >>
* `fx` = <<number-type,<number> >> +
Guarantee to be the circle defined by `cx`, `cy` and `r`.
* `fy` = <<number-type,<number> >> +
Guarantee to be inside the circle defined by `cx`, `cy` and `r`.
* `r` = <<positive-number-type,<positive-number> >>
* `gradientUnits` = `userSpaceOnUse`
* `spreadMethod` = `reflect | repeat`?
* `gradientTransform` = <<transform-type,<transform> >>?
[[stop-element]]
=== The `stop` element
Gradient's `stop` children will always have unique, ordered `offset` values
in the 0..1 range.
*Children:*
* none
*Attributes:*
* `offset` = <<offset-type,<offset> >>
* `stop-color` = <<color-type,<color> >>
* `stop-opacity` = <<opacity-type,<opacity> >>? +
Default: 1
[[pattern-element]]
=== The `pattern` element
Doesn't have a `xlink:href` attribute because all attributes and children will be resolved.
*Children:*
* `g`
* `path`
* `image`
*Attributes:*
* `id` = <<string-type,<string> >> +
The element ID. Always set. Guarantee to be unique.
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `width` = <<positive-number-type,<positive-number> >>
* `height` = <<positive-number-type,<positive-number> >>
* `patternUnits` = `userSpaceOnUse`
* `patternTransform` = <<transform-type,<transform> >>?
[[clipPath-element]]
=== The `clipPath` element
*Children:*
* `path`
*Attributes:*
* `id` = <<string-type,<string> >> +
The element ID. Always set. Guarantee to be unique.
* `clip-path` = <<func-iri-type,<FuncIRI> >>? +
An optional reference to a supplemental `clipPath`. +
Default: none
* `transform` = <<transform-type,<transform> >>?
[[mask-element]]
=== The `mask` element
*Children:*
* `g`
* `path`
* `image`
*Attributes:*
* `id` = <<string-type,<string> >> +
The element ID. Always set. Guarantee to be unique.
* `mask` = <<func-iri-type,<FuncIRI> >>? +
An optional reference to a supplemental `mask`. +
Default: none
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `width` = <<positive-number-type,<positive-number> >>
* `height` = <<positive-number-type,<positive-number> >>
* `mask-type` = `alpha`? +
Default: luminance
* `maskUnits` = `userSpaceOnUse`
[[filter-element]]
=== The `filter` element
Doesn't have a `xlink:href` attribute because all attributes and children will be resolved.
*Children:*
* <<Filter primitives>>
*Attributes:*
* `id` = <<string-type,<string> >> +
The element ID. Always set. Guarantee to be unique.
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `width` = <<positive-number-type,<positive-number> >>
* `height` = <<positive-number-type,<positive-number> >>
* `filterUnits` = `userSpaceOnUse`
[[g-element]]
=== The `g` element
The group element indicates that a new canvas should be created.
All group's children elements will be rendered on it and then merged into
the parent canvas.
Since it's pretty expensive, especially memory wise, _usvg_
will remove as many groups as possible.
And all the remaining one will indicate that a new canvas must be created.
A group can have no children when it has a `filter` attribute.
A group will have at least one of the attributes present.
*Children:*
* <<g-element,g>>
* <<path-element,path>>
* <<image-element,image>>
*Attributes:*
* `id` = <<string-type,<string> >>? +
An optional, but never empty, element ID.
* `opacity` = <<opacity-type,<opacity> >>?
* `clip-path` = <<func-iri-type,<FuncIRI> >>? +
Cannot be set to `none`.
* `mask` = <<func-iri-type,<FuncIRI> >>? +
Cannot be set to `none`.
* `filter` = <<func-iri-type,<FuncIRI> >>+ +
Cannot be set to `none`.
* `transform` = <<transform-type,<transform> >>?
* `style` = <<string-type,<string> >>? +
This is the only place where the `style` attribute is used.
For reasons unknown, `mix-blend-mode` and `isolation` properties must not be set as attributes,
only as part of the `style` attribute. +
The set attribute will look like `mix-blend-mode:screen;isolation:isolate`.
Both properties are always set. +
The attribute is not present only in case of `mix-blend-mode:norma;isolation:auto`
[[path-element]]
=== The `path` element
*Children:*
* none
*Attributes:*
* `id` = <<string-type,<string> >>? +
An optional, but never empty, element ID.
* `d` = <<path-data-type,<path-data> >> +
* `fill` = `none` | <<color-type,<color> >> | <<func-iri-type,<FuncIRI> >> +
If set to `none` than all fill-* attributes will not be set too. +
Default: black
* `fill-opacity` = <<opacity-type,<opacity> >>? +
Default: 1
* `fill-rule` = `evenodd`? +
Default: nonzero
* `stroke` = `none` | <<color-type,<color> >> | <<func-iri-type,<FuncIRI> >> +
If set to `none` than all stroke-* attributes will not be set too. +
Default: none
* `stroke-width` = <<positive-number-type,<positive-number> >>? +
Default: 1
* `stroke-linecap` = `round | square`? +
Default: butt
* `stroke-linejoin` = `round | bevel`? +
Default: miter
* `stroke-miterlimit` = <<positive-number-type,<positive-number> >>? +
Guarantee to be > 1. +
Default: 4
* `stroke-dasharray` = `<list-of-numbers>`? +
Guarantee to have even amount of numbers. +
Default: none
* `stroke-dashoffset` = <<number-type,<number> >>?
* `stroke-opacity` = <<opacity-type,<opacity> >>? +
Default: 1
* `paint-order` = `normal | stroke`? +
Default: `normal` +
Only `stroke` will be written.
* `clip-rule` = `evenodd`? +
Will be set only inside the <<clipPath-element,clipPath>>, instead of `fill-rule`.
* `clip-path` = <<func-iri-type,<FuncIRI> >>? +
Available only inside the <<clipPath-element,clipPath>>.
* `shape-rendering` = `optimizeSpeed | crispEdges`? +
Default: geometricPrecision
* `visibility` = `hidden`? +
Default: visible
* `transform` = <<transform-type,<transform> >>? +
Can only be set on paths inside of `clipPath`.
[[image-element]]
=== The `image` element
*Children:*
* none
*Attributes:*
* `id` = <<string-type,<string> >>? +
An optional, but never empty, element ID.
* `xlink:href` = <<iri-type,<IRI> >> +
The IRI contains a base64 encoded image.
* `width` = <<positive-number-type,<positive-number> >>
* `height` = <<positive-number-type,<positive-number> >>
* `image-rendering` = `optimizeSpeed`? +
Default: optimizeQuality
* `visibility` = `hidden`? +
Default: visible
== Filter primitives
=== Filter primitive attributes
The attributes below are the same for all filter primitives.
* `color-interpolation-filters` = `sRGB`? +
Default: linearRGB
* `x` = <<number-type,<number> >>?
* `y` = <<number-type,<number> >>?
* `width` = <<number-type,<number> >>?
* `height` = <<number-type,<number> >>?
* `result` = <<string-type,<string> >>
The `x`, `y`, `width` and `height` attributes can be omitted.
SVG has a pretty complex
https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion[rules of resolving them]
and I don't fully understand them yet.
Neither do others, because they are pretty poorly implemented.
=== Filter primitive `feBlend`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `in2` = <<filter-input-type,<filter-input> >>
* `mode` = `normal | multiply | screen | overlay | darken | lighten | color-dodge |color-burn |
hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity`
* <<Filter primitive attributes>>
=== Filter primitive `feColorMatrix`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `type` = `matrix | saturate | hueRotate | luminanceToAlpha`
* `values` = `<list-of-numbers>`? +
** For `type=matrix`, contains 20 numbers.
** For `type=saturate`, contains a single number in a 0..1 range.
** For `type=hueRotate`, contains a single number.
** Not present for `type=luminanceToAlpha`.
* <<Filter primitive attributes>>
=== Filter primitive `feComponentTransfer`
*Children:*
* `feFuncR`
* `feFuncG`
* `feFuncB`
* `feFuncA`
The all four will always be present.
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* <<Filter primitive attributes>>
*`feFunc(R|G|B|A)` attributes:*
* `type` = `identity | table | discrete | linear | gamma`
* `tableValues` = `<list-of-numbers>`? +
Present only when `type=table | discrete`. Can be empty.
* `slope` = <<number-type,<number> >>? +
Present only when `type=linear`.
* `intercept` = <<number-type,<number> >>? +
Present only when `type=linear`.
* `amplitude` = <<number-type,<number> >>? +
Present only when `type=gamma`.
* `exponent` = <<number-type,<number> >>? +
Present only when `type=gamma`.
* `offset` = <<number-type,<number> >>? +
Present only when `type=gamma`.
=== Filter primitive `feComposite`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `in2` = <<filter-input-type,<filter-input> >>
* `operator` = `over | in | out | atop | xor | arithmetic`
* `k1` = <<number-type,<number> >>? +
Present only when `operator=arithmetic`.
* `k2` = <<number-type,<number> >>? +
Present only when `operator=arithmetic`.
* `k3` = <<number-type,<number> >>? +
Present only when `operator=arithmetic`.
* `k4` = <<number-type,<number> >>? +
Present only when `operator=arithmetic`.
* <<Filter primitive attributes>>
=== Filter primitive `feConvolveMatrix`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `order` = <<positive-integer-type,<positive-integer> >> " " <<positive-integer-type,<positive-integer> >> +
Both numbers are never 0.
* `kernelMatrix` = `<list-of-numbers>`
* `divisor` = <<number-type,<number> >> +
Never 0.
* `bias` = <<number-type,<number> >>
* `targetX` = <<positive-integer-type,<positive-integer> >> +
Always smaller than the number of columns in the matrix.
* `targetY` = <<positive-integer-type,<positive-integer> >> +
Always smaller than the number of rows in the matrix.
* `edgeMode` = `none | duplicate | wrap`
* `preserveAlpha` = `true | false`
* <<Filter primitive attributes>>
=== Filter primitive `feDiffuseLighting`
*Children:*
Only one of:
* `feDistantLight`
* `fePointLight`
* `feSpotLight`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `surfaceScale` = <<number-type,<number> >>
* `diffuseConstant` = <<number-type,<number> >>
* `lighting-color` = <<color-type,<color> >>
* <<Filter primitive attributes>>
`feDistantLight` *attributes:*
* `azimuth` = <<number-type,<number> >>
* `elevation` = <<number-type,<number> >>
`fePointLight` *attributes:*
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `z` = <<number-type,<number> >>
`feSpotLight` *attributes:*
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `z` = <<number-type,<number> >>
* `pointsAtX` = <<number-type,<number> >>
* `pointsAtY` = <<number-type,<number> >>
* `pointsAtZ` = <<number-type,<number> >>
* `specularExponent` = <<positive-number-type,<positive-number> >>
* `limitingConeAngle` = <<number-type,<number> >>?
=== Filter primitive `feDisplacementMap`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `in2` = <<filter-input-type,<filter-input> >>
* `scale` = <<number-type,<number> >>
* `xChannelSelector` = `R | G | B | A`
* `yChannelSelector` = `R | G | B | A`
* <<Filter primitive attributes>>
=== Filter primitive `feDropShadow`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `stdDeviation` = <<positive-number-type,<positive-number> >> " " <<positive-number-type,<positive-number> >>
* `dx` = <<number-type,<number> >>
* `dy` = <<number-type,<number> >>
* `flood-color` = <<color-type,<color> >>
* `flood-opacity` = <<opacity-type,<opacity> >>
* <<Filter primitive attributes>>
=== Filter primitive `feFlood`
*Attributes:*
* `flood-color` = <<color-type,<color> >>
* `flood-opacity` = <<opacity-type,<opacity> >>
* <<Filter primitive attributes>>
=== Filter primitive `feGaussianBlur`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `stdDeviation` = <<positive-number-type,<positive-number> >> " " <<positive-number-type,<positive-number> >>
* <<Filter primitive attributes>>
=== Filter primitive `feImage`
*Attributes:*
* `xlink:href` = <<iri-type,<IRI> >> +
The IRI contains a link to an element (like `use`).
base64 encoded is not allowed and will be represented as a link to an `image`.
* <<Filter primitive attributes>>
=== Filter primitive `feMerge`
*Children:*
* `feMergeNode`
*Attributes:*
* <<Filter primitive attributes>>
*`feMergeNode` attributes:*
* `in` = <<filter-input-type,<filter-input> >>
=== Filter primitive `feMorphology`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `operator` = `erode | dilate`
* `radius` = <<positive-number-type,<positive-number> >> " " <<positive-number-type,<positive-number> >>
* <<Filter primitive attributes>>
=== Filter primitive `feOffset`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `dx` = <<number-type,<number> >>
* `dy` = <<number-type,<number> >>
* <<Filter primitive attributes>>
=== Filter primitive `feSpecularLighting`
*Children:*
Only one of:
* `feDistantLight`
* `fePointLight`
* `feSpotLight`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* `surfaceScale` = <<number-type,<number> >>
* `specularConstant` = <<number-type,<number> >>
* `specularExponent` = <<number-type,<number> >> +
Number in a 1..128 range.
* `lighting-color` = <<color-type,<color> >>
* <<Filter primitive attributes>>
`feDistantLight` *attributes:*
* `azimuth` = <<number-type,<number> >>
* `elevation` = <<number-type,<number> >>
`fePointLight` *attributes:*
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `z` = <<number-type,<number> >>
`feSpotLight` *attributes:*
* `x` = <<number-type,<number> >>
* `y` = <<number-type,<number> >>
* `z` = <<number-type,<number> >>
* `pointsAtX` = <<number-type,<number> >>
* `pointsAtY` = <<number-type,<number> >>
* `pointsAtZ` = <<number-type,<number> >>
* `specularExponent` = <<positive-number-type,<positive-number> >>
* `limitingConeAngle` = <<number-type,<number> >>?
=== Filter primitive `feTile`
*Attributes:*
* `in` = <<filter-input-type,<filter-input> >>
* <<Filter primitive attributes>>
=== Filter primitive `feTurbulence`
*Attributes:*
* `baseFrequency` = <<positive-number-type,<positive-number> >> " " <<positive-number-type,<positive-number> >>
* `numOctaves` = <<positive-integer-type,<positive-integer> >>
* `seed` = <<integer-type,<integer> >>
* `stitchTiles` = `stitch | noStitch`
* `type` = `fractalNoise | turbulence`
* <<Filter primitive attributes>>
== Data types
If an attribute has the `?` symbol after the type that's mean that
that this attribute is optional.
[[string-type]]
*<string>* - A Unicode (UTF-8) string.
[[number-type]]
*<number>* - A real number. +
`number ::= [-]? [0-9]+ "." [0-9]+`
[[positive-number-type]]
*<positive-number>* - A positive real <<number-type,number>>. +
`positive-number ::= [0-9]+ "." [0-9]+`
[[integer-type]]
*<integer>* - An integer. +
`integer ::= [-]? [0-9]+`
[[positive-integer-type]]
*<positive-integer>* - A positive integer. +
`positive-integer ::= [0-9]+`
[[opacity-type]]
*<opacity>* - A real <<number-type,number>> in a 0..1 range. +
`opacity ::= positive-number`
[[offset-type]]
*<offset>* - A real <<number-type,number>> in a 0..1 range. +
`offset ::= positive-number`
[[color-type]]
*<color>* - A hex-encoded RGB color.
```
color ::= "#" hexdigit hexdigit hexdigit hexdigit hexdigit hexdigit
hexdigit ::= [0-9a-f]
```
[[iri-type]]
*<IRI>* - An Internationalized Resource Identifier.
Always a valid, local reference. +
`IRI ::= string`
[[func-iri-type]]
*<FuncIRI>* - Functional notation for an <<iri-type,IRI>>.
Always a valid, local reference. +
`FuncIRI ::= url( <IRI> )`
[[filter-input-type]]
*<filter-input>* - A filter source. A reference to a _result_ guarantee to be valid.
```
filter-input ::= SourceGraphic | SourceAlpha | <string>
```
We do not support `FillPaint`, `StrokePaint`, `BackgroundImage` and `BackgroundAlpha`.
[[transform-type]]
*<transform>* - A transformation matrix.
Always a `matrix` and not `translate`, `scale`, etc.
Numbers are space-separated. +
`transform ::= matrix( <number> " " <number> " " <number> " " <number> " " <number> " " <number> )`
[[path-data-type]]
*<path-data>* - A path data.
* Contains only absolute MoveTo, LineTo, CurveTo and ClosePath segments.
* All segments are explicit.
* The first segment is guarantee to be MoveTo.
* Segments, commands and coordinates are separated only by space.
* Path and all subpaths are guarantee to have at least two segments.
Grammar:
```
svg-path:
moveto-drawto-command-groups
moveto-drawto-command-groups:
moveto-drawto-command-group
| moveto-drawto-command-group " " moveto-drawto-command-groups
moveto-drawto-command-group:
moveto " " drawto-commands
drawto-commands:
drawto-command
| drawto-command " " drawto-commands
drawto-command:
closepath
| lineto
| curveto
moveto:
"M " coordinate-pair
lineto:
"L " coordinate-pair
curveto:
"C " coordinate-pair " " coordinate-pair " " coordinate-pair
closepath:
"Z"
coordinate-pair:
coordinate " " coordinate
coordinate:
sign? digit-sequence "." digit-sequence
sign:
"-"
digit-sequence:
digit
| digit digit-sequence
digit:
"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
```
Basically, a path looks like this: `M 10.5 20 L 30 40`.
Commands and numbers are separated by a space.
Numbers with an exponent are not allowed.
Trimmed numbers like `-.5` are not allowed.