CSS Custom Properties — var()

CSS custom properties, or the var() feature, let one define named variables with CSS values that can then be substituted where a property is used. To quote an example from the spec:

:root {
  --main-color: #06c;
  --accent-color: #006;
}
/* The rest of the CSS file */
#foo h1 {
  color: var(--main-color);
}

Additionally, var()) can specify a fallback value, in case the implementation does not support defining custom properties:

.foo { color: var(--main-color, #aabbcc); }

In this example, if --main-color is not defined, it will be substituted with #aabbcc.

OpenType fonts with SVG data and a color substitution table (“emoji fonts”)

OpenType allows fonts to define some glyphs in terms of SVG documents. These glyphs can be recolored: OpenType also allows fonts to have a color substitution table that will be applied to the SVG. To do this, RGB entries in the color table are effectively turned into custom properties named color0, color1, etc. with RGB values. Then, SVG elements specify their colors like <path fill="var(--color, yellow)" d="..." />, often with a fallback.

OpenType’s minimal requirements are that SVG implementations support var() just in places where a color may be specified (i.e. the properties that specify SVG paint servers), and that they support the fallback value. It does not require that implementations actually support defining custom values for the color0/color1/colorN variables, just that fallbacks are used.

This lets librsvg approach supporting CSS custom properties in an incremental fashion.

Roadmap for incremental support

  • Stage 1 (#997): support var(--blah, fallback) just for colors in properties that take paint servers, plus properties like lightingColor (filters) and stopColor (gradients). Look in property_defs.rs for places that use the Color type. This should will make OpenType fonts with color fallbacks work in minimal fashion.

  • Stage 2 (#459): support defining custom properties and referencing them. I wanted to cut&paste Servo’s implementation of this, but it is a bit involved and may require plenty of refactoring to accomodate it from librsvg’s code. If it is too complex, maybe we can have a homegrown implementation that just lets one define --foo: value; in a :root selector, and that just substitutes whole values without substitution into other tokens (e.g. width: var(--some_number)px; wouldn’t work).

  • Stage 3, full support for custom properties with substitution into other values.

Letting the caller define values for custom properties

Adobe’s SVG Native Viewer has a simple API to specify a color map that maps string names to RGBA colors. I think it would be more future-proof to actually let the caller specify the values in a :root selector via an external stylesheet; this way we can accomodate media queries in a clean fashion without growing the public API. Media queries are often used to set the custom property values depending on the media’s characteristics (e.g. change colors depending on dark-mode), and later the properties are used with var().

Security considerations

If we fully support variable substitution, be careful about the macro expansion attack that can be done with them. The spec mentions a mitigation; I think the Servo code already does this.

References