-
Notifications
You must be signed in to change notification settings - Fork 143
/
Copy pathOverview.bs
1024 lines (791 loc) · 39.6 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
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<pre class='metadata'>
Title: CSS Painting API Level 1
Status: ED
Group: houdini
ED: https://drafts.css-houdini.org/css-paint-api-1/
TR: http://www.w3.org/TR/css-paint-api-1/
Previous Version: https://www.w3.org/TR/2018/WD-css-paint-api-1-20180410/
Previous Version: https://www.w3.org/TR/2016/WD-css-paint-api-1-20160607/
Shortname: css-paint-api
Level: 1
Abstract:
An API for allowing web developers to define a custom CSS <<image>> with javascript, which will
respond to style and size changes.
See <a href="https://github.com/w3c/css-houdini-drafts/blob/master/css-paint-api/EXPLAINER.md">EXPLAINER</a>.
Former Editor: Shane Stephens, [email protected], w3cid 47691
Editor: Ian Kilpatrick, [email protected], w3cid 73001
Editor: Dean Jackson, [email protected], w3cid 42080
Ignored Terms: PaintWorklet
</pre>
<style>
/* Put nice boxes around each algorithm. */
[data-algorithm]:not(.heading) {
padding: .5em;
border: thin solid #ddd; border-radius: .5em;
margin: .5em calc(-0.5em - 1px);
}
[data-algorithm]:not(.heading) > :first-child {
margin-top: 0;
}
[data-algorithm]:not(.heading) > :last-child {
margin-bottom: 0;
}
</style>
<pre class="link-defaults">
spec:infra; type:dfn; text:list
spec:webidl; type:dfn; text:converting
spec:html; type:dfn;
text:set bitmap dimensions;
text:reset the rendering context to its default state
</pre>
<pre class="anchors">
urlPrefix: https://tc39.github.io/ecma262/#sec-; type: dfn;
text: constructor
text: Construct
text: IsArray
text: IsCallable
text: IsConstructor
url: ecmascript-data-types-and-values; text: type
url: get-o-p; text: Get
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 could be added for defining the clip, global alpha,
filter on a portion of a box (for example on the background layers).
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 [=worklet global scope type=] is {{PaintWorkletGlobalScope}}.
The {{paintWorklet}}'s <a>worklet destination type</a> is <code>"paintworklet"</code>.
<pre class='idl'>
partial namespace CSS {
[SameObject] readonly attribute Worklet paintWorklet;
};
</pre>
A {{PaintWorkletGlobalScope}} is a global execution context of the {{paintWorklet}}.
A {{PaintWorkletGlobalScope}} has a {{PaintWorkletGlobalScope/devicePixelRatio}} property which is
identical to the Window.{{Window/devicePixelRatio}} property.
<pre class='idl'>
[Global=(Worklet,PaintWorklet),Exposed=PaintWorklet]
interface PaintWorkletGlobalScope : WorkletGlobalScope {
undefined registerPaint(DOMString name, VoidFunction paintCtor);
readonly attribute unrestricted double devicePixelRatio;
};
</pre>
The {{PaintRenderingContext2DSettings}} contains the settings for the rendering context associated
with the paint canvas. The {{PaintRenderingContext2DSettings}} provides a supported subset of canvas
rendering context 2D settings. In the future, it may be extended to support color management in
paint canvas.
<pre class='idl'>
dictionary PaintRenderingContext2DSettings {
boolean alpha = true;
};
</pre>
<div class='note'>
Note: The shape of the class should be:
<pre class='lang-javascript'>
class MyPaint {
static get inputProperties() { return ['--foo']; }
static get inputArguments() { return ['<color>']; }
static get contextOptions() { return {alpha: true}; }
paint(ctx, size, styleMap) {
// Paint code goes here.
}
}
</pre>
</div>
Concepts {#concepts}
====================
A <dfn>paint definition</dfn> is a [=struct=] which describes the information needed by the
{{PaintWorkletGlobalScope}} about the author defined <<image>> (which can be referenced by the
<<paint()>> function). It consists of:
- <dfn for="paint definition">class constructor</dfn> which is the class [=constructor=].
- <dfn for="paint definition">paint function</dfn> which is the paint [=Function=]
[=callback function=] type.
- <dfn for="paint definition">constructor valid flag</dfn>.
- <dfn for="paint definition">input properties</dfn> which is a [=list=] of
<code>DOMStrings</code>.
- A <dfn for="paint definition">PaintRenderingContext2DSettings object</dfn>.
A <dfn>document paint definition</dfn> is a [=struct=] which describes the information
needed by the [=document=] about the author defined <<image>> function (which can be referenced
by the paint function). It consists of:
- A <dfn for="document paint definition">input properties</dfn> which is a [=list=] of
<code>DOMStrings</code>.
- A <dfn for="document paint definition">input argument syntaxes</dfn> which is a [=list=] of
[=syntax definitions=].
- A <dfn for="document paint definition">PaintRenderingContext2DSettings object</dfn>.
Registering Custom Paint {#registering-custom-paint}
====================================================
The [=document=] has a [=map=] of <dfn>document paint definitions</dfn>. Initially
this map is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.
A {{PaintWorkletGlobalScope}} has a [=map=] of <dfn>paint definitions</dfn>. Initially this map
is empty; it is populated when {{registerPaint(name, paintCtor)}} is called.
A {{PaintWorkletGlobalScope}} has a [=map=] of <dfn>paint class instances</dfn>. 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 class instances=] 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.
<div algorithm>
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 an empty string, [=throw=] a [=TypeError=] and abort all these steps.
2. Let |paintDefinitionMap| be {{PaintWorkletGlobalScope}}'s [=paint definitions=] map.
3. If |paintDefinitionMap|[|name|] [=map/exists=] [=throw=] a
"{{InvalidModificationError}}" {{DOMException}} and abort all these steps.
4. Let |inputProperties| be an empty <code>sequence<DOMString></code>.
5. Let |inputPropertiesIterable| be the result of [=Get=](|paintCtor|, "inputProperties").
6. If |inputPropertiesIterable| is not undefined, then set |inputProperties| to the result of
[=converting=] |inputPropertiesIterable| to a <code>sequence<DOMString></code>. If an
exception is [=thrown=], rethrow the exception and abort all these steps.
7. Filter |inputProperties| so that it only contains [=supported CSS properties=] and
[=custom properties=].
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>.
8. Let |inputArguments| be an empty <code>sequence<DOMString></code>.
9. Let |inputArgumentsIterable| be the result of [=Get=](|paintCtor|, "inputArguments").
10. If |inputArgumentsIterable| is not undefined, then set |inputArguments| to the result of
[=converting=] |inputArgumentsIterable| to a <code>sequence<DOMString></code>. If an
exception is thrown, rethrow the exception and abort all these steps.
11. Let |inputArgumentSyntaxes| be an [=list/empty=] [=list=].
12. [=list/For each=] |item| in |inputArguments| perform the following substeps:
1. Attempt to [=consume a syntax definition=] from |item|.
If failure was returned, [=throw=] a [=TypeError=] and abort all these steps.
Otherwise, let |parsedSyntax| be the returned [=syntax definition=].
2. [=list/Append=] |parsedSyntax| to |inputArgumentSyntaxes|.
13. Let |contextOptionsValue| be the result of [=Get=](|paintCtor|, "contextOptions").
14. Let |paintRenderingContext2DSettings| be the result of [=converting=]
|contextOptionsValue| to a {{PaintRenderingContext2DSettings}}.
If an exception is [=thrown=], rethrow the exception and abort all these steps.
Note: Setting <code>paintRenderingContext2DSettings.alpha</code> is <code>false</code> allows user agents
to anti-alias text in addition to performing "visibility" optimizations, e.g. not
painting an image behind the paint image as the paint image is opaque.
15. If the result of [=IsConstructor=](|paintCtor|) is false, [=throw=] a [=TypeError=]
and abort all these steps.
16. Let |prototype| be the result of [=Get=](|paintCtor|, "prototype").
17. If the result of [=Type=](|prototype|) is not Object, [=throw=] a [=TypeError=] and
abort all these steps.
18. Let |paintValue| be the result of [=Get=](|prototype|, "paint").
19. Let |paint| be the result of [=converting=] |paintValue| to the [=Function=]
[=callback function=] type. Rethrow any exceptions from the conversion.
20. Let |definition| be a new [=paint definition=] with:
- [=paint definition/class constructor=] being |paintCtor|.
- [=paint function=] being |paint|.
- [=paint definition/constructor valid flag=] being <b>true</b>.
- [=paint definition/input properties=] being |inputProperties|.
- [=paint definition/PaintRenderingContext2DSettings object=] being |paintRenderingContext2DSettings|.
21. [=map/Set=] |paintDefinitionMap|[|name|] to |definition|.
22. [=Queue a task=] to run the following steps:
1. Let |documentPaintDefinitionMap| be the associated [=document's=] [=document paint
definitions=] [=map=].
2. Let |documentDefinition| be a new [=document paint definition=] with:
- [=document paint definition/input properties=] being |inputProperties|.
- [=document paint definition/input argument syntaxes=] being
|inputArgumentSyntaxes|.
- [=document paint definition/PaintRenderingContext2DSettings object=] being |paintRenderingContext2DSettings|.
3. If |documentPaintDefinitionMap|[|name|] [=map/exists=], run the following steps:
1. Let |existingDocumentDefinition| be the result of [=map/get=]
|documentPaintDefinitionMap|[|name|].
2. If |existingDocumentDefinition| is <code>"invalid"</code>, abort all these steps.
3. If |existingDocumentDefinition| and |documentDefinition| are not equivalent, (that is
[=document paint definition/input properties=], <a for="document paint
definition">input argument syntaxes</a>, and <a for="document paint
definition">PaintRenderingContext2DSettings object</a> are different), then:
[=map/Set=] |documentPaintDefinitionMap|[|name|] to <code>"invalid"</code>.
Log an error to the debugging console stating that the same class was registered
with different <code>inputProperties</code>, <code>inputArguments</code>, or
<code>paintRenderingContext2DSettings</code>.
4. Otherwise, [=map/set=] |documentPaintDefinitionMap|[|name|] to
|documentDefinition|.
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 could have the ability 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.
</div>
Paint Notation {#paint-notation}
================================
<pre class='prod'>
<dfn>paint()</dfn> = paint( <<ident>>, <<declaration-value>>? )
</pre>
The <<paint()>> function is an additional notation to be supported by the <<image>> type.
<div class="example">
<pre class=lang-markup>
<style>
.logo { background-image: paint(company-logo); }
.chat-bubble { background-image: paint(chat-bubble, blue); }
</style>
</pre>
</div>
For the 'cursor' property, the <<paint()>> function should be treated as an [=invalid image=] and
fallback to the next supported <<image>>.
At [=computed value=] time the <<paint()>> function does <em>not</em> need to match the grammar
registered by {{registerPaint()}}. Instead this will result in an [=invalid image=] when the
parsing occurs inside [=draw a paint image=].
The 2D rendering context {#2d-rendering-context}
================================================
<pre class='idl'>
[Exposed=PaintWorklet]
interface PaintRenderingContext2D {
};
PaintRenderingContext2D includes CanvasState;
PaintRenderingContext2D includes CanvasTransform;
PaintRenderingContext2D includes CanvasCompositing;
PaintRenderingContext2D includes CanvasImageSmoothing;
PaintRenderingContext2D includes CanvasFillStrokeStyles;
PaintRenderingContext2D includes CanvasShadowStyles;
PaintRenderingContext2D includes CanvasRect;
PaintRenderingContext2D includes CanvasDrawPath;
PaintRenderingContext2D includes CanvasDrawImage;
PaintRenderingContext2D includes CanvasPathDrawingStyles;
PaintRenderingContext2D includes CanvasPath;
PaintRenderingContext2D includes CanvasFilters;
</pre>
Note: The {{PaintRenderingContext2D}} implements a subset of the {{CanvasRenderingContext2D}} API.
Specifically it doesn't implement the {{CanvasImageData}}, {{CanvasUserInterface}},
{{CanvasText}}, or {{CanvasTextDrawingStyles}} APIs.
A {{PaintRenderingContext2D}} object has a <dfn for=PaintRenderingContext2D>output bitmap</dfn>.
This is initialised when the object is created.
The size of the [=PaintRenderingContext2D/output bitmap=] is the [=concrete object size=]
of the object it is rendering to.
A {{PaintRenderingContext2D}} object also has an <dfn for="PaintRenderingContext2D">alpha</dfn> flag,
which can be set to true or false.
Initially, when the context is created,
its alpha flag must be set to true.
When a {{PaintRenderingContext2D}} object has its alpha flag set to false,
then its alpha channel must be fixed to 1.0 (fully opaque) for all pixels,
and attempts to change the alpha component of any pixel must be silently ignored.
The size of the [=PaintRenderingContext2D/output 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.
Additionally the user agent may record the sequence of drawing operations which have been applied to
the [=PaintRenderingContext2D/output 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 [=PaintRenderingContext2D/output bitmap=] repeatably while the visual viewport is being zoomed for example.
Whenever <code>"currentColor"</code> is used as a color in the {{PaintRenderingContext2D}} API, it
is treated as opaque black.
<div class=example>
The code below will produce a solid black rectange.
<pre class=lang-javascript>
registerPaint('currentcolor', class {
paint(ctx, size) {
ctx.fillStyle = 'currentColor';
ctx.fillRect(0, 0, size.width, size.height);
}
});
</pre>
</div>
<div algorithm>
When the user agent is to <dfn>create a PaintRenderingContext2D object</dfn> for a given |width|,
|height|, and |paintRenderingContext2DSettings|, it <em>must</em> run the following steps:
1. Create a new {{PaintRenderingContext2D}}.
2. [=Set bitmap dimensions=] for the context's [=PaintRenderingContext2D/output bitmap=] to the rounded values of |width| and |height|.
3. Set the {{PaintRenderingContext2D}}'s [=PaintRenderingContext2D/alpha=] flag to |paintRenderingContext2DSettings|'s {{alpha}}.
4. 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
[=PaintRenderingContext2D/output bitmap=].
</div>
Drawing a CSSImageValue {#drawing-a-cssimagevalue}
--------------------------------------------------
The {{CanvasImageSource}} typedef is extended to also include the {{CSSImageValue}} type to be used
as an image source.
For interfaces which use the {{CanvasDrawImage}} mixin:
- When a {{CanvasImageSource}} object represents an {{CSSImageValue}}, the result of invoking
the value's underlying image algorithm must be used as the source image for the purposes of
{{CanvasDrawImage/drawImage}}.
Note: This should eventually be moved to the canvas section of the HTML specification.
See <a href="https://github.com/w3c/css-houdini-drafts/issues/819">Issue 819</a>.
Drawing an image {#drawing-an-image}
====================================
If a <<paint()>> function image for a [=box=] is within the visual viewport, the user agent
<em>must</em> display an image output from an invocation of the [=draw a paint image=] algorithm.
Note: The user agent doesn't have to run [=draw a paint image=] each frame for a <<paint()>>
function within the visual viewport. It can cache results, (potentially using additional
invalidation steps) to display the correct image output.
Note: The user agent can optionally defer drawing images which are outside 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 is required to
[=draw a paint image=] and display the result for the current frame.
</div>
The [=draw a paint image=] function is invoked by the user agent during the [=object size
negotiation=] algorithm which is responsible for rendering an <<image>>, with
|snappedConcreteObjectSize| defined as follows. Let |concreteObjectSize| be the [=concrete object
size=] of the [=box=]. The |snappedConcreteObjectSize| is usually the same as the
|concreteObjectSize|. However, the user agent may adjust the size such that it paints to pixel
boundaries. If it does, the user agent should adjust the |snappedConcreteObjectSize| by the
proportional change from its original size such that the <<paint()>> function can adjust the drawing
accordingly.
For the purposes of the [=object size negotiation=] algorithm,
the paint image has no [=natural dimensions=].
Note: In a future version of the spec, the author could have the ability to specify the [=natural
dimensions=] of the paint image. This will probably be exposed as a callback allowing the
author to define static [=natural dimensions=] or dynamically updating the [=natural
dimensions=] based on computed style and size changes.
The {{PaintSize}} object represents the size of the image that the author should draw. This is
the |snappedConcreteObjectSize| given by the user agent.
Note: See [[css-images-3#object-sizing-examples]] for examples on how the [=concrete object
size=] is calculated.
The [=draw a paint image=] function may be speculatively invoked by the user agent at any point,
with any |snappedConcreteObjectSize|. The resulting image is not displayed.
Note: User agents may use any heuristic to speculate a possible future value for
|snappedConcreteObjectSize|, for example speculating that the size remains unchanged.
Note: Although the image is not displayed, it may still be cached, and subsequent invocations of
<<paint()>> may use the cached image.
<pre class='idl'>
[Exposed=PaintWorklet]
interface PaintSize {
readonly attribute double width;
readonly attribute double height;
};
</pre>
<div algorithm>
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 its associated
with), given |snappedConcreteObjectSize| it <em>must</em> run the following steps:
1. Let |paintFunction| be the <<paint()>> function on the |box| which the user agent wants to
draw.
2. Let |name| be the first argument of the |paintFunction|.
3. Let |documentPaintDefinitionMap| be the associated [=document's=] [=document paint
definitions=] map.
4. If |documentPaintDefinitionMap|[|name|] does not [=map/exist=], let the image output
be an [=invalid image=] and abort all these steps.
5. Let |documentDefinition| be the result of [=map/get=]
|documentPaintDefinitionMap|[|name|].
6. If |documentDefinition| is <code>"invalid"</code>, let the image output be an [=invalid
image=] and abort all these steps.
7. Let |inputArgumentSyntaxes| be |documentDefinition|'s <a for="document paint
definition">input argument syntaxes</a>.
8. Let |inputArguments| be the [=list=] of all the |paintFunction| arguments <em>after</em>
the "paint name" argument.
9. If |inputArguments| do not match the registered grammar given by |inputArgumentSyntaxes|, let
the image output be an [=invalid image=] and abort all these steps.
<div class=example>
This step may fail in the following cases:
<pre class=lang-javascript>
// paint.js
registerPaint('failing-argument-syntax', class {
static get inputArguments() { return ['<length>']; }
paint(ctx, size, styleMap, args) { /* paint code here. */ }
});
</pre>
<pre class=lang-markup>
<style>
.example-1 {
background-image: paint(failing-argument-syntax, red);
}
.example-2 {
background-image: paint(failing-argument-syntax, 1px, 2px);
}
</style>
<div class=example-1></div>
<div class=example-2></div>
<script>
CSS.paintWorklet.addModule('paint.js');
</script>
</pre>
<code>example-1</code> produces an [=invalid image=] as <code>"red"</code> does not
match the registered grammar.
<code>example-2</code> produces an [=invalid image=] as there are too many function
arguments.
</div>
10. Let |workletGlobalScope| be a {{PaintWorkletGlobalScope}} from the the paint {{Worklet}}'s
[=Worklet/global scopes=], following the rules defined in [[#global-scope-selection]].
The user agent <em>may</em> also [=create a worklet global scope=] at this time, given the
paint {{Worklet}}.
11. Run [=invoke a paint callback=] given |name|, |inputArguments|, |snappedConcreteObjectSize|,
|workletGlobalScope| optionally [=in parallel=].
Note: If the user agent runs [=invoke a paint callback=] on a thread [=in parallel=],
it should select a paint worklet global scope which can be used on that thread.
</div>
<div algorithm>
When the user agent wants to <dfn>invoke a paint callback</dfn> given |name|, |inputArguments|,
|snappedConcreteObjectSize|, and |workletGlobalScope|, it <em>must</em> run the following steps:
1. Let |paintDefinitionMap| be |workletGlobalScope|'s [=paint definitions=] map.
2. If |paintDefinitionMap|[|name|] does not [=map/exist=], run the following steps:
1. [=Queue a task=] to run the following steps:
1. Let |documentPaintDefinitionMap| be the associated [=document=]'s [=document
paint definitions=] map.
2. [=map/Set=] |documentPaintDefinitionMap|[|name|] to <code>"invalid"</code>.
3. The user agent <em>should</em> log an error to the debugging console stating that a
class wasn't registered in all {{PaintWorkletGlobalScope}}s.
2. Let the image output be an [=invalid image=] and abort all these steps.
Note: This handles the case where there could be a paint worklet global scope which didn't
receive the {{registerPaint(name, paintCtor)}} for |name| (however another global scope
did). A paint callback which is invoked on the other global scope could succeed, but
wont succeed on a subsequent frame when [=draw a paint image=] is called.
3. Let |definition| be the result of [=get=] |paintDefinitionMap|[|name|].
4. Let |paintClassInstanceMap| be |workletGlobalScope|'s [=paint class instances=] map.
5. Let |paintInstance| be the result of [=get=] |paintClassInstanceMap|[|name]|. If
|paintInstance| is null, run the following steps:
1. If the [=paint definition/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 [=paint definition/class constructor=] on |definition|.
3. Let |paintInstance| be the result of [=Construct=](|paintCtor|).
If [=construct=] throws an exception,
set the |definition|'s [=paint definition/constructor valid flag=] to false,
let the image output be an [=invalid image=] and abort all these
steps.
4. [=map/Set=] |paintClassInstanceMap|[|name|] to |paintInstance|.
6. Let |inputProperties| be |definition|'s [=paint definition/input properties=].
7. Let |styleMap| be a new {{StylePropertyMapReadOnly}} populated with <em>only</em> the
[=computed value=]'s for properties listed in |inputProperties|.
8. Let |renderingContext| be the result of [=create a PaintRenderingContext2D object=] given:
- "width" - The width given by |snappedConcreteObjectSize|.
- "height" - The height given by |snappedConcreteObjectSize|.
- "paintRenderingContext2DSettings" - The
[=paint definition/PaintRenderingContext2DSettings object=] given by |definition|.
Note: The |renderingContext| is 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.
9. Let |paintSize| be a new {{PaintSize}} initialized to the width and height defined by
|snappedConcreteObjectSize|.
10. At this stage the user agent may re-use an image from a previous invocation if |paintSize|,
|styleMap|, |inputArguments| are equivalent to that previous invocation. If so let the image
output be that cached image and abort all these steps.
<div class=note>
In the example below, both <code>div-1</code> and <code>div-2</code> have paint
functions which have equivalent javascript arguments. A user-agent can cache the result
of one invocation and use it for both elements.
<pre class=lang-javascript>
// paint.js
registerPaint('simple', class {
paint(ctx, size) {
ctx.fillStyle = 'green';
ctx.fillRect(0, 0, size.width, size.height);
}
});
</pre>
<pre class=lang-markup>
<style>
.div-1 {
width: 50px;
height: 50px;
background-image: paint(simple);
}
.div-2 {
width: 100px;
height: 100px;
background-size: 50% 50%;
background-image: paint(simple);
}
</style>
<div class=div-1></div>
<div class=div-2></div>
<script>
CSS.paintWorklet.addModule('paint.js');
</script>
</pre>
</div>
11. Let |paintFunctionCallback| be |definition|'s [=paint function=].
12. [=Invoke=] |paintFunctionCallback| with arguments «|renderingContext|, |paintSize|,
|styleMap|, |inputArguments|», and with |paintInstance| as the [=callback this value=].
If |paintFunctionCallback| does not complete within an acceptable time (as determined by the
user agent, i.e. it is a "long running script") the user agent <em>may</em> terminate the
script, let the image output be an [=invalid image=], and abort all these steps.
Note: User agents could provide tooling within their debugging tools to show authors how
expensive their paint classes are. User agents could also how an "unresponsive script"
dialog in this case if appropriate.
13. 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=].
Note: The contents of the resulting image are not designed to be accessible. Authors can communicate
any useful information through the standard accessibility APIs.
</div>
Global Scope Selection {#global-scope-selection}
------------------------------------------------
When the user agent needs to select a {{PaintWorkletGlobalScope}} from the paint {{Worklet}}'s
[=Worklet/global scopes=] [=list=] it <em>must</em>:
- Select from at <em>least</em> two {{PaintWorkletGlobalScope}}s, unless the user agent is under
memory constraints.
- <em>Not</em> re-use the same {{PaintWorkletGlobalScope}} more than 1000 times in a row.
Note: The 1000 limit was picked as a high upper bound, this limit may improve (downwards)
over time.
Note: These rules exist to ensure that authors do not rely on being able to store state on the
global object or non-regeneratable state on the class. See
<a href="https://html.spec.whatwg.org/multipage/worklets.html#worklets-idempotent">the
discussion in the worklets specification about code idempotence</a>.
Examples {#examples}
====================
Example 1: Colored Circle {#example-1}
--------------------------------------
The example below makes use of the fact that <<paint()>> functions are able to be animated. E.g.
when the textarea is focused in the example below, the <code>--circle-color</code> property will
transition from <code>deepskyblue</code> to <code>purple</code>.
This ability isn't limited to just transitions, it also applies to CSS animations, and the Web
Animations API.
<pre class='lang-markup'>
<!DOCTYPE html>
<style>
#example {
--circle-color: deepskyblue;
background-image: paint(circle);
font-family: sans-serif;
font-size: 36px;
transition: --circle-color 1s;
}
#example:focus {
--circle-color: purple;
}
</style>
<textarea id="example">
CSS is awesome.
</textarea>
<script>
CSS.registerProperty({
name: '--circle-color',
syntax: '<color>',
initialValue: 'black',
inherits: false
});
CSS.paintWorklet.addModule('circle.js');
</script>
</pre>
<pre class='lang-javascript'>
// circle.js
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.cssText;
// 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'>
<!DOCTYPE html>
<style>
#example {
--image: url('#someUrlWhichIsLoading');
background-image: paint(image-with-placeholder);
}
</style>
<div id="example"></div>
<script>
CSS.registerProperty({
name: '--image',
syntax: '<image> | none',
initialValue: 'none',
});
CSS.paintWorklet.addModule('image-placeholder.js');
</script>
</pre>
<pre class='lang-javascript'>
// image-placeholder.js
registerPaint('image-with-placeholder', 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: Arcs {#example-3}
----------------------------
<pre class='lang-markup'>
<!DOCTYPE html>
<style>
#example {
width: 200px;
height: 200px;
background-image:
paint(arc, purple, 0.4turn, 0.8turn, 40px, 15px),
paint(arc, blue, -20deg, 170deg, 30px, 20px),
paint(arc, red, 45deg, 220deg, 50px, 10px);
}
</style>
<div id="example"></div>
<script>
CSS.paintWorklet.addModule('arc.js');
</script>
</pre>
<pre class='lang-javascript'>
// arc.js
registerPaint('arc', class {
static get inputArguments() {
return [
'<color>',
'<angle>', // startAngle
'<angle>', // endAngle
'<length>', // radius
'<length>', // lineWidth
];
}
paint(ctx, geom, _, args) {
ctx.strokeStyle = args[0].cssText;
// Determine the center point.
const x = geom.width / 2;
const y = geom.height / 2;
// Convert the start and end angles to radians.
const startAngle = this.convertAngle(args[1]) - Math.PI / 2;
const endAngle = this.convertAngle(args[2]) - Math.PI / 2;
// Convert the radius and lineWidth to px.
const radius = this.convertLength(args[3]);
const lineWidth = this.convertLength(args[4]);
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, false);
ctx.stroke();
}
convertAngle(angle) {
switch (angle.unit) {
case 'deg':
return angle.value * Math.PI / 180;
case 'rad':
return angle.value;
case 'grad':
return angle.value * Math.PI / 200;
case 'turn':
return angle.value * Math.PI / 0.5;
default:
throw Error(`Unknown angle unit: ${angle.unit}`);
}
}
convertLength(length) {
switch (length.type) {
case 'px':
return length.value;
default:
throw Error(`Unkown length type: ${length.type}`);
}
}
});
</pre>
Example 4: Different Colors (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>
<script>
CSS.paintWorklet.addModule('heading-color.js');
</script>
</pre>
<pre class='lang-javascript'>
// heading-color.js
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>
Example 5: Drawing outside an element's area {#example-5}
---------------------------------------------------------
It is possible to draw outside an element's area by using the 'border-image' property.
<pre class='lang-markup'>
<style>
#overdraw {
--border-width: 10;
border-style: solid;
border-width: calc(var(--border-width) * 1px);
border-image-source: paint(overdraw);
border-image-slice: 0 fill;
border-image-outset: calc(var(--border-width) * 1px);
width: 200px;
height: 200px;
}
</style>
<div id="overdraw"></div>
<script>
CSS.paintWorklet.addModule('overdraw.js');
</script>
</pre>
<pre class='lang-javascript'>
// overdraw.js
registerPaint('overdraw', class {
static get inputProperties() { return ['--border-width']; }
paint(ctx, geom, properties) {
const borderWidth = parseInt(properties.get('--border-width'));
ctx.shadowColor = 'rgba(0,0,0,0.25)';
ctx.shadowBlur = borderWidth;
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(borderWidth,
borderWidth,
geom.width - 2 * borderWidth,
geom.height - 2 * borderWidth);
}
});
</pre>
Security Considerations {#security-considerations}
==================================================
There are no known security issues introduced by these features.
Privacy Considerations {#privacy-considerations}
================================================