Proposals/Layered Paint Properties

From SVG

This proposal outlines a new set of properties to make it easier to control layered paint servers in the fill or stroke of a shape or text. The fill and stroke properties would become shorthands for a set of properties that control the source, size, and positioning of each paint layer. The proposal also includes a z-index like property for controlling the paint order of fill, stroke, and markers (which would replace the paint-order property currently proposed in the draft spec). Finally, it provides a more expanded definition of context keywords for coordinating markers with shapes and for creating paint server elements that can be re-used with customized colors.

Drafted by Amelia Bellamy-Royds in May 2015.

Current SVG 2 Draft

See latest published draft at the time I write this or the dynamic editor's draft (which may have changed by the time you read this).

The following requirements with respect to paint (fill and stroke) have been adopted for SVG 2:

  • Allow multiple paints in fill and stroke.
  • Add new paint values for referencing current fill paint, stroke paint, etc.
  • Support control of the order of filling, stroke and painting markers on shapes.

In addition, at the 12 March 2015 teleconference, the WG was supportive of the idea that authors should be able to specify the reference bounding box for each paint layer, subject to a complete proposal being made and approved. This aims to be that proposal.

The current SVG 2 spec uses the following syntax to describe allowed values for "paint" (stroke or fill), most of which is copied directly from the CSS background shorthand:

<paint> = [ <paint-layer> , ]* <final-paint-layer>

<paint-layer> = <paint-source>|| <position> [ / <paint-size> ]? || <repeat-style> ||
             [ <shape-box> | fill | stroke | view-box ] || [ <shape-box> | fill | stroke | view-box ]

<final-paint-layer> = <paint-source> || <position> [ / <paint-size> ]? || <repeat-style> ||
             [ <shape-box> | fill | stroke | view-box ] ||
             [ <shape-box> | fill | stroke | view-box ] || <color>

<paint-source> = none | <image> | <url> | context-fill | context-stroke

There isn't currently any detailed text in SVG 2 drafts about what this all means. The CSS background shorthand is defined as follows:

The ‘background’ property is a shorthand property for setting most background properties at the same place in the style sheet. The number of comma-separated items defines the number of background layers. Given a valid declaration, for each layer the shorthand first sets the corresponding layer of each of ‘background-image’, ‘background-position’, ‘background-size’, ‘background-repeat’, ‘background-origin’, ‘background-clip’ and ‘background-attachment’ to that property's initial value, then assigns any explicit values specified for this layer in the declaration. Finally ‘background-color’ is set to the specified color, if any, else set to its initial value.

If one <box> value is present then it sets both ‘background-origin’ and ‘background-clip’ to that value. If two values are present, then the first sets ‘background-origin’ and the second ‘background-clip’.

This is sufficiently complex to warrant separating it out into sub-properties for greater control, so authors can change one aspect without having to repeat the entire definition.

The CSS background properties also do not correspond directly to SVG paint servers:

  • I see no reason to have "repeat-style" when (a) we have pattern paint servers and (b) paint servers provide infinite content.
  • The size of a paint server determines the scale of the content, not a finite region, so separate clip and size properties don't seem appropriate.
  • Although it could be useful to specify a particular finite size and position, particularly when using raster images as fill, I think that can be safely deferred to future specifications (for now, paint would always scale to fill the entire reference box).
  • Clipping to a box is unlikely to be useful compared to clipping to the area with or without the stroke, and the stroke position property already allows for a similar effect.

There is also a big difference between how colors are treated in CSS backgrounds versus in SVG paint: in CSS backgrounds, a single solid color background layer is always painted underneath any background images. It defaults to transparent. In SVG 1.1 paint, a color specified alongside a paint server is used only as a fallback. Furthermore, there are different defaults between stroke and fill (no stroke paint, which is different from transparent paint, versus solid black fill).

The following definition has also been adopted:

context element

The context element of an element is defined as follows:

  • If the element is within a ‘marker’, and is being rendered as part of that marker due to being referenced via a marker property, then the context element is the element referencing that ‘marker’.
  • If the element is within a sub-tree that is instantiated with a ‘use’ element, then the context element is that ‘use’ element.
  • Otherwise, there is no context element.

This would be used with the context-fill and and context-stroke keywords to allow markers to coordinate their fill/stroke styles with the shape they are marking. However, it does not address many other situations in which re-usable graphical content (e.g., patterns, gradients) may need to coordinate with the element using that content.

