CSS Painting API Level 1

W3C First Public Working Draft,

This version:
http://www.w3.org/TR/2016/WD-css-paint-api-1-20160607/
Latest version:
http://www.w3.org/TR/css-paint-api-1/
Editor's Draft:
https://drafts.css-houdini.org/css-paint-api-1/
Feedback:
public-houdini@w3.org with subject line “[css-paint-api] … message topic …” (archives)
Issue Tracking:
GitHub
Inline In Spec
Editors:

Abstract

This specification describes an API which allows developers to paint a part of an box in response to geometry / computed style changes with an additional <image> function.

Status of this document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at http://www.w3.org/TR/.

This document is a First Public Working Draft.

Publication as a First Public Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

The (archived) public mailing list public-houdini@w3.org (see instructions) is preferred for discussion of this specification. When sending e-mail, please put the text “css-paint-api” in the subject, preferably like this: “[css-paint-api] …summary of comment…

This document was jointly produced by the CSS Working Group and the Technical Architecture Group.

This document was produced by groups operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures ( CSS, TAG) made in connection with the deliverables of the groups; those pages also include instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 1 September 2015 W3C Process Document.

1. Introduction

The paint stage of CSS is responsible for painting the background, content and highlight of a box based on that box’s geometry (as generated by the layout stage) and computed style.

This specification describes an API which allows developers to paint a part of an box in response to geometry / computed style changes with an additional <image> function.

Note: In a future version of the spec, support may be added for defining the clip, global alpha, filter on a portion of an box (for example on the background layers).

2. Paint Invalidation

A document has an associated paint name to input properties map. Initially it is empty and is populated when registerPaint(name, paintCtor) is called.

Each <paint()> function for a box has an associated paint valid flag. It may be either paint-valid or paint-invalid. It is initially set to paint-invalid.

When the geometry (as determined by layout) of a box changes, each <paint()> function’s paint valid flag should be set to paint-invalid.

When the computed style for an box changes, the user agent must run the following steps:

  1. For each <paint()> function on the box, perform the following substeps:

    1. Let paintFunction be the current <paint()> function on the box.

    2. Let name be the first argument of the <paint()> function.

    3. Let inputProperties be the result of lookuping up name on paint name to input properties map.

    4. For each property in inputProperties, if the property’s computed value has changed, set the paint valid flag on the paintFunction to paint-invalid.

Performing draw a paint image results in the paint valid flag for a <paint()> function on a box to be set to paint-valid.

Note: In a future version of the spec, support may be added for partial invalidation. The user agent will be able to specify a region of the rendering context which needs to be re-painted by the paint class.

3. Paint Worklet

The paintWorklet attribute allows access to the Worklet responsible for all the classes which are related to painting.

The paintWorklet's worklet global scope type is PaintWorkletGlobalScope.

partial interface Window {
    [SameObject] readonly attribute Worklet paintWorklet;
};

The PaintWorkletGlobalScope is the global execution context of the paintWorklet.

callback VoidFunction = void ();

[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
    void registerPaint(DOMString name, VoidFunction paintCtor);
};
Note: This is how the class should look.
callback interface PaintClass {
    readonly attribute sequence<DOMString> inputProperties;
    void paint(PaintRenderingContext2D ctx, Geometry geom, StylePropertyMap inputProperties);
};

4. Concepts

A paint image definition describes an author defined <image> which can be referenced by the <paint()> function. It consists of:

5. Registering Custom Paint

The PaintWorkletGlobalScope has a paint name to paint image definition map. Initially this map is empty; it is populated when registerPaint(name, paintCtor) is called.

The PaintWorkletGlobalScope has a paint name to instance map. Initially this map is empty; it is populated when draw a paint image is invoked by the user agent.

Instances of paint classes in the paint name to instance map may be disposed and removed from the map by the user agent at any time. This may be done when a <paint()> function no longer is used, or the user agent needs to reclaim memory.

