-
Notifications
You must be signed in to change notification settings - Fork 143
/
Copy pathOverview.bs
553 lines (421 loc) · 21.9 KB
/
Overview.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
<pre class='metadata'>
Title: CSS Painting API Level 1
Status: ED
Group: houdini
ED: https://drafts.css-houdini.org/css-paint-api-1/
Previous Version: http://www.w3.org/TR/2016/WD-css-paint-api-1-20160607/
Shortname: css-paint-api
Level: 1
Abstract:
Editor: Shane Stephens, shanestephens@google.com
Editor: Ian Kilpatrick, ikilpatrick@chromium.org
Editor: Dean Jackson, dino@apple.com
</pre>
<pre class="link-defaults">
spec:css-break-3; type:dfn; text:fragment
</pre>
<pre class="anchors">
urlPrefix: https://heycam.github.io/webidl/; type: dfn;
text: NotSupportedError
urlPrefix: #dfn-;
text: callback this value
text: exception
text: throw
url: throw; text: thrown
url: es-invoking-callback-functions; text: Invoke
urlPrefix: https://html.spec.whatwg.org/multipage/scripting.html; type: dfn;
text: reset the rendering context to its default state
text: output bitmap
text: set bitmap dimensions
url: concept-canvas-alpha; text: alpha
urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
text: constructor
text: Construct
text: IsArray
text: IsCallable
text: IsConstructor
text: HasProperty
url: get-o-p; text: Get
url: terms-and-definitions-function; text: function
urlPrefix: native-error-types-used-in-this-standard-
text: TypeError
</pre>
Introduction {#intro}
=====================
The paint stage of CSS is responsible for painting the background, content and highlight of a
box based on that box's size (as generated by the layout stage) and computed style.
This specification describes an API which allows developers to paint a part of a box in
response to size / 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 a box (for example on the background layers).
Paint Invalidation {#paint-invalidation}
========================================
A <a>document</a> has an associated <dfn>paint name to input properties map</dfn>. Initially it is
empty and is populated when {{registerPaint(name, paintCtor)}} is called.
Each <<paint()>> function for a box has an associated <dfn>paint valid flag</dfn>. It may be either
<dfn>paint-valid</dfn> or <dfn>paint-invalid</dfn>. It is initially set to <a>paint-invalid</a>.
When the size (as determined by layout) of a |box| changes, each <<paint()>> function's <a>paint
valid flag</a> should be set to <a>paint-invalid</a>.
When the computed style for a |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 <a>paint name to input
properties map</a>.
4. For each |property| in |inputProperties|, if the |property|'s <a>computed value</a> has
changed, set the <a>paint valid flag</a> on the |paintFunction| to <a>paint-invalid</a>.
Performing <a>draw a paint image</a> results in the <a>paint valid flag</a> for a <<paint()>>
function on a box to be set to <a>paint-valid</a>.
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.
Paint Worklet {#paint-worklet}
==============================
The {{paintWorklet}} attribute allows access to the {{Worklet}} responsible for all the classes
which are related to painting.
The {{paintWorklet}}'s <a>worklet global scope type</a> is {{PaintWorkletGlobalScope}}.
<pre class='idl'>
partial interface Window {
[SameObject] readonly attribute Worklet paintWorklet;
};
</pre>
The {{PaintWorkletGlobalScope}} is the global execution context of the {{paintWorklet}}.
<pre class='idl'>
callback VoidFunction = void ();
[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
void registerPaint(DOMString name, VoidFunction paintCtor);
};
</pre>
<div class='note'>
Note: This is how the class should look.
<pre class='idl'>
callback interface PaintClass {
readonly attribute sequence<DOMString> inputProperties;
void paint(PaintRenderingContext2D ctx, PaintSize geom, StylePropertyMap inputProperties);
};
</pre>
</div>
Concepts {#concepts}
====================
A <dfn>paint image definition</dfn> describes an author defined <<image>> which can be referenced by
the <<paint()>> function. It consists of:
- A <dfn>paint image name</dfn>
- A <dfn>class constructor</dfn> which is the class <a>constructor</a>
- A <dfn>paint function</dfn> which is the paint <a>function</a> callback
- A <dfn>class constructor valid flag</dfn>
Registering Custom Paint {#registering-custom-paint}
====================================================
The {{PaintWorkletGlobalScope}} has a <dfn>paint name to paint image definition map</dfn>. Initially
this map is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.
The {{PaintWorkletGlobalScope}} has a <dfn>paint name to instance map</dfn>. Initially this map is
empty; it is populated when <a>draw a paint image</a> is invoked by the user agent.
Instances of paint classes in the <a>paint name to instance map</a> 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 <dfn method for=PaintWorkletGlobalScope>registerPaint(|name|, |paintCtor|)</dfn> method is
called, the user agent <em>must</em> run the following steps:
1. If the |name| is not a valid <<ident>>, <a>throw</a> a <a>TypeError</a> and abort all these
steps.
2. If the |name| exists as a key in the <a>paint name to paint image definition map</a>,
<a>throw</a> a <a>NotSupportedError</a> and abort all these steps.
3. Let |inputProperties| be the result of <a>Get</a>(|paintCtor|, "inputProperties").
4. If |inputProperties| is not undefined, and the result of <a>IsArray</a>(|inputProperties|) is
false, <a>throw</a> a <a>TypeError</a> 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 <a>Type</a>(|item|) is not String, <a>throw</a> a <a>TypeError</a>
and abort all these steps.
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
<code>margin-bikeshed-property</code>.
5. If the result of <a>IsConstructor</a>(|paintCtor|) is false, <a>throw</a> a <a>TypeError</a>
and abort all these steps.
6. Let |prototype| be the result of <a>Get</a>(|paintCtor|, "prototype").
7. If the result of <a>Type</a>(|prototype|) is not Object, <a>throw</a> a <a>TypeError</a> and
abort all these steps.
8. Let |paint| be the result of <a>Get</a>(|prototype|, "paint").
9. If the result of <a>IsCallable</a>(|paint|) is false, <a>throw</a> a <a>TypeError</a> and
abort all these steps.
10. Let |definition| be a new <a>paint image definition</a> with:
- <a>paint image name</a> being |name|
- <a>class constructor</a> being |constructor|
- <a>paint function</a> being |paint|
- <a>class constructor valid flag</a> being true
11. Add the key-value pair (|name| - |inputProperties|) to the <a>paint name to input properties
map</a> of the associated <a>document</a>.
12. Add the key-value pair (|name| - |definition|) to the <a>paint name to paint image
definition map</a> of the associated <a>document</a>.
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
{{PaintSize}} and {{StylePropertyMap}} as inputs.
Paint Notation {#paint-notation}
================================
<pre class='prod'>
<dfn>paint()</dfn> = paint( <<ident>> )
</pre>
The <<paint()>> function is an additional notation to be supported by the <<image>> type.
<div class="example">
<pre>background-image: paint(my_logo);</pre>
</div>
For the 'cursor' property, the <<paint()>> function should be treated as an <a>invalid image</a> and
fallback to the next supported <<image>>.
Issue(w3c/css-houdini-drafts#100): 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.
The 2D rendering context {#2d-rendering-context}
================================================
<pre class='idl'>
[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;
</pre>
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 <a>output bitmap</a>. This is initialised when the
object is created. The size of the <a>output bitmap</a> is the size of the fragment it is
rendering.
The size of the <a>output bitmap</a> 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 <a>alpha</a> 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 <a>output bitmap</a> 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 <a>output
bitmap</a> repeatably while the visual viewport is being zoomed for example.
When the user agent is to <dfn>create a PaintRenderingContext2D object</dfn> for a given |width|,
|height| it <em>must</em> run the following steps:
1. Create a new {{PaintRenderingContext2D}}.
2. <a>Set bitmap dimensions</a> for the context's <a>output bitmap</a> to |width| and |height|.
3. Return the new {{PaintRenderingContext2D}}.
Note: The initial state of the rendering context is set inside the <a>set bitmap dimensions</a>
algorithm, as it invokes <a>reset the rendering context to its default state</a> and clears the
<a>output bitmap</a>.
Drawing an image {#drawing-an-image}
====================================
If a <<paint()>> function for a fragment is <a>paint-invalid</a> and the fragment is within the
visual viewport, then user agent <em>must</em> <a>draw a paint image</a> for the current frame. The
user agent <em>may not</em> defer the <a>draw a paint image</a> operation until a subsequent frame.
Note: The user agent may choose to <a>draw a paint image</a> for <<paint()>> functions not within
the visual viewport.
<div class="example">
If an author updates a style inside a <code>requestAnimationFrame</code>, e.g.
<pre class='lang-javascript'>
requestAnimationFrame(function() {
element.styleMap.set('--custom-prop-invalidates-paint', 42);
});
</pre>
And the <code>element</code> is inside the visual viewport, the user agent <em>must</em> <a>draw
a paint image</a> and display the result on the current frame.
</div>
The <a>draw a paint image</a> function should be invoked by the user agent during the <a>object size
negotiation</a> algorithm which is responsible for rendering an <<image>>.
For the purposes of the <a>object size negotiation</a> algorithm, the paint image has no
<a>intrinsic dimensions</a>.
Note: In a future version of the spec, the author may be able to specify the <a>intrinsic
dimensions</a> of the paint image. This will probably be exposed as a callback allowing the
author to define static <a>intrinsic dimensions</a> or dynamically updating the <a>intrinsic
dimensions</a> based on computed style and size changes.
The {{PaintSize}} object represents the size of the image that the author should draw. This is
the <a>concrete object size</a> given by the user agent.
<pre class='idl'>
[Exposed=PaintWorklet]
interface PaintSize {
readonly attribute double width;
readonly attribute double height;
};
</pre>
When the user agent wants to <dfn>draw a paint image</dfn> 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| (<a>concrete object size</a>) it <em>must</em> run the
following steps:
1. Let |paintFunction| be the current <<paint()>> function on the |box|.
2. If the <a>paint valid flag</a> for the |paintFunction| is <a>paint-valid</a> the user agent
<em>may</em> use the drawn image from the previous invocation. If so it <em>may</em> 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 <a>worklet's
WorkletGlobalScopes</a> from the paint {{Worklet}}.
The user agent <em>may</em> also <a>create a WorkletGlobalScope</a> given the paint
{{Worklet}} and use that.
Note: The user agent <em>may</em> 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 <a>paint
name to paint image definition map</a>.
If |definition| does not exist, let the image output be an <a>invalid image</a> and abort
all these steps.
6. Let |paintInstance| be the result of looking up |name| on |workletGlobalScope|'s <a>paint
name to instance map</a>. If |paintInstance| is null run the following substeps:
1. If the <a>class constructor valid flag</a> on |definition| is false, let the image output
be an <a>invalid image</a> and abort all these steps.
2. Let |paintCtor| be the <a>class constructor</a> on |definition|.
3. Let |paintInstance| be the result of <a>Construct</a>(|paintCtor|).
If <a>Construct</a> throws an exception, set the |definition|'s <a>constructor valid
flag</a> to false, let the image output be an <a>invalid image</a> and abort all these
steps.
4. Add the key-value pair (|name| - |paintInstance|) to the <a>paint name to instance
map</a> of the |workletGlobalScope|.
7. Let |inputProperties| be the result of looking up |name| on the associated <a>document</a>'s
<a>paint name to input properties map</a>.
8. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with <em>only</em> the
<a>computed value</a>'s for properties listed in |inputProperties|.
9. Let |renderingContext| be the result of <a>create a PaintRenderingContext2D object</a> 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 |paintSize| be a new {{PaintSize}} initialized to the width and height defined by
|concreteObjectSize|.
11. Let |paintFunction| be |definition|'s <a>paint function</a>.
12. <a>Invoke</a> |paintFunction| with arguments «|renderingContext|, |paintSize|, |styleMap|»,
and with |paintInstance| as the <a>callback this value</a>.
13. The image output is to be produced from the |renderingContext| given to the method.
If an exception is <a>thrown</a> the let the image output be an <a>invalid image</a>.
14. Set the <a>paint valid flag</a> for the |paintFunction| to <a>paint-valid</a>.
Note: The user agent <em>should</em> consider long running paint functions similar to long running
script in the main execution context. For example, they <em>should</em> show a "unresponsive
script" dialog or similar. In addition user agents <em>should</em> 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 <em>should</em>
communicate any useful information through the standard accessibility APIs.
Examples {#examples}
====================
Example 1: A colored circle. {#example-1}
-----------------------------------------
<pre class='lang-markup'>
<div id="myElement">
CSS is awesome.
</div>
<style>
#myElement {
--circle-color: red;
background-image: paint(circle);
}
</style>
</pre>
<pre class='lang-javascript'>
// 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();
}
});
</pre>
Example 2: Image placeholder. {#example-2}
------------------------------------------
It is possible for an author to use paint to draw a placeholder image while an image is being
loaded.
<pre class='lang-markup'>
<div id="myElement">
</div>
<style>
#myElement {
--image: url('#someUrlWhichIsLoading');
background-image: paint(image-with-placeholder);
}
</style>
<script>
document.registerProperty({
name: '--image',
syntax: '<image>'
});
</script>
</pre>
<pre class='lang-javascript'>
// 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;
}
}
});
</pre>
Example 3: Conic-gradient {#example-3}
--------------------------------------
Issue: Add conic-gradient as a use case once we have function arguments.
Example 4: Different color based on size {#example-4}
-----------------------------------------------------
<pre class='lang-markup'>
<h1>
Heading 1
</h1>
<h1>
Another heading
</h1>
<style>
h1 {
background-image: paint(heading-color);
}
</style>
</pre>
<pre class='lang-javascript'>
// 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);
}
});
</pre>