Proposal

I propose creating the following properties to describe the layered content:

stroke-paint / fill-paint
The list of paint sources to be used on each layer, or the keyword color to indicate that the corresponding solid color should be used as a paint layer.
stroke-box / fill-box
The reference bounding box or view box to use when scaling each paint layer.
stroke-index / fill-index
A z-index like ordering value for each paint layer, which will control the layering of the multiple fill layers, strokes, and markers.
stroke-color / fill-color
A list of color values to be used
  1. as a solid paint color if the corresponding paint layer is set to color or is unavailable or invalid
  2. as the context color for the corresponding paint server element

Using context styles

I propose the following, more expansive, definition of context element of another element:

  • For any element that is rendered because of a reference in a style property of another element, the context is the element with the referencing style property. If there are multiple references (e.g., a shape is filled with a pattern that includes shapes filled with gradients), only the first reference is followed.
  • For any element that is rendered because of a <use> element, the context is that <use> element. If there are nested duplications (e.g., a use element duplicates a group that includes other use elements), only the first reference is followed.
  • For any other element (SVG or otherwise), it is the parent element.

Using that definition, the following keywords are defined for use in the stroke and fill paint properties -- both the new properties and existing properties such as fill-opacity and stroke-opacity:

context-fill
The computed value of the equivalent fill property on the context element.
context-stroke
The computed value of the equivalent stroke property on the context element.

For example, within the stroke-opacity property, the context-fill keyword would equate to the value of fill-opacity on the context element. Both keywords could equate to a list of values.

An additional keyword could be used anywhere a <color> value is accepted:

context-color
A single computed color value
  • For any SVG content that is rendered as part of a paint server, the fallback color for that paint layer as defined by the fill-color or stroke-color property on the context element.
  • For any other element, the computed value of the color property on the context element.

In addition, a new context universal keyword is introduced. Similar to initial and inherit, it can be used as the value of any style property in an SVG 2-supporting user agent, and sets the property to the computed value of the equivalent property on the context element. Given the definition of the context element above, the context keyword is a direct synonym of the inherit keyword for elements other than elements within SVG graphical effects or duplicated content.

This means that the following two declarations are equivalent:

fill: context-fill;
fill: context;

The reason for having special keywords for context-fill and context-stroke is so that a stroke paint stack can be used as a fill paint stack and vice versa. For example, it is often desirable to have the fill of an arrowhead match the stroke of the line that it marks. The reason for still having a context keyword is so that authors can coordinate other styles, such as fonts (e.g., in highway markers on a map).

All of these keywords would be replaced by their computed value for the purpose of property inheritance. This avoids complicated situations when you have indirect <use> references that change the local context element.

Finally, a new context-var() function is introduced as an extension of the var() function defined in the CSS Custom Properties Module. It would work in the exact same way as the function defined in that specification, except that the value of the variable as defined on the context element would be used. As with var(), it would be inherited as a function reference, and would only be converted to a computed value when used.

Any of these context keywords may create invalid circular references. For example, if you used `context-fill` within a paint server that was used as the fill of an element, the computed value of that property would refer back to itself. Same if you used `marker: context` within a marker element. What to do would be defined by the error processing section, but a forgiving error handling could be to treat the invalid declaration as `initial` or `inherit`.

Alternatively, instead of using keywords, we could use function notation that allows the author to specify fallback, similar to the var() function.

Other issues to consider:

  • The currentcolor keyword is all one word (and is case-insensitive, so can be written currentColor). Does it make sense to use contextColor for consistency, or should we use context-color for readability? What about contextfill and contextstroke versus context-fill and context-stroke?
  • What is the context element for a root element? Itself? The initial values for all properties?
  • Do we want to allow an SVG-as-image or SVG-as-object to use context keywords and the context-var() function to reference styles set on the <img> or <object> element that embeds it, or the element of which is is used as a CSS background image (with cross-origin security restrictions, of course)? This could address the issue of how to pass styles and CSS variable parameters to an embedded element, but could complicate implementations.

Shorthand properties

The fill and stroke properties would become shorthands for the new sub-properties. The shorthand syntax would be:

<paint> = <paint-layer># | context-fill | context-stroke | none