When the registerPaint(name, paintCtor) method is called, the user agent must run the following steps:

  1. If the name is not a valid <ident>, throw a TypeError and abort all these steps.

  2. If the name exists as a key in the paint name to paint image definition map, throw a NotSupportedError and abort all these steps.

  3. Let inputProperties be the result of Get(O=paintCtor, P="inputProperties").

  4. If inputProperties is not undefined, and the result of IsArray(argument= inputProperties) is false, throw a TypeError and abort all these steps.

    If inputProperties is undefined, let inputProperties be a new empty array.

  5. For each item in inputProperties perform the following substeps:

    1. If the result of Type(item) is not String, throw a TypeError and abort all these steps.

    2. If item is a vendor prefixed property, item must be removed from inputProperties and the user agent should provide a warning to the author.

    Note: The list of CSS properties provided by the input properties getter can either be custom or native CSS properties.

    Note: The list of CSS properties may contain shorthands.

    Note: In order for a paint image class to be forwards compatible, the list of CSS properties can also contains currently invalid properties for the user agent. For example margin-bikeshed-property.

  6. If the result of IsConstructor(argument=paintCtor) is false, throw a TypeError and abort all these steps.

  7. Let prototype be the result of Get(O=paintCtor, P="prototype").

  8. If the result of Type(argument=prototype) is not Object, throw a TypeError and abort all these steps.

  9. If the result of IsCallable(argument=Get(O=prototype, P="paint")) is false, throw a TypeError and abort all these steps.

  10. Let definition be a new paint image definition with:

  11. Add the key-value pair (name - inputProperties) to the paint name to input properties map of the associated document.

  12. Add the key-value pair (name - definition) to the paint name to paint image definition map of the associated document.

Note: The list of input properties should only be looked up once, the class doesn’t have the opportunity to dynamically change its input properties.

Note: In a future version of the spec, the author may be able to set an option to receive a different type of RenderingContext. In particular the author may want a WebGL rendering context to render 3D effects. There are complexities in setting up a WebGL rendering context to take the Geometry and StylePropertyMap as inputs.

6. Paint Notation

paint() = paint( <ident> )

The <paint()> function is an additional notation to be supported by the <image> type.

background-image: paint(my_logo);

For the cursor property, the <paint()> function should be treated as an invalid image and fallback to the next supported <image>.

Support additional arbitrary arguments for the paint function. This is difficult to specify, as you need to define a sane grammar. A better way would be to expose a token stream which you can parse into Typed OM objects. This would allow a full arbitrary set of function arguments, and be future proof. <https://github.com/w3c/css-houdini-drafts/issues/100>

7. The 2D rendering context

[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D implements CanvasState;
PaintRenderingContext2D implements CanvasTransform;
PaintRenderingContext2D implements CanvasCompositing;
PaintRenderingContext2D implements CanvasImageSmoothing;
PaintRenderingContext2D implements CanvasFillStrokeStyles;
PaintRenderingContext2D implements CanvasShadowStyles;
PaintRenderingContext2D implements CanvasRect;
PaintRenderingContext2D implements CanvasDrawPath;
PaintRenderingContext2D implements CanvasDrawImage;
PaintRenderingContext2D implements CanvasPathDrawingStyles;
PaintRenderingContext2D implements CanvasPath;

Note: The PaintRenderingContext2D implements a subset of the CanvasRenderingContext2D API. Specifically it doesn’t implement the CanvasHitRegion, CanvasImageData, CanvasUserInterface, CanvasText or CanvasTextDrawingStyles APIs.

A PaintRenderingContext2D object has a scratch bitmap. This is initialised when the object is created. The size of the scratch bitmap is the size of the fragment it is rendering.

The size of the scratch bitmap does not necessarily represent the size of the actual bitmap that the user agent will use internally or during rendering. For example, if the visual viewport is zoomed the user agent may internally use bitmaps which correspond to the number of device pixels in the coordinate space, so that the resulting rendering is of high quality.

The PaintRenderingContext2D object should have its alpha flag set to true. That is the rendering context is initially fully transparent.

Additionally the user agent may record the sequence of drawing operations which have been applied to the scratch bitmap such that the user agent can subsequently draw onto a device bitmap at the correct resolution. This also allows user agents to re-use the same output of the scratch bitmap repeatably while the visual viewport is being zoomed for example.

When the user agent is to create a PaintRenderingContext2D object for a given width, height it must run the following steps:

  1. Create a new PaintRenderingContext2D.

  2. Set bitmap dimensions for the context’s scratch bitmap to width and height.

  3. Return the new PaintRenderingContext2D.

Note: The initial state of the rendering context is set inside the set bitmap dimensions algorithm, as it invokes reset the rendering context to its default state and clears the scratch bitmap.

8. Drawing an image

If a <paint()> function for a fragment is paint-invalid and the fragment is within the visual viewport, then user agent must draw a paint image for the current frame. The user agent may not defer the draw a paint image operation until a subsequent frame.

Note: The user agent may choose to draw a paint image for <paint()> functions not within the visual viewport.

If an author updates a style inside a requestAnimationFrame, e.g.
requestAnimationFrame(function() {
    element.styleMap.set('--custom-prop-invalidates-paint', 42);
});

And the element is inside the visual viewport, the user agent msut draw a paint image and display the result on the current frame.

The draw a paint image function should be invoked by the user agent during the object size negotiation algorithm which is responsible for rendering an <image>.

For the purposes of the object size negotiation algorithm, the paint image has no intrinsic dimensions.

Note: In a future version of the spec, the author may be able to specify the intrinsic dimensions of the paint image. This will probably be exposed as a callback allowing the author to define static intrinsic dimensions or dynamically updating the intrinsic dimensions based on computed style and geometry changes.

The Geometry object represents the geometry of the image that the author should draw. This is the concrete object size given by the user agent.

[Exposed=PaintWorklet]
interface Geometry {
    readonly attribute double width;
    readonly attribute double height;
};

When the user agent wants to draw a paint image of a <paint()> function for a box into its appropriate stacking level (as defined by the property the CSS property it’s associated with), given it’s concreteObjectSize (concrete object size) it must run the following steps:

  1. Let paintFunction be the current <paint()> function on the box.

  2. If the paint valid flag for the paintFunction is paint-valid the user agent may use the drawn image from the previous invocation. If so it may abort all these steps and use the previously drawn image.

    Note: The user agent for implementation reasons may also continue with all these steps in this case. It can do this every frame, or multiple times per frame.

  3. Let name be the first argument of the paintFunction.

  4. Let workletGlobalScope be a PaintWorkletGlobalScope from the list of worklet’s WorkletGlobalScopes from the paint Worklet.

    The user agent may also create a WorkletGlobalScope given the paint Worklet and use that.

    Note: The user agent may use any policy for which PaintWorkletGlobalScope to select or create. It may use a single PaintWorkletGlobalScope or multiple and randomly assign between them.

  5. Let definition be the result of looking up name on the workletGlobalScope’s paint name to paint image definition map.

    If definition does not exist, let the image output be an invalid image and abort all these steps.

  6. Let paintInstance be the result of looking up name on workletGlobalScope’s paint name to instance map. If paintInstance is null run the following substeps:

    1. If the class constructor valid flag on definition is false, let the image output be an invalid image and abort all these steps.

    2. Let paintCtor be the class constructor on definition.

    3. Let paintInstance be the result of Construct(paintCtor).

      If Construct throws an exception, set the definition’s constructor valid flag to false, let the image output be an invalid image and abort all these steps.

    4. Add the key-value pair (name - paintInstance) to the paint name to instance map of the workletGlobalScope.

  7. Let inputProperties be the result of looking up name on the associated document’s paint name to input properties map.

  8. Let styleMap be a new StylePropertyMapReadOnly populated with only the computed value’s for properties listed in inputProperties.

  9. Let renderingContext be the result of create a PaintRenderingContext2D object given:

    • "width" - The width given by concreteObjectSize.

    • "height" - The height given by concreteObjectSize.

    Note: The renderingContext must not be re-used between invocations of paint. Implicitly this means that there is no stored data, or state on the renderingContext between invocations. For example you can’t setup a clip on the context, and expect the same clip to be applied next time the paint method is called.

    Note: Implicitly this also means that renderingContext is effectively "neutered" after a paint method is complete. The author code may hold a reference to renderingContext and invoke methods on it, but this will have no effect on the current image, or subsequent images.

  10. Let geometry be a new Geometry initialized to the width and height defined by concreteObjectSize.

  11. Perform Invoke(O=paintInstance, P="paint", Arguments=[renderingContext, geometry, styleMap).

  12. The image output is to be produced from the renderingContext given to the method.

    If an exception is thrown the let the image output be an invalid image.

  13. Set the paint valid flag for the paintFunction to paint-valid.

Note: The user agent should consider long running paint functions similar to long running script in the main execution context. For example, they should show a "unresponsive script" dialog or similar. In addition user agents should provide tooling within their debugging tools to show authors how expensive their paint classes are.

Note: The contents of the resulting image are not designed to be accessible. Authors should communicate any useful information through the standard accessibility APIs.

9. Examples

9.1. Example 1: A colored circle.

<div id="myElement">
    CSS is awesome.
</div>

<style>
#myElement {
    --circle-color: red;
    background-image: paint(circle);
}
</style>
// Inside PaintWorkletGlobalScope.
registerPaint('circle', class {
    static get inputProperties() { return ['--circle-color']; }
    paint(ctx, geom, properties) {
        // Change the fill color.
        const color = properties.get('--circle-color');
        ctx.fillStyle = color;

        // Determine the center point and radius.
        const x = geom.width / 2;
        const y = geom.height / 2;
        const radius = Math.min(x, y);

        // Draw the circle \o/
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
        ctx.fill();
    }
});

9.2. Example 2: Image placeholder.

It is possible for an author to use paint to draw a placeholder image while an image is being loaded.

<div id="myElement">
</div>

<style>
#myElement {
    --image: url('#someUrlWhichIsLoading');
    background-image: paint(image-with-placeholder);
}
</style>