<paint-layer> = <paint-source> || <color> [ <scaling-box> || <layering-index> ]?

<paint-source> = <image> | <url> | child | <child-selector> | color

<scaling-box> = auto | <shape-box> | view-box | fill-box | stroke-box | decorated-box 
                context-fill-box | context-stroke-box | context-decorated-box

<layering-index> = <integer>

<color> includes the new context-color keyword

In English, that means (if I haven't messed up the syntax): The value of fill and stroke can be specified as a list of paint layers or as a reference to the fill or stroke paint list of the context element, or it can be set to none to disable painting that part of the shape. Each paint layer consists of a paint source and/or a color, and can optionally have a reference scaling box and layering index.

Using the context-fill and context-stroke keywords would have the same effect as using those keywords on each of the sub-properties (*-paint, *-color, *-box, and *-index). Using the none keyword would set the *-paint property to none and would reset the other properties to their initial value.

The default would continue to be fill: black and stroke: none. This would be created by the default values for the sub-properties are described below. However, when the paint is specified as layers, the default <paint-source> for any given layer is color. This allows you to define solid color layer by only setting the color value.

To create a backwards-compatible computed value, the following serialization rules would need to be included:

  • The context-* keywords are replaced by their computed value.
  • The shorthand only has as many layers as the *-source property, regardless of whether extra values are specified for the other sub-properties.
  • If the value of the *-source property is none, then the computed value of the paint property is also none.
  • The color keyword is never included in the shorthand computed value. For any paint layer where the *-source property is color, then the computed value of that paint-layer explicitly includes the value of the *-color property for that layer (even if this is the default black).
  • All other initial values for each layer are excluded from the computed value.

The other fill and stroke properties (opacity properties and stroke geometry properties) would also be extended to allow lists of values corresponding to the list of paint layers. However, these would not be part of the shorthand and would not be re-set when the shorthand is used. This is necessary to ensure backwards compatibility.

When using the individual properties, the number of paint layers is determined by the number of values in fill-paint and stroke-paint. Lists for the other properties are repeated as necessary, similar to how layered backgrounds work. Also similar to layered backgrounds, the default paint order for multiple fill/stroke layers (or more generally, for any layers that have the same layering index) is top to bottom in the order they are written.

Specifying the paint sources

Each layer of paint is specified either as a reference to a paint server, a CSS image value (including raster images and CSS gradients), or as an instruction to use the fallback color for that layer. Alternatively, the list of layers can set to an empty list with the none keyword, or a paint layer list from the context element can be duplicated in its entirety.

stroke-paint = <paint-source># | context-fill | context-stroke | none
fill-paint = <paint-source># | context-fill | context-stroke | none

<paint-source> = <image> | <url> | child | <child-selector> | color

The default for stroke-paint would be none, the default for fill-paint would be a single color layer.

Issues to consider:

  • Is color a good keyword? Another keyword I considered was solid, but to my mind that implies "opaque", which won't necessarily be true. Or maybe use-color?

Specifying the layer color

Each layer of paint has a color specified for it. A single color value will be repeated as necessary to match the number of paint layers. Alternatively, the color list can be copied from the stroke-color or fill-color property of the context element.

stroke-color = <color># | context-fill | context-stroke
fill-color = <color># | context-fill | context-stroke

The default for both fill-color and stroke-color would be black. The difference in the overall default behavior is controlled by the *-paint properties.

The color can be specified using the currentColor or context-color (contextColor?) keywords. The context-color keyword always equates to a single color value, not a list, and so is very different from using the context-fill and context-stroke keywords.

Specifying the paint scale

In SVG 1.1, the scale and coordinate system for paint servers is set by a gradientUnits or patternUnits attribute on the paint server element. The only options are userSpaceOnUse, which should mean to use the scale and coordinate system origin in place for the context element, versus objectBoundingBox, which applies a non-uniform scale to the context element coordinate system such that one unit equals the width/height of the shape's bounding box. The default bounding box does not include any stroke or marker area, so for straight horizontal/vertical lines the bounding box has zero area and the scaling transform is degenerate; the paint server then generates an error, and the fallback color (if any) is used.

In SVG 2, we add definitions for bounding boxes that include the stroke or even stroke plus markers (decorations). The Masking Module uses the terms fill-box and stroke-box to define the bounding box with and without the stroke. It would be preferable to be able to use these for object bounding box units. However, this should be a setting associated with a given use of a paint server, not with the paint server element itself.

That said, a userSpaceOnUse paint server is not interchangeable with an objectBoundingBox paint server, because the scale of units is so completely different. It gets even more complicated with patterns, since there is both a patternUnits and patternContentUnits. For lengths affected by patternUnits=objectBoundingBox, 100% is reset to equal 1 unit (i.e., the width/height of the box), but for lengths affected by patternContentUnits (and also units in markers, masks, and clip paths) percentages are scaled proportional to the change in scale of a single unit. And then there is the viewBox attribute on a pattern, which causes patternContentUnits to be ignored, and creates a uniform or non-uniform scaling transformation, but still doesn't reset the ratio between percentages and user units.

The Masking module tries to extend the idea of unit types with control over the reference box, but the current spec text is very problematic:

  • Clip paths defined entirely in CSS (using the shape functions) have a behavior that is in-between objectBoundingBox and userSpaceOnUse: user space units are used, but the origin is re-set to the reference box.
  • It re-defines objectBoundingBox units in a way that is not backwards compatible, to be the same as clip-paths defined entirely in CSS. ("The coordinate system has its origin at the top left corner of the bounding box of the element to which the clipping path applies to and the same width and height of this bounding box. User coordinates are sized equivalently to the CSS px unit.") Browsers so far have not implemented this change.
  • It does not clearly define how userSpaceOnUse masks and clip-paths should work on non-SVG content, with many complaints from authors as a result.

In other words, making something that is logical, consistent between specs, and backwards-compatible is rather difficult...

I therefore recommend that the choice of which box to use for scaling be introduced as an independent and orthogonal issue to what type of scaling to use.

What type of scaling would be defined by the XxxUnits attributes on the paint server elements (and the same approach could be used for masks, clipping paths, and filters). A new option would be added that would reset the origin to the reference box origin, but would not change the scale of units, similar to how CSS shapes as clipping paths work.

userSpaceOnUse
Use the units and coordinate system origin in effect for the reference scaling box.
translatedUserSpace
Use the same units and scale as userSpaceOnUse, but re-position the origin to the minimum corner of the reference scaling box (e.g., the top-left corner if transformations haven't been used). This would be the implicit behavior when using CSS <image> values as paint, just as it is the implicit default for masking images and clip-paths defined with CSS shape functions.
objectBoundingBox
Translate the coordinate system origin to the minimum corner of the reference scaling box, and then apply a non-uniform scale such that 1 user unit in each of the horizontal and vertical direction equals the width and height of the reference scaling box.

For patternContentUnits, a value of userSpaceOnUse would be treated equivalent to translatedUserSpace, matching current behavior.

There would be three types of reference scaling boxes:

  • SVG view boxes (view-box, farthest-view-box)
  • SVG bounding boxes (fill-box, stroke-box, and decorated-box)
  • CSS layout boxes (margin-box | border-box | padding-box | content-box)

For SVG view boxes, the coordinate system origin is that created by the viewBox property (or automatic viewBox) of the element's viewportElement or farthestViewportElement (view-box and farthest-view-box respectively). The height/width and minimum corner of the box are those directly defined by the viewBox, or by the actual dimensions of that element if it does not have an explicit viewBox. These options will allow you to use image fills (including CSS gradients), or paint servers defined with objectBoundingBox units, in a way similar to userSpaceOnUse SVG gradients (although results would not be identical in all cases, since the origin would be re-set).

For SVG bounding boxes, the coordinate system is that used when drawing that shape (and will therefore be the same as for view-box when userSpaceOnUse scaling is used). The height/width and minimum corner are those returned by the corresponding getBBox() method.

For CSS layout boxes, the coordinate system is the page coordinate system. The height/width and minimum corner are those defined by that specific layout box. For SVG elements that do not have their own CSS layout box, the CSS layout box of the farthest viewport element would be used.

In all cases, any transformations on the element or its ancestors affect the direction, origin, and scale of the coordinate system, just as they affect the direction, origin, and scale of the values returned by the getBBox() methods.

The syntax of the new paint properties would therefore be as follows:

fill-box = <scaling-box># | context-fill | context-stroke
stroke-box = <scaling-box># | context-fill | context-stroke

<scaling-box> = <shape-box> | view-box | farthest-view-box
                fill-box | stroke-box | decorated-box 
                context-fill-box | context-stroke-box | context-decorated-box

<shape-box> = margin-box | border-box | padding-box | content-box

The default would be fill-box. As mentioned above, this would have the same effect as view-box for userSpaceOnUse paint servers, matching the current behavior of these paint servers. For translatedUserSpace paint servers (or image fills) it would re-position the paint server origin to the minimum corner of the fill bounding box, but would not apply a scale. For objectBoundingBox paint servers, the current behavior would apply (including the error if the fill bounding box has zero area).

The context-*-box keywords would allow a paint server on a marker to be scaled as a continuous extension of the same paint server on the context element, instead of re-scaling it to the local shape. In other words, they refer to the context element of the shape with the fill/stroke property (which is itself the context element of the paint server). There is no context-view-box keyword; since elements that are not directly part of the rendering tree (i.e., paint server content) does not have a clearly defined viewport element, the view-box always indicates the viewport element for the context element in those cases.

The context-fill and context-stroke keywords would duplicate the entire list of values from the context element, with the exception that fill-box, stroke-box, and decorated-box would be replaced by context-fill-box, context-stroke-box, and context-decorated-box, respectively.

Issues to consider:

  • Is there a better name than translatedUserSpace?
  • What should be done about markerUnits, which have different options (strokeWidth vs objectBoundingBox) and default behavior (coordinate system origin is always translated to the marked point)?
  • For CSS layout boxes, should the coordinate system be that used for absolute positioning instead of the page? It would affect how userSpaceOnUse effects are positioned, and also the value of percentages in translatedUserSpace or objectBoundingBox.
  • What can be done to clarify & rationalize how viewport elements and percentages work without breaking backwards compatibility?

Specifying the paint order

SVG 1.1 defines a fixed order for layering fill, stroke, and markers. A previous proposal accepted for SVG 2 introduces a paint-order property to change the order of those three categories by re-arranging a list of keywords. However, this approach is not scalable to multiple fills and strokes, nor does it allow control of paint order for individual markers.

I propose a system whereby each shape or text element essentially becomes its own stacking context, and each paint layer or marker has a z-index to control how they stack together. In this way, you could create a shape with a thick stroke underneath the fill and a thin stroke overtop.

For stroke and fill, the z-index would be set by the stroke-index and fill-index properties, which would accept a list of values for each layer or a single value that would be repeated for all layers. Both would default to a single value 0. Higher index layers are painted later, on top of lower index layers.

stroke-index = <integer># | context-fill | context-stroke
fill-index = <integer># | context-fill | context-stroke

When layers have the same index, they would be painted according to the default paint order:

  1. all fill layers, from the last in the list to the first
  2. all stroke layers, from the last in the list to the first
  3. all start/mid/end markers, from the beginning to the end of path
  4. all other markers in the order specified in the markers spec

To create the example above (with strokes above and below the fill), you could use the following code:

stroke-paint: black, gray;
stroke-width: 1px, 10px;
stroke-index: 1, -1;
fill-paint: white;

The fill-index would default to 0, and would therefore be in-between the two strokes.

Issues to consider:

  • Should similar style properties be used to assign z-index values to each class of marker (and if so, with what syntax), or should marker elements simply use their z-index property? The latter option does not allow you to use the same marker with different layering indexes.

General considerations & impacts

There are a few additional complications to turning fill and stroke into shorthand properties:

  • There would need to be a clear order of precedence when both a shorthand and a sub-property are specified as presentation attributes. (Other shorthand properties are not supported as presentation attributes, so there has been no need for this sort of distinction, but fill and stroke would need to be supported as presentation attributes for backwards compatibility.)
  • It may be confusing to authors that many stroke properties (i.e., all the existing properties that control stroke geometry) as well as the stroke-opacity and fill-opacity properties are not part of the shorthand.
  • The fallback behavior would change when there is an error in a paint server: there would always be a fallback color (the default fill-color/stroke-color), regardless of whether or not it is explicitly set in the shorthand. As this only affects SVGs that are in error, I don't think this is a major backwards compatibility issue, but it is worth noting.

Comments?

If anyone has additional suggestions or concerns, you can add a section below. However, I expect the main discussion to be on the www-svg mailing list.