<script>
document.registerProperty({
    name: '--image',
    syntax: '<image>'
});
</script>
// Inside PaintWorkletGlobalScope.
registerPaint('circle', class {
    static get inputProperties() { return ['--image']; }
    paint(ctx, geom, properties) {
        const img = properties.get('--image');

        switch (img.state) {
            case 'ready':
                // The image is loaded! Draw the image.
                ctx.drawImage(img, 0, 0, geom.width, geom.height);
                break;
            case 'pending':
                // The image is loading, draw some mountains.
                drawMountains(ctx);
                break;
            case 'invalid':
            default:
                // The image is invalid (e.g. it didn’t load), draw a sad face.
                drawSadFace(ctx);
                break;
        }
    }
});

9.3. Example 3: Conic-gradient

Add conic-gradient as a use case once we have function arguments.

9.4. Example 4: Different color based on geometry

<h1>
Heading 1
</h1>
<h1>
Another heading
</h1>

<style>
h1 {
    background-image: paint(heading-color);
}
</style>
// Inside PaintWorkletGlobalScope.
registerPaint('heading-color', class {
    static get inputProperties() { return []; }
    paint(ctx, geom, properties) {
        // Select a color based on the width and height of the image.
        const width = geom.width;
        const height = geom.height;
        const color = colorArray[(width * height) % colorArray.length];

        // Draw just a solid image.
        ctx.fillStyle = color;
        ctx.fillRect(0, 0, width, height);
    }
});

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Experimental implementations

To avoid clashes with future CSS features, the CSS2.1 specification reserves a prefixed syntax for proprietary and experimental extensions to CSS.

Prior to a specification reaching the Candidate Recommendation stage in the W3C process, all implementations of a CSS feature are considered experimental. The CSS Working Group recommends that implementations use a vendor-prefixed syntax for such features, including those in W3C Working Drafts. This avoids incompatibilities with future changes in the draft.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at http://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-CASCADE-3]
Elika Etemad; Tab Atkins Jr.. CSS Cascading and Inheritance Level 3. 19 May 2016. CR. URL: http://www.w3.org/TR/css-cascade-3/
[CSS-IMAGES-3]
CSS Image Values and Replaced Content Module Level 3 URL: https://www.w3.org/TR/css3-images/
[CSS-TYPED-OM-1]
CSS Typed Object Model Level 1 URL: https://drafts.css-houdini.org/css-typed-om-1/
[CSS-UI-3]
Tantek Çelik; Florian Rivoal. CSS Basic User Interface Module Level 3 (CSS3 UI). 7 July 2015. CR. URL: http://www.w3.org/TR/css-ui-3/
[HTML]
Ian Hickson. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[MEDIAQUERIES-4]
Florian Rivoal; Tab Atkins Jr.. Media Queries Level 4. 26 January 2016. WD. URL: http://www.w3.org/TR/mediaqueries-4/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[WebIDL-1]
Cameron McCormack; Boris Zbarsky. WebIDL Level 1. 8 March 2016. CR. URL: http://www.w3.org/TR/WebIDL-1/
[WHATWG-DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[WORKLETS-1]
Worklets Level 1 URL: https://drafts.css-houdini.org/worklets/

IDL Index

partial interface Window {
    [SameObject] readonly attribute Worklet paintWorklet;
};

callback VoidFunction = void ();

[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
    void registerPaint(DOMString name, VoidFunction paintCtor);
};

[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D implements CanvasState;
PaintRenderingContext2D implements CanvasTransform;
PaintRenderingContext2D implements CanvasCompositing;
PaintRenderingContext2D implements CanvasImageSmoothing;
PaintRenderingContext2D implements CanvasFillStrokeStyles;
PaintRenderingContext2D implements CanvasShadowStyles;
PaintRenderingContext2D implements CanvasRect;
PaintRenderingContext2D implements CanvasDrawPath;
PaintRenderingContext2D implements CanvasDrawImage;
PaintRenderingContext2D implements CanvasPathDrawingStyles;
PaintRenderingContext2D implements CanvasPath;

[Exposed=PaintWorklet]
interface Geometry {
    readonly attribute double width;
    readonly attribute double height;
};

Issues Index

Support additional arbitrary arguments for the paint function. This is difficult to specify, as you need to define a sane grammar. A better way would be to expose a token stream which you can parse into Typed OM objects. This would allow a full arbitrary set of function arguments, and be future proof. <https://github.com/w3c/css-houdini-drafts/issues/100>
Add conic-gradient as a use case once we have function arguments.