summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/canvas/tools/yaml
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/canvas/tools/yaml')
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/drawing-text-to-the-canvas.yaml969
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml1997
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/line-styles.yaml939
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/meta.yaml538
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/path-objects.yaml3378
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/pixel-manipulation.yaml1008
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/shadows.yaml1025
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/text-styles.yaml452
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-element.yaml142
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-state.yaml89
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/element/transformations.yaml358
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml1585
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/line-styles.yaml773
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/meta.yaml538
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/path-objects.yaml3073
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/pixel-manipulation.yaml735
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/shadows.yaml947
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/text.yaml1478
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-canvas-state.yaml92
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-offscreen-canvas.yaml275
-rw-r--r--testing/web-platform/tests/html/canvas/tools/yaml/offscreen/transformations.yaml320
21 files changed, 20711 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/drawing-text-to-the-canvas.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/drawing-text-to-the-canvas.yaml
new file mode 100644
index 0000000000..880e3fb1a1
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/drawing-text-to-the-canvas.yaml
@@ -0,0 +1,969 @@
+- name: 2d.text.draw.fill.basic
+ desc: fillText draws filled text
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('PASS', 5, 35);
+ expected: &passfill |
+ size 100 50
+ cr.set_source_rgb(0, 0, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ cr.set_source_rgb(0, 1, 0)
+ cr.select_font_face("Arial")
+ cr.set_font_size(35)
+ cr.translate(5, 35)
+ cr.text_path("PASS")
+ cr.fill()
+
+- name: 2d.text.draw.fill.unaffected
+ desc: fillText does not start a new path or subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('FAIL', 5, 35);
+
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.text.draw.fill.rtl
+ desc: fillText respects Right-To-Left Override characters
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('\u202eFAIL \xa0 \xa0 SSAP', 5, 35);
+ expected: *passfill
+- name: 2d.text.draw.fill.maxWidth.large
+ desc: fillText handles maxWidth correctly
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('PASS', 5, 35, 200);
+ expected: *passfill
+- name: 2d.text.draw.fill.maxWidth.small
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', -100, 35, 90);
+ _assertGreen(ctx, 100, 50);
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.zero
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', 5, 35, 0);
+ _assertGreen(ctx, 100, 50);
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.negative
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', 5, 35, -1);
+ _assertGreen(ctx, 100, 50);
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.NaN
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', 5, 35, NaN);
+ _assertGreen(ctx, 100, 50);
+ expected: green
+
+- name: 2d.text.draw.stroke.basic
+ desc: strokeText draws stroked text
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.fillStyle = '#f00';
+ ctx.lineWidth = 1;
+ ctx.font = '35px Arial, sans-serif';
+ ctx.strokeText('PASS', 5, 35);
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ cr.set_source_rgb(0, 1, 0)
+ cr.select_font_face("Arial")
+ cr.set_font_size(35)
+ cr.set_line_width(1)
+ cr.translate(5, 35)
+ cr.text_path("PASS")
+ cr.stroke()
+
+- name: 2d.text.draw.stroke.unaffected
+ desc: strokeText does not start a new path or subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+
+ ctx.font = '35px Arial, sans-serif';
+ ctx.strokeStyle = '#f00';
+ ctx.strokeText('FAIL', 5, 35);
+
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.text.draw.kern.consistent
+ desc: Stroked and filled text should have exactly the same kerning so it overlaps
+ manual:
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 3;
+ ctx.font = '20px Arial, sans-serif';
+ ctx.fillText('VAVAVAVAVAVAVA', -50, 25);
+ ctx.fillText('ToToToToToToTo', -50, 45);
+ ctx.strokeText('VAVAVAVAVAVAVA', -50, 25);
+ ctx.strokeText('ToToToToToToTo', -50, 45);
+ expected: green
+
+# CanvasTest is:
+# A = (0, 0) to (1em, 0.75em) (above baseline)
+# B = (0, 0) to (1em, -0.25em) (below baseline)
+# C = (0, -0.25em) to (1em, 0.75em) (the em square) plus some Xs above and below
+# D = (0, -0.25em) to (1em, 0.75em) (the em square) plus some Xs left and right
+# E = (0, -0.25em) to (1em, 0.75em) (the em square)
+# space = empty, 1em wide
+#
+# At 50px, "E" will fill the canvas vertically
+# At 67px, "A" will fill the canvas vertically
+#
+# Ideographic baseline is 0.125em above alphabetic
+# Mathematical baseline is 0.375em above alphabetic
+# Hanging baseline is 0.500em above alphabetic
+
+# WebKit doesn't block onload on font loads, so we try to make it a bit more reliable
+# by waiting with step_timeout after load before drawing
+
+- name: 2d.text.draw.fill.maxWidth.fontface
+ desc: fillText works on @font-face fonts
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillText('EEEE', -50, 37.5, 40);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.bound
+ desc: fillText handles maxWidth based on line size, not bounding box size
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('DD', 0, 37.5, 100);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.fontface
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '67px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('AA', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.fontface.repeat
+ desc: Draw with the font immediately, then wait a bit until and draw again. (This
+ crashes some version of WebKit.)
+ fonts:
+ - CanvasTest
+ fonthack: 0
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.font = '67px CanvasTest';
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('AA', 0, 50);
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillText('AA', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.fontface.notinpage
+ desc: '@font-face fonts should work even if they are not used in the page'
+ fonts:
+ - CanvasTest
+ fonthack: 0
+ code: |
+ ctx.font = '67px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('AA', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.left
+ desc: textAlign left is the left of the first em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'left';
+ ctx.fillText('DD', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.right
+ desc: textAlign right is the right of the last em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'right';
+ ctx.fillText('DD', 100, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.start.ltr
+ desc: textAlign start with ltr is the left edge
+ fonts:
+ - CanvasTest
+ canvas: width="100" height="50" dir="ltr"
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'start';
+ ctx.fillText('DD', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.start.rtl
+ desc: textAlign start with rtl is the right edge
+ fonts:
+ - CanvasTest
+ canvas: width="100" height="50" dir="rtl"
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'start';
+ ctx.fillText('DD', 100, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.end.ltr
+ desc: textAlign end with ltr is the right edge
+ fonts:
+ - CanvasTest
+ canvas: width="100" height="50" dir="ltr"
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'end';
+ ctx.fillText('DD', 100, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.end.rtl
+ desc: textAlign end with rtl is the left edge
+ fonts:
+ - CanvasTest
+ canvas: width="100" height="50" dir="rtl"
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'end';
+ ctx.fillText('DD', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.align.center
+ desc: textAlign center is the center of the em squares (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'center';
+ ctx.fillText('DD', 50, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+
+- name: 2d.text.draw.space.basic
+ desc: U+0020 is rendered the correct size (1em wide)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E EE', -100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.nonspace
+ desc: Non-space characters are not converted to U+0020 and collapsed
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E\x0b EE', -150, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.measure.width.basic
+ desc: The width of character is same as font used
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ @assert ctx.measureText('A').width === 50;
+ @assert ctx.measureText('AA').width === 100;
+ @assert ctx.measureText('ABCD').width === 200;
+
+ ctx.font = '100px CanvasTest';
+ @assert ctx.measureText('A').width === 100;
+ }), 500);
+ });
+
+- name: 2d.text.measure.width.empty
+ desc: The empty string has zero width
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ @assert ctx.measureText("").width === 0;
+ }), 500);
+ });
+
+- name: 2d.text.measure.advances
+ desc: Testing width advances
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ // Some platforms may return '-0'.
+ @assert Math.abs(ctx.measureText('Hello').advances[0]) === 0;
+ // Different platforms may render text slightly different.
+ @assert ctx.measureText('Hello').advances[1] >= 36;
+ @assert ctx.measureText('Hello').advances[2] >= 58;
+ @assert ctx.measureText('Hello').advances[3] >= 70;
+ @assert ctx.measureText('Hello').advances[4] >= 80;
+
+ var tm = ctx.measureText('Hello');
+ @assert ctx.measureText('Hello').advances[0] === tm.advances[0];
+ @assert ctx.measureText('Hello').advances[1] === tm.advances[1];
+ @assert ctx.measureText('Hello').advances[2] === tm.advances[2];
+ @assert ctx.measureText('Hello').advances[3] === tm.advances[3];
+ @assert ctx.measureText('Hello').advances[4] === tm.advances[4];
+ }), 500);
+ });
+
+- name: 2d.text.measure.actualBoundingBox
+ desc: Testing actualBoundingBox
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ ctx.baseline = 'alphabetic'
+ // Different platforms may render text slightly different.
+ // Values that are nominally expected to be zero might actually vary by a pixel or so
+ // if the UA accounts for antialiasing at glyph edges, so we allow a slight deviation.
+ @assert Math.abs(ctx.measureText('A').actualBoundingBoxLeft) <= 1;
+ @assert ctx.measureText('A').actualBoundingBoxRight >= 50;
+ @assert ctx.measureText('A').actualBoundingBoxAscent >= 35;
+ @assert Math.abs(ctx.measureText('A').actualBoundingBoxDescent) <= 1;
+
+ @assert ctx.measureText('D').actualBoundingBoxLeft >= 48;
+ @assert ctx.measureText('D').actualBoundingBoxLeft <= 52;
+ @assert ctx.measureText('D').actualBoundingBoxRight >= 75;
+ @assert ctx.measureText('D').actualBoundingBoxRight <= 80;
+ @assert ctx.measureText('D').actualBoundingBoxAscent >= 35;
+ @assert ctx.measureText('D').actualBoundingBoxAscent <= 40;
+ @assert ctx.measureText('D').actualBoundingBoxDescent >= 12;
+ @assert ctx.measureText('D').actualBoundingBoxDescent <= 15;
+
+ @assert Math.abs(ctx.measureText('ABCD').actualBoundingBoxLeft) <= 1;
+ @assert ctx.measureText('ABCD').actualBoundingBoxRight >= 200;
+ @assert ctx.measureText('ABCD').actualBoundingBoxAscent >= 85;
+ @assert ctx.measureText('ABCD').actualBoundingBoxDescent >= 37;
+ }), 500);
+ });
+
+- name: 2d.text.measure.fontBoundingBox
+ desc: Testing fontBoundingBox
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert ctx.measureText('A').fontBoundingBoxAscent === 85;
+ @assert ctx.measureText('A').fontBoundingBoxDescent === 39;
+
+ @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 85;
+ @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 39;
+ }), 500);
+ });
+
+- name: 2d.text.measure.fontBoundingBox.ahem
+ desc: Testing fontBoundingBox for font ahem
+ fonts:
+ - Ahem
+ code: |
+ deferTest();
+ var f = new FontFace("Ahem", "/fonts/Ahem.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px Ahem';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert ctx.measureText('A').fontBoundingBoxAscent === 40;
+ @assert ctx.measureText('A').fontBoundingBoxDescent === 10;
+
+ @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 40;
+ @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
+ }), 500);
+ });
+
+- name: 2d.text.measure.emHeights
+ desc: Testing emHeights
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert ctx.measureText('A').emHeightAscent === 37.5;
+ @assert ctx.measureText('A').emHeightDescent === 12.5;
+ @assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 50;
+
+ @assert ctx.measureText('ABCD').emHeightAscent === 37.5;
+ @assert ctx.measureText('ABCD').emHeightDescent === 12.5;
+ @assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 50;
+ }), 500);
+ });
+
+- name: 2d.text.measure.baselines
+ desc: Testing baselines
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert Math.abs(ctx.measureText('A').getBaselines().alphabetic) === 0;
+ @assert ctx.measureText('A').getBaselines().ideographic === -39;
+ @assert ctx.measureText('A').getBaselines().hanging === 68;
+
+ @assert Math.abs(ctx.measureText('ABCD').getBaselines().alphabetic) === 0;
+ @assert ctx.measureText('ABCD').getBaselines().ideographic === -39;
+ @assert ctx.measureText('ABCD').getBaselines().hanging === 68;
+ }), 500);
+ });
+
+- name: 2d.text.drawing.style.spacing
+ desc: Testing letter spacing and word spacing
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+
+ ctx.letterSpacing = '3px';
+ @assert ctx.letterSpacing === '3px';
+ @assert ctx.wordSpacing === '0px';
+
+ ctx.wordSpacing = '5px';
+ @assert ctx.letterSpacing === '3px';
+ @assert ctx.wordSpacing === '5px';
+
+ ctx.letterSpacing = '-1px';
+ ctx.wordSpacing = '-1px';
+ @assert ctx.letterSpacing === '-1px';
+ @assert ctx.wordSpacing === '-1px';
+
+ ctx.letterSpacing = '1PX';
+ ctx.wordSpacing = '1EM';
+ @assert ctx.letterSpacing === '1px';
+ @assert ctx.wordSpacing === '1em';
+
+- name: 2d.text.drawing.style.nonfinite.spacing
+ desc: Testing letter spacing and word spacing with nonfinite inputs
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+
+ function test_word_spacing(value) {
+ ctx.wordSpacing = value;
+ ctx.letterSpacing = value;
+ @assert ctx.wordSpacing === '0px';
+ @assert ctx.letterSpacing === '0px';
+ }
+ @nonfinite test_word_spacing(<0 NaN Infinity -Infinity>);
+
+- name: 2d.text.drawing.style.invalid.spacing
+ desc: Testing letter spacing and word spacing with invalid units
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+
+ function test_word_spacing(value) {
+ ctx.wordSpacing = value;
+ ctx.letterSpacing = value;
+ @assert ctx.wordSpacing === '0px';
+ @assert ctx.letterSpacing === '0px';
+ }
+ @nonfinite test_word_spacing(< '0s' '1min' '1deg' '1pp'>);
+
+- name: 2d.text.drawing.style.letterSpacing.measure
+ desc: Testing letter spacing and word spacing
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ var width_normal = ctx.measureText('Hello World').width;
+
+ function test_letter_spacing(value, difference_spacing, epsilon) {
+ ctx.letterSpacing = value;
+ @assert ctx.letterSpacing === value;
+ @assert ctx.wordSpacing === '0px';
+ width_with_letter_spacing = ctx.measureText('Hello World').width;
+ assert_approx_equals(width_with_letter_spacing, width_normal + difference_spacing, epsilon, "letter spacing doesn't work.");
+ }
+
+ // The first value is the letter Spacing to be set, the second value the
+ // change in length of string 'Hello World', note that there are 11 letters
+ // in 'hello world', so the length difference is always letterSpacing * 11.
+ // and the third value is the acceptable differencee for the length change,
+ // note that unit such as 1cm/1mm doesn't map to an exact pixel value.
+ test_cases = [['3px', 33, 0],
+ ['5px', 55, 0],
+ ['-2px', -22, 0],
+ ['1em', 110, 0],
+ ['1in', 1056, 0],
+ ['-0.1cm', -41.65, 0.2],
+ ['-0.6mm', -24,95, 0.2]]
+
+ for (const test_case of test_cases) {
+ test_letter_spacing(test_case[0], test_case[1], test_case[2]);
+ }
+
+- name: 2d.text.drawing.style.wordSpacing.measure
+ desc: Testing if word spacing is working properly
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ var width_normal = ctx.measureText('Hello World, again').width;
+
+ function test_word_spacing(value, difference_spacing, epsilon) {
+ ctx.wordSpacing = value;
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === value;
+ width_with_word_spacing = ctx.measureText('Hello World, again').width;
+ assert_approx_equals(width_with_word_spacing, width_normal + difference_spacing, epsilon, "word spacing doesn't work.");
+ }
+
+ // The first value is the word Spacing to be set, the second value the
+ // change in length of string 'Hello World', note that there are 2 words
+ // in 'Hello World, again', so the length difference is always wordSpacing * 2.
+ // and the third value is the acceptable differencee for the length change,
+ // note that unit such as 1cm/1mm doesn't map to an exact pixel value.
+ test_cases = [['3px', 6, 0],
+ ['5px', 10, 0],
+ ['-2px', -4, 0],
+ ['1em', 20, 0],
+ ['1in', 192, 0],
+ ['-0.1cm', -7.57, 0.2],
+ ['-0.6mm', -4.54, 0.2]]
+
+ for (const test_case of test_cases) {
+ test_word_spacing(test_case[0], test_case[1], test_case[2]);
+ }
+
+- name: 2d.text.drawing.style.letterSpacing.change.font
+ desc: Set letter spacing and word spacing to font dependent value and verify it works after font change.
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ // Get the width for 'Hello World' at default size, 10px.
+ var width_normal = ctx.measureText('Hello World').width;
+
+ ctx.letterSpacing = '1em';
+ @assert ctx.letterSpacing === '1em';
+ // 1em = 10px. Add 10px after each letter in "Hello World",
+ // makes it 110px longer.
+ var width_with_spacing = ctx.measureText('Hello World').width;
+ @assert width_with_spacing === width_normal + 110;
+
+ // Changing font to 20px. Without resetting the spacing, 1em letterSpacing
+ // is now 20px, so it's suppose to be 220px longer without any letterSpacing set.
+ ctx.font = '20px serif';
+ width_with_spacing = ctx.measureText('Hello World').width;
+ // Now calculate the reference spacing for "Hello World" with no spacing.
+ ctx.letterSpacing = '0em';
+ width_normal = ctx.measureText('Hello World').width;
+ @assert width_with_spacing === width_normal + 220;
+
+- name: 2d.text.drawing.style.wordSpacing.change.font
+ desc: Set word spacing and word spacing to font dependent value and verify it works after font change.
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ // Get the width for 'Hello World, again' at default size, 10px.
+ var width_normal = ctx.measureText('Hello World, again').width;
+
+ ctx.wordSpacing = '1em';
+ @assert ctx.wordSpacing === '1em';
+ // 1em = 10px. Add 10px after each word in "Hello World, again",
+ // makes it 20px longer.
+ var width_with_spacing = ctx.measureText('Hello World, again').width;
+ @assert width_with_spacing === width_normal + 20;
+
+ // Changing font to 20px. Without resetting the spacing, 1em wordSpacing
+ // is now 20px, so it's suppose to be 40px longer without any wordSpacing set.
+ ctx.font = '20px serif';
+ width_with_spacing = ctx.measureText('Hello World, again').width;
+ // Now calculate the reference spacing for "Hello World, again" with no spacing.
+ ctx.wordSpacing = '0em';
+ width_normal = ctx.measureText('Hello World, again').width;
+ @assert width_with_spacing === width_normal + 40;
+
+- name: 2d.text.drawing.style.fontKerning
+ desc: Testing basic functionalities of fontKerning for canvas
+ code: |
+ @assert ctx.fontKerning === "auto";
+ ctx.fontKerning = "normal";
+ @assert ctx.fontKerning === "normal";
+ width_normal = ctx.measureText("TAWATAVA").width;
+ ctx.fontKerning = "none";
+ @assert ctx.fontKerning === "none";
+ width_none = ctx.measureText("TAWATAVA").width;
+ @assert width_normal < width_none;
+
+- name: 2d.text.drawing.style.fontKerning.with.uppercase
+ desc: Testing basic functionalities of fontKerning for canvas
+ code: |
+ @assert ctx.fontKerning === "auto";
+ ctx.fontKerning = "Normal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "normal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "noRmal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "NoRMal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "NORMAL";
+ @assert ctx.fontKerning === "normal";
+
+ ctx.fontKerning = "None";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "none";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "nOne";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "nonE";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "Auto";
+ ctx.fontKerning = "NONE";
+ @assert ctx.fontKerning === "none";
+
+- name: 2d.text.drawing.style.fontVariant.settings
+ desc: Testing basic functionalities of fontKerning for canvas
+ code: |
+ // Setting fontVariantCaps with lower cases
+ @assert ctx.fontVariantCaps === "normal";
+
+ ctx.fontVariantCaps = "normal";
+ @assert ctx.fontVariantCaps === "normal";
+
+ ctx.fontVariantCaps = "small-caps";
+ @assert ctx.fontVariantCaps === "small-caps";
+
+ ctx.fontVariantCaps = "all-small-caps";
+ @assert ctx.fontVariantCaps === "all-small-caps";
+
+ ctx.fontVariantCaps = "petite-caps";
+ @assert ctx.fontVariantCaps === "petite-caps";
+
+ ctx.fontVariantCaps = "all-petite-caps";
+ @assert ctx.fontVariantCaps === "all-petite-caps";
+
+ ctx.fontVariantCaps = "unicase";
+ @assert ctx.fontVariantCaps === "unicase";
+
+ ctx.fontVariantCaps = "titling-caps";
+ @assert ctx.fontVariantCaps === "titling-caps";
+
+ // Setting fontVariantCaps with lower cases and upper cases word.
+ ctx.fontVariantCaps = "nORmal";
+ @assert ctx.fontVariantCaps === "normal";
+
+ ctx.fontVariantCaps = "smaLL-caps";
+ @assert ctx.fontVariantCaps === "small-caps";
+
+ ctx.fontVariantCaps = "all-small-CAPS";
+ @assert ctx.fontVariantCaps === "all-small-caps";
+
+ ctx.fontVariantCaps = "pEtitE-caps";
+ @assert ctx.fontVariantCaps === "petite-caps";
+
+ ctx.fontVariantCaps = "All-Petite-Caps";
+ @assert ctx.fontVariantCaps === "all-petite-caps";
+
+ ctx.fontVariantCaps = "uNIcase";
+ @assert ctx.fontVariantCaps === "unicase";
+
+ ctx.fontVariantCaps = "titling-CAPS";
+ @assert ctx.fontVariantCaps === "titling-caps";
+
+ // Setting fontVariantCaps with non-existing font variant.
+ ctx.fontVariantCaps = "abcd";
+ @assert ctx.fontVariantCaps === "titling-caps";
+
+- name: 2d.text.drawing.style.textRendering.settings
+ desc: Testing basic functionalities of textRendering in Canvas
+ code: |
+ // Setting textRendering with lower cases
+ @assert ctx.textRendering === "auto";
+
+ ctx.textRendering = "auto";
+ @assert ctx.textRendering === "auto";
+
+ ctx.textRendering = "optimizespeed";
+ @assert ctx.textRendering === "optimizeSpeed";
+
+ ctx.textRendering = "optimizelegibility";
+ @assert ctx.textRendering === "optimizeLegibility";
+
+ ctx.textRendering = "geometricprecision";
+ @assert ctx.textRendering === "geometricPrecision";
+
+ // Setting textRendering with lower cases and upper cases word.
+ ctx.textRendering = "aUto";
+ @assert ctx.textRendering === "auto";
+
+ ctx.textRendering = "OPtimizeSpeed";
+ @assert ctx.textRendering === "optimizeSpeed";
+
+ ctx.textRendering = "OPtimizELEgibility";
+ @assert ctx.textRendering === "optimizeLegibility";
+
+ ctx.textRendering = "GeometricPrecision";
+ @assert ctx.textRendering === "geometricPrecision";
+
+ // Setting textRendering with non-existing font variant.
+ ctx.textRendering = "abcd";
+ @assert ctx.textRendering === "geometricPrecision";
+
+# TODO: shadows, alpha, composite, clip
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml
new file mode 100644
index 0000000000..fe7b8841ae
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml
@@ -0,0 +1,1997 @@
+- name: 2d.fillStyle.parse.current.basic
+ desc: currentColor is computed from the canvas element
+ code: |
+ canvas.setAttribute('style', 'color: #0f0');
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = 'currentColor';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.fillStyle.parse.current.changed
+ desc: currentColor is computed when the attribute is set, not when it is painted
+ code: |
+ canvas.setAttribute('style', 'color: #0f0');
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = 'currentColor';
+ canvas.setAttribute('style', 'color: #f00');
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.fillStyle.parse.current.removed
+ desc: currentColor is solid black when the canvas element is not in a document
+ code: |
+ // Try not to let it undetectably incorrectly pick up opaque-black
+ // from other parts of the document:
+ document.body.parentNode.setAttribute('style', 'color: #f00');
+ document.body.setAttribute('style', 'color: #f00');
+ canvas.setAttribute('style', 'color: #f00');
+
+ var canvas2 = document.createElement('canvas');
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillStyle = 'currentColor';
+ ctx2.fillRect(0, 0, 100, 50);
+ ctx.drawImage(canvas2, 0, 0);
+
+ document.body.parentNode.removeAttribute('style');
+ document.body.removeAttribute('style');
+
+ @assert pixel 50,25 == 0,0,0,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.fillStyle.invalidstring
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillStyle = 'invalid';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.fillStyle.invalidtype
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillStyle = null;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.fillStyle.get.solid
+ code: |
+ ctx.fillStyle = '#fa0';
+ @assert ctx.fillStyle === '#ffaa00';
+
+- name: 2d.fillStyle.get.semitransparent
+ code: |
+ ctx.fillStyle = 'rgba(255,255,255,0.45)';
+ @assert ctx.fillStyle =~ /^rgba\(255, 255, 255, 0\.4\d+\)$/;
+
+- name: 2d.fillStyle.get.halftransparent
+ code: |
+ ctx.fillStyle = 'rgba(255,255,255,0.5)';
+ @assert ctx.fillStyle === 'rgba(255, 255, 255, 0.5)';
+
+- name: 2d.fillStyle.get.transparent
+ code: |
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ @assert ctx.fillStyle === 'rgba(0, 0, 0, 0)';
+
+- name: 2d.fillStyle.default
+ code: |
+ @assert ctx.fillStyle === '#000000';
+
+- name: 2d.fillStyle.toStringFunctionCallback
+ desc: Passing a function in to ctx.fillStyle or ctx.strokeStyle with a toString callback works as specified
+ code: |
+ ctx.fillStyle = { toString: function() { return "#008000"; } };
+ @assert ctx.fillStyle === "#008000";
+ ctx.fillStyle = {};
+ @assert ctx.fillStyle === "#008000";
+ ctx.fillStyle = 800000;
+ @assert ctx.fillStyle === "#008000";
+ @assert throws TypeError ctx.fillStyle = { toString: function() { throw new TypeError; } };
+ ctx.strokeStyle = { toString: function() { return "#008000"; } };
+ @assert ctx.strokeStyle === "#008000";
+ ctx.strokeStyle = {};
+ @assert ctx.strokeStyle === "#008000";
+ ctx.strokeStyle = 800000;
+ @assert ctx.strokeStyle === "#008000";
+ @assert throws TypeError ctx.strokeStyle = { toString: function() { throw new TypeError; } };
+
+- name: 2d.strokeStyle.default
+ code: |
+ @assert ctx.strokeStyle === '#000000';
+
+
+- name: 2d.gradient.object.type
+ desc: window.CanvasGradient exists and has the right properties
+ notes: &bindings Defined in "Web IDL" (draft)
+ code: |
+ @assert window.CanvasGradient !== undefined;
+ @assert window.CanvasGradient.prototype.addColorStop !== undefined;
+
+- name: 2d.gradient.object.return
+ desc: createLinearGradient() and createRadialGradient() returns objects implementing
+ CanvasGradient
+ code: |
+ window.CanvasGradient.prototype.thisImplementsCanvasGradient = true;
+
+ var g1 = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert g1.addColorStop !== undefined;
+ @assert g1.thisImplementsCanvasGradient === true;
+
+ var g2 = ctx.createRadialGradient(0, 0, 10, 0, 0, 20);
+ @assert g2.addColorStop !== undefined;
+ @assert g2.thisImplementsCanvasGradient === true;
+
+- name: 2d.gradient.interpolate.solid
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.interpolate.color
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(1, '#00f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,25 ==~ 191,191,63,255 +/- 3;
+ @assert pixel 50,25 ==~ 127,127,127,255 +/- 3;
+ @assert pixel 75,25 ==~ 63,63,191,255 +/- 3;
+ expected: |
+ size 100 50
+ g = cairo.LinearGradient(0, 0, 100, 0)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.gradient.interpolate.alpha
+ code: |
+ ctx.fillStyle = '#ff0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, 'rgba(0,0,255, 0)');
+ g.addColorStop(1, 'rgba(0,0,255, 1)');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,25 ==~ 191,191,63,255 +/- 3;
+ @assert pixel 50,25 ==~ 127,127,127,255 +/- 3;
+ @assert pixel 75,25 ==~ 63,63,191,255 +/- 3;
+ expected: |
+ size 100 50
+ g = cairo.LinearGradient(0, 0, 100, 0)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.gradient.interpolate.coloralpha
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, 'rgba(255,255,0, 0)');
+ g.addColorStop(1, 'rgba(0,0,255, 1)');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,25 ==~ 190,190,65,65 +/- 3;
+ @assert pixel 50,25 ==~ 126,126,128,128 +/- 3;
+ @assert pixel 75,25 ==~ 62,62,192,192 +/- 3;
+ expected: |
+ size 100 50
+ g = cairo.LinearGradient(0, 0, 100, 0)
+ g.add_color_stop_rgba(0, 1,1,0, 0)
+ g.add_color_stop_rgba(1, 0,0,1, 1)
+ cr.set_source(g)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.gradient.interpolate.outside
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(25, 0, 75, 0);
+ g.addColorStop(0.4, '#0f0');
+ g.addColorStop(0.6, '#0f0');
+
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 20,25 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 80,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.interpolate.zerosize.fill
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.rect(0, 0, 100, 50);
+ ctx.fill();
+ @assert pixel 40,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.interpolate.zerosize.stroke
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.strokeStyle = g;
+ ctx.rect(20, 20, 60, 10);
+ ctx.stroke();
+ @assert pixel 19,19 == 0,255,0,255;
+ @assert pixel 20,19 == 0,255,0,255;
+ @assert pixel 21,19 == 0,255,0,255;
+ @assert pixel 19,20 == 0,255,0,255;
+ @assert pixel 20,20 == 0,255,0,255;
+ @assert pixel 21,20 == 0,255,0,255;
+ @assert pixel 19,21 == 0,255,0,255;
+ @assert pixel 20,21 == 0,255,0,255;
+ @assert pixel 21,21 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.interpolate.zerosize.fillRect
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 40,20 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.gradient.interpolate.zerosize.strokeRect
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.strokeStyle = g;
+ ctx.strokeRect(20, 20, 60, 10);
+ @assert pixel 19,19 == 0,255,0,255;
+ @assert pixel 20,19 == 0,255,0,255;
+ @assert pixel 21,19 == 0,255,0,255;
+ @assert pixel 19,20 == 0,255,0,255;
+ @assert pixel 20,20 == 0,255,0,255;
+ @assert pixel 21,20 == 0,255,0,255;
+ @assert pixel 19,21 == 0,255,0,255;
+ @assert pixel 20,21 == 0,255,0,255;
+ @assert pixel 21,21 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.interpolate.zerosize.fillText
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.font = '100px sans-serif';
+ ctx.fillText("AA", 0, 50);
+ _assertGreen(ctx, 100, 50);
+ expected: green
+
+- name: 2d.gradient.interpolate.zerosize.strokeText
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.strokeStyle = g;
+ ctx.font = '100px sans-serif';
+ ctx.strokeText("AA", 0, 50);
+ _assertGreen(ctx, 100, 50);
+ expected: green
+
+
+- name: 2d.gradient.interpolate.vertical
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 0, 50);
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(1, '#00f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,12 ==~ 191,191,63,255 +/- 10;
+ @assert pixel 50,25 ==~ 127,127,127,255 +/- 5;
+ @assert pixel 50,37 ==~ 63,63,191,255 +/- 10;
+ expected: |
+ size 100 50
+ g = cairo.LinearGradient(0, 0, 0, 50)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.gradient.interpolate.multiple
+ code: |
+ canvas.width = 200;
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(0.5, '#0ff');
+ g.addColorStop(1, '#f0f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 200, 50);
+ @assert pixel 50,25 ==~ 127,255,127,255 +/- 3;
+ @assert pixel 100,25 ==~ 0,255,255,255 +/- 3;
+ @assert pixel 150,25 ==~ 127,127,255,255 +/- 3;
+ expected: |
+ size 200 50
+ g = cairo.LinearGradient(0, 0, 200, 0)
+ g.add_color_stop_rgb(0.0, 1,1,0)
+ g.add_color_stop_rgb(0.5, 0,1,1)
+ g.add_color_stop_rgb(1.0, 1,0,1)
+ cr.set_source(g)
+ cr.rectangle(0, 0, 200, 50)
+ cr.fill()
+
+- name: 2d.gradient.interpolate.overlap
+ code: |
+ canvas.width = 200;
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(0.25, '#00f');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.25, '#ff0');
+ g.addColorStop(0.5, '#00f');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.75, '#00f');
+ g.addColorStop(0.75, '#f00');
+ g.addColorStop(0.75, '#ff0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.5, '#ff0');
+ g.addColorStop(1, '#00f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 200, 50);
+ @assert pixel 49,25 ==~ 0,0,255,255 +/- 16;
+ @assert pixel 51,25 ==~ 255,255,0,255 +/- 16;
+ @assert pixel 99,25 ==~ 0,0,255,255 +/- 16;
+ @assert pixel 101,25 ==~ 255,255,0,255 +/- 16;
+ @assert pixel 149,25 ==~ 0,0,255,255 +/- 16;
+ @assert pixel 151,25 ==~ 255,255,0,255 +/- 16;
+ expected: |
+ size 200 50
+ g = cairo.LinearGradient(0, 0, 50, 0)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(0, 0, 50, 50)
+ cr.fill()
+
+ g = cairo.LinearGradient(50, 0, 100, 0)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(50, 0, 50, 50)
+ cr.fill()
+
+ g = cairo.LinearGradient(100, 0, 150, 0)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(100, 0, 50, 50)
+ cr.fill()
+
+ g = cairo.LinearGradient(150, 0, 200, 0)
+ g.add_color_stop_rgb(0, 1,1,0)
+ g.add_color_stop_rgb(1, 0,0,1)
+ cr.set_source(g)
+ cr.rectangle(150, 0, 50, 50)
+ cr.fill()
+
+- name: 2d.gradient.interpolate.overlap2
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ var ps = [ 0, 1/10, 1/4, 1/3, 1/2, 3/4, 1 ];
+ for (var p = 0; p < ps.length; ++p)
+ {
+ g.addColorStop(ps[p], '#0f0');
+ for (var i = 0; i < 15; ++i)
+ g.addColorStop(ps[p], '#f00');
+ g.addColorStop(ps[p], '#0f0');
+ }
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 30,25 == 0,255,0,255;
+ @assert pixel 40,25 == 0,255,0,255;
+ @assert pixel 60,25 == 0,255,0,255;
+ @assert pixel 80,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.empty
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(0, 0, 0, 50);
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.object.update
+ code: |
+ var g = ctx.createLinearGradient(-100, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ g.addColorStop(0.1, '#0f0');
+ g.addColorStop(0.9, '#0f0');
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.object.compare
+ code: |
+ var g1 = ctx.createLinearGradient(0, 0, 100, 0);
+ var g2 = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert g1 !== g2;
+ ctx.fillStyle = g1;
+ @assert ctx.fillStyle === g1;
+
+- name: 2d.gradient.object.crosscanvas
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = document.createElement('canvas').getContext('2d').createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.object.current
+ code: |
+ canvas.setAttribute('style', 'color: #f00');
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, 'currentColor');
+ g.addColorStop(1, 'currentColor');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,0,0,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.gradient.object.invalidoffset
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert throws INDEX_SIZE_ERR g.addColorStop(-1, '#000');
+ @assert throws INDEX_SIZE_ERR g.addColorStop(2, '#000');
+ @assert throws TypeError g.addColorStop(Infinity, '#000');
+ @assert throws TypeError g.addColorStop(-Infinity, '#000');
+ @assert throws TypeError g.addColorStop(NaN, '#000');
+
+- name: 2d.gradient.object.invalidcolor
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert throws SYNTAX_ERR g.addColorStop(0, "");
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)');
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'null');
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'undefined');
+ @assert throws SYNTAX_ERR g.addColorStop(0, null);
+ @assert throws SYNTAX_ERR g.addColorStop(0, undefined);
+
+ var g = ctx.createRadialGradient(0, 0, 0, 100, 0, 0);
+ @assert throws SYNTAX_ERR g.addColorStop(0, "");
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)');
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'null');
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'undefined');
+ @assert throws SYNTAX_ERR g.addColorStop(0, null);
+ @assert throws SYNTAX_ERR g.addColorStop(0, undefined);
+
+
+- name: 2d.gradient.linear.nonfinite
+ desc: createLinearGradient() throws TypeError if arguments are not finite
+ notes: *bindings
+ code: |
+ @nonfinite @assert throws TypeError ctx.createLinearGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+
+- name: 2d.gradient.linear.transform.1
+ desc: Linear gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.75, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(-50, 0);
+ ctx.fillRect(50, 0, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.linear.transform.2
+ desc: Linear gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ ctx.translate(100, 0);
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.75, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(-150, 0);
+ ctx.fillRect(50, 0, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.linear.transform.3
+ desc: Linear gradient transforms do not experience broken caching effects
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.75, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(-50, 0);
+ ctx.fillRect(50, 0, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.negative
+ desc: createRadialGradient() throws INDEX_SIZE_ERR if either radius is negative
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, -0.1, 0, 0, 1);
+ @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, 1, 0, 0, -0.1);
+ @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, -0.1, 0, 0, -0.1);
+
+- name: 2d.gradient.radial.nonfinite
+ desc: createRadialGradient() throws TypeError if arguments are not finite
+ notes: *bindings
+ code: |
+ @nonfinite @assert throws TypeError ctx.createRadialGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
+
+- name: 2d.gradient.radial.inside1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(50, 25, 100, 50, 25, 200);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.inside2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.inside3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.993, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.outside1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(200, 25, 10, 200, 25, 20);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.outside2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.outside3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.001, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.touch1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(150, 25, 50, 200, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.gradient.radial.touch2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.01, '#0f0');
+ g.addColorStop(0.99, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.touch3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(120, -15, 25, 140, -30, 50);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.gradient.radial.equal
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(50, 25, 20, 50, 25, 20);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.gradient.radial.cone.behind
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(120, 25, 10, 211, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.gradient.radial.cone.front
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(311, 25, 10, 210, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.cone.bottom
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 101);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.cone.top
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(230, 25, 100, 100, 25, 101);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.cone.beside
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(0, 100, 40, 100, 100, 50);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.gradient.radial.cone.cylinder
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 100);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.cone.shape1
+ code: |
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(30+tol, 40);
+ ctx.lineTo(110, -20+tol);
+ ctx.lineTo(110, 100-tol);
+ ctx.fill();
+
+ var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.cone.shape2
+ code: |
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(30-tol, 40);
+ ctx.lineTo(110, -20-tol);
+ ctx.lineTo(110, 100+tol);
+ ctx.fill();
+
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.transform.1
+ desc: Radial gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.51, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(50, 25);
+ ctx.scale(10, 10);
+ ctx.fillRect(-5, -2.5, 10, 5);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.transform.2
+ desc: Radial gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ ctx.translate(100, 0);
+ var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.51, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(-50, 25);
+ ctx.scale(10, 10);
+ ctx.fillRect(-5, -2.5, 10, 5);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.radial.transform.3
+ desc: Radial gradient transforms do not experience broken caching effects
+ code: |
+ var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.51, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(50, 25);
+ ctx.scale(10, 10);
+ ctx.fillRect(-5, -2.5, 10, 5);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.gradient.conic.positive.rotation
+ desc: Conic gradient with positive rotation
+ code: |
+ const g = ctx.createConicGradient(3*Math.PI/2, 50, 25);
+ // It's red in the upper right region and green on the lower left region
+ g.addColorStop(0, "#f00");
+ g.addColorStop(0.25, "#0f0");
+ g.addColorStop(0.50, "#0f0");
+ g.addColorStop(0.75, "#f00");
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,15 ==~ 255,0,0,255 +/- 3;
+ @assert pixel 75,40 ==~ 0,255,0,255 +/- 3;
+ expected: green
+
+- name: 2d.gradient.conic.negative.rotation
+ desc: Conic gradient with negative rotation
+ code: |
+ const g = ctx.createConicGradient(-Math.PI/2, 50, 25);
+ // It's red in the upper right region and green on the lower left region
+ g.addColorStop(0, "#f00");
+ g.addColorStop(0.25, "#0f0");
+ g.addColorStop(0.50, "#0f0");
+ g.addColorStop(0.75, "#f00");
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,15 ==~ 255,0,0,255 +/- 3;
+ @assert pixel 75,40 ==~ 0,255,0,255 +/- 3;
+ expected: green
+
+- name: 2d.gradient.conic.invalid.inputs
+ desc: Conic gradient function with invalid inputs
+ code: |
+ @nonfinite @assert throws TypeError ctx.createConicGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
+
+ const g = ctx.createConicGradient(0, 0, 25);
+ @nonfinite @assert throws TypeError g.addColorStop(<Infinity -Infinity NaN>, <'#f00'>);
+ @nonfinite @assert throws SYNTAX_ERR g.addColorStop(<0>, <Infinity -Infinity NaN>);
+
+- name: 2d.pattern.basic.type
+ images:
+ - green.png
+ code: |
+ @assert window.CanvasPattern !== undefined;
+
+ window.CanvasPattern.prototype.thisImplementsCanvasPattern = true;
+
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ @assert pattern.thisImplementsCanvasPattern;
+
+- name: 2d.pattern.basic.image
+ images:
+ - green.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.basic.canvas
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.basic.zerocanvas
+ code: |
+ canvas.width = 0;
+ canvas.height = 10;
+ @assert canvas.width === 0;
+ @assert canvas.height === 10;
+ @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
+
+ canvas.width = 10;
+ canvas.height = 0;
+ @assert canvas.width === 10;
+ @assert canvas.height === 0;
+ @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
+
+ canvas.width = 0;
+ canvas.height = 0;
+ @assert canvas.width === 0;
+ @assert canvas.height === 0;
+ @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
+
+- name: 2d.pattern.basic.nocontext
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.transform.identity
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+ pattern.setTransform(new DOMMatrix());
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.transform.infinity
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+ pattern.setTransform({a: Infinity});
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.transform.invalid
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+ @assert throws TypeError pattern.setTransform({a: 1, m11: 2});
+
+- name: 2d.pattern.image.undefined
+ notes: *bindings
+ code: |
+ @assert throws TypeError ctx.createPattern(undefined, 'repeat');
+
+- name: 2d.pattern.image.null
+ notes: *bindings
+ code: |
+ @assert throws TypeError ctx.createPattern(null, 'repeat');
+
+- name: 2d.pattern.image.string
+ notes: *bindings
+ code: |
+ @assert throws TypeError ctx.createPattern('../images/red.png', 'repeat');
+
+- name: 2d.pattern.image.incomplete.nosrc
+ code: |
+ var img = new Image();
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.image.incomplete.immediate
+ images:
+ - red.png
+ code: |
+ var img = new Image();
+ img.src = '../images/red.png';
+ // This triggers the "update the image data" algorithm.
+ // The image will not go to the "completely available" state
+ // until a fetch task in the networking task source is processed,
+ // so the image must not be fully decodable yet:
+ @assert ctx.createPattern(img, 'repeat') === null; @moz-todo
+
+- name: 2d.pattern.image.incomplete.reload
+ images:
+ - yellow.png
+ - red.png
+ code: |
+ var img = document.getElementById('yellow.png');
+ img.src = '../images/red.png';
+ // This triggers the "update the image data" algorithm,
+ // and resets the image to the "unavailable" state.
+ // The image will not go to the "completely available" state
+ // until a fetch task in the networking task source is processed,
+ // so the image must not be fully decodable yet:
+ @assert ctx.createPattern(img, 'repeat') === null; @moz-todo
+
+- name: 2d.pattern.image.incomplete.emptysrc
+ images:
+ - red.png
+ mozilla: {throws: !!null ''}
+ code: |
+ var img = document.getElementById('red.png');
+ img.src = "";
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.image.incomplete.removedsrc
+ images:
+ - red.png
+ mozilla: {throws: !!null ''}
+ code: |
+ var img = document.getElementById('red.png');
+ img.removeAttribute('src');
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.image.broken
+ images:
+ - broken.png
+ code: |
+ var img = document.getElementById('broken.png');
+ @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat');
+
+- name: 2d.pattern.image.nonexistent
+ images:
+ - no-such-image-really.png
+ code: |
+ var img = document.getElementById('no-such-image-really.png');
+ @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat');
+
+- name: 2d.pattern.svgimage.nonexistent
+ svgimages:
+ - no-such-image-really.png
+ code: |
+ var img = document.getElementById('no-such-image-really.png');
+ @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat');
+
+- name: 2d.pattern.image.nonexistent-but-loading
+ code: |
+ var img = document.createElement("img");
+ img.src = "/images/no-such-image-really.png";
+ @assert ctx.createPattern(img, 'repeat') === null;
+ var img = document.createElementNS("http://www.w3.org/2000/svg", "image");
+ img.src = "/images/no-such-image-really.png";
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.image.nosrc
+ code: |
+ var img = document.createElement("img");
+ @assert ctx.createPattern(img, 'repeat') === null;
+ var img = document.createElementNS("http://www.w3.org/2000/svg", "image");
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.image.zerowidth
+ images:
+ - red-zerowidth.svg
+ code: |
+ var img = document.getElementById('red-zerowidth.svg');
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.image.zeroheight
+ images:
+ - red-zeroheight.svg
+ code: |
+ var img = document.getElementById('red-zeroheight.svg');
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.svgimage.zerowidth
+ svgimages:
+ - red-zerowidth.svg
+ code: |
+ var img = document.getElementById('red-zerowidth.svg');
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.svgimage.zeroheight
+ svgimages:
+ - red-zeroheight.svg
+ code: |
+ var img = document.getElementById('red-zeroheight.svg');
+ @assert ctx.createPattern(img, 'repeat') === null;
+
+- name: 2d.pattern.repeat.empty
+ images:
+ - green-1x1.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var img = document.getElementById('green-1x1.png');
+ var pattern = ctx.createPattern(img, "");
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 200, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.repeat.null
+ code: |
+ @assert ctx.createPattern(canvas, null) != null;
+
+- name: 2d.pattern.repeat.undefined
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, undefined);
+
+- name: 2d.pattern.repeat.unrecognised
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "invalid");
+
+- name: 2d.pattern.repeat.unrecognisednull
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "null");
+
+- name: 2d.pattern.repeat.case
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "Repeat");
+
+- name: 2d.pattern.repeat.nullsuffix
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "repeat\0");
+
+- name: 2d.pattern.modify.image1
+ images:
+ - green.png
+ code: |
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ deferTest();
+ img.onload = t.step_func_done(function ()
+ {
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ });
+ img.src = '/images/red.png';
+ expected: green
+
+- name: 2d.pattern.modify.image2
+ images:
+ - green.png
+ code: |
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#00f';
+ ctx.fillRect(0, 0, 100, 50);
+ deferTest();
+ img.onload = t.step_func_done(function ()
+ {
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ });
+ img.src = '/images/red.png';
+ expected: green
+
+- name: 2d.pattern.modify.canvas1
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.modify.canvas2
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.crosscanvas
+ images:
+ - green.png
+ code: |
+ var img = document.getElementById('green.png');
+
+ var pattern = document.createElement('canvas').getContext('2d').createPattern(img, 'no-repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.norepeat.basic
+ images:
+ - green.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.norepeat.outside
+ images:
+ - red.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('red.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+ ctx.fillRect(-100, 0, 100, 50);
+ ctx.fillRect(0, 50, 100, 50);
+ ctx.fillRect(100, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.norepeat.coord1
+ images:
+ - green.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 0);
+ ctx.fillRect(-50, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.norepeat.coord2
+ images:
+ - green.png
+ code: |
+ var img = document.getElementById('green.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 50, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 0);
+ ctx.fillRect(-50, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.norepeat.coord3
+ images:
+ - red.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('red.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 25);
+ ctx.fillRect(-50, -25, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 25);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeat.basic
+ images:
+ - green-16x16.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('green-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeat.outside
+ images:
+ - green-16x16.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('green-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 25);
+ ctx.fillRect(-50, -25, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeat.coord1
+ images:
+ - rgrg-256x256.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('rgrg-256x256.png');
+ var pattern = ctx.createPattern(img, 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(-128, -78);
+ ctx.fillRect(128, 78, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeat.coord2
+ images:
+ - ggrr-256x256.png
+ code: |
+ var img = document.getElementById('ggrr-256x256.png');
+ var pattern = ctx.createPattern(img, 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeat.coord3
+ images:
+ - rgrg-256x256.png
+ code: |
+ var img = document.getElementById('rgrg-256x256.png');
+ var pattern = ctx.createPattern(img, 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(-128, -78);
+ ctx.fillRect(128, 78, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeatx.basic
+ images:
+ - green-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 16);
+
+ var img = document.getElementById('green-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat-x');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeatx.outside
+ images:
+ - red-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('red-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat-x');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 16);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeatx.coord1
+ images:
+ - red-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('red-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat-x');
+ ctx.fillStyle = pattern;
+ ctx.translate(0, 16);
+ ctx.fillRect(0, -16, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 16);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeaty.basic
+ images:
+ - green-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 16, 50);
+
+ var img = document.getElementById('green-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat-y');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeaty.outside
+ images:
+ - red-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('red-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat-y');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 16, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.repeaty.coord1
+ images:
+ - red-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('red-16x16.png');
+ var pattern = ctx.createPattern(img, 'repeat-y');
+ ctx.fillStyle = pattern;
+ ctx.translate(48, 0);
+ ctx.fillRect(-48, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 16, 50);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.orientation.image
+ desc: Image patterns do not get flipped when painted
+ images:
+ - rrgg-256x256.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var img = document.getElementById('rrgg-256x256.png');
+ var pattern = ctx.createPattern(img, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.save();
+ ctx.translate(0, -103);
+ ctx.fillRect(0, 103, 100, 50);
+ ctx.restore();
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 25);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.pattern.paint.orientation.canvas
+ desc: Canvas patterns do not get flipped when painted
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 25);
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 25, 100, 25);
+
+ var pattern = ctx.createPattern(canvas2, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 25);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.pattern.animated.gif
+ desc: createPattern() of an animated GIF draws the first frame
+ images:
+ - anim-gr.gif
+ code: |
+ deferTest();
+ step_timeout(function () {
+ var pattern = ctx.createPattern(document.getElementById('anim-gr.gif'), 'repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 50, 50);
+ step_timeout(t.step_func_done(function () {
+ ctx.fillRect(50, 0, 50, 50);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 250);
+ }, 250);
+ expected: green
+
+- name: 2d.fillStyle.CSSRGB
+ desc: CSSRGB works as color input
+ code: |
+ ctx.fillStyle = new CSSRGB(1, 0, 1);
+ @assert ctx.fillStyle === '#ff00ff';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 255,0,255,255;
+
+ const color = new CSSRGB(0, CSS.percent(50), 0);
+ ctx.fillStyle = color;
+ @assert ctx.fillStyle === '#008000';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,128,0,255;
+ color.g = 0;
+ ctx.fillStyle = color;
+ @assert ctx.fillStyle === '#000000';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,0,0,255;
+
+ color.alpha = 0;
+ ctx.fillStyle = color;
+ @assert ctx.fillStyle === 'rgba(0, 0, 0, 0)';
+ ctx.reset();
+ color.alpha = 0.5;
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,0,0,128;
+
+ ctx.fillStyle = new CSSHSL(CSS.deg(0), 1, 1).toRGB();
+ @assert ctx.fillStyle === '#ffffff';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 255,255,255,255;
+
+ color.alpha = 1;
+ color.g = 1;
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, 100, 50);
+ expected: green
+
+- name: 2d.fillStyle.CSSHSL
+ desc: CSSHSL works as color input
+ code: |
+ ctx.fillStyle = new CSSHSL(CSS.deg(180), 0.5, 0.5);
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 64,191,191,255 +/- 3;
+
+ const color = new CSSHSL(CSS.deg(180), 1, 1);
+ ctx.fillStyle = color;
+ @assert ctx.fillStyle === '#ffffff';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 255,255,255,255;
+ color.l = 0.5;
+ ctx.fillStyle = color;
+ @assert ctx.fillStyle === '#00ffff';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,255,255;
+
+ ctx.fillStyle = new CSSRGB(1, 0, 1).toHSL();
+ @assert ctx.fillStyle === '#ff00ff';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 255,0,255,255;
+
+ color.h = CSS.deg(120);
+ color.s = 1;
+ color.l = 0.5;
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, 100, 50);
+ expected: green
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/line-styles.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/line-styles.yaml
new file mode 100644
index 0000000000..604f4f3659
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/line-styles.yaml
@@ -0,0 +1,939 @@
+- name: 2d.line.defaults
+ code: |
+ @assert ctx.lineWidth === 1;
+ @assert ctx.lineCap === 'butt';
+ @assert ctx.lineJoin === 'miter';
+ @assert ctx.miterLimit === 10;
+
+- name: 2d.line.width.basic
+ desc: lineWidth determines the width of line strokes
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 20;
+ // Draw a green line over a red box, to check the line is not too small
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 15, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+
+ // Draw a green box over a red line, to check the line is not too large
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.fillRect(65, 15, 20, 20);
+
+ @assert pixel 14,25 == 0,255,0,255;
+ @assert pixel 15,25 == 0,255,0,255;
+ @assert pixel 16,25 == 0,255,0,255;
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 34,25 == 0,255,0,255;
+ @assert pixel 35,25 == 0,255,0,255;
+ @assert pixel 36,25 == 0,255,0,255;
+
+ @assert pixel 64,25 == 0,255,0,255;
+ @assert pixel 65,25 == 0,255,0,255;
+ @assert pixel 66,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ @assert pixel 84,25 == 0,255,0,255;
+ @assert pixel 85,25 == 0,255,0,255;
+ @assert pixel 86,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.width.transformed
+ desc: Line stroke widths are affected by scale transformations
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 4;
+ // Draw a green line over a red box, to check the line is not too small
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 15, 20, 20);
+ ctx.save();
+ ctx.scale(5, 1);
+ ctx.beginPath();
+ ctx.moveTo(5, 15);
+ ctx.lineTo(5, 35);
+ ctx.stroke();
+ ctx.restore();
+
+ // Draw a green box over a red line, to check the line is not too large
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.save();
+ ctx.scale(-5, 1);
+ ctx.beginPath();
+ ctx.moveTo(-15, 15);
+ ctx.lineTo(-15, 35);
+ ctx.stroke();
+ ctx.restore();
+ ctx.fillRect(65, 15, 20, 20);
+
+ @assert pixel 14,25 == 0,255,0,255;
+ @assert pixel 15,25 == 0,255,0,255;
+ @assert pixel 16,25 == 0,255,0,255;
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 34,25 == 0,255,0,255;
+ @assert pixel 35,25 == 0,255,0,255;
+ @assert pixel 36,25 == 0,255,0,255;
+
+ @assert pixel 64,25 == 0,255,0,255;
+ @assert pixel 65,25 == 0,255,0,255;
+ @assert pixel 66,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ @assert pixel 84,25 == 0,255,0,255;
+ @assert pixel 85,25 == 0,255,0,255;
+ @assert pixel 86,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.width.scaledefault
+ desc: Default lineWidth strokes are affected by scale transformations
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.scale(50, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.moveTo(0, 0.5);
+ ctx.lineTo(2, 0.5);
+ ctx.stroke();
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ @assert pixel 50,5 == 0,255,0,255;
+ @assert pixel 50,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.width.valid
+ desc: Setting lineWidth to valid values works
+ code: |
+ ctx.lineWidth = 1.5;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = "1e1";
+ @assert ctx.lineWidth === 10;
+
+ ctx.lineWidth = 1/1024;
+ @assert ctx.lineWidth === 1/1024;
+
+ ctx.lineWidth = 1000;
+ @assert ctx.lineWidth === 1000;
+
+- name: 2d.line.width.invalid
+ desc: Setting lineWidth to invalid values is ignored
+ code: |
+ ctx.lineWidth = 1.5;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = 0;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = -1;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = Infinity;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = -Infinity;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = NaN;
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = 'string';
+ @assert ctx.lineWidth === 1.5;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = true;
+ @assert ctx.lineWidth === 1;
+
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = false;
+ @assert ctx.lineWidth === 1.5;
+
+- name: 2d.line.cap.butt
+ desc: lineCap 'butt' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineCap = 'butt';
+ ctx.lineWidth = 20;
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 15, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.fillRect(65, 15, 20, 20);
+
+ @assert pixel 25,14 == 0,255,0,255;
+ @assert pixel 25,15 == 0,255,0,255;
+ @assert pixel 25,16 == 0,255,0,255;
+ @assert pixel 25,34 == 0,255,0,255;
+ @assert pixel 25,35 == 0,255,0,255;
+ @assert pixel 25,36 == 0,255,0,255;
+
+ @assert pixel 75,14 == 0,255,0,255;
+ @assert pixel 75,15 == 0,255,0,255;
+ @assert pixel 75,16 == 0,255,0,255;
+ @assert pixel 75,34 == 0,255,0,255;
+ @assert pixel 75,35 == 0,255,0,255;
+ @assert pixel 75,36 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.cap.round
+ desc: lineCap 'round' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+
+ ctx.lineCap = 'round';
+ ctx.lineWidth = 20;
+
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+
+ ctx.beginPath();
+ ctx.moveTo(35-tol, 15);
+ ctx.arc(25, 15, 10-tol, 0, Math.PI, true);
+ ctx.arc(25, 35, 10-tol, Math.PI, 0, true);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+
+
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(85+tol, 15);
+ ctx.arc(75, 15, 10+tol, 0, Math.PI, true);
+ ctx.arc(75, 35, 10+tol, Math.PI, 0, true);
+ ctx.fill();
+
+ @assert pixel 17,6 == 0,255,0,255;
+ @assert pixel 25,6 == 0,255,0,255;
+ @assert pixel 32,6 == 0,255,0,255;
+ @assert pixel 17,43 == 0,255,0,255;
+ @assert pixel 25,43 == 0,255,0,255;
+ @assert pixel 32,43 == 0,255,0,255;
+
+ @assert pixel 67,6 == 0,255,0,255;
+ @assert pixel 75,6 == 0,255,0,255;
+ @assert pixel 82,6 == 0,255,0,255;
+ @assert pixel 67,43 == 0,255,0,255;
+ @assert pixel 75,43 == 0,255,0,255;
+ @assert pixel 82,43 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.cap.square
+ desc: lineCap 'square' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineCap = 'square';
+ ctx.lineWidth = 20;
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 5, 20, 40);
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.fillRect(65, 5, 20, 40);
+
+ @assert pixel 25,4 == 0,255,0,255;
+ @assert pixel 25,5 == 0,255,0,255;
+ @assert pixel 25,6 == 0,255,0,255;
+ @assert pixel 25,44 == 0,255,0,255;
+ @assert pixel 25,45 == 0,255,0,255;
+ @assert pixel 25,46 == 0,255,0,255;
+
+ @assert pixel 75,4 == 0,255,0,255;
+ @assert pixel 75,5 == 0,255,0,255;
+ @assert pixel 75,6 == 0,255,0,255;
+ @assert pixel 75,44 == 0,255,0,255;
+ @assert pixel 75,45 == 0,255,0,255;
+ @assert pixel 75,46 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.cap.open
+ desc: Line caps are drawn at the corners of an unclosed rectangle
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineCap = 'square';
+ ctx.lineWidth = 400;
+
+ ctx.beginPath();
+ ctx.moveTo(200, 200);
+ ctx.lineTo(200, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 200);
+ ctx.lineTo(200, 200);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.cap.closed
+ desc: Line caps are not drawn at the corners of an unclosed rectangle
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineCap = 'square';
+ ctx.lineWidth = 400;
+
+ ctx.beginPath();
+ ctx.moveTo(200, 200);
+ ctx.lineTo(200, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 200);
+ ctx.closePath();
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.cap.valid
+ desc: Setting lineCap to valid values works
+ code: |
+ ctx.lineCap = 'butt'
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'round';
+ @assert ctx.lineCap === 'round';
+
+ ctx.lineCap = 'square';
+ @assert ctx.lineCap === 'square';
+
+- name: 2d.line.cap.invalid
+ desc: Setting lineCap to invalid values is ignored
+ code: |
+ ctx.lineCap = 'butt'
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'invalid';
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'ROUND';
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'round\0';
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'round ';
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'butt';
+ ctx.lineCap = "";
+ @assert ctx.lineCap === 'butt';
+
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'bevel';
+ @assert ctx.lineCap === 'butt';
+
+- name: 2d.line.join.bevel
+ desc: lineJoin 'bevel' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineWidth = 20;
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+
+ ctx.fillRect(10, 10, 20, 20);
+ ctx.fillRect(20, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(30, 20);
+ ctx.lineTo(40-tol, 20);
+ ctx.lineTo(30, 10+tol);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.moveTo(10, 20);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(30, 40);
+ ctx.stroke();
+
+
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+
+ ctx.beginPath();
+ ctx.moveTo(60, 20);
+ ctx.lineTo(80, 20);
+ ctx.lineTo(80, 40);
+ ctx.stroke();
+
+ ctx.fillRect(60, 10, 20, 20);
+ ctx.fillRect(70, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(80, 20);
+ ctx.lineTo(90+tol, 20);
+ ctx.lineTo(80, 10-tol);
+ ctx.fill();
+
+ @assert pixel 34,16 == 0,255,0,255;
+ @assert pixel 34,15 == 0,255,0,255;
+ @assert pixel 35,15 == 0,255,0,255;
+ @assert pixel 36,15 == 0,255,0,255;
+ @assert pixel 36,14 == 0,255,0,255;
+
+ @assert pixel 84,16 == 0,255,0,255;
+ @assert pixel 84,15 == 0,255,0,255;
+ @assert pixel 85,15 == 0,255,0,255;
+ @assert pixel 86,15 == 0,255,0,255;
+ @assert pixel 86,14 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.join.round
+ desc: lineJoin 'round' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 20;
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+
+ ctx.fillRect(10, 10, 20, 20);
+ ctx.fillRect(20, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(30, 20);
+ ctx.arc(30, 20, 10-tol, 0, 2*Math.PI, true);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.moveTo(10, 20);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(30, 40);
+ ctx.stroke();
+
+
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+
+ ctx.beginPath();
+ ctx.moveTo(60, 20);
+ ctx.lineTo(80, 20);
+ ctx.lineTo(80, 40);
+ ctx.stroke();
+
+ ctx.fillRect(60, 10, 20, 20);
+ ctx.fillRect(70, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(80, 20);
+ ctx.arc(80, 20, 10+tol, 0, 2*Math.PI, true);
+ ctx.fill();
+
+ @assert pixel 36,14 == 0,255,0,255;
+ @assert pixel 36,13 == 0,255,0,255;
+ @assert pixel 37,13 == 0,255,0,255;
+ @assert pixel 38,13 == 0,255,0,255;
+ @assert pixel 38,12 == 0,255,0,255;
+
+ @assert pixel 86,14 == 0,255,0,255;
+ @assert pixel 86,13 == 0,255,0,255;
+ @assert pixel 87,13 == 0,255,0,255;
+ @assert pixel 88,13 == 0,255,0,255;
+ @assert pixel 88,12 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.join.miter
+ desc: lineJoin 'miter' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineJoin = 'miter';
+ ctx.lineWidth = 20;
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+
+ ctx.fillRect(10, 10, 30, 20);
+ ctx.fillRect(20, 10, 20, 30);
+
+ ctx.beginPath();
+ ctx.moveTo(10, 20);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(30, 40);
+ ctx.stroke();
+
+
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+
+ ctx.beginPath();
+ ctx.moveTo(60, 20);
+ ctx.lineTo(80, 20);
+ ctx.lineTo(80, 40);
+ ctx.stroke();
+
+ ctx.fillRect(60, 10, 30, 20);
+ ctx.fillRect(70, 10, 20, 30);
+
+ @assert pixel 38,12 == 0,255,0,255;
+ @assert pixel 39,11 == 0,255,0,255;
+ @assert pixel 40,10 == 0,255,0,255;
+ @assert pixel 41,9 == 0,255,0,255;
+ @assert pixel 42,8 == 0,255,0,255;
+
+ @assert pixel 88,12 == 0,255,0,255;
+ @assert pixel 89,11 == 0,255,0,255;
+ @assert pixel 90,10 == 0,255,0,255;
+ @assert pixel 91,9 == 0,255,0,255;
+ @assert pixel 92,8 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.join.open
+ desc: Line joins are not drawn at the corner of an unclosed rectangle
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineJoin = 'miter';
+ ctx.lineWidth = 200;
+
+ ctx.beginPath();
+ ctx.moveTo(100, 50);
+ ctx.lineTo(100, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 50);
+ ctx.lineTo(100, 50);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.join.closed
+ desc: Line joins are drawn at the corner of a closed rectangle
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineJoin = 'miter';
+ ctx.lineWidth = 200;
+
+ ctx.beginPath();
+ ctx.moveTo(100, 50);
+ ctx.lineTo(100, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 50);
+ ctx.closePath();
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.join.parallel
+ desc: Line joins are drawn at 180-degree joins
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 300;
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(0, 25);
+ ctx.lineTo(-100, 25);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.join.valid
+ desc: Setting lineJoin to valid values works
+ code: |
+ ctx.lineJoin = 'bevel'
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'round';
+ @assert ctx.lineJoin === 'round';
+
+ ctx.lineJoin = 'miter';
+ @assert ctx.lineJoin === 'miter';
+
+- name: 2d.line.join.invalid
+ desc: Setting lineJoin to invalid values is ignored
+ code: |
+ ctx.lineJoin = 'bevel'
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'invalid';
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'ROUND';
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'round\0';
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'round ';
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = "";
+ @assert ctx.lineJoin === 'bevel';
+
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'butt';
+ @assert ctx.lineJoin === 'bevel';
+
+- name: 2d.line.miter.exceeded
+ desc: Miter joins are not drawn when the miter limit is exceeded
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.414;
+ ctx.beginPath();
+ ctx.moveTo(200, 1000);
+ ctx.lineTo(200, 200);
+ ctx.lineTo(1000, 201); // slightly non-right-angle to avoid being a special case
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.miter.acute
+ desc: Miter joins are drawn correctly with acute angles
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+
+ ctx.strokeStyle = '#0f0';
+ ctx.miterLimit = 2.614;
+ ctx.beginPath();
+ ctx.moveTo(100, 1000);
+ ctx.lineTo(100, 100);
+ ctx.lineTo(1000, 1000);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 2.613;
+ ctx.beginPath();
+ ctx.moveTo(100, 1000);
+ ctx.lineTo(100, 100);
+ ctx.lineTo(1000, 1000);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.miter.obtuse
+ desc: Miter joins are drawn correctly with obtuse angles
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 1600;
+ ctx.lineJoin = 'miter';
+
+ ctx.strokeStyle = '#0f0';
+ ctx.miterLimit = 1.083;
+ ctx.beginPath();
+ ctx.moveTo(800, 10000);
+ ctx.lineTo(800, 300);
+ ctx.lineTo(10000, -8900);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.082;
+ ctx.beginPath();
+ ctx.moveTo(800, 10000);
+ ctx.lineTo(800, 300);
+ ctx.lineTo(10000, -8900);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.miter.rightangle
+ desc: Miter joins are not drawn when the miter limit is exceeded, on exact right
+ angles
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.414;
+ ctx.beginPath();
+ ctx.moveTo(200, 1000);
+ ctx.lineTo(200, 200);
+ ctx.lineTo(1000, 200);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.miter.lineedge
+ desc: Miter joins are not drawn when the miter limit is exceeded at the corners
+ of a zero-height rectangle
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.414;
+ ctx.beginPath();
+ ctx.strokeRect(100, 25, 200, 0);
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.miter.within
+ desc: Miter joins are drawn when the miter limit is not quite exceeded
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+
+ ctx.strokeStyle = '#0f0';
+ ctx.miterLimit = 1.416;
+ ctx.beginPath();
+ ctx.moveTo(200, 1000);
+ ctx.lineTo(200, 200);
+ ctx.lineTo(1000, 201);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.miter.valid
+ desc: Setting miterLimit to valid values works
+ code: |
+ ctx.miterLimit = 1.5;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = "1e1";
+ @assert ctx.miterLimit === 10;
+
+ ctx.miterLimit = 1/1024;
+ @assert ctx.miterLimit === 1/1024;
+
+ ctx.miterLimit = 1000;
+ @assert ctx.miterLimit === 1000;
+
+- name: 2d.line.miter.invalid
+ desc: Setting miterLimit to invalid values is ignored
+ code: |
+ ctx.miterLimit = 1.5;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = 0;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = -1;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = Infinity;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = -Infinity;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = NaN;
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = 'string';
+ @assert ctx.miterLimit === 1.5;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = true;
+ @assert ctx.miterLimit === 1;
+
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = false;
+ @assert ctx.miterLimit === 1.5;
+
+- name: 2d.line.cross
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'bevel';
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(110, 50);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(100, 60);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.union
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 24);
+ ctx.lineTo(100, 25);
+ ctx.lineTo(0, 26);
+ ctx.closePath();
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 25,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 25,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.line.invalid.strokestyle
+ desc: Verify correct behavior of canvas on an invalid strokeStyle()
+ code: |
+ ctx.strokeStyle = 'rgb(0, 255, 0)';
+ ctx.strokeStyle = 'nonsense';
+ ctx.lineWidth = 200;
+ ctx.moveTo(0,100);
+ ctx.lineTo(200,100);
+ ctx.stroke();
+ var imageData = ctx.getImageData(0, 0, 200, 200);
+ var imgdata = imageData.data;
+ @assert imgdata[4] == 0;
+ @assert imgdata[5] == 255;
+ @assert imgdata[6] == 0;
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/meta.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/meta.yaml
new file mode 100644
index 0000000000..b8ffbd5075
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/meta.yaml
@@ -0,0 +1,538 @@
+- meta: |
+ cases = [
+ ("zero", "0", 0),
+ ("empty", "", None),
+ ("onlyspace", " ", None),
+ ("space", " 100", 100),
+ ("whitespace", "\r\n\t\f100", 100),
+ ("plus", "+100", 100),
+ ("minus", "-100", None),
+ ("octal", "0100", 100),
+ ("hex", "0x100", 0),
+ ("exp", "100e1", 100),
+ ("decimal", "100.999", 100),
+ ("percent", "100%", 100),
+ ("em", "100em", 100),
+ ("junk", "#!?", None),
+ ("trailingjunk", "100#!?", 100),
+ ]
+ def gen(name, string, exp, code):
+ if exp is None:
+ code += "@assert canvas.width === 300;\n@assert canvas.height === 150;\n"
+ expected = "size 300 150"
+ else:
+ code += "@assert canvas.width === %s;\n@assert canvas.height === %s;\n" % (exp, exp)
+ expected = "size %s %s" % (exp, exp)
+
+ # With "100%", Opera gets canvas.width = 100 but renders at 100% of the frame width,
+ # so check the CSS display width
+ code += '@assert window.getComputedStyle(canvas, null).getPropertyValue("width") === "%spx";\n' % (exp, )
+
+ code += "@assert canvas.getAttribute('width') === %r;\n" % string
+ code += "@assert canvas.getAttribute('height') === %r;\n" % string
+
+ if exp == 0:
+ expected = None # can't generate zero-sized PNGs for the expected image
+
+ return code, expected
+
+ for name, string, exp in cases:
+ code = ""
+ code, expected = gen(name, string, exp, code)
+ # We need to replace \r with &#xD; because \r\n gets converted to \n in the HTML parser.
+ htmlString = string.replace('\r', '&#xD;')
+ tests.append( {
+ "name": "size.attributes.parse.%s" % name,
+ "desc": "Parsing of non-negative integers",
+ "canvas": 'width="%s" height="%s"' % (htmlString, htmlString),
+ "code": code,
+ "expected": expected
+ } )
+
+ for name, string, exp in cases:
+ code = "canvas.setAttribute('width', %r);\ncanvas.setAttribute('height', %r);\n" % (string, string)
+ code, expected = gen(name, string, exp, code)
+ tests.append( {
+ "name": "size.attributes.setAttribute.%s" % name,
+ "desc": "Parsing of non-negative integers in setAttribute",
+ "canvas": 'width="50" height="50"',
+ "code": code,
+ "expected": expected
+ } )
+
+- meta: |
+ state = [ # some non-default values to test with
+ ('strokeStyle', '"#ff0000"'),
+ ('fillStyle', '"#ff0000"'),
+ ('globalAlpha', 0.5),
+ ('lineWidth', 0.5),
+ ('lineCap', '"round"'),
+ ('lineJoin', '"round"'),
+ ('miterLimit', 0.5),
+ ('shadowOffsetX', 5),
+ ('shadowOffsetY', 5),
+ ('shadowBlur', 5),
+ ('shadowColor', '"#ff0000"'),
+ ('globalCompositeOperation', '"copy"'),
+ ('font', '"25px serif"'),
+ ('textAlign', '"center"'),
+ ('textBaseline', '"bottom"'),
+ ]
+ for key,value in state:
+ tests.append( {
+ 'name': '2d.state.saverestore.%s' % key,
+ 'desc': 'save()/restore() works for %s' % key,
+ 'code':
+ """// Test that restore() undoes any modifications
+ var old = ctx.%(key)s;
+ ctx.save();
+ ctx.%(key)s = %(value)s;
+ ctx.restore();
+ @assert ctx.%(key)s === old;
+
+ // Also test that save() doesn't modify the values
+ ctx.%(key)s = %(value)s;
+ old = ctx.%(key)s;
+ // we're not interested in failures caused by get(set(x)) != x (e.g.
+ // from rounding), so compare against 'old' instead of against %(value)s
+ ctx.save();
+ @assert ctx.%(key)s === old;
+ ctx.restore();
+ """ % { 'key':key, 'value':value }
+ } )
+
+ tests.append( {
+ 'name': 'initial.reset.2dstate',
+ 'desc': 'Resetting the canvas state resets 2D state variables',
+ 'code':
+ """canvas.width = 100;
+ var default_val;
+ """ + "".join(
+ """
+ default_val = ctx.%(key)s;
+ ctx.%(key)s = %(value)s;
+ canvas.width = 100;
+ @assert ctx.%(key)s === default_val;
+ """ % { 'key':key, 'value':value }
+ for key,value in state),
+ } )
+
+- meta: |
+ # Composite operation tests
+ # <http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2007-March/010608.html>
+ ops = [
+ # name FA FB
+ ('source-over', '1', '1-aA'),
+ ('destination-over', '1-aB', '1'),
+ ('source-in', 'aB', '0'),
+ ('destination-in', '0', 'aA'),
+ ('source-out', '1-aB', '0'),
+ ('destination-out', '0', '1-aA'),
+ ('source-atop', 'aB', '1-aA'),
+ ('destination-atop', '1-aB', 'aA'),
+ ('xor', '1-aB', '1-aA'),
+ ('copy', '1', '0'),
+ ('lighter', '1', '1'),
+ ]
+
+ # The ones that change the output when src = (0,0,0,0):
+ ops_trans = [ 'source-in', 'destination-in', 'source-out', 'destination-atop', 'copy' ];
+
+ def calc_output(A, B, FA_code, FB_code):
+ (RA, GA, BA, aA) = A
+ (RB, GB, BB, aB) = B
+ rA, gA, bA = RA*aA, GA*aA, BA*aA
+ rB, gB, bB = RB*aB, GB*aB, BB*aB
+
+ FA = eval(FA_code)
+ FB = eval(FB_code)
+
+ rO = rA*FA + rB*FB
+ gO = gA*FA + gB*FB
+ bO = bA*FA + bB*FB
+ aO = aA*FA + aB*FB
+
+ rO = min(255, rO)
+ gO = min(255, gO)
+ bO = min(255, bO)
+ aO = min(1, aO)
+
+ if aO:
+ RO = rO / aO
+ GO = gO / aO
+ BO = bO / aO
+ else: RO = GO = BO = 0
+
+ return (RO, GO, BO, aO)
+
+ def to_test(color):
+ r, g, b, a = color
+ return '%d,%d,%d,%d' % (round(r), round(g), round(b), round(a*255))
+ def to_cairo(color):
+ r, g, b, a = color
+ return '%f,%f,%f,%f' % (r/255., g/255., b/255., a)
+
+ for (name, src, dest) in [
+ ('solid', (255, 255, 0, 1.0), (0, 255, 255, 1.0)),
+ ('transparent', (0, 0, 255, 0.75), (0, 255, 0, 0.5)),
+ # catches the atop, xor and lighter bugs in Opera 9.10
+ ]:
+ for op, FA_code, FB_code in ops:
+ expected = calc_output(src, dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, src, to_test(expected)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % to_cairo(expected),
+ } )
+
+ for (name, src, dest) in [ ('image', (255, 255, 0, 0.75), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ expected = calc_output(src, dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'images': [ 'yellow75.png' ],
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.drawImage(document.getElementById('yellow75.png'), 0, 0);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, to_test(expected)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % to_cairo(expected),
+ } )
+
+ for (name, src, dest) in [ ('canvas', (255, 255, 0, 0.75), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ expected = calc_output(src, dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'images': [ 'yellow75.png' ],
+ 'code': """
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = canvas.width;
+ canvas2.height = canvas.height;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.drawImage(document.getElementById('yellow75.png'), 0, 0);
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.drawImage(canvas2, 0, 0);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, to_test(expected)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % to_cairo(expected),
+ } )
+
+
+ for (name, src, dest) in [ ('uncovered.fill', (0, 0, 255, 0.75), (0, 255, 0, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.fillStyle = 'rgba%s';
+ ctx.translate(0, 25);
+ ctx.fillRect(0, 50, 100, 50);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, src, to_test(expected0)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % (to_cairo(expected0)),
+ } )
+
+ for (name, src, dest) in [ ('uncovered.image', (255, 255, 0, 1.0), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'drawImage() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'images': [ 'yellow.png' ],
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.drawImage(document.getElementById('yellow.png'), 40, 40, 10, 10, 40, 50, 10, 10);
+ @assert pixel 15,15 ==~ %s +/- 5;
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, to_test(expected0), to_test(expected0)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % (to_cairo(expected0)),
+ } )
+
+ for (name, src, dest) in [ ('uncovered.nocontext', (255, 255, 0, 1.0), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'drawImage() of a canvas with no context draws pixels as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ var canvas2 = document.createElement('canvas');
+ ctx.drawImage(canvas2, 0, 0);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, to_test(expected0)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % (to_cairo(expected0)),
+ } )
+
+ for (name, src, dest) in [ ('uncovered.pattern', (255, 255, 0, 1.0), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'images': [ 'yellow.png' ],
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.fillStyle = ctx.createPattern(document.getElementById('yellow.png'), 'no-repeat');
+ ctx.fillRect(0, 50, 100, 50);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ """ % (dest, op, to_test(expected0)),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%s)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % (to_cairo(expected0)),
+ } )
+
+ for op, FA_code, FB_code in ops:
+ tests.append( {
+ 'name': '2d.composite.clip.%s' % (op),
+ 'desc': 'fill() does not affect pixels outside the clip region.',
+ 'code': """
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.rect(-20, -20, 10, 10);
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ """ % (op),
+ 'expected': 'green'
+ } )
+
+- meta: |
+ # Color parsing tests
+
+ # Try most of the CSS3 Color <color> values - http://www.w3.org/TR/css3-color/#colorunits
+ big_float = '1' + ('0' * 39)
+ big_double = '1' + ('0' * 310)
+ for name, string, r,g,b,a, notes in [
+ ('html4', 'limE', 0,255,0,255, ""),
+ ('hex3', '#0f0', 0,255,0,255, ""),
+ ('hex4', '#0f0f', 0,255,0,255, ""),
+ ('hex6', '#00fF00', 0,255,0,255, ""),
+ ('hex8', '#00ff00ff', 0,255,0,255, ""),
+ ('rgb-num', 'rgb(0,255,0)', 0,255,0,255, ""),
+ ('rgb-clamp-1', 'rgb(-1000, 1000, -1000)', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-2', 'rgb(-200%, 200%, -200%)', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-3', 'rgb(-2147483649, 4294967298, -18446744073709551619)', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-4', 'rgb(-'+big_float+', '+big_float+', -'+big_float+')', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-5', 'rgb(-'+big_double+', '+big_double+', -'+big_double+')', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-percent', 'rgb(0% ,100% ,0%)', 0,255,0,255, 'CSS3 Color says "The integer value 255 corresponds to 100%". (In particular, it is not 254...)'),
+ ('rgb-eof', 'rgb(0, 255, 0', 0,255,0,255, ""), # see CSS2.1 4.2 "Unexpected end of style sheet"
+ ('rgba-solid-1', 'rgba( 0 , 255 , 0 , 1 )', 0,255,0,255, ""),
+ ('rgba-solid-2', 'rgba( 0 , 255 , 0 , 1.0 )', 0,255,0,255, ""),
+ ('rgba-solid-3', 'rgba( 0 , 255 , 0 , +1 )', 0,255,0,255, ""),
+ ('rgba-solid-4', 'rgba( -0 , 255 , +0 , 1 )', 0,255,0,255, ""),
+ ('rgba-num-1', 'rgba( 0 , 255 , 0 , .499 )', 0,255,0,127, ""),
+ ('rgba-num-2', 'rgba( 0 , 255 , 0 , 0.499 )', 0,255,0,127, ""),
+ ('rgba-percent', 'rgba(0%,100%,0%,0.499)', 0,255,0,127, ""), # 0.499*255 rounds to 127, both down and nearest, so it should be safe
+ ('rgba-clamp-1', 'rgba(0, 255, 0, -2)', 0,0,0,0, ""),
+ ('rgba-clamp-2', 'rgba(0, 255, 0, 2)', 0,255,0,255, ""),
+ ('rgba-eof', 'rgba(0, 255, 0, 1', 0,255,0,255, ""),
+ ('transparent-1', 'transparent', 0,0,0,0, ""),
+ ('transparent-2', 'TrAnSpArEnT', 0,0,0,0, ""),
+ ('hsl-1', 'hsl(120, 100%, 50%)', 0,255,0,255, ""),
+ ('hsl-2', 'hsl( -240 , 100% , 50% )', 0,255,0,255, ""),
+ ('hsl-3', 'hsl(360120, 100%, 50%)', 0,255,0,255, ""),
+ ('hsl-4', 'hsl(-360240, 100%, 50%)', 0,255,0,255, ""),
+ ('hsl-5', 'hsl(120.0, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('hsl-6', 'hsl(+120, +100%, +50%)', 0,255,0,255, ""),
+ ('hsl-clamp-1', 'hsl(120, 200%, 50%)', 0,255,0,255, ""),
+ ('hsl-clamp-2', 'hsl(120, -200%, 49.9%)', 127,127,127,255, ""),
+ ('hsl-clamp-3', 'hsl(120, 100%, 200%)', 255,255,255,255, ""),
+ ('hsl-clamp-4', 'hsl(120, 100%, -200%)', 0,0,0,255, ""),
+ ('hsla-1', 'hsla(120, 100%, 50%, 0.499)', 0,255,0,127, ""),
+ ('hsla-2', 'hsla( 120.0 , 100.0% , 50.0% , 1 )', 0,255,0,255, ""),
+ ('hsla-clamp-1', 'hsla(120, 200%, 50%, 1)', 0,255,0,255, ""),
+ ('hsla-clamp-2', 'hsla(120, -200%, 49.9%, 1)', 127,127,127,255, ""),
+ ('hsla-clamp-3', 'hsla(120, 100%, 200%, 1)', 255,255,255,255, ""),
+ ('hsla-clamp-4', 'hsla(120, 100%, -200%, 1)', 0,0,0,255, ""),
+ ('hsla-clamp-5', 'hsla(120, 100%, 50%, 2)', 0,255,0,255, ""),
+ ('hsla-clamp-6', 'hsla(120, 100%, 0%, -2)', 0,0,0,0, ""),
+ ('svg-1', 'gray', 128,128,128,255, ""),
+ ('svg-2', 'grey', 128,128,128,255, ""),
+ # css-color-4 rgb() color function
+ # https://drafts.csswg.org/css-color/#numeric-rgb
+ ('css-color-4-rgb-1', 'rgb(0, 255.0, 0)', 0,255,0,255, ""),
+ ('css-color-4-rgb-2', 'rgb(0, 255, 0, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgb-3', 'rgb(0, 255, 0, 20%)', 0,255,0,51, ""),
+ ('css-color-4-rgb-4', 'rgb(0 255 0)', 0,255,0,255, ""),
+ ('css-color-4-rgb-5', 'rgb(0 255 0 / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgb-6', 'rgb(0 255 0 / 20%)', 0,255,0,51, ""),
+ ('css-color-4-rgba-1', 'rgba(0, 255.0, 0)', 0,255,0,255, ""),
+ ('css-color-4-rgba-2', 'rgba(0, 255, 0, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgba-3', 'rgba(0, 255, 0, 20%)', 0,255,0,51, ""),
+ ('css-color-4-rgba-4', 'rgba(0 255 0)', 0,255,0,255, ""),
+ ('css-color-4-rgba-5', 'rgba(0 255 0 / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgba-6', 'rgba(0 255 0 / 20%)', 0,255,0,51, ""),
+ # css-color-4 hsl() color function
+ # https://drafts.csswg.org/css-color/#the-hsl-notation
+ ('css-color-4-hsl-1', 'hsl(120 100.0% 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-2', 'hsl(120 100.0% 50.0% / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsl-3', 'hsl(120.0, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsl-4', 'hsl(120.0, 100.0%, 50.0%, 20%)', 0,255,0,51, ""),
+ ('css-color-4-hsl-5', 'hsl(120deg, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsl-6', 'hsl(120deg, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-7', 'hsl(133.33333333grad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-8', 'hsl(2.0943951024rad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-9', 'hsl(0.3333333333turn, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-1', 'hsl(120 100.0% 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-2', 'hsl(120 100.0% 50.0% / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsla-3', 'hsl(120.0, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsla-4', 'hsl(120.0, 100.0%, 50.0%, 20%)', 0,255,0,51, ""),
+ ('css-color-4-hsla-5', 'hsl(120deg, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsla-6', 'hsl(120deg, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-7', 'hsl(133.33333333grad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-8', 'hsl(2.0943951024rad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-9', 'hsl(0.3333333333turn, 100.0%, 50.0%)', 0,255,0,255, ""),
+ # currentColor is handled later
+ ]:
+ # TODO: test by retrieving fillStyle, instead of actually drawing?
+ # TODO: test strokeStyle, shadowColor in the same way
+ test = {
+ 'name': '2d.fillStyle.parse.%s' % name,
+ 'notes': notes,
+ 'code': """
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = '%s';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == %d,%d,%d,%d;
+ """ % (string, r,g,b,a),
+ 'expected': """size 100 50
+ cr.set_source_rgba(%f, %f, %f, %f)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ """ % (r/255., g/255., b/255., a/255.),
+ }
+ tests.append(test)
+
+ # Also test that invalid colors are ignored
+ for name, string in [
+ ('hex1', '#f'),
+ ('hex2', '#f0'),
+ ('hex3', '#g00'),
+ ('hex4', '#fg00'),
+ ('hex5', '#ff000'),
+ ('hex6', '#fg0000'),
+ ('hex7', '#ff0000f'),
+ ('hex8', '#fg0000ff'),
+ ('rgb-1', 'rgb(255.0, 0, 0,)'),
+ ('rgb-2', 'rgb(100%, 0, 0)'),
+ ('rgb-3', 'rgb(255, - 1, 0)'),
+ ('rgba-1', 'rgba(100%, 0, 0, 1)'),
+ ('rgba-2', 'rgba(255, 0, 0, 1. 0)'),
+ ('rgba-3', 'rgba(255, 0, 0, 1.)'),
+ ('rgba-4', 'rgba(255, 0, 0, '),
+ ('rgba-5', 'rgba(255, 0, 0, 1,)'),
+ ('hsl-1', 'hsl(0%, 100%, 50%)'),
+ ('hsl-2', 'hsl(z, 100%, 50%)'),
+ ('hsl-3', 'hsl(0, 0, 50%)'),
+ ('hsl-4', 'hsl(0, 100%, 0)'),
+ ('hsl-5', 'hsl(0, 100.%, 50%)'),
+ ('hsl-6', 'hsl(0, 100%, 50%,)'),
+ ('hsla-1', 'hsla(0%, 100%, 50%, 1)'),
+ ('hsla-2', 'hsla(0, 0, 50%, 1)'),
+ ('hsla-3', 'hsla(0, 0, 50%, 1,)'),
+ ('name-1', 'darkbrown'),
+ ('name-2', 'firebrick1'),
+ ('name-3', 'red blue'),
+ ('name-4', '"red"'),
+ ('name-5', '"red'),
+ # css-color-4 color function
+ # comma and comma-less expressions should not mix together.
+ ('css-color-4-rgb-1', 'rgb(255, 0, 0 / 1)'),
+ ('css-color-4-rgb-2', 'rgb(255 0 0, 1)'),
+ ('css-color-4-rgb-3', 'rgb(255, 0 0)'),
+ ('css-color-4-rgba-1', 'rgba(255, 0, 0 / 1)'),
+ ('css-color-4-rgba-2', 'rgba(255 0 0, 1)'),
+ ('css-color-4-rgba-3', 'rgba(255, 0 0)'),
+ ('css-color-4-hsl-1', 'hsl(0, 100%, 50% / 1)'),
+ ('css-color-4-hsl-2', 'hsl(0 100% 50%, 1)'),
+ ('css-color-4-hsl-3', 'hsl(0, 100% 50%)'),
+ ('css-color-4-hsla-1', 'hsla(0, 100%, 50% / 1)'),
+ ('css-color-4-hsla-2', 'hsla(0 100% 50%, 1)'),
+ ('css-color-4-hsla-3', 'hsla(0, 100% 50%)'),
+ # trailing slash
+ ('css-color-4-rgb-4', 'rgb(0 0 0 /)'),
+ ('css-color-4-rgb-5', 'rgb(0, 0, 0 /)'),
+ ('css-color-4-hsl-4', 'hsl(0 100% 50% /)'),
+ ('css-color-4-hsl-5', 'hsl(0, 100%, 50% /)'),
+ ]:
+ test = {
+ 'name': '2d.fillStyle.parse.invalid.%s' % name,
+ 'code': """
+ ctx.fillStyle = '#0f0';
+ try { ctx.fillStyle = '%s'; } catch (e) { } // this shouldn't throw, but it shouldn't matter here if it does
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ """ % string,
+ 'expected': 'green'
+ }
+ tests.append(test)
+
+ # Some can't have positive tests, only negative tests, because we don't know what color they're meant to be
+ for name, string in [
+ ('system', 'ThreeDDarkShadow'),
+ #('flavor', 'flavor'), # removed from latest CSS3 Color drafts
+ ]:
+ test = {
+ 'name': '2d.fillStyle.parse.%s' % name,
+ 'code': """
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = '%s';
+ @assert ctx.fillStyle =~ /^#(?!(FF0000|ff0000|f00)$)/; // test that it's not red
+ """ % (string,),
+ }
+ tests.append(test)
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/path-objects.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/path-objects.yaml
new file mode 100644
index 0000000000..eec142c769
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/path-objects.yaml
@@ -0,0 +1,3378 @@
+- name: 2d.path.initial
+ #mozilla: { bug: TODO }
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.closePath();
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.beginPath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.moveTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rect(0, 0, 10, 50);
+ ctx.moveTo(100, 0);
+ ctx.lineTo(10, 0);
+ ctx.lineTo(10, 50);
+ ctx.lineTo(100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 90,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.moveTo.newsubpath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.moveTo(100, 0);
+ ctx.moveTo(100, 50);
+ ctx.moveTo(0, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.moveTo.multiple
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.moveTo(0, 25);
+ ctx.moveTo(100, 25);
+ ctx.moveTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.moveTo.nonfinite
+ desc: moveTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.moveTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.closePath.empty
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.closePath();
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.closePath.newline
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-100, -100);
+ ctx.lineTo(200, -100);
+ ctx.lineTo(200, 25);
+ ctx.closePath();
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.closePath.nextpoint
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-100, -1000);
+ ctx.closePath();
+ ctx.lineTo(1000, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.lineTo.ensuresubpath.1
+ desc: If there is no subpath, the point is added and nothing is drawn
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.lineTo(100, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.lineTo.ensuresubpath.2
+ desc: If there is no subpath, the point is added and used for subsequent drawing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.lineTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.lineTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.lineTo.nextpoint
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(-100, -100);
+ ctx.lineTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.lineTo.nonfinite
+ desc: lineTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.lineTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.lineTo.nonfinite.details
+ desc: lineTo() with Infinity/NaN for first arg still converts the second arg
+ code: |
+ for (var arg1 of [Infinity, -Infinity, NaN]) {
+ var converted = false;
+ ctx.lineTo(arg1, { valueOf: function() { converted = true; return 0; } });
+ @assert converted;
+ }
+ expected: clear
+
+- name: 2d.path.quadraticCurveTo.ensuresubpath.1
+ desc: If there is no subpath, the first control point is added (and nothing is drawn
+ up to it)
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.quadraticCurveTo(100, 50, 200, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 95,45 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.quadraticCurveTo.ensuresubpath.2
+ desc: If there is no subpath, the first control point is added
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.quadraticCurveTo(0, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.quadraticCurveTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.quadraticCurveTo(100, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.quadraticCurveTo.shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 55;
+ ctx.beginPath();
+ ctx.moveTo(-1000, 1050);
+ ctx.quadraticCurveTo(0, -1000, 1200, 1050);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.quadraticCurveTo.scaled
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(1000, 1000);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 0.055;
+ ctx.beginPath();
+ ctx.moveTo(-1, 1.05);
+ ctx.quadraticCurveTo(0, -1, 1.2, 1.05);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.quadraticCurveTo.nonfinite
+ desc: quadraticCurveTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.quadraticCurveTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.bezierCurveTo.ensuresubpath.1
+ desc: If there is no subpath, the first control point is added (and nothing is drawn
+ up to it)
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.bezierCurveTo(100, 50, 200, 50, 200, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 95,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.bezierCurveTo.ensuresubpath.2
+ desc: If there is no subpath, the first control point is added
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.bezierCurveTo(0, 25, 100, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.bezierCurveTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.bezierCurveTo(100, 25, 100, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.bezierCurveTo.shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 55;
+ ctx.beginPath();
+ ctx.moveTo(-2000, 3100);
+ ctx.bezierCurveTo(-2000, -1000, 2100, -1000, 2100, 3100);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.bezierCurveTo.scaled
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(1000, 1000);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 0.055;
+ ctx.beginPath();
+ ctx.moveTo(-2, 3.1);
+ ctx.bezierCurveTo(-2, -1, 2.1, -1, 2.1, 3.1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.bezierCurveTo.nonfinite
+ desc: bezierCurveTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.bezierCurveTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.ensuresubpath.1
+ desc: If there is no subpath, the first control point is added (and nothing is drawn
+ up to it)
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arcTo(100, 50, 200, 50, 0.1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.ensuresubpath.2
+ desc: If there is no subpath, the first control point is added
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arcTo(0, 25, 50, 250, 0.1); // adds (x1,y1), draws nothing
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.coincide.1
+ desc: arcTo() has no effect if P0 = P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(0, 25, 50, 1000, 1);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arcTo(50, 25, 100, 25, 1);
+ ctx.stroke();
+
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.coincide.2
+ desc: arcTo() draws a straight line to P1 if P1 = P2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 100, 25, 1);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.collinear.1
+ desc: arcTo() with all points on a line, and P1 between P0/P2, draws a straight
+ line to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 200, 25, 1);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(-100, 25);
+ ctx.arcTo(0, 25, 100, 25, 1);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.collinear.2
+ desc: arcTo() with all points on a line, and P2 between P0/P1, draws a straight
+ line to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 10, 25, 1);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 25);
+ ctx.arcTo(200, 25, 110, 25, 1);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.collinear.3
+ desc: arcTo() with all points on a line, and P0 between P1/P2, draws a straight
+ line to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, -100, 25, 1);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 25);
+ ctx.arcTo(200, 25, 0, 25, 1);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(-100, 25);
+ ctx.arcTo(0, 25, -200, 25, 1);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.shape.curve1
+ desc: arcTo() curves in the right kind of shape
+ code: |
+ var tol = 1.5; // tolerance to avoid antialiasing artifacts
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 10;
+ ctx.beginPath();
+ ctx.moveTo(10, 25);
+ ctx.arcTo(75, 25, 75, 60, 20);
+ ctx.stroke();
+
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.rect(10, 20, 45, 10);
+ ctx.moveTo(80, 45);
+ ctx.arc(55, 45, 25+tol, 0, -Math.PI/2, true);
+ ctx.arc(55, 45, 15-tol, -Math.PI/2, 0, false);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 55,19 == 0,255,0,255;
+ @assert pixel 55,20 == 0,255,0,255;
+ @assert pixel 55,21 == 0,255,0,255;
+ @assert pixel 64,22 == 0,255,0,255;
+ @assert pixel 65,21 == 0,255,0,255;
+ @assert pixel 72,28 == 0,255,0,255;
+ @assert pixel 73,27 == 0,255,0,255;
+ @assert pixel 78,36 == 0,255,0,255;
+ @assert pixel 79,35 == 0,255,0,255;
+ @assert pixel 80,44 == 0,255,0,255;
+ @assert pixel 80,45 == 0,255,0,255;
+ @assert pixel 80,46 == 0,255,0,255;
+ @assert pixel 65,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.shape.curve2
+ desc: arcTo() curves in the right kind of shape
+ code: |
+ var tol = 1.5; // tolerance to avoid antialiasing artifacts
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.rect(10, 20, 45, 10);
+ ctx.moveTo(80, 45);
+ ctx.arc(55, 45, 25-tol, 0, -Math.PI/2, true);
+ ctx.arc(55, 45, 15+tol, -Math.PI/2, 0, false);
+ ctx.fill();
+
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 10;
+ ctx.beginPath();
+ ctx.moveTo(10, 25);
+ ctx.arcTo(75, 25, 75, 60, 20);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 55,19 == 0,255,0,255;
+ @assert pixel 55,20 == 0,255,0,255;
+ @assert pixel 55,21 == 0,255,0,255;
+ @assert pixel 64,22 == 0,255,0,255;
+ @assert pixel 65,21 == 0,255,0,255;
+ @assert pixel 72,28 == 0,255,0,255;
+ @assert pixel 73,27 == 0,255,0,255;
+ @assert pixel 78,36 == 0,255,0,255;
+ @assert pixel 79,35 == 0,255,0,255;
+ @assert pixel 80,44 == 0,255,0,255;
+ @assert pixel 80,45 == 0,255,0,255;
+ @assert pixel 80,46 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.shape.start
+ desc: arcTo() draws a straight line from P0 to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(200, 25, 200, 50, 10);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.shape.end
+ desc: arcTo() does not draw anything from P1 to P2
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(-100, -100);
+ ctx.arcTo(-100, 25, 200, 25, 10);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.negative
+ desc: arcTo() with negative radius throws an exception
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.arcTo(0, 0, 0, 0, -1);
+ var path = new Path2D();
+ @assert throws INDEX_SIZE_ERR path.arcTo(10, 10, 20, 20, -5);
+
+- name: 2d.path.arcTo.zero.1
+ desc: arcTo() with zero radius draws a straight line from P0 to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 100, 100, 0);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(0, -25);
+ ctx.arcTo(50, -25, 50, 50, 0);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.zero.2
+ desc: arcTo() with zero radius draws a straight line from P0 to P1, even when all
+ points are collinear
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, -100, 25, 0);
+ ctx.stroke();
+
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 25);
+ ctx.arcTo(200, 25, 50, 25, 0);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.transformation
+ desc: arcTo joins up to the last subpath point correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 50);
+ ctx.translate(100, 0);
+ ctx.arcTo(50, 50, 50, 0, 50);
+ ctx.lineTo(-100, 0);
+ ctx.fill();
+
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.scale
+ desc: arcTo scales the curve, not just the control points
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 50);
+ ctx.translate(100, 0);
+ ctx.scale(0.1, 1);
+ ctx.arcTo(50, 50, 50, 0, 50);
+ ctx.lineTo(-1000, 0);
+ ctx.fill();
+
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arcTo.nonfinite
+ desc: arcTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.arcTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.path.arc.empty
+ desc: arc() with an empty path does not draw a straight line to the start point
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(200, 25, 5, 0, 2*Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.nonempty
+ desc: arc() with a non-empty path does draw a straight line to the start point
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arc(200, 25, 5, 0, 2*Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.end
+ desc: arc() adds the end point of the arc to the subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(-100, 0);
+ ctx.arc(-100, 0, 25, -Math.PI/2, Math.PI/2, true);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.default
+ desc: arc() with missing last argument defaults to clockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, -Math.PI, Math.PI/2);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.angle.1
+ desc: arc() draws pi/2 .. -pi anticlockwise correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, Math.PI/2, -Math.PI, true);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.angle.2
+ desc: arc() draws -3pi/2 .. -pi anticlockwise correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, -3*Math.PI/2, -Math.PI, true);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.angle.3
+ desc: arc() wraps angles mod 2pi when anticlockwise and end > start+2pi
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, (512+1/2)*Math.PI, (1024-1)*Math.PI, true);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.angle.4
+ desc: arc() draws a full circle when clockwise and end > start+2pi
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arc(50, 25, 60, (512+1/2)*Math.PI, (1024-1)*Math.PI, false);
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.angle.5
+ desc: arc() wraps angles mod 2pi when clockwise and start > end+2pi
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, (1024-1)*Math.PI, (512+1/2)*Math.PI, false);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.angle.6
+ desc: arc() draws a full circle when anticlockwise and start > end+2pi
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arc(50, 25, 60, (1024-1)*Math.PI, (512+1/2)*Math.PI, true);
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.zero.1
+ desc: arc() draws nothing when startAngle = endAngle and anticlockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 0, true);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.zero.2
+ desc: arc() draws nothing when startAngle = endAngle and clockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 0, false);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.twopie.1
+ desc: arc() draws nothing when end = start + 2pi-e and anticlockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI - 1e-4, true);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.twopie.2
+ desc: arc() draws a full circle when end = start + 2pi-e and clockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI - 1e-4, false);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.twopie.3
+ desc: arc() draws a full circle when end = start + 2pi+e and anticlockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI + 1e-4, true);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.twopie.4
+ desc: arc() draws nothing when end = start + 2pi+e and clockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI + 1e-4, false);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.shape.1
+ desc: arc() from 0 to pi does not draw anything in the wrong half
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(50, 50, 50, 0, Math.PI, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 20,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.shape.2
+ desc: arc() from 0 to pi draws stuff in the right half
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 100;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(50, 50, 50, 0, Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 20,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.shape.3
+ desc: arc() from 0 to -pi/2 does not draw anything in the wrong quadrant
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 100;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(0, 50, 50, 0, -Math.PI/2, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.shape.4
+ desc: arc() from 0 to -pi/2 draws stuff in the right quadrant
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 150;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(-50, 50, 100, 0, -Math.PI/2, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.shape.5
+ desc: arc() from 0 to 5pi does not draw crazy things
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(300, 0, 100, 0, 5*Math.PI, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.selfintersect.1
+ desc: arc() with lineWidth > 2*radius is drawn sensibly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(100, 50, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(0, 0, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.selfintersect.2
+ desc: arc() with lineWidth > 2*radius is drawn sensibly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 180;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(-50, 50, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(100, 0, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,10 == 0,255,0,255;
+ @assert pixel 97,1 == 0,255,0,255;
+ @assert pixel 97,2 == 0,255,0,255;
+ @assert pixel 97,3 == 0,255,0,255;
+ @assert pixel 2,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.negative
+ desc: arc() with negative radius throws INDEX_SIZE_ERR
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.arc(0, 0, -1, 0, 0, true);
+ var path = new Path2D();
+ @assert throws INDEX_SIZE_ERR path.arc(10, 10, -5, 0, 1, false);
+
+- name: 2d.path.arc.zeroradius
+ desc: arc() with zero radius draws a line to the start point
+ code: |
+ ctx.fillStyle = '#f00'
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arc(200, 25, 0, 0, Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.scale.1
+ desc: Non-uniformly scaled arcs are the right shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(2, 0.5);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(25, 50, 56, 0, 2*Math.PI, false);
+ ctx.fill();
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(-25, 50);
+ ctx.arc(-25, 50, 24, 0, 2*Math.PI, false);
+ ctx.moveTo(75, 50);
+ ctx.arc(75, 50, 24, 0, 2*Math.PI, false);
+ ctx.moveTo(25, -25);
+ ctx.arc(25, -25, 24, 0, 2*Math.PI, false);
+ ctx.moveTo(25, 125);
+ ctx.arc(25, 125, 24, 0, 2*Math.PI, false);
+ ctx.fill();
+
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.scale.2
+ desc: Highly scaled arcs are the right shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(100, 100);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 1.2;
+ ctx.beginPath();
+ ctx.arc(0, 0, 0.6, 0, Math.PI/2, false);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.arc.nonfinite
+ desc: arc() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.arc(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <2*Math.PI Infinity -Infinity NaN>, <true>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.path.rect.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.rect(0, 0, 100, 50);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.newsubpath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-50, 25);
+ ctx.rect(200, 25, 1, 1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.closed
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.rect(100, 50, 100, 100);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.end.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.rect(200, 100, 400, 1000);
+ ctx.lineTo(-2000, -1000);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.end.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 450;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'bevel';
+ ctx.rect(150, 150, 2000, 2000);
+ ctx.lineTo(160, 160);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.zero.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.rect(0, 50, 100, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.zero.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.rect(50, -100, 0, 250);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.zero.3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.rect(50, 25, 0, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.zero.4
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.rect(100, 25, 0, 0);
+ ctx.lineTo(0, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.zero.5
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, 0);
+ ctx.rect(100, 25, 0, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.rect.zero.6
+ #mozilla: { bug: TODO }
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 1.5;
+ ctx.lineWidth = 200;
+ ctx.beginPath();
+ ctx.rect(100, 25, 1000, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.rect.negative
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#0f0';
+ ctx.rect(0, 0, 50, 25);
+ ctx.rect(100, 0, -50, 25);
+ ctx.rect(0, 50, 50, -25);
+ ctx.rect(100, 50, -50, -25);
+ ctx.fill();
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+
+- name: 2d.path.rect.winding
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#f00';
+ ctx.rect(0, 0, 50, 50);
+ ctx.rect(100, 50, -50, -50);
+ ctx.rect(0, 25, 100, -25);
+ ctx.rect(100, 25, -100, 25);
+ ctx.fill();
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+
+- name: 2d.path.rect.selfintersect
+ #mozilla: { bug: TODO }
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 90;
+ ctx.beginPath();
+ ctx.rect(45, 20, 10, 10);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.rect.nonfinite
+ desc: rect() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.rect(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.newsubpath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-50, 25);
+ ctx.roundRect(200, 25, 1, 1, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.closed
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.roundRect(100, 50, 100, 100, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.end.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.roundRect(200, 100, 400, 1000, [0]);
+ ctx.lineTo(-2000, -1000);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.end.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 450;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'bevel';
+ ctx.roundRect(150, 150, 2000, 2000, [0]);
+ ctx.lineTo(160, 160);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.end.3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.roundRect(101, 51, 2000, 2000, [500, 500, 500, 500]);
+ ctx.lineTo(-1, -1);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.end.4
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 10;
+ ctx.roundRect(-1, -1, 2000, 2000, [1000, 1000, 1000, 1000]);
+ ctx.lineTo(-150, -150);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.zero.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.roundRect(0, 50, 100, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.zero.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.roundRect(50, -100, 0, 250, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.zero.3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.roundRect(50, 25, 0, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.zero.4
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.roundRect(100, 25, 0, 0, [0]);
+ ctx.lineTo(0, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.zero.5
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, 0);
+ ctx.roundRect(100, 25, 0, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.zero.6
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 1.5;
+ ctx.lineWidth = 200;
+ ctx.beginPath();
+ ctx.roundRect(100, 25, 1000, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.negative
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#0f0';
+ ctx.roundRect(0, 0, 50, 25, [10, 0, 0, 0]);
+ ctx.roundRect(100, 0, -50, 25, [10, 0, 0, 0]);
+ ctx.roundRect(0, 50, 50, -25, [10, 0, 0, 0]);
+ ctx.roundRect(100, 50, -50, -25, [10, 0, 0, 0]);
+ ctx.fill();
+ // All rects drawn
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+ // Correct corners are rounded.
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.winding
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#f00';
+ ctx.roundRect(0, 0, 50, 50, [0]);
+ ctx.roundRect(100, 50, -50, -50, [0]);
+ ctx.roundRect(0, 25, 100, -25, [0]);
+ ctx.roundRect(100, 25, -100, 25, [0]);
+ ctx.fill();
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+
+- name: 2d.path.roundrect.selfintersect
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.roundRect(0, 0, 100, 50, [0]);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 90;
+ ctx.beginPath();
+ ctx.roundRect(45, 20, 10, 10, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.nonfinite
+ desc: roundRect() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.roundRect(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <[0] [Infinity] [-Infinity] [NaN] [Infinity,0] [-Infinity,0] [NaN,0] [0,Infinity] [0,-Infinity] [0,NaN] [Infinity,0,0] [-Infinity,0,0] [NaN,0,0] [0,Infinity,0] [0,-Infinity,0] [0,NaN,0] [0,0,Infinity] [0,0,-Infinity] [0,0,NaN] [Infinity,0,0,0] [-Infinity,0,0,0] [NaN,0,0,0] [0,Infinity,0,0] [0,-Infinity,0,0] [0,NaN,0,0] [0,0,Infinity,0] [0,0,-Infinity,0] [0,0,NaN,0] [0,0,0,Infinity] [0,0,0,-Infinity] [0,0,0,NaN]>);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(10, Infinity)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(10, -Infinity)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(10, NaN)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(Infinity, 10)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(-Infinity, 10)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(NaN, 10)]);
+ ctx.roundRect(0, 0, 100, 100, [{x: 10, y: Infinity}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: 10, y: -Infinity}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: 10, y: NaN}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: Infinity, y: 10}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: -Infinity, y: 10}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: NaN, y: 10}]);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.roundrect.4.radii.1.double
+ desc: Verify that when four radii are given to roundRect(), the first radius, specified as a double, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20, 0, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.1.dompoint
+ desc: Verify that when four radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20), 0, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.1.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the first radius, specified as a DOMPointInit, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}, 0, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.2.double
+ desc: Verify that when four radii are given to roundRect(), the second radius, specified as a double, applies to the top-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 20, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.2.dompoint
+ desc: Verify that when four radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, new DOMPoint(40, 20), 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.2.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the second radius, specified as a DOMPointInit, applies to the top-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, {x: 40, y: 20}, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.3.double
+ desc: Verify that when four radii are given to roundRect(), the third radius, specified as a double, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 20, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.3.dompoint
+ desc: Verify that when four radii are given to roundRect(), the third radius, specified as a DOMPoint, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, new DOMPoint(40, 20), 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.3.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the third radius, specified as a DOMPointInit, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, {x: 40, y: 20}, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.4.double
+ desc: Verify that when four radii are given to roundRect(), the fourth radius, specified as a double, applies to the bottom-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 0, 20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.4.radii.4.dompoint
+ desc: Verify that when four radii are given to roundRect(), the fourth radius, specified as a DOMPoint, applies to the bottom-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 0, new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.4.radii.4.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the fourth radius, specified as a DOMPointInit, applies to the bottom-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 0, {x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.1.double
+ desc: Verify that when three radii are given to roundRect(), the first radius, specified as a double, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.1.dompoint
+ desc: Verify that when three radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20), 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.1.dompointinit
+ desc: Verify that when three radii are given to roundRect(), the first radius, specified as a DOMPointInit, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.2.double
+ desc: Verify that when three radii are given to roundRect(), the second radius, specified as a double, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 20, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.3.radii.2.dompoint
+ desc: Verify that when three radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, new DOMPoint(40, 20), 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.2.dompointinit
+ desc: Verify that when three radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, {x: 40, y: 20}, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.3.double
+ desc: Verify that when three radii are given to roundRect(), the third radius, specified as a double, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.3.dompoint
+ desc: Verify that when three radii are given to roundRect(), the third radius, specified as a DOMPoint, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.3.radii.3.dompointinit
+ desc: Verify that when three radii are given to roundRect(), the third radius, specified as a DOMPointInit, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, {x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.2.radii.1.double
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a double, applies to the top-left and bottom-right corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.2.radii.1.dompoint
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left and bottom-right corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20), 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.2.radii.1.dompointinit
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a DOMPointInit, applies to the top-left and bottom-right corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.2.radii.2.double
+ desc: Verify that when two radii are given to roundRect(), the second radius, specified as a double, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.2.radii.2.dompoint
+ desc: Verify that when two radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.2.radii.2.dompointinit
+ desc: Verify that when two radii are given to roundRect(), the second radius, specified as a DOMPointInit, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, {x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+
+- name: 2d.path.roundrect.1.radius.double
+ desc: Verify that when one radius is given to roundRect(), specified as a double, it applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.1.radius.double.single.argument
+ desc: Verify that when one radius is given to roundRect() as a non-array argument, specified as a double, it applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, 20);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.1.radius.dompoint
+ desc: Verify that when one radius is given to roundRect(), specified as a DOMPoint, it applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+- name: 2d.path.roundrect.1.radius.dompoint.single argument
+ desc: Verify that when one radius is given to roundRect() as a non-array argument, specified as a DOMPoint, it applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, new DOMPoint(40, 20));
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+- name: 2d.path.roundrect.1.radius.dompointinit
+ desc: Verify that when one radius is given to roundRect(), specified as a DOMPointInit, applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+- name: 2d.path.roundrect.1.radius.dompointinit.single.argument
+ desc: Verify that when one radius is given to roundRect() as a non-array argument, specified as a DOMPointInit, applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, {x: 40, y: 20});
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+- name: 2d.path.roundrect.radius.intersecting.1
+ desc: Check that roundRects with intersecting corner arcs are rendered correctly.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [40, 40, 40, 40]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 2,25 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 97,25 == 0,255,0,255;
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.radius.intersecting.2
+ desc: Check that roundRects with intersecting corner arcs are rendered correctly.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [1000, 1000, 1000, 1000]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 2,25 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 97,25 == 0,255,0,255;
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+
+- name: 2d.path.roundrect.radius.none
+ desc: Check that roundRect throws an RangeError if radii is an empty array.
+ code: |
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 100, 50, [])});
+
+- name: 2d.path.roundrect.radius.noargument
+ desc: Check that roundRect draws a rectangle when no radii are provided.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(10, 10, 80, 30);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ // upper left corner (10, 10)
+ @assert pixel 10,9 == 255,0,0,255;
+ @assert pixel 9,10 == 255,0,0,255;
+ @assert pixel 10,10 == 0,255,0,255;
+
+ // upper right corner (89, 10)
+ @assert pixel 90,10 == 255,0,0,255;
+ @assert pixel 89,9 == 255,0,0,255;
+ @assert pixel 89,10 == 0,255,0,255;
+
+ // lower right corner (89, 39)
+ @assert pixel 89,40 == 255,0,0,255;
+ @assert pixel 90,39 == 255,0,0,255;
+ @assert pixel 89,39 == 0,255,0,255;
+
+ // lower left corner (10, 30)
+ @assert pixel 9,39 == 255,0,0,255;
+ @assert pixel 10,40 == 255,0,0,255;
+ @assert pixel 10,39 == 0,255,0,255;
+
+- name: 2d.path.roundrect.radius.toomany
+ desc: Check that roundRect throws an IndeSizeError if radii has more than four items.
+ code: |
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 100, 50, [0, 0, 0, 0, 0])});
+
+- name: 2d.path.roundrect.radius.negative
+ desc: roundRect() with negative radius throws an exception
+ code: |
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [-1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [1, -1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [new DOMPoint(-1, 1), 1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [new DOMPoint(1, -1)])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [{x: -1, y: 1}, 1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [{x: 1, y: -1}])});
+
+- name: 2d.path.roundrect.badinput
+ desc: roundRect() throws or does not throw errors given the strange inputs.
+ code: |
+ ctx.roundRect(0, 0, 100, 100, { foo: "bar" }); //=> DOMPointInit
+ ctx.roundRect(0, 0, 100, 100, undefined); //=> "missing" -> 0
+ ctx.roundRect(0, 0, 100, 100, [[]]); //=> « DOMPointInit »
+ ctx.roundRect(0, 0, 100, 100, [[25]]); //=> « DOMPointInit »
+ ctx.roundRect(0, 0, 100, 100, [undefined]); //=> « DOMPointInit »
+ @assert throws TypeError ctx.roundRect(0, 0, 100, 100, 0n);
+ @assert throws TypeError ctx.roundRect(0, 0, 100, 100, { x: 0n });
+ @assert throws TypeError ctx.roundRect(0, 0, 100, 100, [{ x: 0n }]);
+
+- name: 2d.path.ellipse.basics
+ desc: Verify canvas throws error when drawing ellipse with negative radii.
+ code: |
+ ctx.ellipse(10, 10, 10, 5, 0, 0, 1, false);
+ ctx.ellipse(10, 10, 10, 0, 0, 0, 1, false);
+ ctx.ellipse(10, 10, -0, 5, 0, 0, 1, false);
+ @assert throws INDEX_SIZE_ERR ctx.ellipse(10, 10, -2, 5, 0, 0, 1, false);
+ @assert throws INDEX_SIZE_ERR ctx.ellipse(10, 10, 0, -1.5, 0, 0, 1, false);
+ @assert throws INDEX_SIZE_ERR ctx.ellipse(10, 10, -2, -5, 0, 0, 1, false);
+ ctx.ellipse(80, 0, 10, 4294967277, Math.PI / -84, -Math.PI / 2147483436, false);
+
+- name: 2d.path.fill.overlap
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
+ ctx.rect(0, 0, 100, 50);
+ ctx.closePath();
+ ctx.rect(10, 10, 80, 30);
+ ctx.fill();
+
+ @assert pixel 50,25 ==~ 0,127,0,255 +/- 1;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0.5, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.path.fill.winding.add
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.fill.winding.subtract.1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.fill.winding.subtract.2
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.fill.winding.subtract.3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(-20, -20);
+ ctx.lineTo(120, -20);
+ ctx.lineTo(120, 70);
+ ctx.lineTo(-20, 70);
+ ctx.lineTo(-20, -20);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.fill.closed.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.fill.closed.unaffected
+ code: |
+ ctx.fillStyle = '#00f';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ @assert pixel 90,10 == 0,255,0,255;
+ @assert pixel 10,40 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.overlap
+ desc: Stroked subpaths are combined before being drawn
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, 20);
+ ctx.lineTo(100, 20);
+ ctx.moveTo(0, 30);
+ ctx.lineTo(100, 30);
+ ctx.stroke();
+
+ @assert pixel 50,25 ==~ 0,127,0,255 +/- 1;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0.5, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.path.stroke.union
+ desc: Strokes in opposite directions are unioned, not subtracted
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 40;
+ ctx.moveTo(0, 10);
+ ctx.lineTo(100, 10);
+ ctx.moveTo(100, 40);
+ ctx.lineTo(0, 40);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.unaffected
+ desc: Stroking does not start a new path or subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-100, -100);
+ ctx.lineTo(200, -100);
+ ctx.lineTo(200, 25);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+
+ ctx.closePath();
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.scale1
+ desc: Stroke line widths are scaled by the current transformation matrix
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.rect(25, 12.5, 50, 25);
+ ctx.save();
+ ctx.scale(50, 25);
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ ctx.restore();
+
+ ctx.beginPath();
+ ctx.rect(-25, -12.5, 150, 75);
+ ctx.save();
+ ctx.scale(50, 25);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.scale2
+ desc: Stroke line widths are scaled by the current transformation matrix
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.rect(25, 12.5, 50, 25);
+ ctx.save();
+ ctx.rotate(Math.PI/2);
+ ctx.scale(25, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ ctx.restore();
+
+ ctx.beginPath();
+ ctx.rect(-25, -12.5, 150, 75);
+ ctx.save();
+ ctx.rotate(Math.PI/2);
+ ctx.scale(25, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.skew
+ desc: Strokes lines are skewed by the current transformation matrix
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(49, -50);
+ ctx.lineTo(201, -50);
+ ctx.rotate(Math.PI/4);
+ ctx.scale(1, 283);
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ ctx.restore();
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.translate(-150, 0);
+ ctx.moveTo(49, -50);
+ ctx.lineTo(199, -50);
+ ctx.rotate(Math.PI/4);
+ ctx.scale(1, 142);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.translate(-150, 0);
+ ctx.moveTo(49, -50);
+ ctx.lineTo(199, -50);
+ ctx.rotate(Math.PI/4);
+ ctx.scale(1, 142);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.empty
+ desc: Empty subpaths are not stroked
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ ctx.beginPath();
+ ctx.moveTo(40, 25);
+ ctx.moveTo(60, 25);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.stroke.prune.line
+ desc: Zero-length line segments from lineTo are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.lineTo(50, 25);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.stroke.prune.closed
+ desc: Zero-length line segments from closed paths are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.lineTo(50, 25);
+ ctx.closePath();
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.stroke.prune.curve
+ desc: Zero-length line segments from quadraticCurveTo and bezierCurveTo are removed
+ before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.quadraticCurveTo(50, 25, 50, 25);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.bezierCurveTo(50, 25, 50, 25, 50, 25);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.stroke.prune.arc
+ desc: Zero-length line segments from arcTo and arc are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arcTo(50, 25, 150, 25, 10);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(60, 25);
+ ctx.arc(50, 25, 10, 0, 0, false);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.stroke.prune.rect
+ desc: Zero-length line segments from rect and strokeRect are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+
+ ctx.beginPath();
+ ctx.rect(50, 25, 0, 0);
+ ctx.stroke();
+
+ ctx.strokeRect(50, 25, 0, 0);
+
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ expected: green
+
+- name: 2d.path.stroke.prune.corner
+ desc: Zero-length line segments are removed before stroking with miters
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 1.4;
+
+ ctx.beginPath();
+ ctx.moveTo(-1000, 200);
+ ctx.lineTo(-100, 200);
+ ctx.lineTo(-100, 200);
+ ctx.lineTo(-100, 200);
+ ctx.lineTo(-100, 1000);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.path.transformation.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(-100, 0);
+ ctx.rect(100, 0, 100, 50);
+ ctx.translate(0, -100);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.transformation.multiple
+ # TODO: change this name
+ desc: Transformations are applied while building paths, not when drawing
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.translate(-100, 0);
+ ctx.rect(0, 0, 100, 50);
+ ctx.fill();
+ ctx.translate(100, 0);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.translate(0, -50);
+ ctx.moveTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ ctx.translate(0, 50);
+ ctx.stroke();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.transformation.changing
+ desc: Transformations are applied while building paths, not when drawing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(0, 0);
+ ctx.translate(100, 0);
+ ctx.lineTo(0, 0);
+ ctx.translate(0, 50);
+ ctx.lineTo(0, 0);
+ ctx.translate(-100, 0);
+ ctx.lineTo(0, 0);
+ ctx.translate(1000, 1000);
+ ctx.rotate(Math.PI/2);
+ ctx.scale(0.1, 0.1);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.path.clip.empty
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.clip();
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.clip.basic.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.rect(0, 0, 100, 50);
+ ctx.clip();
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.clip.basic.2
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.rect(-100, 0, 100, 50);
+ ctx.clip();
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.clip.intersect
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.beginPath();
+ ctx.rect(50, 0, 50, 50)
+ ctx.clip();
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.clip.winding.1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.clip();
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.clip.winding.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.beginPath();
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.clip();
+
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(0, 0);
+ ctx.clip();
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.path.clip.unaffected
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.clip();
+
+ ctx.lineTo(0, 0);
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+
+
+- name: 2d.path.isPointInPath.basic.1
+ desc: isPointInPath() detects whether the point is inside the path
+ code: |
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInPath(10, 10) === true;
+ @assert ctx.isPointInPath(30, 10) === false;
+
+- name: 2d.path.isPointInPath.basic.2
+ desc: isPointInPath() detects whether the point is inside the path
+ code: |
+ ctx.rect(20, 0, 20, 20);
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(30, 10) === true;
+
+- name: 2d.path.isPointInPath.edge
+ desc: isPointInPath() counts points on the path as being inside
+ code: |
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInPath(0, 0) === true;
+ @assert ctx.isPointInPath(10, 0) === true;
+ @assert ctx.isPointInPath(20, 0) === true;
+ @assert ctx.isPointInPath(20, 10) === true;
+ @assert ctx.isPointInPath(20, 20) === true;
+ @assert ctx.isPointInPath(10, 20) === true;
+ @assert ctx.isPointInPath(0, 20) === true;
+ @assert ctx.isPointInPath(0, 10) === true;
+ @assert ctx.isPointInPath(10, -0.01) === false;
+ @assert ctx.isPointInPath(10, 20.01) === false;
+ @assert ctx.isPointInPath(-0.01, 10) === false;
+ @assert ctx.isPointInPath(20.01, 10) === false;
+
+- name: 2d.path.isPointInPath.empty
+ desc: isPointInPath() works when there is no path
+ code: |
+ @assert ctx.isPointInPath(0, 0) === false;
+
+- name: 2d.path.isPointInPath.subpath
+ desc: isPointInPath() uses the current path, not just the subpath
+ code: |
+ ctx.rect(0, 0, 20, 20);
+ ctx.beginPath();
+ ctx.rect(20, 0, 20, 20);
+ ctx.closePath();
+ ctx.rect(40, 0, 20, 20);
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(30, 10) === true;
+ @assert ctx.isPointInPath(50, 10) === true;
+
+- name: 2d.path.isPointInPath.outside
+ desc: isPointInPath() works on paths outside the canvas
+ code: |
+ ctx.rect(0, -100, 20, 20);
+ ctx.rect(20, -10, 20, 20);
+ @assert ctx.isPointInPath(10, -110) === false;
+ @assert ctx.isPointInPath(10, -90) === true;
+ @assert ctx.isPointInPath(10, -70) === false;
+ @assert ctx.isPointInPath(30, -20) === false;
+ @assert ctx.isPointInPath(30, 0) === true;
+ @assert ctx.isPointInPath(30, 20) === false;
+
+- name: 2d.path.isPointInPath.unclosed
+ desc: isPointInPath() works on unclosed subpaths
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(20, 0);
+ ctx.lineTo(20, 20);
+ ctx.lineTo(0, 20);
+ @assert ctx.isPointInPath(10, 10) === true;
+ @assert ctx.isPointInPath(30, 10) === false;
+
+- name: 2d.path.isPointInPath.arc
+ desc: isPointInPath() works on arcs
+ code: |
+ ctx.arc(50, 25, 10, 0, Math.PI, false);
+ @assert ctx.isPointInPath(50, 10) === false;
+ @assert ctx.isPointInPath(50, 20) === false;
+ @assert ctx.isPointInPath(50, 30) === true;
+ @assert ctx.isPointInPath(50, 40) === false;
+ @assert ctx.isPointInPath(30, 20) === false;
+ @assert ctx.isPointInPath(70, 20) === false;
+ @assert ctx.isPointInPath(30, 30) === false;
+ @assert ctx.isPointInPath(70, 30) === false;
+
+- name: 2d.path.isPointInPath.bigarc
+ desc: isPointInPath() works on unclosed arcs larger than 2pi
+ opera: {bug: 320937}
+ code: |
+ ctx.arc(50, 25, 10, 0, 7, false);
+ @assert ctx.isPointInPath(50, 10) === false;
+ @assert ctx.isPointInPath(50, 20) === true;
+ @assert ctx.isPointInPath(50, 30) === true;
+ @assert ctx.isPointInPath(50, 40) === false;
+ @assert ctx.isPointInPath(30, 20) === false;
+ @assert ctx.isPointInPath(70, 20) === false;
+ @assert ctx.isPointInPath(30, 30) === false;
+ @assert ctx.isPointInPath(70, 30) === false;
+
+- name: 2d.path.isPointInPath.bezier
+ desc: isPointInPath() works on Bezier curves
+ code: |
+ ctx.moveTo(25, 25);
+ ctx.bezierCurveTo(50, -50, 50, 100, 75, 25);
+ @assert ctx.isPointInPath(25, 20) === false;
+ @assert ctx.isPointInPath(25, 30) === false;
+ @assert ctx.isPointInPath(30, 20) === true;
+ @assert ctx.isPointInPath(30, 30) === false;
+ @assert ctx.isPointInPath(40, 2) === false;
+ @assert ctx.isPointInPath(40, 20) === true;
+ @assert ctx.isPointInPath(40, 30) === false;
+ @assert ctx.isPointInPath(40, 47) === false;
+ @assert ctx.isPointInPath(45, 20) === true;
+ @assert ctx.isPointInPath(45, 30) === false;
+ @assert ctx.isPointInPath(55, 20) === false;
+ @assert ctx.isPointInPath(55, 30) === true;
+ @assert ctx.isPointInPath(60, 2) === false;
+ @assert ctx.isPointInPath(60, 20) === false;
+ @assert ctx.isPointInPath(60, 30) === true;
+ @assert ctx.isPointInPath(60, 47) === false;
+ @assert ctx.isPointInPath(70, 20) === false;
+ @assert ctx.isPointInPath(70, 30) === true;
+ @assert ctx.isPointInPath(75, 20) === false;
+ @assert ctx.isPointInPath(75, 30) === false;
+
+- name: 2d.path.isPointInPath.winding
+ desc: isPointInPath() uses the non-zero winding number rule
+ code: |
+ // Create a square ring, using opposite windings to make a hole in the centre
+ ctx.moveTo(0, 0);
+ ctx.lineTo(50, 0);
+ ctx.lineTo(50, 50);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(10, 10);
+ ctx.lineTo(10, 40);
+ ctx.lineTo(40, 40);
+ ctx.lineTo(40, 10);
+ ctx.lineTo(10, 10);
+
+ @assert ctx.isPointInPath(5, 5) === true;
+ @assert ctx.isPointInPath(25, 5) === true;
+ @assert ctx.isPointInPath(45, 5) === true;
+ @assert ctx.isPointInPath(5, 25) === true;
+ @assert ctx.isPointInPath(25, 25) === false;
+ @assert ctx.isPointInPath(45, 25) === true;
+ @assert ctx.isPointInPath(5, 45) === true;
+ @assert ctx.isPointInPath(25, 45) === true;
+ @assert ctx.isPointInPath(45, 45) === true;
+
+- name: 2d.path.isPointInPath.transform.1
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.translate(50, 0);
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInPath(-40, 10) === false;
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(49, 10) === false;
+ @assert ctx.isPointInPath(51, 10) === true;
+ @assert ctx.isPointInPath(69, 10) === true;
+ @assert ctx.isPointInPath(71, 10) === false;
+
+- name: 2d.path.isPointInPath.transform.2
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.rect(50, 0, 20, 20);
+ ctx.translate(50, 0);
+ @assert ctx.isPointInPath(-40, 10) === false;
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(49, 10) === false;
+ @assert ctx.isPointInPath(51, 10) === true;
+ @assert ctx.isPointInPath(69, 10) === true;
+ @assert ctx.isPointInPath(71, 10) === false;
+
+- name: 2d.path.isPointInPath.transform.3
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.scale(-1, 1);
+ ctx.rect(-70, 0, 20, 20);
+ @assert ctx.isPointInPath(-40, 10) === false;
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(49, 10) === false;
+ @assert ctx.isPointInPath(51, 10) === true;
+ @assert ctx.isPointInPath(69, 10) === true;
+ @assert ctx.isPointInPath(71, 10) === false;
+
+- name: 2d.path.isPointInPath.transform.4
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.translate(50, 0);
+ ctx.rect(50, 0, 20, 20);
+ ctx.translate(0, 50);
+ @assert ctx.isPointInPath(60, 10) === false;
+ @assert ctx.isPointInPath(110, 10) === true;
+ @assert ctx.isPointInPath(110, 60) === false;
+
+- name: 2d.path.isPointInPath.nonfinite
+ desc: isPointInPath() returns false for non-finite arguments
+ code: |
+ ctx.rect(-100, -50, 200, 100);
+ @assert ctx.isPointInPath(Infinity, 0) === false;
+ @assert ctx.isPointInPath(-Infinity, 0) === false;
+ @assert ctx.isPointInPath(NaN, 0) === false;
+ @assert ctx.isPointInPath(0, Infinity) === false;
+ @assert ctx.isPointInPath(0, -Infinity) === false;
+ @assert ctx.isPointInPath(0, NaN) === false;
+ @assert ctx.isPointInPath(NaN, NaN) === false;
+
+
+- name: 2d.path.isPointInStroke.scaleddashes
+ desc: isPointInStroke() should return correct results on dashed paths at high scale
+ factors
+ code: |
+ var scale = 20;
+ ctx.setLineDash([10, 21.4159]); // dash from t=0 to t=10 along the circle
+ ctx.scale(scale, scale);
+ ctx.ellipse(6, 10, 5, 5, 0, 2*Math.PI, false);
+ ctx.stroke();
+
+ // hit-test the beginning of the dash (t=0)
+ @assert ctx.isPointInStroke(11*scale, 10*scale) === true;
+ // hit-test the middle of the dash (t=5)
+ @assert ctx.isPointInStroke(8.70*scale, 14.21*scale) === true;
+ // hit-test the end of the dash (t=9.8)
+ @assert ctx.isPointInStroke(4.10*scale, 14.63*scale) === true;
+ // hit-test past the end of the dash (t=10.2)
+ @assert ctx.isPointInStroke(3.74*scale, 14.46*scale) === false;
+
+- name: 2d.path.isPointInPath.basic
+ desc: Verify the winding rule in isPointInPath works for for rect path.
+ code: |
+ canvas.width = 200;
+ canvas.height = 200;
+
+ // Testing default isPointInPath
+ ctx.beginPath();
+ ctx.rect(0, 0, 100, 100);
+ ctx.rect(25, 25, 50, 50);
+ @assert ctx.isPointInPath(50, 50) === true;
+ @assert ctx.isPointInPath(NaN, 50) === false;
+ @assert ctx.isPointInPath(50, NaN) === false;
+
+ // Testing nonzero isPointInPath
+ ctx.beginPath();
+ ctx.rect(0, 0, 100, 100);
+ ctx.rect(25, 25, 50, 50);
+ @assert ctx.isPointInPath(50, 50, 'nonzero') === true;
+
+ // Testing evenodd isPointInPath
+ ctx.beginPath();
+ ctx.rect(0, 0, 100, 100);
+ ctx.rect(25, 25, 50, 50);
+ @assert ctx.isPointInPath(50, 50, 'evenodd') === false;
+
+ // Testing extremely large scale
+ ctx.save();
+ ctx.scale(Number.MAX_VALUE, Number.MAX_VALUE);
+ ctx.beginPath();
+ ctx.rect(-10, -10, 20, 20);
+ @assert ctx.isPointInPath(0, 0, 'nonzero') === true;
+ @assert ctx.isPointInPath(0, 0, 'evenodd') === true;
+ ctx.restore();
+
+ // Check with non-invertible ctm.
+ ctx.save();
+ ctx.scale(0, 0);
+ ctx.beginPath();
+ ctx.rect(-10, -10, 20, 20);
+ @assert ctx.isPointInPath(0, 0, 'nonzero') === false;
+ @assert ctx.isPointInPath(0, 0, 'evenodd') === false;
+ ctx.restore();
+
+- name: 2d.path.isPointInpath.multi.path
+ desc: Verify the winding rule in isPointInPath works for path object.
+ code: |
+ canvas.width = 200;
+ canvas.height = 200;
+
+ // Testing default isPointInPath with Path object');
+ path = new Path2D();
+ path.rect(0, 0, 100, 100);
+ path.rect(25, 25, 50, 50);
+ @assert ctx.isPointInPath(path, 50, 50) === true;
+ @assert ctx.isPointInPath(path, 50, 50, undefined) === true;
+ @assert ctx.isPointInPath(path, NaN, 50) === false;
+ @assert ctx.isPointInPath(path, 50, NaN) === false;
+
+ // Testing nonzero isPointInPath with Path object');
+ path = new Path2D();
+ path.rect(0, 0, 100, 100);
+ path.rect(25, 25, 50, 50);
+ @assert ctx.isPointInPath(path, 50, 50, 'nonzero') === true;
+
+ // Testing evenodd isPointInPath with Path object');
+ path = new Path2D();
+ path.rect(0, 0, 100, 100);
+ path.rect(25, 25, 50, 50);
+ assert_false(ctx.isPointInPath(path, 50, 50, 'evenodd'));
+
+- name: 2d.path.isPointInpath.invalid
+ desc: Verify isPointInPath throws exceptions with invalid inputs.
+ code: |
+ canvas.width = 200;
+ canvas.height = 200;
+ path = new Path2D();
+ path.rect(0, 0, 100, 100);
+ path.rect(25, 25, 50, 50);
+ // Testing invalid enumeration isPointInPath (w/ and w/o Path object');
+ @assert throws TypeError ctx.isPointInPath(path, 50, 50, 'gazonk');
+ @assert throws TypeError ctx.isPointInPath(50, 50, 'gazonk');
+
+ // Testing invalid type isPointInPath with Path object');
+ @assert throws TypeError ctx.isPointInPath(null, 50, 50);
+ @assert throws TypeError ctx.isPointInPath(null, 50, 50, 'nonzero');
+ @assert throws TypeError ctx.isPointInPath(null, 50, 50, 'evenodd');
+ @assert throws TypeError ctx.isPointInPath(null, 50, 50, null);
+ @assert throws TypeError ctx.isPointInPath(path, 50, 50, null);
+ @assert throws TypeError ctx.isPointInPath(undefined, 50, 50);
+ @assert throws TypeError ctx.isPointInPath(undefined, 50, 50, 'nonzero');
+ @assert throws TypeError ctx.isPointInPath(undefined, 50, 50, 'evenodd');
+ @assert throws TypeError ctx.isPointInPath(undefined, 50, 50, undefined);
+ @assert throws TypeError ctx.isPointInPath([], 50, 50);
+ @assert throws TypeError ctx.isPointInPath([], 50, 50, 'nonzero');
+ @assert throws TypeError ctx.isPointInPath([], 50, 50, 'evenodd');
+ @assert throws TypeError ctx.isPointInPath({}, 50, 50);
+ @assert throws TypeError ctx.isPointInPath({}, 50, 50, 'nonzero');
+ @assert throws TypeError ctx.isPointInPath({}, 50, 50, 'evenodd');
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/pixel-manipulation.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/pixel-manipulation.yaml
new file mode 100644
index 0000000000..eca8e8b2bd
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/pixel-manipulation.yaml
@@ -0,0 +1,1008 @@
+- name: 2d.imageData.create2.basic
+ desc: createImageData(sw, sh) exists and returns something
+ code: |
+ @assert ctx.createImageData(1, 1) !== null;
+
+- name: 2d.imageData.create1.basic
+ desc: createImageData(imgdata) exists and returns something
+ code: |
+ @assert ctx.createImageData(ctx.createImageData(1, 1)) !== null;
+
+- name: 2d.imageData.create2.type
+ desc: createImageData(sw, sh) returns an ImageData object containing a Uint8ClampedArray
+ object
+ code: |
+ @assert window.ImageData !== undefined;
+ @assert window.Uint8ClampedArray !== undefined;
+ window.ImageData.prototype.thisImplementsImageData = true;
+ window.Uint8ClampedArray.prototype.thisImplementsUint8ClampedArray = true;
+ var imgdata = ctx.createImageData(1, 1);
+ @assert imgdata.thisImplementsImageData;
+ @assert imgdata.data.thisImplementsUint8ClampedArray;
+
+- name: 2d.imageData.create1.type
+ desc: createImageData(imgdata) returns an ImageData object containing a Uint8ClampedArray
+ object
+ code: |
+ @assert window.ImageData !== undefined;
+ @assert window.Uint8ClampedArray !== undefined;
+ window.ImageData.prototype.thisImplementsImageData = true;
+ window.Uint8ClampedArray.prototype.thisImplementsUint8ClampedArray = true;
+ var imgdata = ctx.createImageData(ctx.createImageData(1, 1));
+ @assert imgdata.thisImplementsImageData;
+ @assert imgdata.data.thisImplementsUint8ClampedArray;
+
+- name: 2d.imageData.create2.this
+ desc: createImageData(sw, sh) should throw when called with the wrong |this|
+ notes: &bindings Defined in "Web IDL" (draft)
+ code: |
+ @assert throws TypeError CanvasRenderingContext2D.prototype.createImageData.call(null, 1, 1); @moz-todo
+ @assert throws TypeError CanvasRenderingContext2D.prototype.createImageData.call(undefined, 1, 1); @moz-todo
+ @assert throws TypeError CanvasRenderingContext2D.prototype.createImageData.call({}, 1, 1); @moz-todo
+
+- name: 2d.imageData.create1.this
+ desc: createImageData(imgdata) should throw when called with the wrong |this|
+ notes: *bindings
+ code: |
+ var imgdata = ctx.createImageData(1, 1);
+ @assert throws TypeError CanvasRenderingContext2D.prototype.createImageData.call(null, imgdata); @moz-todo
+ @assert throws TypeError CanvasRenderingContext2D.prototype.createImageData.call(undefined, imgdata); @moz-todo
+ @assert throws TypeError CanvasRenderingContext2D.prototype.createImageData.call({}, imgdata); @moz-todo
+
+- name: 2d.imageData.create2.initial
+ desc: createImageData(sw, sh) returns transparent black data of the right size
+ code: |
+ var imgdata = ctx.createImageData(10, 20);
+ @assert imgdata.data.length === imgdata.width*imgdata.height*4;
+ @assert imgdata.width < imgdata.height;
+ @assert imgdata.width > 0;
+ var isTransparentBlack = true;
+ for (var i = 0; i < imgdata.data.length; ++i)
+ if (imgdata.data[i] !== 0)
+ isTransparentBlack = false;
+ @assert isTransparentBlack;
+
+- name: 2d.imageData.create1.initial
+ desc: createImageData(imgdata) returns transparent black data of the right size
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata1 = ctx.getImageData(0, 0, 10, 20);
+ var imgdata2 = ctx.createImageData(imgdata1);
+ @assert imgdata2.data.length === imgdata1.data.length;
+ @assert imgdata2.width === imgdata1.width;
+ @assert imgdata2.height === imgdata1.height;
+ var isTransparentBlack = true;
+ for (var i = 0; i < imgdata2.data.length; ++i)
+ if (imgdata2.data[i] !== 0)
+ isTransparentBlack = false;
+ @assert isTransparentBlack;
+
+- name: 2d.imageData.create2.large
+ desc: createImageData(sw, sh) works for sizes much larger than the canvas
+ code: |
+ var imgdata = ctx.createImageData(1000, 2000);
+ @assert imgdata.data.length === imgdata.width*imgdata.height*4;
+ @assert imgdata.width < imgdata.height;
+ @assert imgdata.width > 0;
+ var isTransparentBlack = true;
+ for (var i = 0; i < imgdata.data.length; i += 7813) // check ~1024 points (assuming normal scaling)
+ if (imgdata.data[i] !== 0)
+ isTransparentBlack = false;
+ @assert isTransparentBlack;
+
+- name: 2d.imageData.create2.negative
+ desc: createImageData(sw, sh) takes the absolute magnitude of the size arguments
+ code: |
+ var imgdata1 = ctx.createImageData(10, 20);
+ var imgdata2 = ctx.createImageData(-10, 20);
+ var imgdata3 = ctx.createImageData(10, -20);
+ var imgdata4 = ctx.createImageData(-10, -20);
+ @assert imgdata1.data.length === imgdata2.data.length;
+ @assert imgdata2.data.length === imgdata3.data.length;
+ @assert imgdata3.data.length === imgdata4.data.length;
+
+- name: 2d.imageData.create2.zero
+ desc: createImageData(sw, sh) throws INDEX_SIZE_ERR if size is zero
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(10, 0);
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(0, 10);
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(0, 0);
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(0.99, 10);
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(10, 0.1);
+
+- name: 2d.imageData.create2.nonfinite
+ desc: createImageData() throws TypeError if arguments are not finite
+ notes: *bindings
+ code: |
+ @nonfinite @assert throws TypeError ctx.createImageData(<10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ var posinfobj = { valueOf: function() { return Infinity; } },
+ neginfobj = { valueOf: function() { return -Infinity; } },
+ nanobj = { valueOf: function() { return -Infinity; } };
+ @nonfinite @assert throws TypeError ctx.createImageData(<10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>);
+
+- name: 2d.imageData.create1.zero
+ desc: createImageData(null) throws TypeError
+ code: |
+ @assert throws TypeError ctx.createImageData(null);
+
+- name: 2d.imageData.create2.double
+ desc: createImageData(w, h) double is converted to long
+ code: |
+ var imgdata1 = ctx.createImageData(10.01, 10.99);
+ var imgdata2 = ctx.createImageData(-10.01, -10.99);
+ @assert imgdata1.width === 10;
+ @assert imgdata1.height === 10;
+ @assert imgdata2.width === 10;
+ @assert imgdata2.height === 10;
+
+- name: 2d.imageData.create.and.resize
+ desc: Verify no crash when resizing an image bitmap to zero.
+ images:
+ - red.png
+ code: |
+ var image = new Image();
+ image.onload = t.step_func(function() {
+ var options = { resizeHeight: 0 };
+ var p1 = createImageBitmap(image, options);
+ p1.catch(function(error){});
+ t.done();
+ });
+ image.src = 'red.png';
+
+- name: 2d.imageData.get.basic
+ desc: getImageData() exists and returns something
+ code: |
+ @assert ctx.getImageData(0, 0, 100, 50) !== null;
+
+- name: 2d.imageData.get.type
+ desc: getImageData() returns an ImageData object containing a Uint8ClampedArray
+ object
+ code: |
+ @assert window.ImageData !== undefined;
+ @assert window.Uint8ClampedArray !== undefined;
+ window.ImageData.prototype.thisImplementsImageData = true;
+ window.Uint8ClampedArray.prototype.thisImplementsUint8ClampedArray = true;
+ var imgdata = ctx.getImageData(0, 0, 1, 1);
+ @assert imgdata.thisImplementsImageData;
+ @assert imgdata.data.thisImplementsUint8ClampedArray;
+
+- name: 2d.imageData.get.zero
+ desc: getImageData() throws INDEX_SIZE_ERR if size is zero
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 10, 0);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 0, 10);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 0, 0);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 0.1, 10);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 10, 0.99);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, -0.1, 10);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 10, -0.99);
+
+- name: 2d.imageData.get.nonfinite
+ desc: getImageData() throws TypeError if arguments are not finite
+ notes: *bindings
+ code: |
+ @nonfinite @assert throws TypeError ctx.getImageData(<10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ var posinfobj = { valueOf: function() { return Infinity; } },
+ neginfobj = { valueOf: function() { return -Infinity; } },
+ nanobj = { valueOf: function() { return -Infinity; } };
+ @nonfinite @assert throws TypeError ctx.getImageData(<10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>);
+
+- name: 2d.imageData.get.source.outside
+ desc: getImageData() returns transparent black outside the canvas
+ code: |
+ ctx.fillStyle = '#08f';
+ ctx.fillRect(0, 0, 100, 50);
+
+ var imgdata1 = ctx.getImageData(-10, 5, 1, 1);
+ @assert imgdata1.data[0] === 0;
+ @assert imgdata1.data[1] === 0;
+ @assert imgdata1.data[2] === 0;
+ @assert imgdata1.data[3] === 0;
+
+ var imgdata2 = ctx.getImageData(10, -5, 1, 1);
+ @assert imgdata2.data[0] === 0;
+ @assert imgdata2.data[1] === 0;
+ @assert imgdata2.data[2] === 0;
+ @assert imgdata2.data[3] === 0;
+
+ var imgdata3 = ctx.getImageData(200, 5, 1, 1);
+ @assert imgdata3.data[0] === 0;
+ @assert imgdata3.data[1] === 0;
+ @assert imgdata3.data[2] === 0;
+ @assert imgdata3.data[3] === 0;
+
+ var imgdata4 = ctx.getImageData(10, 60, 1, 1);
+ @assert imgdata4.data[0] === 0;
+ @assert imgdata4.data[1] === 0;
+ @assert imgdata4.data[2] === 0;
+ @assert imgdata4.data[3] === 0;
+
+ var imgdata5 = ctx.getImageData(100, 10, 1, 1);
+ @assert imgdata5.data[0] === 0;
+ @assert imgdata5.data[1] === 0;
+ @assert imgdata5.data[2] === 0;
+ @assert imgdata5.data[3] === 0;
+
+ var imgdata6 = ctx.getImageData(0, 10, 1, 1);
+ @assert imgdata6.data[0] === 0;
+ @assert imgdata6.data[1] === 136;
+ @assert imgdata6.data[2] === 255;
+ @assert imgdata6.data[3] === 255;
+
+ var imgdata7 = ctx.getImageData(-10, 10, 20, 20);
+ @assert imgdata7.data[ 0*4+0] === 0;
+ @assert imgdata7.data[ 0*4+1] === 0;
+ @assert imgdata7.data[ 0*4+2] === 0;
+ @assert imgdata7.data[ 0*4+3] === 0;
+ @assert imgdata7.data[ 9*4+0] === 0;
+ @assert imgdata7.data[ 9*4+1] === 0;
+ @assert imgdata7.data[ 9*4+2] === 0;
+ @assert imgdata7.data[ 9*4+3] === 0;
+ @assert imgdata7.data[10*4+0] === 0;
+ @assert imgdata7.data[10*4+1] === 136;
+ @assert imgdata7.data[10*4+2] === 255;
+ @assert imgdata7.data[10*4+3] === 255;
+ @assert imgdata7.data[19*4+0] === 0;
+ @assert imgdata7.data[19*4+1] === 136;
+ @assert imgdata7.data[19*4+2] === 255;
+ @assert imgdata7.data[19*4+3] === 255;
+ @assert imgdata7.data[20*4+0] === 0;
+ @assert imgdata7.data[20*4+1] === 0;
+ @assert imgdata7.data[20*4+2] === 0;
+ @assert imgdata7.data[20*4+3] === 0;
+
+- name: 2d.imageData.get.source.negative
+ desc: getImageData() works with negative width and height, and returns top-to-bottom
+ left-to-right
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(20, 10, 60, 10);
+
+ var imgdata1 = ctx.getImageData(85, 25, -10, -10);
+ @assert imgdata1.data[0] === 255;
+ @assert imgdata1.data[1] === 255;
+ @assert imgdata1.data[2] === 255;
+ @assert imgdata1.data[3] === 255;
+ @assert imgdata1.data[imgdata1.data.length-4+0] === 0;
+ @assert imgdata1.data[imgdata1.data.length-4+1] === 0;
+ @assert imgdata1.data[imgdata1.data.length-4+2] === 0;
+ @assert imgdata1.data[imgdata1.data.length-4+3] === 255;
+
+ var imgdata2 = ctx.getImageData(0, 0, -1, -1);
+ @assert imgdata2.data[0] === 0;
+ @assert imgdata2.data[1] === 0;
+ @assert imgdata2.data[2] === 0;
+ @assert imgdata2.data[3] === 0;
+
+- name: 2d.imageData.get.source.size
+ desc: getImageData() returns bigger ImageData for bigger source rectangle
+ code: |
+ var imgdata1 = ctx.getImageData(0, 0, 10, 10);
+ var imgdata2 = ctx.getImageData(0, 0, 20, 20);
+ @assert imgdata2.width > imgdata1.width;
+ @assert imgdata2.height > imgdata1.height;
+
+- name: 2d.imageData.get.double
+ desc: createImageData(w, h) double is converted to long
+ code: |
+ var imgdata1 = ctx.getImageData(0, 0, 10.01, 10.99);
+ var imgdata2 = ctx.getImageData(0, 0, -10.01, -10.99);
+ @assert imgdata1.width === 10;
+ @assert imgdata1.height === 10;
+ @assert imgdata2.width === 10;
+ @assert imgdata2.height === 10;
+
+- name: 2d.imageData.get.nonpremul
+ desc: getImageData() returns non-premultiplied colors
+ code: |
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(10, 10, 10, 10);
+ @assert imgdata.data[0] > 200;
+ @assert imgdata.data[1] > 200;
+ @assert imgdata.data[2] > 200;
+ @assert imgdata.data[3] > 100;
+ @assert imgdata.data[3] < 200;
+
+- name: 2d.imageData.get.range
+ desc: getImageData() returns values in the range [0, 255]
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(20, 10, 60, 10);
+ var imgdata1 = ctx.getImageData(10, 5, 1, 1);
+ @assert imgdata1.data[0] === 0;
+ var imgdata2 = ctx.getImageData(30, 15, 1, 1);
+ @assert imgdata2.data[0] === 255;
+
+- name: 2d.imageData.get.clamp
+ desc: getImageData() clamps colors to the range [0, 255]
+ code: |
+ ctx.fillStyle = 'rgb(-100, -200, -300)';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = 'rgb(256, 300, 400)';
+ ctx.fillRect(20, 10, 60, 10);
+ var imgdata1 = ctx.getImageData(10, 5, 1, 1);
+ @assert imgdata1.data[0] === 0;
+ @assert imgdata1.data[1] === 0;
+ @assert imgdata1.data[2] === 0;
+ var imgdata2 = ctx.getImageData(30, 15, 1, 1);
+ @assert imgdata2.data[0] === 255;
+ @assert imgdata2.data[1] === 255;
+ @assert imgdata2.data[2] === 255;
+
+- name: 2d.imageData.get.length
+ desc: getImageData() returns a correctly-sized Uint8ClampedArray
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data.length === imgdata.width*imgdata.height*4;
+
+- name: 2d.imageData.get.order.cols
+ desc: getImageData() returns leftmost columns first
+ code: |
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 2, 50);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[0] === 0;
+ @assert imgdata.data[Math.round(imgdata.width/2*4)] === 255;
+ @assert imgdata.data[Math.round((imgdata.height/2)*imgdata.width*4)] === 0;
+
+- name: 2d.imageData.get.order.rows
+ desc: getImageData() returns topmost rows first
+ code: |
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 2);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[0] === 0;
+ @assert imgdata.data[Math.floor(imgdata.width/2*4)] === 0;
+ @assert imgdata.data[(imgdata.height/2)*imgdata.width*4] === 255;
+
+- name: 2d.imageData.get.order.rgb
+ desc: getImageData() returns R then G then B
+ code: |
+ ctx.fillStyle = '#48c';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[0] === 0x44;
+ @assert imgdata.data[1] === 0x88;
+ @assert imgdata.data[2] === 0xCC;
+ @assert imgdata.data[3] === 255;
+ @assert imgdata.data[4] === 0x44;
+ @assert imgdata.data[5] === 0x88;
+ @assert imgdata.data[6] === 0xCC;
+ @assert imgdata.data[7] === 255;
+
+- name: 2d.imageData.get.order.alpha
+ desc: getImageData() returns A in the fourth component
+ code: |
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[3] < 200;
+ @assert imgdata.data[3] > 100;
+
+- name: 2d.imageData.get.unaffected
+ desc: getImageData() is not affected by context state
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50)
+ ctx.save();
+ ctx.translate(50, 0);
+ ctx.globalAlpha = 0.1;
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#f00';
+ ctx.rect(0, 0, 5, 5);
+ ctx.clip();
+ var imgdata = ctx.getImageData(0, 0, 50, 50);
+ ctx.restore();
+ ctx.putImageData(imgdata, 50, 0);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ expected: green
+
+
+- name: 2d.imageData.get.large.crash
+ desc: Test that canvas crash when image data cannot be allocated.
+ code: |
+ @assert throws TypeError ctx.getImageData(10, 0xffffffff, 2147483647, 10);
+
+- name: 2d.imageData.get.rounding
+ desc: Test the handling of non-integer source coordinates in getImageData().
+ code: |
+ function testDimensions(sx, sy, sw, sh, width, height)
+ {
+ imageData = ctx.getImageData(sx, sy, sw, sh);
+ @assert imageData.width == width;
+ @assert imageData.height == height;
+ }
+
+ testDimensions(0, 0, 20, 10, 20, 10);
+
+ testDimensions(.1, .2, 20, 10, 20, 10);
+ testDimensions(.9, .8, 20, 10, 20, 10);
+
+ testDimensions(0, 0, 20.9, 10.9, 20, 10);
+ testDimensions(0, 0, 20.1, 10.1, 20, 10);
+
+ testDimensions(-1, -1, 20, 10, 20, 10);
+
+ testDimensions(-1.1, 0, 20, 10, 20, 10);
+ testDimensions(-1.9, 0, 20, 10, 20, 10);
+
+- name: 2d.imageData.get.invalid
+ desc: Verify getImageData() behavior in invalid cases.
+ code: |
+ imageData = ctx.getImageData(0,0,2,2);
+ var testValues = [NaN, true, false, "\"garbage\"", "-1",
+ "0", "1", "2", Infinity, -Infinity,
+ -5, -0.5, 0, 0.5, 5,
+ 5.4, 255, 256, null, undefined];
+ var testResults = [0, 1, 0, 0, 0,
+ 0, 1, 2, 255, 0,
+ 0, 0, 0, 0, 5,
+ 5, 255, 255, 0, 0];
+ for (var i = 0; i < testValues.length; i++) {
+ imageData.data[0] = testValues[i];
+ @assert imageData.data[0] == testResults[i];
+ }
+ imageData.data['foo']='garbage';
+ @assert imageData.data['foo'] == 'garbage';
+ imageData.data[-1]='garbage';
+ @assert imageData.data[-1] == undefined;
+ imageData.data[17]='garbage';
+ @assert imageData.data[17] == undefined;
+
+- name: 2d.imageData.object.properties
+ desc: ImageData objects have the right properties
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert typeof(imgdata.width) === 'number';
+ @assert typeof(imgdata.height) === 'number';
+ @assert typeof(imgdata.data) === 'object';
+
+- name: 2d.imageData.object.readonly
+ desc: ImageData objects properties are read-only
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ var w = imgdata.width;
+ var h = imgdata.height;
+ var d = imgdata.data;
+ imgdata.width = 123;
+ imgdata.height = 123;
+ imgdata.data = [100,100,100,100];
+ @assert imgdata.width === w;
+ @assert imgdata.height === h;
+ @assert imgdata.data === d;
+ @assert imgdata.data[0] === 0;
+ @assert imgdata.data[1] === 0;
+ @assert imgdata.data[2] === 0;
+ @assert imgdata.data[3] === 0;
+
+- name: 2d.imageData.object.ctor.size
+ desc: ImageData has a usable constructor
+ code: |
+ @assert window.ImageData !== undefined;
+
+ var imgdata = new window.ImageData(2, 3);
+ @assert imgdata.width === 2;
+ @assert imgdata.height === 3;
+ @assert imgdata.data.length === 2 * 3 * 4;
+ for (var i = 0; i < imgdata.data.length; ++i) {
+ @assert imgdata.data[i] === 0;
+ }
+
+- name: 2d.imageData.object.ctor.basics
+ desc: Testing different type of ImageData constructor
+ code: |
+ function setRGBA(imageData, i, rgba)
+ {
+ var s = i * 4;
+ imageData[s] = rgba[0];
+ imageData[s + 1] = rgba[1];
+ imageData[s + 2] = rgba[2];
+ imageData[s + 3] = rgba[3];
+ }
+
+ function getRGBA(imageData, i)
+ {
+ var result = [];
+ var s = i * 4;
+ for (var j = 0; j < 4; j++) {
+ result[j] = imageData[s + j];
+ }
+ return result;
+ }
+
+ function assertArrayEquals(actual, expected)
+ {
+ @assert typeof actual === "object";
+ @assert actual !== null;
+ @assert "length" in actual === true;
+ @assert actual.length === expected.length;
+ for (var i = 0; i < actual.length; i++) {
+ @assert actual.hasOwnProperty(i) === expected.hasOwnProperty(i);
+ @assert actual[i] === expected[i];
+ }
+ }
+
+ @assert ImageData !== undefined;
+ imageData = new ImageData(100, 50);
+
+ @assert imageData !== null;
+ @assert imageData.data !== null;
+ @assert imageData.width === 100;
+ @assert imageData.height === 50;
+ assertArrayEquals(getRGBA(imageData.data, 4), [0, 0, 0, 0]);
+
+ var testColor = [0, 255, 255, 128];
+ setRGBA(imageData.data, 4, testColor);
+ assertArrayEquals(getRGBA(imageData.data, 4), testColor);
+
+ @assert throws TypeError new ImageData(10);
+ @assert throws INDEX_SIZE_ERR new ImageData(0, 10);
+ @assert throws INDEX_SIZE_ERR new ImageData(10, 0);
+ @assert throws INDEX_SIZE_ERR new ImageData('width', 'height');
+ @assert throws INDEX_SIZE_ERR new ImageData(1 << 31, 1 << 31);
+ @assert throws TypeError new ImageData(new Uint8ClampedArray(0));
+ @assert throws INDEX_SIZE_ERR new ImageData(new Uint8Array(100), 25);
+ @assert throws INVALID_STATE_ERR new ImageData(new Uint8ClampedArray(27), 2);
+ @assert throws INDEX_SIZE_ERR new ImageData(new Uint8ClampedArray(28), 7, 0);
+ @assert throws INDEX_SIZE_ERR new ImageData(new Uint8ClampedArray(104), 14);
+ @assert throws INDEX_SIZE_ERR new ImageData(new Uint8ClampedArray([12, 34, 168, 65328]), 1, 151);
+ @assert throws TypeError new ImageData(self, 4, 4);
+ @assert throws TypeError new ImageData(null, 4, 4);
+ @assert throws INDEX_SIZE_ERR new ImageData(imageData.data, 0);
+ @assert throws INDEX_SIZE_ERR new ImageData(imageData.data, 13);
+ @assert throws INDEX_SIZE_ERR new ImageData(imageData.data, 1 << 31);
+ @assert throws INDEX_SIZE_ERR new ImageData(imageData.data, 'biggish');
+ @assert throws INDEX_SIZE_ERR new ImageData(imageData.data, 1 << 24, 1 << 31);
+ @assert new ImageData(new Uint8ClampedArray(28), 7).height === 1;
+
+ imageDataFromData = new ImageData(imageData.data, 100);
+ @assert imageDataFromData.width === 100;
+ @assert imageDataFromData.height === 50;
+ @assert imageDataFromData.data === imageData.data;
+ assertArrayEquals(getRGBA(imageDataFromData.data, 10), getRGBA(imageData.data, 10));
+ setRGBA(imageData.data, 10, testColor);
+ assertArrayEquals(getRGBA(imageDataFromData.data, 10), getRGBA(imageData.data, 10));
+
+ var data = new Uint8ClampedArray(400);
+ data[22] = 129;
+ imageDataFromData = new ImageData(data, 20, 5);
+ @assert imageDataFromData.width === 20;
+ @assert imageDataFromData.height === 5;
+ @assert imageDataFromData.data === data;
+ assertArrayEquals(getRGBA(imageDataFromData.data, 2), getRGBA(data, 2));
+ setRGBA(imageDataFromData.data, 2, testColor);
+ assertArrayEquals(getRGBA(imageDataFromData.data, 2), getRGBA(data, 2));
+
+ if (window.SharedArrayBuffer) {
+ @assert throws TypeError new ImageData(new Uint16Array(new SharedArrayBuffer(32)), 4, 2);
+ }
+
+- name: 2d.imageData.object.ctor.array
+ desc: ImageData has a usable constructor
+ code: |
+ @assert window.ImageData !== undefined;
+
+ var array = new Uint8ClampedArray(8);
+ var imgdata = new window.ImageData(array, 1, 2);
+ @assert imgdata.width === 1;
+ @assert imgdata.height === 2;
+ @assert imgdata.data === array;
+
+- name: 2d.imageData.object.ctor.array.bounds
+ desc: ImageData has a usable constructor
+ code: |
+ @assert window.ImageData !== undefined;
+
+ @assert throws INVALID_STATE_ERR new ImageData(new Uint8ClampedArray(0), 1);
+ @assert throws INVALID_STATE_ERR new ImageData(new Uint8ClampedArray(3), 1);
+ @assert throws INDEX_SIZE_ERR new ImageData(new Uint8ClampedArray(4), 0);
+ @assert throws INDEX_SIZE_ERR new ImageData(new Uint8ClampedArray(4), 1, 2);
+ @assert throws TypeError new ImageData(new Uint8Array(8), 1, 2);
+ @assert throws TypeError new ImageData(new Int8Array(8), 1, 2);
+
+- name: 2d.imageData.object.set
+ desc: ImageData.data can be modified
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ @assert imgdata.data[0] === 100;
+ imgdata.data[0] = 200;
+ @assert imgdata.data[0] === 200;
+
+- name: 2d.imageData.object.undefined
+ desc: ImageData.data converts undefined to 0
+ webidl:
+ - es-octet
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = undefined;
+ @assert imgdata.data[0] === 0;
+
+- name: 2d.imageData.object.nan
+ desc: ImageData.data converts NaN to 0
+ webidl:
+ - es-octet
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = NaN;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = "cheese";
+ @assert imgdata.data[0] === 0;
+
+- name: 2d.imageData.object.string
+ desc: ImageData.data converts strings to numbers with ToNumber
+ webidl:
+ - es-octet
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = "110";
+ @assert imgdata.data[0] === 110;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = "0x78";
+ @assert imgdata.data[0] === 120;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = " +130e0 ";
+ @assert imgdata.data[0] === 130;
+
+- name: 2d.imageData.object.clamp
+ desc: ImageData.data clamps numbers to [0, 255]
+ webidl:
+ - es-octet
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+
+ imgdata.data[0] = 100;
+ imgdata.data[0] = 300;
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -100;
+ @assert imgdata.data[0] === 0;
+
+ imgdata.data[0] = 100;
+ imgdata.data[0] = 200+Math.pow(2, 32);
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -200-Math.pow(2, 32);
+ @assert imgdata.data[0] === 0;
+
+ imgdata.data[0] = 100;
+ imgdata.data[0] = Math.pow(10, 39);
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -Math.pow(10, 39);
+ @assert imgdata.data[0] === 0;
+
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -Infinity;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = Infinity;
+ @assert imgdata.data[0] === 255;
+
+- name: 2d.imageData.object.round
+ desc: ImageData.data rounds numbers with round-to-zero
+ webidl:
+ - es-octet
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 0.499;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 0.5;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 0.501;
+ @assert imgdata.data[0] === 1;
+ imgdata.data[0] = 1.499;
+ @assert imgdata.data[0] === 1;
+ imgdata.data[0] = 1.5;
+ @assert imgdata.data[0] === 2;
+ imgdata.data[0] = 1.501;
+ @assert imgdata.data[0] === 2;
+ imgdata.data[0] = 2.5;
+ @assert imgdata.data[0] === 2;
+ imgdata.data[0] = 3.5;
+ @assert imgdata.data[0] === 4;
+ imgdata.data[0] = 252.5;
+ @assert imgdata.data[0] === 252;
+ imgdata.data[0] = 253.5;
+ @assert imgdata.data[0] === 254;
+ imgdata.data[0] = 254.5;
+ @assert imgdata.data[0] === 254;
+ imgdata.data[0] = 256.5;
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = -0.5;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = -1.5;
+ @assert imgdata.data[0] === 0;
+
+
+
+- name: 2d.imageData.put.null
+ desc: putImageData() with null imagedata throws TypeError
+ code: |
+ @assert throws TypeError ctx.putImageData(null, 0, 0);
+
+- name: 2d.imageData.put.nonfinite
+ desc: putImageData() throws TypeError if arguments are not finite
+ notes: *bindings
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @nonfinite @assert throws TypeError ctx.putImageData(<imgdata>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ @nonfinite @assert throws TypeError ctx.putImageData(<imgdata>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+
+- name: 2d.imageData.put.basic
+ desc: putImageData() puts image data from getImageData() onto the canvas
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.created
+ desc: putImageData() puts image data from createImageData() onto the canvas
+ code: |
+ var imgdata = ctx.createImageData(100, 50);
+ for (var i = 0; i < imgdata.data.length; i += 4) {
+ imgdata.data[i] = 0;
+ imgdata.data[i+1] = 255;
+ imgdata.data[i+2] = 0;
+ imgdata.data[i+3] = 255;
+ }
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.wrongtype
+ desc: putImageData() does not accept non-ImageData objects
+ code: |
+ var imgdata = { width: 1, height: 1, data: [255, 0, 0, 255] };
+ @assert throws TypeError ctx.putImageData(imgdata, 0, 0);
+ @assert throws TypeError ctx.putImageData("cheese", 0, 0);
+ @assert throws TypeError ctx.putImageData(42, 0, 0);
+ expected: green
+
+- name: 2d.imageData.put.cross
+ desc: putImageData() accepts image data got from a different canvas
+ code: |
+ var canvas2 = document.createElement('canvas');
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50)
+ var imgdata = ctx2.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.alpha
+ desc: putImageData() puts non-solid image data correctly
+ code: |
+ ctx.fillStyle = 'rgba(0, 255, 0, 0.25)';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,64;
+ expected: |
+ size 100 50
+ cr.set_source_rgba(0, 1, 0, 0.25)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.imageData.put.modified
+ desc: putImageData() puts modified image data correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(45, 20, 10, 10)
+ var imgdata = ctx.getImageData(45, 20, 10, 10);
+ for (var i = 0, len = imgdata.width*imgdata.height*4; i < len; i += 4)
+ {
+ imgdata.data[i] = 0;
+ imgdata.data[i+1] = 255;
+ }
+ ctx.putImageData(imgdata, 45, 20);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.dirty.zero
+ desc: putImageData() with zero-sized dirty rectangle puts nothing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0, 0, 0, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.dirty.rect1
+ desc: putImageData() only modifies areas inside the dirty rectangle, using width
+ and height
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 20, 20)
+
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(40, 20, 20, 20)
+ ctx.putImageData(imgdata, 40, 20, 0, 0, 20, 20);
+
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 35,25 ==~ 0,255,0,255;
+ @assert pixel 65,25 ==~ 0,255,0,255;
+ @assert pixel 50,15 ==~ 0,255,0,255;
+ @assert pixel 50,45 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.dirty.rect2
+ desc: putImageData() only modifies areas inside the dirty rectangle, using x and
+ y
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(60, 30, 20, 20)
+
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(40, 20, 20, 20)
+ ctx.putImageData(imgdata, -20, -10, 60, 30, 20, 20);
+
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 35,25 ==~ 0,255,0,255;
+ @assert pixel 65,25 ==~ 0,255,0,255;
+ @assert pixel 50,15 ==~ 0,255,0,255;
+ @assert pixel 50,45 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.dirty.negative
+ desc: putImageData() handles negative-sized dirty rectangles correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 20, 20)
+
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(40, 20, 20, 20)
+ ctx.putImageData(imgdata, 40, 20, 20, 20, -20, -20);
+
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 35,25 ==~ 0,255,0,255;
+ @assert pixel 65,25 ==~ 0,255,0,255;
+ @assert pixel 50,15 ==~ 0,255,0,255;
+ @assert pixel 50,45 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.dirty.outside
+ desc: putImageData() handles dirty rectangles outside the canvas correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+
+ ctx.putImageData(imgdata, 100, 20, 20, 20, -20, -20);
+ ctx.putImageData(imgdata, 200, 200, 0, 0, 100, 50);
+ ctx.putImageData(imgdata, 40, 20, -30, -20, 30, 20);
+ ctx.putImageData(imgdata, -30, 20, 0, 0, 30, 20);
+
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 98,15 ==~ 0,255,0,255;
+ @assert pixel 98,25 ==~ 0,255,0,255;
+ @assert pixel 98,45 ==~ 0,255,0,255;
+ @assert pixel 1,5 ==~ 0,255,0,255;
+ @assert pixel 1,25 ==~ 0,255,0,255;
+ @assert pixel 1,45 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.unchanged
+ desc: putImageData(getImageData(...), ...) has no effect
+ code: |
+ var i = 0;
+ for (var y = 0; y < 16; ++y) {
+ for (var x = 0; x < 16; ++x, ++i) {
+ ctx.fillStyle = 'rgba(' + i + ',' + (Math.floor(i*1.5) % 256) + ',' + (Math.floor(i*23.3) % 256) + ',' + (i/256) + ')';
+ ctx.fillRect(x, y, 1, 1);
+ }
+ }
+ var imgdata1 = ctx.getImageData(0.1, 0.2, 15.8, 15.9);
+ var olddata = [];
+ for (var i = 0; i < imgdata1.data.length; ++i)
+ olddata[i] = imgdata1.data[i];
+
+ ctx.putImageData(imgdata1, 0.1, 0.2);
+
+ var imgdata2 = ctx.getImageData(0.1, 0.2, 15.8, 15.9);
+ for (var i = 0; i < imgdata2.data.length; ++i) {
+ @assert olddata[i] === imgdata2.data[i];
+ }
+
+- name: 2d.imageData.put.unaffected
+ desc: putImageData() is not affected by context state
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.globalAlpha = 0.1;
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#f00';
+ ctx.shadowBlur = 1;
+ ctx.translate(100, 50);
+ ctx.scale(0.1, 0.1);
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.clip
+ desc: putImageData() is not affected by clipping regions
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.imageData.put.path
+ desc: putImageData() does not affect the current path
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.rect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.putImageData(imgdata, 0, 0);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/shadows.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/shadows.yaml
new file mode 100644
index 0000000000..7a7c115a7c
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/shadows.yaml
@@ -0,0 +1,1025 @@
+- name: 2d.shadow.attributes.shadowBlur.initial
+ code: |
+ @assert ctx.shadowBlur === 0;
+
+- name: 2d.shadow.attributes.shadowBlur.valid
+ code: |
+ ctx.shadowBlur = 1;
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 0.5;
+ @assert ctx.shadowBlur === 0.5;
+
+ ctx.shadowBlur = 1e6;
+ @assert ctx.shadowBlur === 1e6;
+
+ ctx.shadowBlur = 0;
+ @assert ctx.shadowBlur === 0;
+
+- name: 2d.shadow.attributes.shadowBlur.invalid
+ code: |
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = -2;
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = Infinity;
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = -Infinity;
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = NaN;
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = 'string';
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = true;
+ @assert ctx.shadowBlur === 1;
+
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = false;
+ @assert ctx.shadowBlur === 0;
+
+- name: 2d.shadow.attributes.shadowOffset.initial
+ code: |
+ @assert ctx.shadowOffsetX === 0;
+ @assert ctx.shadowOffsetY === 0;
+
+- name: 2d.shadow.attributes.shadowOffset.valid
+ code: |
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+
+ ctx.shadowOffsetX = 0.5;
+ ctx.shadowOffsetY = 0.25;
+ @assert ctx.shadowOffsetX === 0.5;
+ @assert ctx.shadowOffsetY === 0.25;
+
+ ctx.shadowOffsetX = -0.5;
+ ctx.shadowOffsetY = -0.25;
+ @assert ctx.shadowOffsetX === -0.5;
+ @assert ctx.shadowOffsetY === -0.25;
+
+ ctx.shadowOffsetX = 0;
+ ctx.shadowOffsetY = 0;
+ @assert ctx.shadowOffsetX === 0;
+ @assert ctx.shadowOffsetY === 0;
+
+ ctx.shadowOffsetX = 1e6;
+ ctx.shadowOffsetY = 1e6;
+ @assert ctx.shadowOffsetX === 1e6;
+ @assert ctx.shadowOffsetY === 1e6;
+
+- name: 2d.shadow.attributes.shadowOffset.invalid
+ code: |
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = Infinity;
+ ctx.shadowOffsetY = Infinity;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = -Infinity;
+ ctx.shadowOffsetY = -Infinity;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = NaN;
+ ctx.shadowOffsetY = NaN;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = 'string';
+ ctx.shadowOffsetY = 'string';
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = true;
+ ctx.shadowOffsetY = true;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 1;
+
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = false;
+ ctx.shadowOffsetY = false;
+ @assert ctx.shadowOffsetX === 0;
+ @assert ctx.shadowOffsetY === 0;
+
+- name: 2d.shadow.attributes.shadowColor.initial
+ code: |
+ @assert ctx.shadowColor === 'rgba(0, 0, 0, 0)';
+
+- name: 2d.shadow.attributes.shadowColor.valid
+ code: |
+ ctx.shadowColor = 'lime';
+ @assert ctx.shadowColor === '#00ff00';
+
+ ctx.shadowColor = 'RGBA(0,255, 0,0)';
+ @assert ctx.shadowColor === 'rgba(0, 255, 0, 0)';
+
+- name: 2d.shadow.attributes.shadowColor.invalid
+ code: |
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = 'bogus';
+ @assert ctx.shadowColor === '#00ff00';
+
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = 'red bogus';
+ @assert ctx.shadowColor === '#00ff00';
+
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = ctx;
+ @assert ctx.shadowColor === '#00ff00';
+
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = undefined;
+ @assert ctx.shadowColor === '#00ff00';
+
+- name: 2d.shadow.enable.off.1
+ desc: Shadows are not drawn when only shadowColor is set
+ code: |
+ ctx.shadowColor = '#f00';
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.enable.off.2
+ desc: Shadows are not drawn when only shadowColor is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#f00';
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.enable.blur
+ desc: Shadows are drawn if shadowBlur is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowBlur = 0.1;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.enable.x
+ desc: Shadows are drawn if shadowOffsetX is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 0.1;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.enable.y
+ desc: Shadows are drawn if shadowOffsetY is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 0.1;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.offset.positiveX
+ desc: Shadows can be offset with positive x
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(0, 0, 50, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.offset.negativeX
+ desc: Shadows can be offset with negative x
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = -50;
+ ctx.fillRect(50, 0, 50, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.offset.positiveY
+ desc: Shadows can be offset with positive y
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 25;
+ ctx.fillRect(0, 0, 100, 25);
+ @assert pixel 50,12 == 0,255,0,255;
+ @assert pixel 50,37 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.offset.negativeY
+ desc: Shadows can be offset with negative y
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = -25;
+ ctx.fillRect(0, 25, 100, 25);
+ @assert pixel 50,12 == 0,255,0,255;
+ @assert pixel 50,37 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.outside
+ desc: Shadows of shapes outside the visible area can be offset onto the visible
+ area
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 100;
+ ctx.fillRect(-100, 0, 25, 50);
+ ctx.shadowOffsetX = -100;
+ ctx.fillRect(175, 0, 25, 50);
+ ctx.shadowOffsetX = 0;
+ ctx.shadowOffsetY = 100;
+ ctx.fillRect(25, -100, 50, 25);
+ ctx.shadowOffsetY = -100;
+ ctx.fillRect(25, 125, 50, 25);
+ @assert pixel 12,25 == 0,255,0,255;
+ @assert pixel 87,25 == 0,255,0,255;
+ @assert pixel 50,12 == 0,255,0,255;
+ @assert pixel 50,37 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.clip.1
+ desc: Shadows of clipped shapes are still drawn within the clipping region
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(50, 0, 50, 50);
+ ctx.clip();
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.restore();
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.clip.2
+ desc: Shadows are not drawn outside the clipping region
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.restore();
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.clip.3
+ desc: Shadows of clipped shapes are still drawn within the clipping region
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(-50, 0, 50, 50);
+ ctx.restore();
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.stroke.basic
+ desc: Shadows are drawn for strokes
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.beginPath();
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, -25);
+ ctx.lineTo(100, -25);
+ ctx.stroke();
+
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.stroke.cap.1
+ desc: Shadows are not drawn for areas outside stroke caps
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.beginPath();
+ ctx.lineWidth = 50;
+ ctx.lineCap = 'butt';
+ ctx.moveTo(-50, -25);
+ ctx.lineTo(0, -25);
+ ctx.moveTo(100, -25);
+ ctx.lineTo(150, -25);
+ ctx.stroke();
+
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.stroke.cap.2
+ desc: Shadows are drawn for stroke caps
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.beginPath();
+ ctx.lineWidth = 50;
+ ctx.lineCap = 'square';
+ ctx.moveTo(25, -25);
+ ctx.lineTo(75, -25);
+ ctx.stroke();
+
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.stroke.join.1
+ desc: Shadows are not drawn for areas outside stroke joins
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 100;
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'bevel';
+ ctx.beginPath();
+ ctx.moveTo(-200, -50);
+ ctx.lineTo(-150, -50);
+ ctx.lineTo(-151, -100);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.stroke.join.2
+ desc: Shadows are drawn for stroke joins
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 100;
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.beginPath();
+ ctx.moveTo(-200, -50);
+ ctx.lineTo(-150, -50);
+ ctx.lineTo(-151, -100);
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.stroke.join.3
+ desc: Shadows are drawn for stroke joins respecting miter limit
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 100;
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 0.1;
+ ctx.beginPath();
+ ctx.moveTo(-200, -50);
+ ctx.lineTo(-150, -50);
+ ctx.lineTo(-151, -100); // (not an exact right angle, to avoid some other bug in Firefox 3)
+ ctx.stroke();
+
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.image.basic
+ desc: Shadows are drawn for images
+ images:
+ - red.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.drawImage(document.getElementById('red.png'), 0, -50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.image.transparent.1
+ desc: Shadows are not drawn for transparent images
+ images:
+ - transparent.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.drawImage(document.getElementById('transparent.png'), 0, -50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.image.transparent.2
+ desc: Shadows are not drawn for transparent parts of images
+ images:
+ - redtransparent.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.drawImage(document.getElementById('redtransparent.png'), 50, -50);
+ ctx.shadowColor = '#f00';
+ ctx.drawImage(document.getElementById('redtransparent.png'), -50, -50);
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.image.alpha
+ desc: Shadows are drawn correctly for partially-transparent images
+ images:
+ - transparent50.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.drawImage(document.getElementById('transparent50.png'), 0, -50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.image.section
+ desc: Shadows are not drawn for areas outside image source rectangles
+ images:
+ - redtransparent.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#f00';
+ ctx.drawImage(document.getElementById('redtransparent.png'), 50, 0, 50, 50, 0, -50, 50, 50);
+
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.image.scale
+ desc: Shadows are drawn correctly for scaled images
+ images:
+ - redtransparent.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.drawImage(document.getElementById('redtransparent.png'), 0, 0, 100, 50, -10, -50, 240, 50);
+
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.canvas.basic
+ desc: Shadows are drawn for canvases
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.drawImage(canvas2, 0, -50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.canvas.transparent.1
+ desc: Shadows are not drawn for transparent canvases
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.drawImage(canvas2, 0, -50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.canvas.transparent.2
+ desc: Shadows are not drawn for transparent parts of canvases
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 50, 50);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.drawImage(canvas2, 50, -50);
+ ctx.shadowColor = '#f00';
+ ctx.drawImage(canvas2, -50, -50);
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.canvas.alpha
+ desc: Shadows are drawn correctly for partially-transparent canvases
+ images:
+ - transparent50.png
+ code: |
+ var canvas2 = document.createElement('canvas');
+ canvas2.width = 100;
+ canvas2.height = 50;
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.fillStyle = 'rgba(255, 0, 0, 0.5)';
+ ctx2.fillRect(0, 0, 100, 50);
+
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.drawImage(canvas2, 0, -50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.pattern.basic
+ desc: Shadows are drawn for fill patterns
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ images:
+ - red.png
+ code: |
+ var pattern = ctx.createPattern(document.getElementById('red.png'), 'repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.pattern.transparent.1
+ desc: Shadows are not drawn for transparent fill patterns
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ images:
+ - transparent.png
+ code: |
+ var pattern = ctx.createPattern(document.getElementById('transparent.png'), 'repeat');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.pattern.transparent.2
+ desc: Shadows are not drawn for transparent parts of fill patterns
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ images:
+ - redtransparent.png
+ code: |
+ var pattern = ctx.createPattern(document.getElementById('redtransparent.png'), 'repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.pattern.alpha
+ desc: Shadows are drawn correctly for partially-transparent fill patterns
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ images:
+ - transparent50.png
+ code: |
+ var pattern = ctx.createPattern(document.getElementById('transparent50.png'), 'repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.gradient.basic
+ desc: Shadows are drawn for gradient fills
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, '#f00');
+ gradient.addColorStop(1, '#f00');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.gradient.transparent.1
+ desc: Shadows are not drawn for transparent gradient fills
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0)');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.gradient.transparent.2
+ desc: Shadows are not drawn for transparent parts of gradient fills
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, '#f00');
+ gradient.addColorStop(0.499, '#f00');
+ gradient.addColorStop(0.5, 'rgba(0,0,0,0)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0)');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.gradient.alpha
+ desc: Shadows are drawn correctly for partially-transparent gradient fills
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, 'rgba(255,0,0,0.5)');
+ gradient.addColorStop(1, 'rgba(255,0,0,0.5)');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.transform.1
+ desc: Shadows take account of transformations
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.translate(100, 100);
+ ctx.fillRect(-100, -150, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.transform.2
+ desc: Shadow offsets are not affected by transformations
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.rotate(Math.PI)
+ ctx.fillRect(-100, 0, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.blur.low
+ desc: Shadows look correct for small blurs
+ manual:
+ code: |
+ ctx.fillStyle = '#ff0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#00f';
+ ctx.shadowOffsetY = 25;
+ for (var x = 0; x < 100; ++x) {
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(x, 0, 1, 50);
+ ctx.clip();
+ ctx.shadowBlur = x;
+ ctx.fillRect(-200, -200, 500, 200);
+ ctx.restore();
+ }
+ expected: |
+ size 100 50
+ import math
+ cr.set_source_rgb(0, 0, 1)
+ cr.rectangle(0, 0, 1, 25)
+ cr.fill()
+ cr.set_source_rgb(1, 1, 0)
+ cr.rectangle(0, 25, 1, 25)
+ cr.fill()
+ for x in range(1, 100):
+ sigma = x/2.0
+ filter = []
+ for i in range(-24, 26):
+ filter.append(math.exp(-i*i / (2*sigma*sigma)) / (math.sqrt(2*math.pi)*sigma))
+ accum = [0]
+ for f in filter:
+ accum.append(accum[-1] + f)
+ for y in range(0, 50):
+ cr.set_source_rgb(accum[y], accum[y], 1-accum[y])
+ cr.rectangle(x, y, 1, 1)
+ cr.fill()
+
+- name: 2d.shadow.blur.high
+ desc: Shadows look correct for large blurs
+ manual:
+ code: |
+ ctx.fillStyle = '#ff0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#00f';
+ ctx.shadowOffsetY = 0;
+ ctx.shadowBlur = 100;
+ ctx.fillRect(-200, -200, 200, 400);
+ expected: |
+ size 100 50
+ import math
+ sigma = 100.0/2
+ filter = []
+ for i in range(-200, 100):
+ filter.append(math.exp(-i*i / (2*sigma*sigma)) / (math.sqrt(2*math.pi)*sigma))
+ accum = [0]
+ for f in filter:
+ accum.append(accum[-1] + f)
+ for x in range(0, 100):
+ cr.set_source_rgb(accum[x+200], accum[x+200], 1-accum[x+200])
+ cr.rectangle(x, 0, 1, 50)
+ cr.fill()
+
+- name: 2d.shadow.alpha.1
+ desc: Shadow color alpha components are used
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = 'rgba(255, 0, 0, 0.01)';
+ ctx.shadowOffsetY = 50;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 0,255,0,255 +/- 4;
+ expected: green
+
+- name: 2d.shadow.alpha.2
+ desc: Shadow color alpha components are used
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = 'rgba(0, 0, 255, 0.5)';
+ ctx.shadowOffsetY = 50;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.alpha.3
+ desc: Shadows are affected by globalAlpha
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00'; // (work around broken Firefox globalAlpha caching)
+ ctx.shadowColor = '#00f';
+ ctx.shadowOffsetY = 50;
+ ctx.globalAlpha = 0.5;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.alpha.4
+ desc: Shadows with alpha components are correctly affected by globalAlpha
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00'; // (work around broken Firefox globalAlpha caching)
+ ctx.shadowColor = 'rgba(0, 0, 255, 0.707)';
+ ctx.shadowOffsetY = 50;
+ ctx.globalAlpha = 0.707;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.alpha.5
+ desc: Shadows of shapes with alpha components are drawn correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = 'rgba(64, 0, 0, 0.5)';
+ ctx.shadowColor = '#00f';
+ ctx.shadowOffsetY = 50;
+ ctx.fillRect(0, -50, 100, 50);
+
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0.5, 0, 0.5)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+
+- name: 2d.shadow.composite.1
+ desc: Shadows are drawn using globalCompositeOperation
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = 'xor';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 100;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, 0, 200, 50);
+
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.composite.2
+ desc: Shadows are drawn using globalCompositeOperation
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = 'xor';
+ ctx.shadowColor = '#f00';
+ ctx.shadowBlur = 1;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-10, -10, 120, 70);
+
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
+
+- name: 2d.shadow.composite.3
+ desc: Areas outside shadows are drawn correctly with destination-out
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = 'destination-out';
+ ctx.shadowColor = '#f00';
+ ctx.shadowBlur = 10;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(200, 0, 100, 50);
+
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ expected: green
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/text-styles.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/text-styles.yaml
new file mode 100644
index 0000000000..344a5a0752
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/text-styles.yaml
@@ -0,0 +1,452 @@
+- name: 2d.text.font.parse.basic
+ code: |
+ ctx.font = '20px serif';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20PX SERIF';
+ @assert ctx.font === '20px serif'; @moz-todo
+
+- name: 2d.text.font.parse.tiny
+ code: |
+ ctx.font = '1px sans-serif';
+ @assert ctx.font === '1px sans-serif';
+
+- name: 2d.text.font.parse.complex
+ code: |
+ ctx.font = 'small-caps italic 400 12px/2 Unknown Font, sans-serif';
+ @assert ctx.font === 'italic small-caps 12px "Unknown Font", sans-serif'; @moz-todo
+
+- name: 2d.text.font.parse.family
+ code: |
+ ctx.font = '20px cursive,fantasy,monospace,sans-serif,serif,UnquotedFont,"QuotedFont\\\\\\","';
+ @assert ctx.font === '20px cursive, fantasy, monospace, sans-serif, serif, UnquotedFont, "QuotedFont\\\\\\","';
+
+ # TODO:
+ # 2d.text.font.parse.size.absolute
+ # xx-small x-small small medium large x-large xx-large
+ # 2d.text.font.parse.size.relative
+ # smaller larger
+ # 2d.text.font.parse.size.length.relative
+ # em ex px
+ # 2d.text.font.parse.size.length.absolute
+ # in cm mm pt pc
+
+- name: 2d.text.font.parse.size.percentage
+ canvas: 'style="font-size: 144px" width="100" height="50"'
+ code: |
+ ctx.font = '50% serif';
+ @assert ctx.font === '72px serif'; @moz-todo
+ canvas.setAttribute('style', 'font-size: 100px');
+ @assert ctx.font === '72px serif'; @moz-todo
+
+- name: 2d.text.font.parse.size.percentage.default
+ code: |
+ var canvas2 = document.createElement('canvas');
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.font = '1000% serif';
+ @assert ctx2.font === '100px serif'; @moz-todo
+
+- name: 2d.text.font.parse.system
+ desc: System fonts must be computed to explicit values
+ code: |
+ ctx.font = 'message-box';
+ @assert ctx.font !== 'message-box';
+
+- name: 2d.text.font.parse.invalid
+ code: |
+ ctx.font = '20px serif';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'bogus';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'inherit';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '10px {bogus}';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '10px initial';
+ @assert ctx.font === '20px serif'; @moz-todo
+
+ ctx.font = '20px serif';
+ ctx.font = '10px default';
+ @assert ctx.font === '20px serif'; @moz-todo
+
+ ctx.font = '20px serif';
+ ctx.font = '10px inherit';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '10px revert';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'var(--x)';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'var(--x, 10px serif)';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '1em serif; background: green; margin: 10px';
+ @assert ctx.font === '20px serif';
+
+- name: 2d.text.font.default
+ code: |
+ @assert ctx.font === '10px sans-serif';
+
+- name: 2d.text.font.relative_size
+ code: |
+ var canvas2 = document.createElement('canvas');
+ var ctx2 = canvas2.getContext('2d');
+ ctx2.font = '1em sans-serif';
+ @assert ctx2.font === '10px sans-serif';
+
+- name: 2d.text.align.valid
+ code: |
+ ctx.textAlign = 'start';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'end';
+ @assert ctx.textAlign === 'end';
+
+ ctx.textAlign = 'left';
+ @assert ctx.textAlign === 'left';
+
+ ctx.textAlign = 'right';
+ @assert ctx.textAlign === 'right';
+
+ ctx.textAlign = 'center';
+ @assert ctx.textAlign === 'center';
+
+- name: 2d.text.align.invalid
+ code: |
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'bogus';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'END';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'end ';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'end\0';
+ @assert ctx.textAlign === 'start';
+
+- name: 2d.text.align.default
+ code: |
+ @assert ctx.textAlign === 'start';
+
+
+- name: 2d.text.baseline.valid
+ code: |
+ ctx.textBaseline = 'top';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'hanging';
+ @assert ctx.textBaseline === 'hanging';
+
+ ctx.textBaseline = 'middle';
+ @assert ctx.textBaseline === 'middle';
+
+ ctx.textBaseline = 'alphabetic';
+ @assert ctx.textBaseline === 'alphabetic';
+
+ ctx.textBaseline = 'ideographic';
+ @assert ctx.textBaseline === 'ideographic';
+
+ ctx.textBaseline = 'bottom';
+ @assert ctx.textBaseline === 'bottom';
+
+- name: 2d.text.baseline.invalid
+ code: |
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'bogus';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'MIDDLE';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'middle ';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'middle\0';
+ @assert ctx.textBaseline === 'top';
+
+- name: 2d.text.baseline.default
+ code: |
+ @assert ctx.textBaseline === 'alphabetic';
+
+
+
+
+
+- name: 2d.text.draw.baseline.top
+ desc: textBaseline top is the top of the em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'top';
+ ctx.fillText('CC', 0, 0);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.baseline.bottom
+ desc: textBaseline bottom is the bottom of the em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'bottom';
+ ctx.fillText('CC', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.baseline.middle
+ desc: textBaseline middle is the middle of the em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'middle';
+ ctx.fillText('CC', 0, 25);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.baseline.alphabetic
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'alphabetic';
+ ctx.fillText('CC', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.baseline.ideographic
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'ideographic';
+ ctx.fillText('CC', 0, 31.25);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 95,45 ==~ 0,255,0,255; @moz-todo
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.baseline.hanging
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'hanging';
+ ctx.fillText('CC', 0, 12.5);
+ @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.space
+ desc: Space characters are converted to U+0020, and collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E EE', -100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.other
+ desc: Space characters are converted to U+0020, and collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E \x09\x0a\x0c\x0d \x09\x0a\x0c\x0dEE', -100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.start
+ desc: Space characters at the start of a line are collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText(' EE', 0, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }), 500);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.end
+ desc: Space characters at the end of a line are collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ ctx.font = '50px CanvasTest';
+ deferTest();
+ step_timeout(t.step_func_done(function () {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'right';
+ ctx.fillText('EE ', 100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
+ }), 500);
+ expected: green
+
+
+- name: 2d.text.measure.width.space
+ desc: Space characters are converted to U+0020 and collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ deferTest();
+ var f = new FontFace("CanvasTest", "/fonts/CanvasTest.ttf");
+ document.fonts.add(f);
+ document.fonts.ready.then(() => {
+ step_timeout(t.step_func_done(function () {
+ ctx.font = '50px CanvasTest';
+ @assert ctx.measureText('A B').width === 150;
+ @assert ctx.measureText('A B').width === 200;
+ @assert ctx.measureText('A \x09\x0a\x0c\x0d \x09\x0a\x0c\x0dB').width === 150; @moz-todo
+ @assert ctx.measureText('A \x0b B').width >= 200;
+
+ @assert ctx.measureText(' AB').width === 100; @moz-todo
+ @assert ctx.measureText('AB ').width === 100; @moz-todo
+ }), 500);
+ });
+
+- name: 2d.text.measure.rtl.text
+ desc: Measurement should follow canvas direction instead text direction
+ fonts:
+ - CanvasTest
+ code: |
+ metrics = ctx.measureText('اَلْعَرَبِيَّةُ');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+
+- name: 2d.text.measure.boundingBox.textAlign
+ desc: Measurement should be related to textAlignment
+ code: |
+ ctx.textAlign = "right";
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft > metrics.actualBoundingBoxRight;
+
+ ctx.textAlign = "left"
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+
+- name: 2d.text.measure.boundingBox.direction
+ desc: Measurement should follow text direction
+ code: |
+ ctx.direction = "ltr";
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+
+ ctx.direction = "rtl";
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft > metrics.actualBoundingBoxRight;
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-element.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-element.yaml
new file mode 100644
index 0000000000..5c07375dd2
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-element.yaml
@@ -0,0 +1,142 @@
+- name: 2d.getcontext.exists
+ desc: The 2D context is implemented
+ code: |
+ @assert canvas.getContext('2d') !== null;
+
+- name: 2d.getcontext.invalid.args
+ desc: Calling getContext with invalid arguments.
+ code: |
+ @assert canvas.getContext('') === null;
+ @assert canvas.getContext('2d#') === null;
+ @assert canvas.getContext('This is clearly not a valid context name.') === null;
+ @assert canvas.getContext('2d\0') === null;
+ @assert canvas.getContext('2\uFF44') === null;
+ @assert canvas.getContext('2D') === null;
+ @assert throws TypeError canvas.getContext();
+ @assert canvas.getContext('null') === null;
+ @assert canvas.getContext('undefined') === null;
+
+- name: 2d.getcontext.extraargs.create
+ desc: The 2D context doesn't throw with extra getContext arguments (new context)
+ code: |
+ @assert document.createElement("canvas").getContext('2d', false, {}, [], 1, "2") !== null;
+ @assert document.createElement("canvas").getContext('2d', 123) !== null;
+ @assert document.createElement("canvas").getContext('2d', "test") !== null;
+ @assert document.createElement("canvas").getContext('2d', undefined) !== null;
+ @assert document.createElement("canvas").getContext('2d', null) !== null;
+ @assert document.createElement("canvas").getContext('2d', Symbol.hasInstance) !== null;
+
+- name: 2d.getcontext.extraargs.cache
+ desc: The 2D context doesn't throw with extra getContext arguments (cached)
+ code: |
+ @assert canvas.getContext('2d', false, {}, [], 1, "2") !== null;
+ @assert canvas.getContext('2d', 123) !== null;
+ @assert canvas.getContext('2d', "test") !== null;
+ @assert canvas.getContext('2d', undefined) !== null;
+ @assert canvas.getContext('2d', null) !== null;
+ @assert canvas.getContext('2d', Symbol.hasInstance) !== null;
+
+- name: 2d.type.exists
+ desc: The 2D context interface is a property of 'window'
+ notes: &bindings Defined in "Web IDL" (draft)
+ code: |
+ @assert window.CanvasRenderingContext2D;
+
+- name: 2d.type.prototype
+ desc: window.CanvasRenderingContext2D.prototype are not [[Writable]] and not [[Configurable]],
+ and its methods are [[Configurable]].
+ notes: *bindings
+ code: |
+ @assert window.CanvasRenderingContext2D.prototype;
+ @assert window.CanvasRenderingContext2D.prototype.fill;
+ window.CanvasRenderingContext2D.prototype = null;
+ @assert window.CanvasRenderingContext2D.prototype;
+ delete window.CanvasRenderingContext2D.prototype;
+ @assert window.CanvasRenderingContext2D.prototype;
+ window.CanvasRenderingContext2D.prototype.fill = 1;
+ @assert window.CanvasRenderingContext2D.prototype.fill === 1;
+ delete window.CanvasRenderingContext2D.prototype.fill;
+ @assert window.CanvasRenderingContext2D.prototype.fill === undefined;
+
+- name: 2d.type.replace
+ desc: Interface methods can be overridden
+ notes: *bindings
+ code: |
+ var fillRect = window.CanvasRenderingContext2D.prototype.fillRect;
+ window.CanvasRenderingContext2D.prototype.fillRect = function (x, y, w, h)
+ {
+ this.fillStyle = '#0f0';
+ fillRect.call(this, x, y, w, h);
+ };
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.type.extend
+ desc: Interface methods can be added
+ notes: *bindings
+ code: |
+ window.CanvasRenderingContext2D.prototype.fillRectGreen = function (x, y, w, h)
+ {
+ this.fillStyle = '#0f0';
+ this.fillRect(x, y, w, h);
+ };
+ ctx.fillStyle = '#f00';
+ ctx.fillRectGreen(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.getcontext.unique
+ desc: getContext('2d') returns the same object
+ code: |
+ @assert canvas.getContext('2d') === canvas.getContext('2d');
+
+- name: 2d.getcontext.shared
+ desc: getContext('2d') returns objects which share canvas state
+ code: |
+ var ctx2 = canvas.getContext('2d');
+ ctx.fillStyle = '#f00';
+ ctx2.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.scaled
+ desc: CSS-scaled canvases get drawn correctly
+ canvas: 'width="50" height="25" style="width: 100px; height: 50px"'
+ manual:
+ code: |
+ ctx.fillStyle = '#00f';
+ ctx.fillRect(0, 0, 50, 25);
+ ctx.fillStyle = '#0ff';
+ ctx.fillRect(0, 0, 25, 10);
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0, 1)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ cr.set_source_rgb(0, 1, 1)
+ cr.rectangle(0, 0, 50, 20)
+ cr.fill()
+
+- name: 2d.canvas.reference
+ desc: CanvasRenderingContext2D.canvas refers back to its canvas
+ code: |
+ @assert ctx.canvas === canvas;
+
+- name: 2d.canvas.readonly
+ desc: CanvasRenderingContext2D.canvas is readonly
+ code: |
+ var c = document.createElement('canvas');
+ var d = ctx.canvas;
+ @assert c !== d;
+ ctx.canvas = c;
+ @assert ctx.canvas === d;
+
+- name: 2d.canvas.context
+ desc: checks CanvasRenderingContext2D prototype
+ code: |
+ @assert Object.getPrototypeOf(CanvasRenderingContext2D.prototype) === Object.prototype;
+ @assert Object.getPrototypeOf(ctx) === CanvasRenderingContext2D.prototype;
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-state.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-state.yaml
new file mode 100644
index 0000000000..0452086154
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-state.yaml
@@ -0,0 +1,89 @@
+- name: 2d.state.saverestore.transformation
+ desc: save()/restore() affects the current transformation matrix
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.translate(200, 0);
+ ctx.restore();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(-200, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.state.saverestore.clip
+ desc: save()/restore() affects the clipping path
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.rect(0, 0, 1, 1);
+ ctx.clip();
+ ctx.restore();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.state.saverestore.path
+ desc: save()/restore() does not affect the current path
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.rect(0, 0, 100, 50);
+ ctx.restore();
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.state.saverestore.bitmap
+ desc: save()/restore() does not affect the current bitmap
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.restore();
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.state.saverestore.stack
+ desc: save()/restore() can be nested as a stack
+ code: |
+ ctx.lineWidth = 1;
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.save();
+ ctx.lineWidth = 3;
+ @assert ctx.lineWidth === 3;
+ ctx.restore();
+ @assert ctx.lineWidth === 2;
+ ctx.restore();
+ @assert ctx.lineWidth === 1;
+
+- name: 2d.state.saverestore.stackdepth
+ desc: save()/restore() stack depth is not unreasonably limited
+ code: |
+ var limit = 512;
+ for (var i = 1; i < limit; ++i)
+ {
+ ctx.save();
+ ctx.lineWidth = i;
+ }
+ for (var i = limit-1; i > 0; --i)
+ {
+ @assert ctx.lineWidth === i;
+ ctx.restore();
+ }
+
+- name: 2d.state.saverestore.underflow
+ desc: restore() with an empty stack has no effect
+ code: |
+ for (var i = 0; i < 16; ++i)
+ ctx.restore();
+ ctx.lineWidth = 0.5;
+ ctx.restore();
+ @assert ctx.lineWidth === 0.5;
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/element/transformations.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/transformations.yaml
new file mode 100644
index 0000000000..876c48f849
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/transformations.yaml
@@ -0,0 +1,358 @@
+- name: 2d.transformation.order
+ desc: Transformations are applied in the right order
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.scale(2, 1);
+ ctx.rotate(Math.PI / 2);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, -50, 50, 50);
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.transformation.scale.basic
+ desc: scale() works
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.scale(2, 4);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 12.5);
+ @assert pixel 90,40 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.scale.zero
+ desc: scale() with a scale factor of zero works
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.save();
+ ctx.translate(50, 0);
+ ctx.scale(0, 1);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.restore();
+
+ ctx.save();
+ ctx.translate(0, 25);
+ ctx.scale(1, 0);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.restore();
+
+ canvas.toDataURL();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.scale.negative
+ desc: scale() with negative scale factors works
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.save();
+ ctx.scale(-1, 1);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-50, 0, 50, 50);
+ ctx.restore();
+
+ ctx.save();
+ ctx.scale(1, -1);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, -50, 50, 50);
+ ctx.restore();
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.scale.large
+ desc: scale() with large scale factors works
+ notes: Not really that large at all, but it hits the limits in Firefox.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.scale(1e5, 1e5);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 1, 1);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.scale.nonfinite
+ desc: scale() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(100, 10);
+ @nonfinite ctx.scale(<0.1 Infinity -Infinity NaN>, <0.1 Infinity -Infinity NaN>);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.scale.multiple
+ desc: Multiple scale()s combine
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.scale(Math.sqrt(2), Math.sqrt(2));
+ ctx.scale(Math.sqrt(2), Math.sqrt(2));
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 25);
+ @assert pixel 90,40 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.transformation.rotate.zero
+ desc: rotate() by 0 does nothing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.rotate(0);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.rotate.radians
+ desc: rotate() uses radians
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.rotate(Math.PI); // should fail obviously if this is 3.1 degrees
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.rotate.direction
+ desc: rotate() is clockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.rotate(Math.PI / 2);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, -100, 50, 100);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.rotate.wrap
+ desc: rotate() wraps large positive values correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.rotate(Math.PI * (1 + 4096)); // == pi (mod 2*pi)
+ // We need about pi +/- 0.001 in order to get correct-looking results
+ // 32-bit floats can store pi*4097 with precision 2^-10, so that should
+ // be safe enough on reasonable implementations
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,2 == 0,255,0,255;
+ @assert pixel 98,47 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.rotate.wrapnegative
+ desc: rotate() wraps large negative values correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.rotate(-Math.PI * (1 + 4096));
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,2 == 0,255,0,255;
+ @assert pixel 98,47 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.rotate.nonfinite
+ desc: rotate() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(100, 10);
+ @nonfinite ctx.rotate(<0.1 Infinity -Infinity NaN>);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.translate.basic
+ desc: translate() works
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 90,40 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.translate.nonfinite
+ desc: translate() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(100, 10);
+ @nonfinite ctx.translate(<0.1 Infinity -Infinity NaN>, <0.1 Infinity -Infinity NaN>);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+
+- name: 2d.transformation.transform.identity
+ desc: transform() with the identity matrix does nothing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.transform(1,0, 0,1, 0,0);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.transform.skewed
+ desc: transform() with skewy matrix transforms correctly
+ code: |
+ // Create green with a red square ring inside it
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(20, 10, 60, 30);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(40, 20, 20, 10);
+
+ // Draw a skewed shape to fill that gap, to make sure it is aligned correctly
+ ctx.transform(1,4, 2,3, 5,6);
+ // Post-transform coordinates:
+ // [[20,10],[80,10],[80,40],[20,40],[20,10],[40,20],[40,30],[60,30],[60,20],[40,20],[20,10]];
+ // Hence pre-transform coordinates:
+ var pts=[[-7.4,11.2],[-43.4,59.2],[-31.4,53.2],[4.6,5.2],[-7.4,11.2],
+ [-15.4,25.2],[-11.4,23.2],[-23.4,39.2],[-27.4,41.2],[-15.4,25.2],
+ [-7.4,11.2]];
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var i = 0; i < pts.length; ++i)
+ ctx.lineTo(pts[i][0], pts[i][1]);
+ ctx.fill();
+ @assert pixel 21,11 == 0,255,0,255;
+ @assert pixel 79,11 == 0,255,0,255;
+ @assert pixel 21,39 == 0,255,0,255;
+ @assert pixel 79,39 == 0,255,0,255;
+ @assert pixel 39,19 == 0,255,0,255;
+ @assert pixel 61,19 == 0,255,0,255;
+ @assert pixel 39,31 == 0,255,0,255;
+ @assert pixel 61,31 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.transform.multiply
+ desc: transform() multiplies the CTM
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.transform(1,2, 3,4, 5,6);
+ ctx.transform(-2,1, 3/2,-1/2, 1,-2);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.transform.nonfinite
+ desc: transform() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(100, 10);
+ @nonfinite ctx.transform(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.setTransform.skewed
+ code: |
+ // Create green with a red square ring inside it
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(20, 10, 60, 30);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(40, 20, 20, 10);
+
+ // Draw a skewed shape to fill that gap, to make sure it is aligned correctly
+ ctx.setTransform(1,4, 2,3, 5,6);
+ // Post-transform coordinates:
+ // [[20,10],[80,10],[80,40],[20,40],[20,10],[40,20],[40,30],[60,30],[60,20],[40,20],[20,10]];
+ // Hence pre-transform coordinates:
+ var pts=[[-7.4,11.2],[-43.4,59.2],[-31.4,53.2],[4.6,5.2],[-7.4,11.2],
+ [-15.4,25.2],[-11.4,23.2],[-23.4,39.2],[-27.4,41.2],[-15.4,25.2],
+ [-7.4,11.2]];
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var i = 0; i < pts.length; ++i)
+ ctx.lineTo(pts[i][0], pts[i][1]);
+ ctx.fill();
+ @assert pixel 21,11 == 0,255,0,255;
+ @assert pixel 79,11 == 0,255,0,255;
+ @assert pixel 21,39 == 0,255,0,255;
+ @assert pixel 79,39 == 0,255,0,255;
+ @assert pixel 39,19 == 0,255,0,255;
+ @assert pixel 61,19 == 0,255,0,255;
+ @assert pixel 39,31 == 0,255,0,255;
+ @assert pixel 61,31 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.setTransform.multiple
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.setTransform(1/2,0, 0,1/2, 0,0);
+ ctx.setTransform();
+ ctx.setTransform(2,0, 0,2, 0,0);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 25);
+ @assert pixel 75,35 == 0,255,0,255;
+ expected: green
+
+- name: 2d.transformation.setTransform.nonfinite
+ desc: setTransform() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.translate(100, 10);
+ @nonfinite ctx.setTransform(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+
+ @assert pixel 50,25 == 0,255,0,255;
+ expected: green
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml
new file mode 100644
index 0000000000..9d34588c10
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml
@@ -0,0 +1,1585 @@
+- name: 2d.fillStyle.invalidstring
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillStyle = 'invalid';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.fillStyle.invalidtype
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillStyle = null;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.fillStyle.get.solid
+ code: |
+ ctx.fillStyle = '#fa0';
+ @assert ctx.fillStyle === '#ffaa00';
+ t.done();
+
+- name: 2d.fillStyle.get.semitransparent
+ code: |
+ ctx.fillStyle = 'rgba(255,255,255,0.45)';
+ @assert ctx.fillStyle =~ /^rgba\(255, 255, 255, 0\.4\d+\)$/;
+ t.done();
+
+- name: 2d.fillStyle.get.halftransparent
+ code: |
+ ctx.fillStyle = 'rgba(255,255,255,0.5)';
+ @assert ctx.fillStyle === 'rgba(255, 255, 255, 0.5)';
+ t.done();
+
+- name: 2d.fillStyle.get.transparent
+ code: |
+ ctx.fillStyle = 'rgba(0,0,0,0)';
+ @assert ctx.fillStyle === 'rgba(0, 0, 0, 0)';
+ t.done();
+
+- name: 2d.fillStyle.default
+ code: |
+ @assert ctx.fillStyle === '#000000';
+ t.done();
+
+- name: 2d.strokeStyle.default
+ code: |
+ @assert ctx.strokeStyle === '#000000';
+ t.done();
+
+- name: 2d.fillStyle.toStringFunctionCallback
+ desc: Passing a function in to ctx.fillStyle or ctx.strokeStyle with a toString callback works as specified
+ code: |
+ ctx.fillStyle = { toString: function() { return "#008000"; } };
+ @assert ctx.fillStyle === "#008000";
+ ctx.fillStyle = {};
+ @assert ctx.fillStyle === "#008000";
+ ctx.fillStyle = 800000;
+ @assert ctx.fillStyle === "#008000";
+ @assert throws TypeError ctx.fillStyle = { toString: function() { throw new TypeError; } };
+ ctx.strokeStyle = { toString: function() { return "#008000"; } };
+ @assert ctx.strokeStyle === "#008000";
+ ctx.strokeStyle = {};
+ @assert ctx.strokeStyle === "#008000";
+ ctx.strokeStyle = 800000;
+ @assert ctx.strokeStyle === "#008000";
+ @assert throws TypeError ctx.strokeStyle = { toString: function() { throw new TypeError; } };
+ t.done();
+
+- name: 2d.gradient.interpolate.solid
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.interpolate.color
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(1, '#00f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,25 ==~ 191,191,63,255 +/- 3;
+ @assert pixel 50,25 ==~ 127,127,127,255 +/- 3;
+ @assert pixel 75,25 ==~ 63,63,191,255 +/- 3;
+ t.done();
+
+- name: 2d.gradient.interpolate.alpha
+ code: |
+ ctx.fillStyle = '#ff0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, 'rgba(0,0,255, 0)');
+ g.addColorStop(1, 'rgba(0,0,255, 1)');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,25 ==~ 191,191,63,255 +/- 3;
+ @assert pixel 50,25 ==~ 127,127,127,255 +/- 3;
+ @assert pixel 75,25 ==~ 63,63,191,255 +/- 3;
+ t.done();
+
+- name: 2d.gradient.interpolate.coloralpha
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, 'rgba(255,255,0, 0)');
+ g.addColorStop(1, 'rgba(0,0,255, 1)');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,25 ==~ 190,190,65,65 +/- 3;
+ @assert pixel 50,25 ==~ 126,126,128,128 +/- 3;
+ @assert pixel 75,25 ==~ 62,62,192,192 +/- 3;
+ t.done();
+
+- name: 2d.gradient.interpolate.outside
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(25, 0, 75, 0);
+ g.addColorStop(0.4, '#0f0');
+ g.addColorStop(0.6, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 20,25 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 80,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.interpolate.zerosize.fill
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.rect(0, 0, 100, 50);
+ ctx.fill();
+ @assert pixel 40,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.interpolate.zerosize.stroke
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.strokeStyle = g;
+ ctx.rect(20, 20, 60, 10);
+ ctx.stroke();
+ @assert pixel 19,19 == 0,255,0,255;
+ @assert pixel 20,19 == 0,255,0,255;
+ @assert pixel 21,19 == 0,255,0,255;
+ @assert pixel 19,20 == 0,255,0,255;
+ @assert pixel 20,20 == 0,255,0,255;
+ @assert pixel 21,20 == 0,255,0,255;
+ @assert pixel 19,21 == 0,255,0,255;
+ @assert pixel 20,21 == 0,255,0,255;
+ @assert pixel 21,21 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.interpolate.zerosize.fillRect
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 40,20 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.gradient.interpolate.zerosize.strokeRect
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.strokeStyle = g;
+ ctx.strokeRect(20, 20, 60, 10);
+ @assert pixel 19,19 == 0,255,0,255;
+ @assert pixel 20,19 == 0,255,0,255;
+ @assert pixel 21,19 == 0,255,0,255;
+ @assert pixel 19,20 == 0,255,0,255;
+ @assert pixel 20,20 == 0,255,0,255;
+ @assert pixel 21,20 == 0,255,0,255;
+ @assert pixel 19,21 == 0,255,0,255;
+ @assert pixel 20,21 == 0,255,0,255;
+ @assert pixel 21,21 == 0,255,0,255;
+ t.done();
+
+
+- name: 2d.gradient.interpolate.vertical
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 0, 50);
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(1, '#00f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,12 ==~ 191,191,63,255 +/- 10;
+ @assert pixel 50,25 ==~ 127,127,127,255 +/- 5;
+ @assert pixel 50,37 ==~ 63,63,191,255 +/- 10;
+ t.done();
+
+- name: 2d.gradient.interpolate.multiple
+ code: |
+ canvas.width = 200;
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(0.5, '#0ff');
+ g.addColorStop(1, '#f0f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 200, 50);
+ @assert pixel 50,25 ==~ 127,255,127,255 +/- 3;
+ @assert pixel 100,25 ==~ 0,255,255,255 +/- 3;
+ @assert pixel 150,25 ==~ 127,127,255,255 +/- 3;
+ t.done();
+
+- name: 2d.gradient.interpolate.overlap
+ code: |
+ canvas.width = 200;
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0, '#ff0');
+ g.addColorStop(0.25, '#00f');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.25, '#ff0');
+ g.addColorStop(0.5, '#00f');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.75, '#00f');
+ g.addColorStop(0.75, '#f00');
+ g.addColorStop(0.75, '#ff0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.5, '#ff0');
+ g.addColorStop(1, '#00f');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 200, 50);
+ @assert pixel 49,25 ==~ 0,0,255,255 +/- 16;
+ @assert pixel 51,25 ==~ 255,255,0,255 +/- 16;
+ @assert pixel 99,25 ==~ 0,0,255,255 +/- 16;
+ @assert pixel 101,25 ==~ 255,255,0,255 +/- 16;
+ @assert pixel 149,25 ==~ 0,0,255,255 +/- 16;
+ @assert pixel 151,25 ==~ 255,255,0,255 +/- 16;
+ t.done();
+
+- name: 2d.gradient.interpolate.overlap2
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ var ps = [ 0, 1/10, 1/4, 1/3, 1/2, 3/4, 1 ];
+ for (var p = 0; p < ps.length; ++p)
+ {
+ g.addColorStop(ps[p], '#0f0');
+ for (var i = 0; i < 15; ++i)
+ g.addColorStop(ps[p], '#f00');
+ g.addColorStop(ps[p], '#0f0');
+ }
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 30,25 == 0,255,0,255;
+ @assert pixel 40,25 == 0,255,0,255;
+ @assert pixel 60,25 == 0,255,0,255;
+ @assert pixel 80,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.empty
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createLinearGradient(0, 0, 0, 50);
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.object.update
+ code: |
+ var g = ctx.createLinearGradient(-100, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ g.addColorStop(0.1, '#0f0');
+ g.addColorStop(0.9, '#0f0');
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.object.compare
+ code: |
+ var g1 = ctx.createLinearGradient(0, 0, 100, 0);
+ var g2 = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert g1 !== g2;
+ ctx.fillStyle = g1;
+ @assert ctx.fillStyle === g1;
+ t.done();
+
+- name: 2d.gradient.object.crosscanvas
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var g = offscreenCanvas2.getContext('2d').createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.object.invalidoffset
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert throws INDEX_SIZE_ERR g.addColorStop(-1, '#000');
+ @assert throws INDEX_SIZE_ERR g.addColorStop(2, '#000');
+ @assert throws TypeError g.addColorStop(Infinity, '#000');
+ @assert throws TypeError g.addColorStop(-Infinity, '#000');
+ @assert throws TypeError g.addColorStop(NaN, '#000');
+ t.done();
+
+- name: 2d.gradient.object.invalidcolor
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ @assert throws SYNTAX_ERR g.addColorStop(0, "");
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'null');
+ @assert throws SYNTAX_ERR g.addColorStop(0, 'undefined');
+ @assert throws SYNTAX_ERR g.addColorStop(0, null);
+ @assert throws SYNTAX_ERR g.addColorStop(0, undefined);
+ t.done();
+
+- name: 2d.gradient.linear.nonfinite
+ desc: createLinearGradient() throws TypeError if arguments are not finite
+ code: |
+ @nonfinite @assert throws TypeError ctx.createLinearGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+ t.done();
+
+- name: 2d.gradient.linear.transform.1
+ desc: Linear gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.75, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(-50, 0);
+ ctx.fillRect(50, 0, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.linear.transform.2
+ desc: Linear gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ ctx.translate(100, 0);
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.75, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(-150, 0);
+ ctx.fillRect(50, 0, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.linear.transform.3
+ desc: Linear gradient transforms do not experience broken caching effects
+ code: |
+ var g = ctx.createLinearGradient(0, 0, 200, 0);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.25, '#0f0');
+ g.addColorStop(0.75, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(-50, 0);
+ ctx.fillRect(50, 0, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.negative
+ desc: createRadialGradient() throws INDEX_SIZE_ERR if either radius is negative
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, -0.1, 0, 0, 1);
+ @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, 1, 0, 0, -0.1);
+ @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, -0.1, 0, 0, -0.1);
+ t.done();
+- name: 2d.gradient.radial.nonfinite
+
+ desc: createRadialGradient() throws TypeError if arguments are not finite
+ code: |
+ @nonfinite @assert throws TypeError ctx.createRadialGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
+ t.done();
+
+- name: 2d.gradient.radial.inside1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(50, 25, 100, 50, 25, 200);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.inside2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.inside3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.993, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.outside1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(200, 25, 10, 200, 25, 20);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.outside2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.outside3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.001, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.touch1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(150, 25, 50, 200, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.gradient.radial.touch2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(0.01, '#0f0');
+ g.addColorStop(0.99, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.touch3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(120, -15, 25, 140, -30, 50);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.gradient.radial.equal
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(50, 25, 20, 50, 25, 20);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.gradient.radial.cone.behind
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(120, 25, 10, 211, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.gradient.radial.cone.front
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(311, 25, 10, 210, 25, 100);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.cone.bottom
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 101);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.cone.top
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(230, 25, 100, 100, 25, 101);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.cone.beside
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(0, 100, 40, 100, 100, 50);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255; @moz-todo
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ @assert pixel 98,25 == 0,255,0,255; @moz-todo
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.gradient.radial.cone.cylinder
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 100);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.cone.shape1
+ code: |
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(30+tol, 40);
+ ctx.lineTo(110, -20+tol);
+ ctx.lineTo(110, 100-tol);
+ ctx.fill();
+ var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.cone.shape2
+ code: |
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
+ g.addColorStop(0, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(30-tol, 40);
+ ctx.lineTo(110, -20-tol);
+ ctx.lineTo(110, 100+tol);
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,1 == 0,255,0,255; @moz-todo
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.transform.1
+ desc: Radial gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.51, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(50, 25);
+ ctx.scale(10, 10);
+ ctx.fillRect(-5, -2.5, 10, 5);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.transform.2
+ desc: Radial gradient coordinates are relative to the coordinate space at the time
+ of filling
+ code: |
+ ctx.translate(100, 0);
+ var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.51, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.translate(-50, 25);
+ ctx.scale(10, 10);
+ ctx.fillRect(-5, -2.5, 10, 5);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.radial.transform.3
+ desc: Radial gradient transforms do not experience broken caching effects
+ code: |
+ var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(0.5, '#0f0');
+ g.addColorStop(0.51, '#f00');
+ g.addColorStop(1, '#f00');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(50, 25);
+ ctx.scale(10, 10);
+ ctx.fillRect(-5, -2.5, 10, 5);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.gradient.conic.positive.rotation
+ desc: Conic gradient with positive rotation
+ code: |
+ const g = ctx.createConicGradient(3*Math.PI/2, 50, 25);
+ // It's red in the upper right region and green on the lower left region
+ g.addColorStop(0, "#f00");
+ g.addColorStop(0.25, "#0f0");
+ g.addColorStop(0.50, "#0f0");
+ g.addColorStop(0.75, "#f00");
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,15 ==~ 255,0,0,255 +/- 3;
+ @assert pixel 75,40 ==~ 0,255,0,255 +/- 3;
+ t.done();
+
+- name: 2d.gradient.conic.negative.rotation
+ desc: Conic gradient with negative rotation
+ code: |
+ const g = ctx.createConicGradient(-Math.PI/2, 50, 25);
+ // It's red in the upper right region and green on the lower left region
+ g.addColorStop(0, "#f00");
+ g.addColorStop(0.25, "#0f0");
+ g.addColorStop(0.50, "#0f0");
+ g.addColorStop(0.75, "#f00");
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 25,15 ==~ 255,0,0,255 +/- 3;
+ @assert pixel 75,40 ==~ 0,255,0,255 +/- 3;
+ t.done();
+
+- name: 2d.gradient.conic.invalid.inputs
+ desc: Conic gradient function with invalid inputs
+ code: |
+ @nonfinite @assert throws TypeError ctx.createConicGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
+
+ const g = ctx.createConicGradient(0, 0, 25);
+ @nonfinite @assert throws TypeError g.addColorStop(<Infinity -Infinity NaN>, <'#f00'>);
+ @nonfinite @assert throws SYNTAX_ERR g.addColorStop(<0>, <Infinity -Infinity NaN>);
+ t.done();
+
+- name: 2d.pattern.basic.image
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.basic.canvas
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50);
+ var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.pattern.basic.zerocanvas
+ code: |
+ canvas.width = 0;
+ canvas.height = 10;
+ @assert canvas.width === 0;
+ @assert canvas.height === 10;
+ @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
+ canvas.width = 10;
+ canvas.height = 0;
+ @assert canvas.width === 10;
+ @assert canvas.height === 0;
+ @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
+ canvas.width = 0;
+ canvas.height = 0;
+ @assert canvas.width === 0;
+ @assert canvas.height === 0;
+ @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
+ t.done();
+
+- name: 2d.pattern.basic.nocontext
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.pattern.image.undefined
+ code: |
+ @assert throws TypeError ctx.createPattern(undefined, 'repeat');
+ t.done();
+
+- name: 2d.pattern.image.null
+ code: |
+ @assert throws TypeError ctx.createPattern(null, 'repeat');
+ t.done();
+
+- name: 2d.pattern.image.string
+ code: |
+ @assert throws TypeError ctx.createPattern('../images/red.png', 'repeat');
+ t.done();
+
+- name: 2d.pattern.repeat.empty
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green-1x1.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, "");
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 200, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.repeat.null
+ code: |
+ @assert ctx.createPattern(canvas, null) != null;
+ t.done();
+
+- name: 2d.pattern.repeat.undefined
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, undefined);
+ t.done();
+
+- name: 2d.pattern.repeat.unrecognised
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "invalid");
+ t.done();
+
+- name: 2d.pattern.repeat.unrecognisednull
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "null");
+ t.done();
+
+- name: 2d.pattern.repeat.case
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "Repeat");
+ t.done();
+
+- name: 2d.pattern.repeat.nullsuffix
+ code: |
+ @assert throws SYNTAX_ERR ctx.createPattern(canvas, "repeat\0");
+ t.done();
+
+- name: 2d.pattern.modify.canvas1
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50);
+ var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.pattern.modify.canvas2
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50);
+ var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.pattern.crosscanvas
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var pattern = offscreenCanvas2.getContext('2d').createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.norepeat.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.norepeat.outside
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+ ctx.fillRect(-100, 0, 100, 50);
+ ctx.fillRect(0, 50, 100, 50);
+ ctx.fillRect(100, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.norepeat.coord1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 0);
+ ctx.fillRect(-50, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.norepeat.coord2
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 0);
+ ctx.fillRect(-50, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.norepeat.coord3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 25);
+ ctx.fillRect(-50, -25, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 25);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeat.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeat.outside
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(50, 25);
+ ctx.fillRect(-50, -25, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeat.coord1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/rgrg-256x256.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.translate(-128, -78);
+ ctx.fillRect(128, 78, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeat.coord2
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/grgr-256x256.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeat.coord3
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/rgrg-256x256.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(-128, -78);
+ ctx.fillRect(128, 78, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeatx.basic
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 16);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat-x');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeatx.outside
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat-x');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 16);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeatx.coord1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat-x');
+ ctx.fillStyle = pattern;
+ ctx.translate(0, 16);
+ ctx.fillRect(0, -16, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 16);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeaty.basic
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 16, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/green-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat-y');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeaty.outside
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat-y');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 16, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.repeaty.coord1
+ images:
+ - red-16x16.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red-16x16.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat-y');
+ ctx.fillStyle = pattern;
+ ctx.translate(48, 0);
+ ctx.fillRect(-48, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 16, 50);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.orientation.image
+ desc: Image patterns do not get flipped when painted
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/rrgg-256x256.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.save();
+ ctx.translate(0, -103);
+ ctx.fillRect(0, 103, 100, 50);
+ ctx.restore();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 25);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.pattern.paint.orientation.canvas
+ desc: Canvas patterns do not get flipped when painted
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 25);
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 25, 100, 25);
+ var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 25);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/line-styles.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/line-styles.yaml
new file mode 100644
index 0000000000..984949827d
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/line-styles.yaml
@@ -0,0 +1,773 @@
+- name: 2d.line.defaults
+ code: |
+ @assert ctx.lineWidth === 1;
+ @assert ctx.lineCap === 'butt';
+ @assert ctx.lineJoin === 'miter';
+ @assert ctx.miterLimit === 10;
+ t.done();
+
+- name: 2d.line.width.basic
+ desc: lineWidth determines the width of line strokes
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 20;
+ // Draw a green line over a red box, to check the line is not too small
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 15, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+ // Draw a green box over a red line, to check the line is not too large
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.fillRect(65, 15, 20, 20);
+ @assert pixel 14,25 == 0,255,0,255;
+ @assert pixel 15,25 == 0,255,0,255;
+ @assert pixel 16,25 == 0,255,0,255;
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 34,25 == 0,255,0,255;
+ @assert pixel 35,25 == 0,255,0,255;
+ @assert pixel 36,25 == 0,255,0,255;
+ @assert pixel 64,25 == 0,255,0,255;
+ @assert pixel 65,25 == 0,255,0,255;
+ @assert pixel 66,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ @assert pixel 84,25 == 0,255,0,255;
+ @assert pixel 85,25 == 0,255,0,255;
+ @assert pixel 86,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.width.transformed
+ desc: Line stroke widths are affected by scale transformations
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 4;
+ // Draw a green line over a red box, to check the line is not too small
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 15, 20, 20);
+ ctx.save();
+ ctx.scale(5, 1);
+ ctx.beginPath();
+ ctx.moveTo(5, 15);
+ ctx.lineTo(5, 35);
+ ctx.stroke();
+ ctx.restore();
+ // Draw a green box over a red line, to check the line is not too large
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.save();
+ ctx.scale(-5, 1);
+ ctx.beginPath();
+ ctx.moveTo(-15, 15);
+ ctx.lineTo(-15, 35);
+ ctx.stroke();
+ ctx.restore();
+ ctx.fillRect(65, 15, 20, 20);
+ @assert pixel 14,25 == 0,255,0,255;
+ @assert pixel 15,25 == 0,255,0,255;
+ @assert pixel 16,25 == 0,255,0,255;
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 34,25 == 0,255,0,255;
+ @assert pixel 35,25 == 0,255,0,255;
+ @assert pixel 36,25 == 0,255,0,255;
+ @assert pixel 64,25 == 0,255,0,255;
+ @assert pixel 65,25 == 0,255,0,255;
+ @assert pixel 66,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ @assert pixel 84,25 == 0,255,0,255;
+ @assert pixel 85,25 == 0,255,0,255;
+ @assert pixel 86,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.width.scaledefault
+ desc: Default lineWidth strokes are affected by scale transformations
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(50, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.moveTo(0, 0.5);
+ ctx.lineTo(2, 0.5);
+ ctx.stroke();
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ @assert pixel 50,5 == 0,255,0,255;
+ @assert pixel 50,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.width.valid
+ desc: Setting lineWidth to valid values works
+ code: |
+ ctx.lineWidth = 1.5;
+ @assert ctx.lineWidth === 1.5;
+ ctx.lineWidth = "1e1";
+ @assert ctx.lineWidth === 10;
+ ctx.lineWidth = 1/1024;
+ @assert ctx.lineWidth === 1/1024;
+ ctx.lineWidth = 1000;
+ @assert ctx.lineWidth === 1000;
+ t.done();
+
+- name: 2d.line.width.invalid
+ desc: Setting lineWidth to invalid values is ignored
+ code: |
+ ctx.lineWidth = 1.5;
+ @assert ctx.lineWidth === 1.5;
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = 0;
+ @assert ctx.lineWidth === 1.5;
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = -1;
+ @assert ctx.lineWidth === 1.5;
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = Infinity;
+ @assert ctx.lineWidth === 1.5;
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = -Infinity;
+ @assert ctx.lineWidth === 1.5;
+ ctx.lineWidth = 1.5;
+ ctx.lineWidth = NaN;
+ @assert ctx.lineWidth === 1.5;
+ t.done();
+
+- name: 2d.line.cap.butt
+ desc: lineCap 'butt' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineCap = 'butt';
+ ctx.lineWidth = 20;
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 15, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.fillRect(65, 15, 20, 20);
+ @assert pixel 25,14 == 0,255,0,255;
+ @assert pixel 25,15 == 0,255,0,255;
+ @assert pixel 25,16 == 0,255,0,255;
+ @assert pixel 25,34 == 0,255,0,255;
+ @assert pixel 25,35 == 0,255,0,255;
+ @assert pixel 25,36 == 0,255,0,255;
+ @assert pixel 75,14 == 0,255,0,255;
+ @assert pixel 75,15 == 0,255,0,255;
+ @assert pixel 75,16 == 0,255,0,255;
+ @assert pixel 75,34 == 0,255,0,255;
+ @assert pixel 75,35 == 0,255,0,255;
+ @assert pixel 75,36 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.cap.round
+ desc: lineCap 'round' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+ ctx.lineCap = 'round';
+ ctx.lineWidth = 20;
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(35-tol, 15);
+ ctx.arc(25, 15, 10-tol, 0, Math.PI, true);
+ ctx.arc(25, 35, 10-tol, Math.PI, 0, true);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(85+tol, 15);
+ ctx.arc(75, 15, 10+tol, 0, Math.PI, true);
+ ctx.arc(75, 35, 10+tol, Math.PI, 0, true);
+ ctx.fill();
+ @assert pixel 17,6 == 0,255,0,255;
+ @assert pixel 25,6 == 0,255,0,255;
+ @assert pixel 32,6 == 0,255,0,255;
+ @assert pixel 17,43 == 0,255,0,255;
+ @assert pixel 25,43 == 0,255,0,255;
+ @assert pixel 32,43 == 0,255,0,255;
+ @assert pixel 67,6 == 0,255,0,255;
+ @assert pixel 75,6 == 0,255,0,255;
+ @assert pixel 82,6 == 0,255,0,255;
+ @assert pixel 67,43 == 0,255,0,255;
+ @assert pixel 75,43 == 0,255,0,255;
+ @assert pixel 82,43 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.cap.square
+ desc: lineCap 'square' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineCap = 'square';
+ ctx.lineWidth = 20;
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(15, 5, 20, 40);
+ ctx.beginPath();
+ ctx.moveTo(25, 15);
+ ctx.lineTo(25, 35);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(75, 15);
+ ctx.lineTo(75, 35);
+ ctx.stroke();
+ ctx.fillRect(65, 5, 20, 40);
+ @assert pixel 25,4 == 0,255,0,255;
+ @assert pixel 25,5 == 0,255,0,255;
+ @assert pixel 25,6 == 0,255,0,255;
+ @assert pixel 25,44 == 0,255,0,255;
+ @assert pixel 25,45 == 0,255,0,255;
+ @assert pixel 25,46 == 0,255,0,255;
+ @assert pixel 75,4 == 0,255,0,255;
+ @assert pixel 75,5 == 0,255,0,255;
+ @assert pixel 75,6 == 0,255,0,255;
+ @assert pixel 75,44 == 0,255,0,255;
+ @assert pixel 75,45 == 0,255,0,255;
+ @assert pixel 75,46 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.cap.open
+ desc: Line caps are drawn at the corners of an unclosed rectangle
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineJoin = 'bevel';
+ ctx.lineCap = 'square';
+ ctx.lineWidth = 400;
+ ctx.beginPath();
+ ctx.moveTo(200, 200);
+ ctx.lineTo(200, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 200);
+ ctx.lineTo(200, 200);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.cap.closed
+ desc: Line caps are not drawn at the corners of an unclosed rectangle
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineJoin = 'bevel';
+ ctx.lineCap = 'square';
+ ctx.lineWidth = 400;
+ ctx.beginPath();
+ ctx.moveTo(200, 200);
+ ctx.lineTo(200, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 200);
+ ctx.closePath();
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.cap.valid
+ desc: Setting lineCap to valid values works
+ code: |
+ ctx.lineCap = 'butt'
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'round';
+ @assert ctx.lineCap === 'round';
+ ctx.lineCap = 'square';
+ @assert ctx.lineCap === 'square';
+ t.done();
+
+- name: 2d.line.cap.invalid
+ desc: Setting lineCap to invalid values is ignored
+ code: |
+ ctx.lineCap = 'butt'
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'invalid';
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'ROUND';
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'round\0';
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'round ';
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'butt';
+ ctx.lineCap = "";
+ @assert ctx.lineCap === 'butt';
+ ctx.lineCap = 'butt';
+ ctx.lineCap = 'bevel';
+ @assert ctx.lineCap === 'butt';
+ t.done();
+
+- name: 2d.line.join.bevel
+ desc: lineJoin 'bevel' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+ ctx.lineJoin = 'bevel';
+ ctx.lineWidth = 20;
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(10, 10, 20, 20);
+ ctx.fillRect(20, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(30, 20);
+ ctx.lineTo(40-tol, 20);
+ ctx.lineTo(30, 10+tol);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(10, 20);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(30, 40);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(60, 20);
+ ctx.lineTo(80, 20);
+ ctx.lineTo(80, 40);
+ ctx.stroke();
+ ctx.fillRect(60, 10, 20, 20);
+ ctx.fillRect(70, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(80, 20);
+ ctx.lineTo(90+tol, 20);
+ ctx.lineTo(80, 10-tol);
+ ctx.fill();
+ @assert pixel 34,16 == 0,255,0,255;
+ @assert pixel 34,15 == 0,255,0,255;
+ @assert pixel 35,15 == 0,255,0,255;
+ @assert pixel 36,15 == 0,255,0,255;
+ @assert pixel 36,14 == 0,255,0,255;
+ @assert pixel 84,16 == 0,255,0,255;
+ @assert pixel 84,15 == 0,255,0,255;
+ @assert pixel 85,15 == 0,255,0,255;
+ @assert pixel 86,15 == 0,255,0,255;
+ @assert pixel 86,14 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.join.round
+ desc: lineJoin 'round' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var tol = 1; // tolerance to avoid antialiasing artifacts
+ ctx.lineJoin = 'round';
+ ctx.lineWidth = 20;
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(10, 10, 20, 20);
+ ctx.fillRect(20, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(30, 20);
+ ctx.arc(30, 20, 10-tol, 0, 2*Math.PI, true);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(10, 20);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(30, 40);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(60, 20);
+ ctx.lineTo(80, 20);
+ ctx.lineTo(80, 40);
+ ctx.stroke();
+ ctx.fillRect(60, 10, 20, 20);
+ ctx.fillRect(70, 20, 20, 20);
+ ctx.beginPath();
+ ctx.moveTo(80, 20);
+ ctx.arc(80, 20, 10+tol, 0, 2*Math.PI, true);
+ ctx.fill();
+ @assert pixel 36,14 == 0,255,0,255;
+ @assert pixel 36,13 == 0,255,0,255;
+ @assert pixel 37,13 == 0,255,0,255;
+ @assert pixel 38,13 == 0,255,0,255;
+ @assert pixel 38,12 == 0,255,0,255;
+ @assert pixel 86,14 == 0,255,0,255;
+ @assert pixel 86,13 == 0,255,0,255;
+ @assert pixel 87,13 == 0,255,0,255;
+ @assert pixel 88,13 == 0,255,0,255;
+ @assert pixel 88,12 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.join.miter
+ desc: lineJoin 'miter' is rendered correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineJoin = 'miter';
+ ctx.lineWidth = 20;
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(10, 10, 30, 20);
+ ctx.fillRect(20, 10, 20, 30);
+ ctx.beginPath();
+ ctx.moveTo(10, 20);
+ ctx.lineTo(30, 20);
+ ctx.lineTo(30, 40);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(60, 20);
+ ctx.lineTo(80, 20);
+ ctx.lineTo(80, 40);
+ ctx.stroke();
+ ctx.fillRect(60, 10, 30, 20);
+ ctx.fillRect(70, 10, 20, 30);
+ @assert pixel 38,12 == 0,255,0,255;
+ @assert pixel 39,11 == 0,255,0,255;
+ @assert pixel 40,10 == 0,255,0,255;
+ @assert pixel 41,9 == 0,255,0,255;
+ @assert pixel 42,8 == 0,255,0,255;
+ @assert pixel 88,12 == 0,255,0,255;
+ @assert pixel 89,11 == 0,255,0,255;
+ @assert pixel 90,10 == 0,255,0,255;
+ @assert pixel 91,9 == 0,255,0,255;
+ @assert pixel 92,8 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.join.open
+ desc: Line joins are not drawn at the corner of an unclosed rectangle
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineJoin = 'miter';
+ ctx.lineWidth = 200;
+ ctx.beginPath();
+ ctx.moveTo(100, 50);
+ ctx.lineTo(100, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 50);
+ ctx.lineTo(100, 50);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.join.closed
+ desc: Line joins are drawn at the corner of a closed rectangle
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineJoin = 'miter';
+ ctx.lineWidth = 200;
+ ctx.beginPath();
+ ctx.moveTo(100, 50);
+ ctx.lineTo(100, 1000);
+ ctx.lineTo(1000, 1000);
+ ctx.lineTo(1000, 50);
+ ctx.closePath();
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.join.parallel
+ desc: Line joins are drawn at 180-degree joins
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 300;
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(0, 25);
+ ctx.lineTo(-100, 25);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.join.valid
+ desc: Setting lineJoin to valid values works
+ code: |
+ ctx.lineJoin = 'bevel'
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'round';
+ @assert ctx.lineJoin === 'round';
+ ctx.lineJoin = 'miter';
+ @assert ctx.lineJoin === 'miter';
+ t.done();
+
+- name: 2d.line.join.invalid
+ desc: Setting lineJoin to invalid values is ignored
+ code: |
+ ctx.lineJoin = 'bevel'
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'invalid';
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'ROUND';
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'round\0';
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'round ';
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = "";
+ @assert ctx.lineJoin === 'bevel';
+ ctx.lineJoin = 'bevel';
+ ctx.lineJoin = 'butt';
+ @assert ctx.lineJoin === 'bevel';
+ t.done();
+
+- name: 2d.line.miter.exceeded
+ desc: Miter joins are not drawn when the miter limit is exceeded
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.414;
+ ctx.beginPath();
+ ctx.moveTo(200, 1000);
+ ctx.lineTo(200, 200);
+ ctx.lineTo(1000, 201); // slightly non-right-angle to avoid being a special case
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.miter.acute
+ desc: Miter joins are drawn correctly with acute angles
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.strokeStyle = '#0f0';
+ ctx.miterLimit = 2.614;
+ ctx.beginPath();
+ ctx.moveTo(100, 1000);
+ ctx.lineTo(100, 100);
+ ctx.lineTo(1000, 1000);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 2.613;
+ ctx.beginPath();
+ ctx.moveTo(100, 1000);
+ ctx.lineTo(100, 100);
+ ctx.lineTo(1000, 1000);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.miter.obtuse
+ desc: Miter joins are drawn correctly with obtuse angles
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 1600;
+ ctx.lineJoin = 'miter';
+ ctx.strokeStyle = '#0f0';
+ ctx.miterLimit = 1.083;
+ ctx.beginPath();
+ ctx.moveTo(800, 10000);
+ ctx.lineTo(800, 300);
+ ctx.lineTo(10000, -8900);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.082;
+ ctx.beginPath();
+ ctx.moveTo(800, 10000);
+ ctx.lineTo(800, 300);
+ ctx.lineTo(10000, -8900);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.miter.rightangle
+ desc: Miter joins are not drawn when the miter limit is exceeded, on exact right
+ angles
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.414;
+ ctx.beginPath();
+ ctx.moveTo(200, 1000);
+ ctx.lineTo(200, 200);
+ ctx.lineTo(1000, 200);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.miter.lineedge
+ desc: Miter joins are not drawn when the miter limit is exceeded at the corners
+ of a zero-height rectangle
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.strokeStyle = '#f00';
+ ctx.miterLimit = 1.414;
+ ctx.beginPath();
+ ctx.strokeRect(100, 25, 200, 0);
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.miter.within
+ desc: Miter joins are drawn when the miter limit is not quite exceeded
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+ ctx.strokeStyle = '#0f0';
+ ctx.miterLimit = 1.416;
+ ctx.beginPath();
+ ctx.moveTo(200, 1000);
+ ctx.lineTo(200, 200);
+ ctx.lineTo(1000, 201);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.miter.valid
+ desc: Setting miterLimit to valid values works
+ code: |
+ ctx.miterLimit = 1.5;
+ @assert ctx.miterLimit === 1.5;
+ ctx.miterLimit = "1e1";
+ @assert ctx.miterLimit === 10;
+ ctx.miterLimit = 1/1024;
+ @assert ctx.miterLimit === 1/1024;
+ ctx.miterLimit = 1000;
+ @assert ctx.miterLimit === 1000;
+ t.done();
+
+- name: 2d.line.miter.invalid
+ desc: Setting miterLimit to invalid values is ignored
+ code: |
+ ctx.miterLimit = 1.5;
+ @assert ctx.miterLimit === 1.5;
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = 0;
+ @assert ctx.miterLimit === 1.5;
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = -1;
+ @assert ctx.miterLimit === 1.5;
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = Infinity;
+ @assert ctx.miterLimit === 1.5;
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = -Infinity;
+ @assert ctx.miterLimit === 1.5;
+ ctx.miterLimit = 1.5;
+ ctx.miterLimit = NaN;
+ @assert ctx.miterLimit === 1.5;
+ t.done();
+
+- name: 2d.line.cross
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'bevel';
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(110, 50);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(100, 60);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.line.union
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 24);
+ ctx.lineTo(100, 25);
+ ctx.lineTo(0, 26);
+ ctx.closePath();
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 25,1 == 0,255,0,255;
+ @assert pixel 48,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 25,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ t.done();
+
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/meta.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/meta.yaml
new file mode 100644
index 0000000000..aa850d4e48
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/meta.yaml
@@ -0,0 +1,538 @@
+- meta: |
+ state = [ # some non-default values to test with
+ ('strokeStyle', '"#ff0000"'),
+ ('fillStyle', '"#ff0000"'),
+ ('globalAlpha', 0.5),
+ ('lineWidth', 0.5),
+ ('lineCap', '"round"'),
+ ('lineJoin', '"round"'),
+ ('miterLimit', 0.5),
+ ('shadowOffsetX', 5),
+ ('shadowOffsetY', 5),
+ ('shadowBlur', 5),
+ ('shadowColor', '"#ff0000"'),
+ ('globalCompositeOperation', '"copy"'),
+ ]
+ for key,value in state:
+ tests.append( {
+ 'name': '2d.state.saverestore.%s' % key,
+ 'desc': 'save()/restore() works for %s' % key,
+ 'code':
+ """// Test that restore() undoes any modifications
+ var old = ctx.%(key)s;
+ ctx.save();
+ ctx.%(key)s = %(value)s;
+ ctx.restore();
+ @assert ctx.%(key)s === old;
+
+ // Also test that save() doesn't modify the values
+ ctx.%(key)s = %(value)s;
+ old = ctx.%(key)s;
+ // we're not interested in failures caused by get(set(x)) != x (e.g.
+ // from rounding), so compare against 'old' instead of against %(value)s
+ ctx.save();
+ @assert ctx.%(key)s === old;
+ ctx.restore();
+ t.done();
+ """ % { 'key':key, 'value':value }
+ } )
+
+ tests.append( {
+ 'name': 'initial.reset.2dstate',
+ 'desc': 'Resetting the canvas state resets 2D state variables',
+ 'code':
+ """canvas.width = 100;
+ var default_val;
+ """ + "".join(
+ """
+ default_val = ctx.%(key)s;
+ ctx.%(key)s = %(value)s;
+ canvas.width = 100;
+ @assert ctx.%(key)s === default_val;
+ """ % { 'key':key, 'value':value }
+ for key,value in state) + "\nt.done();",
+ } )
+
+- meta: |
+ # Composite operation tests
+ # <http://lists.whatwg.org/htdig.cgi/whatwg-whatwg.org/2007-March/010608.html>
+ ops = [
+ # name FA FB
+ ('source-over', '1', '1-aA'),
+ ('destination-over', '1-aB', '1'),
+ ('source-in', 'aB', '0'),
+ ('destination-in', '0', 'aA'),
+ ('source-out', '1-aB', '0'),
+ ('destination-out', '0', '1-aA'),
+ ('source-atop', 'aB', '1-aA'),
+ ('destination-atop', '1-aB', 'aA'),
+ ('xor', '1-aB', '1-aA'),
+ ('copy', '1', '0'),
+ ('lighter', '1', '1'),
+ ]
+
+ # The ones that change the output when src = (0,0,0,0):
+ ops_trans = [ 'source-in', 'destination-in', 'source-out', 'destination-atop', 'copy' ];
+
+ def calc_output(A, B, FA_code, FB_code):
+ RA, GA, BA, aA = A
+ RB, GB, BB, aB = B
+ rA, gA, bA = RA*aA, GA*aA, BA*aA
+ rB, gB, bB = RB*aB, GB*aB, BB*aB
+
+ FA = eval(FA_code)
+ FB = eval(FB_code)
+
+ rO = rA*FA + rB*FB
+ gO = gA*FA + gB*FB
+ bO = bA*FA + bB*FB
+ aO = aA*FA + aB*FB
+
+ rO = min(255, rO)
+ gO = min(255, gO)
+ bO = min(255, bO)
+ aO = min(1, aO)
+
+ if aO:
+ RO = rO / aO
+ GO = gO / aO
+ BO = bO / aO
+ else: RO = GO = BO = 0
+
+ return (RO, GO, BO, aO)
+
+ def to_test(color):
+ r, g, b, a = color
+ return '%d,%d,%d,%d' % (round(r), round(g), round(b), round(a*255))
+ def to_cairo(color):
+ r, g, b, a = color
+ return '%f,%f,%f,%f' % (r/255., g/255., b/255., a)
+
+ for (name, src, dest) in [
+ ('solid', (255, 255, 0, 1.0), (0, 255, 255, 1.0)),
+ ('transparent', (0, 0, 255, 0.75), (0, 255, 0, 0.5)),
+ # catches the atop, xor and lighter bugs in Opera 9.10
+ ]:
+ for op, FA_code, FB_code in ops:
+ expected = calc_output(src, dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ t.done();
+ """ % (dest, op, src, to_test(expected)),
+ } )
+
+ for (name, src, dest) in [ ('image', (255, 255, 0, 0.75), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ expected = calc_output(src, dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'images': [ 'yellow75.png' ],
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/yellow75.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 0, 0);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+ """ % (dest, op, to_test(expected)),
+ } )
+
+ for (name, src, dest) in [ ('canvas', (255, 255, 0, 0.75), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ expected = calc_output(src, dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'images': [ 'yellow75.png' ],
+ 'code': """
+ var offscreenCanvas2 = new OffscreenCanvas(canvas.width, canvas.height);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/yellow75.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx2.drawImage(bitmap, 0, 0);
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.drawImage(offscreenCanvas2, 0, 0);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+ """ % (dest, op, to_test(expected)),
+ } )
+
+ for (name, src, dest) in [ ('uncovered.fill', (0, 0, 255, 0.75), (0, 255, 0, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ new_test = {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.fillStyle = 'rgba%s';
+ ctx.translate(0, 25);
+ ctx.fillRect(0, 50, 100, 50);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ t.done();
+ """ % (dest, op, src, to_test(expected0)),
+ }
+ if op == 'destination-in':
+ new_test['timeout'] = 'long'
+ tests.append(new_test)
+
+ for (name, src, dest) in [ ('uncovered.image', (255, 255, 0, 1.0), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'drawImage() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'images': [ 'yellow.png' ],
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/yellow.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 40, 40, 10, 10, 40, 50, 10, 10);
+ @assert pixel 15,15 ==~ %s +/- 5;
+ @assert pixel 50,25 ==~ %s +/- 5;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+ """ % (dest, op, to_test(expected0), to_test(expected0)),
+ } )
+
+ for (name, src, dest) in [ ('uncovered.nocontext', (255, 255, 0, 1.0), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'drawImage() of a canvas with no context draws pixels as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ ctx.drawImage(offscreenCanvas2, 0, 0);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ t.done();
+ """ % (dest, op, to_test(expected0)),
+ } )
+
+ for (name, src, dest) in [ ('uncovered.pattern', (255, 255, 0, 1.0), (0, 255, 255, 0.5)) ]:
+ for op, FA_code, FB_code in ops:
+ if op not in ops_trans: continue
+ expected0 = calc_output((0,0,0,0.0), dest, FA_code, FB_code)
+ tests.append( {
+ 'name': '2d.composite.%s.%s' % (name, op),
+ 'desc': 'Pattern fill() draws pixels not covered by the source object as (0,0,0,0), and does not leave the pixels unchanged.',
+ 'images': [ 'yellow.png' ],
+ 'code': """
+ ctx.fillStyle = 'rgba%s';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/yellow.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.fillStyle = ctx.createPattern(bitmap, 'no-repeat');
+ ctx.fillRect(0, 50, 100, 50);
+ @assert pixel 50,25 ==~ %s +/- 5;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+ """ % (dest, op, to_test(expected0)),
+ } )
+
+ for op, FA_code, FB_code in ops:
+ tests.append( {
+ 'name': '2d.composite.clip.%s' % (op),
+ 'desc': 'fill() does not affect pixels outside the clip region.',
+ 'code': """
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = '%s';
+ ctx.rect(-20, -20, 10, 10);
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+ """ % (op),
+ } )
+
+- meta: |
+ # Color parsing tests
+
+ # Try most of the CSS3 Color <color> values - http://www.w3.org/TR/css3-color/#colorunits
+ big_float = '1' + ('0' * 39)
+ big_double = '1' + ('0' * 310)
+ for name, string, r,g,b,a, notes in [
+ ('html4', 'limE', 0,255,0,255, ""),
+ ('hex3', '#0f0', 0,255,0,255, ""),
+ ('hex4', '#0f0f', 0,255,0,255, ""),
+ ('hex6', '#00fF00', 0,255,0,255, ""),
+ ('hex8', '#00ff00ff', 0,255,0,255, ""),
+ ('rgb-num', 'rgb(0,255,0)', 0,255,0,255, ""),
+ ('rgb-clamp-1', 'rgb(-1000, 1000, -1000)', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-2', 'rgb(-200%, 200%, -200%)', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-3', 'rgb(-2147483649, 4294967298, -18446744073709551619)', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-4', 'rgb(-'+big_float+', '+big_float+', -'+big_float+')', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-clamp-5', 'rgb(-'+big_double+', '+big_double+', -'+big_double+')', 0,255,0,255, 'Assumes colors are clamped to [0,255].'),
+ ('rgb-percent', 'rgb(0% ,100% ,0%)', 0,255,0,255, 'CSS3 Color says "The integer value 255 corresponds to 100%". (In particular, it is not 254...)'),
+ ('rgb-eof', 'rgb(0, 255, 0', 0,255,0,255, ""), # see CSS2.1 4.2 "Unexpected end of style sheet"
+ ('rgba-solid-1', 'rgba( 0 , 255 , 0 , 1 )', 0,255,0,255, ""),
+ ('rgba-solid-2', 'rgba( 0 , 255 , 0 , 1.0 )', 0,255,0,255, ""),
+ ('rgba-solid-3', 'rgba( 0 , 255 , 0 , +1 )', 0,255,0,255, ""),
+ ('rgba-solid-4', 'rgba( -0 , 255 , +0 , 1 )', 0,255,0,255, ""),
+ ('rgba-num-1', 'rgba( 0 , 255 , 0 , .499 )', 0,255,0,127, ""),
+ ('rgba-num-2', 'rgba( 0 , 255 , 0 , 0.499 )', 0,255,0,127, ""),
+ ('rgba-percent', 'rgba(0%,100%,0%,0.499)', 0,255,0,127, ""), # 0.499*255 rounds to 127, both down and nearest, so it should be safe
+ ('rgba-clamp-1', 'rgba(0, 255, 0, -2)', 0,0,0,0, ""),
+ ('rgba-clamp-2', 'rgba(0, 255, 0, 2)', 0,255,0,255, ""),
+ ('rgba-eof', 'rgba(0, 255, 0, 1', 0,255,0,255, ""),
+ ('transparent-1', 'transparent', 0,0,0,0, ""),
+ ('transparent-2', 'TrAnSpArEnT', 0,0,0,0, ""),
+ ('hsl-1', 'hsl(120, 100%, 50%)', 0,255,0,255, ""),
+ ('hsl-2', 'hsl( -240 , 100% , 50% )', 0,255,0,255, ""),
+ ('hsl-3', 'hsl(360120, 100%, 50%)', 0,255,0,255, ""),
+ ('hsl-4', 'hsl(-360240, 100%, 50%)', 0,255,0,255, ""),
+ ('hsl-5', 'hsl(120.0, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('hsl-6', 'hsl(+120, +100%, +50%)', 0,255,0,255, ""),
+ ('hsl-clamp-1', 'hsl(120, 200%, 50%)', 0,255,0,255, ""),
+ ('hsl-clamp-2', 'hsl(120, -200%, 49.9%)', 127,127,127,255, ""),
+ ('hsl-clamp-3', 'hsl(120, 100%, 200%)', 255,255,255,255, ""),
+ ('hsl-clamp-4', 'hsl(120, 100%, -200%)', 0,0,0,255, ""),
+ ('hsla-1', 'hsla(120, 100%, 50%, 0.499)', 0,255,0,127, ""),
+ ('hsla-2', 'hsla( 120.0 , 100.0% , 50.0% , 1 )', 0,255,0,255, ""),
+ ('hsla-clamp-1', 'hsla(120, 200%, 50%, 1)', 0,255,0,255, ""),
+ ('hsla-clamp-2', 'hsla(120, -200%, 49.9%, 1)', 127,127,127,255, ""),
+ ('hsla-clamp-3', 'hsla(120, 100%, 200%, 1)', 255,255,255,255, ""),
+ ('hsla-clamp-4', 'hsla(120, 100%, -200%, 1)', 0,0,0,255, ""),
+ ('hsla-clamp-5', 'hsla(120, 100%, 50%, 2)', 0,255,0,255, ""),
+ ('hsla-clamp-6', 'hsla(120, 100%, 0%, -2)', 0,0,0,0, ""),
+ ('svg-1', 'gray', 128,128,128,255, ""),
+ ('svg-2', 'grey', 128,128,128,255, ""),
+ # css-color-4 rgb() color function
+ # https://drafts.csswg.org/css-color/#numeric-rgb
+ ('css-color-4-rgb-1', 'rgb(0, 255.0, 0)', 0,255,0,255, ""),
+ ('css-color-4-rgb-2', 'rgb(0, 255, 0, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgb-3', 'rgb(0, 255, 0, 20%)', 0,255,0,51, ""),
+ ('css-color-4-rgb-4', 'rgb(0 255 0)', 0,255,0,255, ""),
+ ('css-color-4-rgb-5', 'rgb(0 255 0 / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgb-6', 'rgb(0 255 0 / 20%)', 0,255,0,51, ""),
+ ('css-color-4-rgba-1', 'rgba(0, 255.0, 0)', 0,255,0,255, ""),
+ ('css-color-4-rgba-2', 'rgba(0, 255, 0, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgba-3', 'rgba(0, 255, 0, 20%)', 0,255,0,51, ""),
+ ('css-color-4-rgba-4', 'rgba(0 255 0)', 0,255,0,255, ""),
+ ('css-color-4-rgba-5', 'rgba(0 255 0 / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-rgba-6', 'rgba(0 255 0 / 20%)', 0,255,0,51, ""),
+ # css-color-4 hsl() color function
+ # https://drafts.csswg.org/css-color/#the-hsl-notation
+ ('css-color-4-hsl-1', 'hsl(120 100.0% 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-2', 'hsl(120 100.0% 50.0% / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsl-3', 'hsl(120.0, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsl-4', 'hsl(120.0, 100.0%, 50.0%, 20%)', 0,255,0,51, ""),
+ ('css-color-4-hsl-5', 'hsl(120deg, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsl-6', 'hsl(120deg, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-7', 'hsl(133.33333333grad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-8', 'hsl(2.0943951024rad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsl-9', 'hsl(0.3333333333turn, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-1', 'hsl(120 100.0% 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-2', 'hsl(120 100.0% 50.0% / 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsla-3', 'hsl(120.0, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsla-4', 'hsl(120.0, 100.0%, 50.0%, 20%)', 0,255,0,51, ""),
+ ('css-color-4-hsla-5', 'hsl(120deg, 100.0%, 50.0%, 0.2)', 0,255,0,51, ""),
+ ('css-color-4-hsla-6', 'hsl(120deg, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-7', 'hsl(133.33333333grad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-8', 'hsl(2.0943951024rad, 100.0%, 50.0%)', 0,255,0,255, ""),
+ ('css-color-4-hsla-9', 'hsl(0.3333333333turn, 100.0%, 50.0%)', 0,255,0,255, ""),
+ # currentColor is handled later
+ ]:
+ # TODO: test by retrieving fillStyle, instead of actually drawing?
+ # TODO: test strokeStyle, shadowColor in the same way
+ test = {
+ 'name': '2d.fillStyle.parse.%s' % name,
+ 'notes': notes,
+ 'code': """
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = '%s';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == %d,%d,%d,%d;
+ t.done();
+ """ % (string, r,g,b,a),
+ }
+ tests.append(test)
+
+ # Also test that invalid colors are ignored
+ for name, string in [
+ ('hex1', '#f'),
+ ('hex2', '#f0'),
+ ('hex3', '#g00'),
+ ('hex4', '#fg00'),
+ ('hex5', '#ff000'),
+ ('hex6', '#fg0000'),
+ ('hex7', '#ff0000f'),
+ ('hex8', '#fg0000ff'),
+ ('rgb-1', 'rgb(255.0, 0, 0,)'),
+ ('rgb-2', 'rgb(100%, 0, 0)'),
+ ('rgb-3', 'rgb(255, - 1, 0)'),
+ ('rgba-1', 'rgba(100%, 0, 0, 1)'),
+ ('rgba-2', 'rgba(255, 0, 0, 1. 0)'),
+ ('rgba-3', 'rgba(255, 0, 0, 1.)'),
+ ('rgba-4', 'rgba(255, 0, 0, '),
+ ('rgba-5', 'rgba(255, 0, 0, 1,)'),
+ ('hsl-1', 'hsl(0%, 100%, 50%)'),
+ ('hsl-2', 'hsl(z, 100%, 50%)'),
+ ('hsl-3', 'hsl(0, 0, 50%)'),
+ ('hsl-4', 'hsl(0, 100%, 0)'),
+ ('hsl-5', 'hsl(0, 100.%, 50%)'),
+ ('hsl-6', 'hsl(0, 100%, 50%,)'),
+ ('hsla-1', 'hsla(0%, 100%, 50%, 1)'),
+ ('hsla-2', 'hsla(0, 0, 50%, 1)'),
+ ('hsla-3', 'hsla(0, 0, 50%, 1,)'),
+ ('name-1', 'darkbrown'),
+ ('name-2', 'firebrick1'),
+ ('name-3', 'red blue'),
+ ('name-4', '"red"'),
+ ('name-5', '"red'),
+ # css-color-4 color function
+ # comma and comma-less expressions should not mix together.
+ ('css-color-4-rgb-1', 'rgb(255, 0, 0 / 1)'),
+ ('css-color-4-rgb-2', 'rgb(255 0 0, 1)'),
+ ('css-color-4-rgb-3', 'rgb(255, 0 0)'),
+ ('css-color-4-rgba-1', 'rgba(255, 0, 0 / 1)'),
+ ('css-color-4-rgba-2', 'rgba(255 0 0, 1)'),
+ ('css-color-4-rgba-3', 'rgba(255, 0 0)'),
+ ('css-color-4-hsl-1', 'hsl(0, 100%, 50% / 1)'),
+ ('css-color-4-hsl-2', 'hsl(0 100% 50%, 1)'),
+ ('css-color-4-hsl-3', 'hsl(0, 100% 50%)'),
+ ('css-color-4-hsla-1', 'hsla(0, 100%, 50% / 1)'),
+ ('css-color-4-hsla-2', 'hsla(0 100% 50%, 1)'),
+ ('css-color-4-hsla-3', 'hsla(0, 100% 50%)'),
+ # trailing slash
+ ('css-color-4-rgb-4', 'rgb(0 0 0 /)'),
+ ('css-color-4-rgb-5', 'rgb(0, 0, 0 /)'),
+ ('css-color-4-hsl-4', 'hsl(0 100% 50% /)'),
+ ('css-color-4-hsl-5', 'hsl(0, 100%, 50% /)'),
+ ]:
+ test = {
+ 'name': '2d.fillStyle.parse.invalid.%s' % name,
+ 'code': """
+ ctx.fillStyle = '#0f0';
+ try { ctx.fillStyle = '%s'; } catch (e) { } // this shouldn't throw, but it shouldn't matter here if it does
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ """ % string,
+ }
+ tests.append(test)
+
+ # Some can't have positive tests, only negative tests, because we don't know what color they're meant to be
+ for name, string in [
+ ('system', 'ThreeDDarkShadow'),
+ #('flavor', 'flavor'), # removed from latest CSS3 Color drafts
+ ]:
+ test = {
+ 'name': '2d.fillStyle.parse.%s' % name,
+ 'code': """
+ ctx.fillStyle = '#f00';
+ ctx.fillStyle = '%s';
+ @assert ctx.fillStyle =~ /^#(?!(FF0000|ff0000|f00)$)/; // test that it's not red
+ t.done();
+ """ % (string,),
+ }
+ tests.append(test)
+
+- meta: |
+ cases = [
+ ("zero", "0", 0),
+ ("empty", "", 0),
+ ("onlyspace", " ", 0),
+ ("space", " 100", 100),
+ ("whitespace", "\t\f100", 100),
+ ("plus", "+100", 100),
+ ("minus", "-100", "exception"),
+ ("octal", "0100", 100),
+ ("hex", "0x100", 0x100),
+ ("exp", "100e1", 100e1),
+ ("decimal", "100.999", 100),
+ ("percent", "100%", "exception"),
+ ("em", "100em", "exception"),
+ ("junk", "#!?", "exception"),
+ ("trailingjunk", "100#!?", "exception"),
+ ]
+ def gen(name, string, exp, code):
+ if exp is None:
+ code += "canvas.width = '%s';\ncanvas.height = '%s';\n" % (string, string)
+ code += "@assert canvas.width === 100;\n@assert canvas.height === 50;\n"
+ expected = None
+ elif exp == "exception":
+ code += "@assert throws TypeError canvas.width = '%s';\n" % string
+ expected = None
+ else:
+ code += "canvas.width = '%s';\ncanvas.height = '%s';\n" % (string, string)
+ code += "@assert canvas.width === %s;\n@assert canvas.height === %s;\n" % (exp, exp)
+ expected = None
+
+ code += "t.done();\n"
+
+ if exp == 0:
+ expected = None # can't generate zero-sized PNGs for the expected image
+
+ return code, expected
+
+ for name, string, exp in cases:
+ code = ""
+ code, expected = gen(name, string, exp, code)
+ tests.append( {
+ "name": "size.attributes.parse.%s" % name,
+ "desc": "Parsing of non-negative integers",
+ "code": code,
+ } )
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/path-objects.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/path-objects.yaml
new file mode 100644
index 0000000000..9c70daf59f
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/path-objects.yaml
@@ -0,0 +1,3073 @@
+- name: 2d.path.initial
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.closePath();
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.beginPath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.moveTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rect(0, 0, 10, 50);
+ ctx.moveTo(100, 0);
+ ctx.lineTo(10, 0);
+ ctx.lineTo(10, 50);
+ ctx.lineTo(100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 90,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.moveTo.newsubpath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.moveTo(100, 0);
+ ctx.moveTo(100, 50);
+ ctx.moveTo(0, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.moveTo.multiple
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.moveTo(0, 25);
+ ctx.moveTo(100, 25);
+ ctx.moveTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.moveTo.nonfinite
+ desc: moveTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.moveTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.closePath.empty
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.closePath();
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.closePath.newline
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-100, -100);
+ ctx.lineTo(200, -100);
+ ctx.lineTo(200, 25);
+ ctx.closePath();
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.closePath.nextpoint
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-100, -1000);
+ ctx.closePath();
+ ctx.lineTo(1000, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.lineTo.ensuresubpath.1
+ desc: If there is no subpath, the point is added and nothing is drawn
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.lineTo(100, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.lineTo.ensuresubpath.2
+ desc: If there is no subpath, the point is added and used for subsequent drawing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.lineTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.lineTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.lineTo.nextpoint
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(-100, -100);
+ ctx.lineTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.lineTo.nonfinite
+ desc: lineTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.lineTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.lineTo.nonfinite.details
+ desc: lineTo() with Infinity/NaN for first arg still converts the second arg
+ code: |
+ for (var arg1 of [Infinity, -Infinity, NaN]) {
+ var converted = false;
+ ctx.lineTo(arg1, { valueOf: function() { converted = true; return 0; } });
+ @assert converted;
+ }
+ t.done();
+
+- name: 2d.path.quadraticCurveTo.ensuresubpath.1
+ desc: If there is no subpath, the first control point is added (and nothing is drawn
+ up to it)
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.quadraticCurveTo(100, 50, 200, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 95,45 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.quadraticCurveTo.ensuresubpath.2
+ desc: If there is no subpath, the first control point is added
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.quadraticCurveTo(0, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.quadraticCurveTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.quadraticCurveTo(100, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.quadraticCurveTo.shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 55;
+ ctx.beginPath();
+ ctx.moveTo(-1000, 1050);
+ ctx.quadraticCurveTo(0, -1000, 1200, 1050);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.quadraticCurveTo.scaled
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(1000, 1000);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 0.055;
+ ctx.beginPath();
+ ctx.moveTo(-1, 1.05);
+ ctx.quadraticCurveTo(0, -1, 1.2, 1.05);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.quadraticCurveTo.nonfinite
+ desc: quadraticCurveTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.quadraticCurveTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.bezierCurveTo.ensuresubpath.1
+ desc: If there is no subpath, the first control point is added (and nothing is drawn
+ up to it)
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.bezierCurveTo(100, 50, 200, 50, 200, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 95,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.bezierCurveTo.ensuresubpath.2
+ desc: If there is no subpath, the first control point is added
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.bezierCurveTo(0, 25, 100, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.bezierCurveTo.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.bezierCurveTo(100, 25, 100, 25, 100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.bezierCurveTo.shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 55;
+ ctx.beginPath();
+ ctx.moveTo(-2000, 3100);
+ ctx.bezierCurveTo(-2000, -1000, 2100, -1000, 2100, 3100);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.bezierCurveTo.scaled
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(1000, 1000);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 0.055;
+ ctx.beginPath();
+ ctx.moveTo(-2, 3.1);
+ ctx.bezierCurveTo(-2, -1, 2.1, -1, 2.1, 3.1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.bezierCurveTo.nonfinite
+ desc: bezierCurveTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.bezierCurveTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.ensuresubpath.1
+ desc: If there is no subpath, the first control point is added (and nothing is drawn
+ up to it)
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arcTo(100, 50, 200, 50, 0.1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.ensuresubpath.2
+ desc: If there is no subpath, the first control point is added
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arcTo(0, 25, 50, 250, 0.1); // adds (x1,y1), draws nothing
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.coincide.1
+ desc: arcTo() has no effect if P0 = P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(0, 25, 50, 1000, 1);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arcTo(50, 25, 100, 25, 1);
+ ctx.stroke();
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.coincide.2
+ desc: arcTo() draws a straight line to P1 if P1 = P2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 100, 25, 1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.collinear.1
+ desc: arcTo() with all points on a line, and P1 between P0/P2, draws a straight
+ line to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 200, 25, 1);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(-100, 25);
+ ctx.arcTo(0, 25, 100, 25, 1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.collinear.2
+ desc: arcTo() with all points on a line, and P2 between P0/P1, draws a straight
+ line to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 10, 25, 1);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 25);
+ ctx.arcTo(200, 25, 110, 25, 1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.collinear.3
+ desc: arcTo() with all points on a line, and P0 between P1/P2, draws a straight
+ line to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, -100, 25, 1);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 25);
+ ctx.arcTo(200, 25, 0, 25, 1);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(-100, 25);
+ ctx.arcTo(0, 25, -200, 25, 1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.shape.curve1
+ desc: arcTo() curves in the right kind of shape
+ code: |
+ var tol = 1.5; // tolerance to avoid antialiasing artifacts
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 10;
+ ctx.beginPath();
+ ctx.moveTo(10, 25);
+ ctx.arcTo(75, 25, 75, 60, 20);
+ ctx.stroke();
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.rect(10, 20, 45, 10);
+ ctx.moveTo(80, 45);
+ ctx.arc(55, 45, 25+tol, 0, -Math.PI/2, true);
+ ctx.arc(55, 45, 15-tol, -Math.PI/2, 0, false);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 55,19 == 0,255,0,255;
+ @assert pixel 55,20 == 0,255,0,255;
+ @assert pixel 55,21 == 0,255,0,255;
+ @assert pixel 64,22 == 0,255,0,255;
+ @assert pixel 65,21 == 0,255,0,255;
+ @assert pixel 72,28 == 0,255,0,255;
+ @assert pixel 73,27 == 0,255,0,255;
+ @assert pixel 78,36 == 0,255,0,255;
+ @assert pixel 79,35 == 0,255,0,255;
+ @assert pixel 80,44 == 0,255,0,255;
+ @assert pixel 80,45 == 0,255,0,255;
+ @assert pixel 80,46 == 0,255,0,255;
+ @assert pixel 65,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.shape.curve2
+ desc: arcTo() curves in the right kind of shape
+ code: |
+ var tol = 1.5; // tolerance to avoid antialiasing artifacts
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.rect(10, 20, 45, 10);
+ ctx.moveTo(80, 45);
+ ctx.arc(55, 45, 25-tol, 0, -Math.PI/2, true);
+ ctx.arc(55, 45, 15+tol, -Math.PI/2, 0, false);
+ ctx.fill();
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 10;
+ ctx.beginPath();
+ ctx.moveTo(10, 25);
+ ctx.arcTo(75, 25, 75, 60, 20);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 55,19 == 0,255,0,255;
+ @assert pixel 55,20 == 0,255,0,255;
+ @assert pixel 55,21 == 0,255,0,255;
+ @assert pixel 64,22 == 0,255,0,255;
+ @assert pixel 65,21 == 0,255,0,255;
+ @assert pixel 72,28 == 0,255,0,255;
+ @assert pixel 73,27 == 0,255,0,255;
+ @assert pixel 78,36 == 0,255,0,255;
+ @assert pixel 79,35 == 0,255,0,255;
+ @assert pixel 80,44 == 0,255,0,255;
+ @assert pixel 80,45 == 0,255,0,255;
+ @assert pixel 80,46 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.shape.start
+ desc: arcTo() draws a straight line from P0 to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(200, 25, 200, 50, 10);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.shape.end
+ desc: arcTo() does not draw anything from P1 to P2
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.beginPath();
+ ctx.moveTo(-100, -100);
+ ctx.arcTo(-100, 25, 200, 25, 10);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.negative
+ desc: arcTo() with negative radius throws an exception
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.arcTo(0, 0, 0, 0, -1);
+ t.done();
+
+- name: 2d.path.arcTo.zero.1
+ desc: arcTo() with zero radius draws a straight line from P0 to P1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, 100, 100, 0);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(0, -25);
+ ctx.arcTo(50, -25, 50, 50, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.zero.2
+ desc: arcTo() with zero radius draws a straight line from P0 to P1, even when all
+ points are collinear
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arcTo(100, 25, -100, 25, 0);
+ ctx.stroke();
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 25);
+ ctx.arcTo(200, 25, 50, 25, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.transformation
+ desc: arcTo joins up to the last subpath point correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 50);
+ ctx.translate(100, 0);
+ ctx.arcTo(50, 50, 50, 0, 50);
+ ctx.lineTo(-100, 0);
+ ctx.fill();
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.scale
+ desc: arcTo scales the curve, not just the control points
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 50);
+ ctx.translate(100, 0);
+ ctx.scale(0.1, 1);
+ ctx.arcTo(50, 50, 50, 0, 50);
+ ctx.lineTo(-1000, 0);
+ ctx.fill();
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arcTo.nonfinite
+ desc: arcTo() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.arcTo(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+
+
+- name: 2d.path.arc.empty
+ desc: arc() with an empty path does not draw a straight line to the start point
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(200, 25, 5, 0, 2*Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.nonempty
+ desc: arc() with a non-empty path does draw a straight line to the start point
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arc(200, 25, 5, 0, 2*Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.end
+ desc: arc() adds the end point of the arc to the subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(-100, 0);
+ ctx.arc(-100, 0, 25, -Math.PI/2, Math.PI/2, true);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.default
+ desc: arc() with missing last argument defaults to clockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, -Math.PI, Math.PI/2);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.angle.1
+ desc: arc() draws pi/2 .. -pi anticlockwise correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, Math.PI/2, -Math.PI, true);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.angle.2
+ desc: arc() draws -3pi/2 .. -pi anticlockwise correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, -3*Math.PI/2, -Math.PI, true);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.angle.3
+ desc: arc() wraps angles mod 2pi when anticlockwise and end > start+2pi
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, (512+1/2)*Math.PI, (1024-1)*Math.PI, true);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.angle.4
+ desc: arc() draws a full circle when clockwise and end > start+2pi
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arc(50, 25, 60, (512+1/2)*Math.PI, (1024-1)*Math.PI, false);
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.angle.5
+ desc: arc() wraps angles mod 2pi when clockwise and start > end+2pi
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(100, 0);
+ ctx.arc(100, 0, 150, (1024-1)*Math.PI, (512+1/2)*Math.PI, false);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.angle.6
+ desc: arc() draws a full circle when anticlockwise and start > end+2pi
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arc(50, 25, 60, (1024-1)*Math.PI, (512+1/2)*Math.PI, true);
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.zero.1
+ desc: arc() draws nothing when startAngle = endAngle and anticlockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 0, true);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.zero.2
+ desc: arc() draws nothing when startAngle = endAngle and clockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 0, false);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.twopie.1
+ desc: arc() draws nothing when end = start + 2pi-e and anticlockwise
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI - 1e-4, true);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.twopie.2
+ desc: arc() draws a full circle when end = start + 2pi-e and clockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI - 1e-4, false);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.twopie.3
+ desc: arc() draws a full circle when end = start + 2pi+e and anticlockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI + 1e-4, true);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.twopie.4
+ desc: arc() draws nothing when end = start + 2pi+e and clockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.arc(50, 25, 50, 0, 2*Math.PI + 1e-4, false);
+ ctx.stroke();
+ @assert pixel 50,20 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.shape.1
+ desc: arc() from 0 to pi does not draw anything in the wrong half
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(50, 50, 50, 0, Math.PI, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 20,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.shape.2
+ desc: arc() from 0 to pi draws stuff in the right half
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 100;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(50, 50, 50, 0, Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 20,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.shape.3
+ desc: arc() from 0 to -pi/2 does not draw anything in the wrong quadrant
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 100;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(0, 50, 50, 0, -Math.PI/2, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255; @moz-todo
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.shape.4
+ desc: arc() from 0 to -pi/2 draws stuff in the right quadrant
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 150;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(-50, 50, 100, 0, -Math.PI/2, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.shape.5
+ desc: arc() from 0 to 5pi does not draw crazy things
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(300, 0, 100, 0, 5*Math.PI, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.selfintersect.1
+ desc: arc() with lineWidth > 2*radius is drawn sensibly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 200;
+ ctx.strokeStyle = '#f00';
+ ctx.beginPath();
+ ctx.arc(100, 50, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(0, 0, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255; @moz-todo
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.selfintersect.2
+ desc: arc() with lineWidth > 2*radius is drawn sensibly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 180;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(-50, 50, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(100, 0, 25, 0, -Math.PI/2, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,10 == 0,255,0,255;
+ @assert pixel 97,1 == 0,255,0,255;
+ @assert pixel 97,2 == 0,255,0,255;
+ @assert pixel 97,3 == 0,255,0,255;
+ @assert pixel 2,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.negative
+ desc: arc() with negative radius throws INDEX_SIZE_ERR
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.arc(0, 0, -1, 0, 0, true);
+ t.done();
+
+- name: 2d.path.arc.zeroradius
+ desc: arc() with zero radius draws a line to the start point
+ code: |
+ ctx.fillStyle = '#f00'
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 25);
+ ctx.arc(200, 25, 0, 0, Math.PI, true);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.scale.1
+ desc: Non-uniformly scaled arcs are the right shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(2, 0.5);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.arc(25, 50, 56, 0, 2*Math.PI, false);
+ ctx.fill();
+ ctx.fillStyle = '#f00';
+ ctx.beginPath();
+ ctx.moveTo(-25, 50);
+ ctx.arc(-25, 50, 24, 0, 2*Math.PI, false);
+ ctx.moveTo(75, 50);
+ ctx.arc(75, 50, 24, 0, 2*Math.PI, false);
+ ctx.moveTo(25, -25);
+ ctx.arc(25, -25, 24, 0, 2*Math.PI, false);
+ ctx.moveTo(25, 125);
+ ctx.arc(25, 125, 24, 0, 2*Math.PI, false);
+ ctx.fill();
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.scale.2
+ desc: Highly scaled arcs are the right shape
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(100, 100);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 1.2;
+ ctx.beginPath();
+ ctx.arc(0, 0, 0.6, 0, Math.PI/2, false);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.arc.nonfinite
+ desc: arc() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.arc(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <2*Math.PI Infinity -Infinity NaN>, <true>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+
+
+- name: 2d.path.rect.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.rect(0, 0, 100, 50);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.newsubpath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-50, 25);
+ ctx.rect(200, 25, 1, 1);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.closed
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.rect(100, 50, 100, 100);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.end.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.rect(200, 100, 400, 1000);
+ ctx.lineTo(-2000, -1000);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.end.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 450;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'bevel';
+ ctx.rect(150, 150, 2000, 2000);
+ ctx.lineTo(160, 160);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.zero.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.rect(0, 50, 100, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.zero.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.rect(50, -100, 0, 250);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.zero.3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.rect(50, 25, 0, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.zero.4
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.rect(100, 25, 0, 0);
+ ctx.lineTo(0, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.zero.5
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, 0);
+ ctx.rect(100, 25, 0, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.zero.6
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 1.5;
+ ctx.lineWidth = 200;
+ ctx.beginPath();
+ ctx.rect(100, 25, 1000, 0);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.rect.negative
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#0f0';
+ ctx.rect(0, 0, 50, 25);
+ ctx.rect(100, 0, -50, 25);
+ ctx.rect(0, 50, 50, -25);
+ ctx.rect(100, 50, -50, -25);
+ ctx.fill();
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.winding
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#f00';
+ ctx.rect(0, 0, 50, 50);
+ ctx.rect(100, 50, -50, -50);
+ ctx.rect(0, 25, 100, -25);
+ ctx.rect(100, 25, -100, 25);
+ ctx.fill();
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.rect.selfintersect
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 90;
+ ctx.beginPath();
+ ctx.rect(45, 20, 10, 10);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.rect.nonfinite
+ desc: rect() with Infinity/NaN is ignored
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.rect(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.newsubpath
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-50, 25);
+ ctx.roundRect(200, 25, 1, 1, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.closed
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.roundRect(100, 50, 100, 100, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.end.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.roundRect(200, 100, 400, 1000, [0]);
+ ctx.lineTo(-2000, -1000);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.end.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 450;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'bevel';
+ ctx.roundRect(150, 150, 2000, 2000, [0]);
+ ctx.lineTo(160, 160);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.end.3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.roundRect(101, 51, 2000, 2000, [500, 500, 500, 500]);
+ ctx.lineTo(-1, -1);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.end.4
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 10;
+ ctx.roundRect(-1, -1, 2000, 2000, [1000, 1000, 1000, 1000]);
+ ctx.lineTo(-150, -150);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.zero.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.roundRect(0, 50, 100, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.zero.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.roundRect(50, -100, 0, 250, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.zero.3
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.beginPath();
+ ctx.roundRect(50, 25, 0, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.zero.4
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 50;
+ ctx.roundRect(100, 25, 0, 0, [0]);
+ ctx.lineTo(0, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.zero.5
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, 0);
+ ctx.roundRect(100, 25, 0, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.zero.6
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 1.5;
+ ctx.lineWidth = 200;
+ ctx.beginPath();
+ ctx.roundRect(100, 25, 1000, 0, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.negative
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#0f0';
+ ctx.roundRect(0, 0, 50, 25, [10, 0, 0, 0]);
+ ctx.roundRect(100, 0, -50, 25, [10, 0, 0, 0]);
+ ctx.roundRect(0, 50, 50, -25, [10, 0, 0, 0]);
+ ctx.roundRect(100, 50, -50, -25, [10, 0, 0, 0]);
+ ctx.fill();
+ // All rects drawn
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+ // Correct corners are rounded.
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.winding
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.fillStyle = '#f00';
+ ctx.roundRect(0, 0, 50, 50, [0]);
+ ctx.roundRect(100, 50, -50, -50, [0]);
+ ctx.roundRect(0, 25, 100, -25, [0]);
+ ctx.roundRect(100, 25, -100, 25, [0]);
+ ctx.fill();
+ @assert pixel 25,12 == 0,255,0,255;
+ @assert pixel 75,12 == 0,255,0,255;
+ @assert pixel 25,37 == 0,255,0,255;
+ @assert pixel 75,37 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.selfintersect
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.roundRect(0, 0, 100, 50, [0]);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 90;
+ ctx.beginPath();
+ ctx.roundRect(45, 20, 10, 10, [0]);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.nonfinite
+ desc: roundRect() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ @nonfinite ctx.roundRect(<0 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <[0] [Infinity] [-Infinity] [NaN] [Infinity,0] [-Infinity,0] [NaN,0] [0,Infinity] [0,-Infinity] [0,NaN] [Infinity,0,0] [-Infinity,0,0] [NaN,0,0] [0,Infinity,0] [0,-Infinity,0] [0,NaN,0] [0,0,Infinity] [0,0,-Infinity] [0,0,NaN] [Infinity,0,0,0] [-Infinity,0,0,0] [NaN,0,0,0] [0,Infinity,0,0] [0,-Infinity,0,0] [0,NaN,0,0] [0,0,Infinity,0] [0,0,-Infinity,0] [0,0,NaN,0] [0,0,0,Infinity] [0,0,0,-Infinity] [0,0,0,NaN]>);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(10, Infinity)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(10, -Infinity)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(10, NaN)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(Infinity, 10)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(-Infinity, 10)]);
+ ctx.roundRect(0, 0, 100, 100, [new DOMPoint(NaN, 10)]);
+ ctx.roundRect(0, 0, 100, 100, [{x: 10, y: Infinity}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: 10, y: -Infinity}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: 10, y: NaN}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: Infinity, y: 10}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: -Infinity, y: 10}]);
+ ctx.roundRect(0, 0, 100, 100, [{x: NaN, y: 10}]);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 90,45 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.path.roundrect.4.radii.1.double
+ desc: Verify that when four radii are given to roundRect(), the first radius, specified as a double, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20, 0, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.1.dompoint
+ desc: Verify that when four radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20), 0, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.1.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the first radius, specified as a DOMPointInit, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}, 0, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.2.double
+ desc: Verify that when four radii are given to roundRect(), the second radius, specified as a double, applies to the top-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 20, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.2.dompoint
+ desc: Verify that when four radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, new DOMPoint(40, 20), 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.2.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the second radius, specified as a DOMPointInit, applies to the top-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, {x: 40, y: 20}, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.3.double
+ desc: Verify that when four radii are given to roundRect(), the third radius, specified as a double, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 20, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.3.dompoint
+ desc: Verify that when four radii are given to roundRect(), the third radius, specified as a DOMPoint, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, new DOMPoint(40, 20), 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.3.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the third radius, specified as a DOMPointInit, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, {x: 40, y: 20}, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.4.double
+ desc: Verify that when four radii are given to roundRect(), the fourth radius, specified as a double, applies to the bottom-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 0, 20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.4.dompoint
+ desc: Verify that when four radii are given to roundRect(), the fourth radius, specified as a DOMPoint, applies to the bottom-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 0, new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.4.radii.4.dompointinit
+ desc: Verify that when four radii are given to roundRect(), the fourth radius, specified as a DOMPointInit, applies to the bottom-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 0, {x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.1.double
+ desc: Verify that when three radii are given to roundRect(), the first radius, specified as a double, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.1.dompoint
+ desc: Verify that when three radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20), 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.1.dompointinit
+ desc: Verify that when three radii are given to roundRect(), the first radius, specified as a DOMPointInit, applies to the top-left corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}, 0, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.2.double
+ desc: Verify that when three radii are given to roundRect(), the second radius, specified as a double, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 20, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.2.dompoint
+ desc: Verify that when three radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, new DOMPoint(40, 20), 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.2.dompointinit
+ desc: Verify that when three radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottomInit-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, {x: 40, y: 20}, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.3.double
+ desc: Verify that when three radii are given to roundRect(), the third radius, specified as a double, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, 20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.3.dompoint
+ desc: Verify that when three radii are given to roundRect(), the third radius, specified as a DOMPoint, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.3.radii.3.dompointinit
+ desc: Verify that when three radii are given to roundRect(), the third radius, specified as a DOMPointInit, applies to the bottom-right corner.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 0, {x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.2.radii.1.double
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a double, applies to the top-left and bottom-right corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.2.radii.1.dompoint
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left and bottom-right corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20), 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.2.radii.1.dompointinit
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left and bottomInit-right corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}, 0]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 98,1 == 0,255,0,255;
+ @assert pixel 1,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.2.radii.2.double
+ desc: Verify that when two radii are given to roundRect(), the second radius, specified as a double, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, 20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.2.radii.2.dompoint
+ desc: Verify that when two radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.2.radii.2.dompointinit
+ desc: Verify that when two radii are given to roundRect(), the second radius, specified as a DOMPoint, applies to the top-right and bottomInit-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [0, {x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+
+ // other corners
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.1.radius.double
+ desc: Verify that when one radius is given to roundRect(), specified as a double, it applies to all corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [20]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.1.radius.dompoint
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left and bottom-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [new DOMPoint(40, 20)]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.1.radius.dompointinit
+ desc: Verify that when two radii are given to roundRect(), the first radius, specified as a DOMPoint, applies to the top-left and bottomInit-left corners.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [{x: 40, y: 20}]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ // top-left corner
+ @assert pixel 20,1 == 255,0,0,255;
+ @assert pixel 41,1 == 0,255,0,255;
+ @assert pixel 1,10 == 255,0,0,255;
+ @assert pixel 1,21 == 0,255,0,255;
+
+ // top-right corner
+ @assert pixel 79,1 == 255,0,0,255;
+ @assert pixel 58,1 == 0,255,0,255;
+ @assert pixel 98,10 == 255,0,0,255;
+ @assert pixel 98,21 == 0,255,0,255;
+
+ // bottom-right corner
+ @assert pixel 79,48 == 255,0,0,255;
+ @assert pixel 58,48 == 0,255,0,255;
+ @assert pixel 98,39 == 255,0,0,255;
+ @assert pixel 98,28 == 0,255,0,255;
+
+ // bottom-left corner
+ @assert pixel 20,48 == 255,0,0,255;
+ @assert pixel 41,48 == 0,255,0,255;
+ @assert pixel 1,39 == 255,0,0,255;
+ @assert pixel 1,28 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.radius.intersecting.1
+ desc: Check that roundRects with intersecting corner arcs are rendered correctly.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [40, 40, 40, 40]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 2,25 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 97,25 == 0,255,0,255;
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.radius.intersecting.2
+ desc: Check that roundRects with intersecting corner arcs are rendered correctly.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.roundRect(0, 0, 100, 50, [1000, 1000, 1000, 1000]);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 2,25 == 0,255,0,255;
+ @assert pixel 50,1 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 50,48 == 0,255,0,255;
+ @assert pixel 97,25 == 0,255,0,255;
+ @assert pixel 1,1 == 255,0,0,255;
+ @assert pixel 98,1 == 255,0,0,255;
+ @assert pixel 1,48 == 255,0,0,255;
+ @assert pixel 98,48 == 255,0,0,255;
+ t.done();
+
+- name: 2d.path.roundrect.radius.none
+ desc: Check that roundRect throws an RangeError if radii is an empty array.
+ code: |
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 100, 50, [])});
+ t.done();
+
+- name: 2d.path.roundrect.radius.toomany
+ desc: Check that roundRect throws an IndeSizeError if radii has more than four items.
+ code: |
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 100, 50, [0, 0, 0, 0, 0])});
+ t.done();
+
+- name: 2d.path.roundrect.radius.negative
+ desc: roundRect() with negative radius throws an exception
+ code: |
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [-1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [1, -1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [new DOMPoint(-1, 1), 1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [new DOMPoint(1, -1)])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [{x: -1, y: 1}, 1])});
+ assert_throws_js(RangeError, () => { ctx.roundRect(0, 0, 0, 0, [{x: 1, y: -1}])});
+ t.done();
+
+- name: 2d.path.fill.overlap
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
+ ctx.rect(0, 0, 100, 50);
+ ctx.closePath();
+ ctx.rect(10, 10, 80, 30);
+ ctx.fill();
+ @assert pixel 50,25 ==~ 0,127,0,255 +/- 1;
+ t.done();
+
+- name: 2d.path.fill.winding.add
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.fill.winding.subtract.1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.fill.winding.subtract.2
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.fill.winding.subtract.3
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(-20, -20);
+ ctx.lineTo(120, -20);
+ ctx.lineTo(120, 70);
+ ctx.lineTo(-20, 70);
+ ctx.lineTo(-20, -20);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.fill.closed.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.fill.closed.unaffected
+ code: |
+ ctx.fillStyle = '#00f';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 90,10 == 0,255,0,255;
+ @assert pixel 10,40 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.overlap
+ desc: Stroked subpaths are combined before being drawn
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, 20);
+ ctx.lineTo(100, 20);
+ ctx.moveTo(0, 30);
+ ctx.lineTo(100, 30);
+ ctx.stroke();
+ @assert pixel 50,25 ==~ 0,127,0,255 +/- 1;
+ t.done();
+
+- name: 2d.path.stroke.union
+ desc: Strokes in opposite directions are unioned, not subtracted
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 40;
+ ctx.moveTo(0, 10);
+ ctx.lineTo(100, 10);
+ ctx.moveTo(100, 40);
+ ctx.lineTo(0, 40);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.unaffected
+ desc: Stroking does not start a new path or subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.lineWidth = 50;
+ ctx.moveTo(-100, 25);
+ ctx.lineTo(-100, -100);
+ ctx.lineTo(200, -100);
+ ctx.lineTo(200, 25);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.closePath();
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.scale1
+ desc: Stroke line widths are scaled by the current transformation matrix
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.rect(25, 12.5, 50, 25);
+ ctx.save();
+ ctx.scale(50, 25);
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ ctx.restore();
+ ctx.beginPath();
+ ctx.rect(-25, -12.5, 150, 75);
+ ctx.save();
+ ctx.scale(50, 25);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.scale2
+ desc: Stroke line widths are scaled by the current transformation matrix
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.rect(25, 12.5, 50, 25);
+ ctx.save();
+ ctx.rotate(Math.PI/2);
+ ctx.scale(25, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ ctx.restore();
+ ctx.beginPath();
+ ctx.rect(-25, -12.5, 150, 75);
+ ctx.save();
+ ctx.rotate(Math.PI/2);
+ ctx.scale(25, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.skew
+ desc: Strokes lines are skewed by the current transformation matrix
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(49, -50);
+ ctx.lineTo(201, -50);
+ ctx.rotate(Math.PI/4);
+ ctx.scale(1, 283);
+ ctx.strokeStyle = '#0f0';
+ ctx.stroke();
+ ctx.restore();
+ ctx.save();
+ ctx.beginPath();
+ ctx.translate(-150, 0);
+ ctx.moveTo(49, -50);
+ ctx.lineTo(199, -50);
+ ctx.rotate(Math.PI/4);
+ ctx.scale(1, 142);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+ ctx.save();
+ ctx.beginPath();
+ ctx.translate(-150, 0);
+ ctx.moveTo(49, -50);
+ ctx.lineTo(199, -50);
+ ctx.rotate(Math.PI/4);
+ ctx.scale(1, 142);
+ ctx.strokeStyle = '#f00';
+ ctx.stroke();
+ ctx.restore();
+ @assert pixel 0,0 == 0,255,0,255;
+ @assert pixel 50,0 == 0,255,0,255;
+ @assert pixel 99,0 == 0,255,0,255;
+ @assert pixel 0,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 99,25 == 0,255,0,255;
+ @assert pixel 0,49 == 0,255,0,255;
+ @assert pixel 50,49 == 0,255,0,255;
+ @assert pixel 99,49 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.empty
+ desc: Empty subpaths are not stroked
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(40, 25);
+ ctx.moveTo(60, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.stroke.prune.line
+ desc: Zero-length line segments from lineTo are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.lineTo(50, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.stroke.prune.closed
+ desc: Zero-length line segments from closed paths are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.lineTo(50, 25);
+ ctx.closePath();
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.stroke.prune.curve
+ desc: Zero-length line segments from quadraticCurveTo and bezierCurveTo are removed
+ before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.quadraticCurveTo(50, 25, 50, 25);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.bezierCurveTo(50, 25, 50, 25, 50, 25);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.stroke.prune.arc
+ desc: Zero-length line segments from arcTo and arc are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.moveTo(50, 25);
+ ctx.arcTo(50, 25, 150, 25, 10);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(60, 25);
+ ctx.arc(50, 25, 10, 0, 0, false);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.stroke.prune.rect
+ desc: Zero-length line segments from rect and strokeRect are removed before stroking
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 100;
+ ctx.lineCap = 'round';
+ ctx.lineJoin = 'round';
+ ctx.beginPath();
+ ctx.rect(50, 25, 0, 0);
+ ctx.stroke();
+ ctx.strokeRect(50, 25, 0, 0);
+ @assert pixel 50,25 == 0,255,0,255; @moz-todo
+ t.done();
+
+- name: 2d.path.stroke.prune.corner
+ desc: Zero-length line segments are removed before stroking with miters
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 400;
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 1.4;
+ ctx.beginPath();
+ ctx.moveTo(-1000, 200);
+ ctx.lineTo(-100, 200);
+ ctx.lineTo(-100, 200);
+ ctx.lineTo(-100, 200);
+ ctx.lineTo(-100, 1000);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.transformation.basic
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(-100, 0);
+ ctx.rect(100, 0, 100, 50);
+ ctx.translate(0, -100);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.transformation.multiple
+ desc: Transformations are applied while building paths, not when drawing
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.translate(-100, 0);
+ ctx.rect(0, 0, 100, 50);
+ ctx.fill();
+ ctx.translate(100, 0);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.strokeStyle = '#f00';
+ ctx.lineWidth = 50;
+ ctx.translate(0, -50);
+ ctx.moveTo(0, 25);
+ ctx.lineTo(100, 25);
+ ctx.stroke();
+ ctx.translate(0, 50);
+ ctx.stroke();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.transformation.changing
+ desc: Transformations are applied while building paths, not when drawing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.moveTo(0, 0);
+ ctx.translate(100, 0);
+ ctx.lineTo(0, 0);
+ ctx.translate(0, 50);
+ ctx.lineTo(0, 0);
+ ctx.translate(-100, 0);
+ ctx.lineTo(0, 0);
+ ctx.translate(1000, 1000);
+ ctx.rotate(Math.PI/2);
+ ctx.scale(0.1, 0.1);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.empty
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.basic.1
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.rect(0, 0, 100, 50);
+ ctx.clip();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.basic.2
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.rect(-100, 0, 100, 50);
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.intersect
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.beginPath();
+ ctx.rect(50, 0, 50, 50)
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.winding.1
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.winding.2
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.beginPath();
+ ctx.moveTo(-10, -10);
+ ctx.lineTo(110, -10);
+ ctx.lineTo(110, 60);
+ ctx.lineTo(-10, 60);
+ ctx.lineTo(-10, -10);
+ ctx.clip();
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.lineTo(0, 0);
+ ctx.clip();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.clip.unaffected
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(100, 50);
+ ctx.lineTo(100, 0);
+ ctx.clip();
+ ctx.lineTo(0, 0);
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.path.isPointInPath.basic.1
+ desc: isPointInPath() detects whether the point is inside the path
+ code: |
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInPath(10, 10) === true;
+ @assert ctx.isPointInPath(30, 10) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.basic.2
+ desc: isPointInPath() detects whether the point is inside the path
+ code: |
+ ctx.rect(20, 0, 20, 20);
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(30, 10) === true;
+ t.done();
+
+- name: 2d.path.isPointInPath.edge
+ desc: isPointInPath() counts points on the path as being inside
+ code: |
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInPath(0, 0) === true;
+ @assert ctx.isPointInPath(10, 0) === true;
+ @assert ctx.isPointInPath(20, 0) === true;
+ @assert ctx.isPointInPath(20, 10) === true;
+ @assert ctx.isPointInPath(20, 20) === true;
+ @assert ctx.isPointInPath(10, 20) === true;
+ @assert ctx.isPointInPath(0, 20) === true;
+ @assert ctx.isPointInPath(0, 10) === true;
+ @assert ctx.isPointInPath(10, -0.01) === false;
+ @assert ctx.isPointInPath(10, 20.01) === false;
+ @assert ctx.isPointInPath(-0.01, 10) === false;
+ @assert ctx.isPointInPath(20.01, 10) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.empty
+ desc: isPointInPath() works when there is no path
+ code: |
+ @assert ctx.isPointInPath(0, 0) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.subpath
+ desc: isPointInPath() uses the current path, not just the subpath
+ code: |
+ ctx.rect(0, 0, 20, 20);
+ ctx.beginPath();
+ ctx.rect(20, 0, 20, 20);
+ ctx.closePath();
+ ctx.rect(40, 0, 20, 20);
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(30, 10) === true;
+ @assert ctx.isPointInPath(50, 10) === true;
+ t.done();
+
+- name: 2d.path.isPointInPath.outside
+ desc: isPointInPath() works on paths outside the canvas
+ code: |
+ ctx.rect(0, -100, 20, 20);
+ ctx.rect(20, -10, 20, 20);
+ @assert ctx.isPointInPath(10, -110) === false;
+ @assert ctx.isPointInPath(10, -90) === true;
+ @assert ctx.isPointInPath(10, -70) === false;
+ @assert ctx.isPointInPath(30, -20) === false;
+ @assert ctx.isPointInPath(30, 0) === true;
+ @assert ctx.isPointInPath(30, 20) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.unclosed
+ desc: isPointInPath() works on unclosed subpaths
+ code: |
+ ctx.moveTo(0, 0);
+ ctx.lineTo(20, 0);
+ ctx.lineTo(20, 20);
+ ctx.lineTo(0, 20);
+ @assert ctx.isPointInPath(10, 10) === true;
+ @assert ctx.isPointInPath(30, 10) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.arc
+ desc: isPointInPath() works on arcs
+ code: |
+ ctx.arc(50, 25, 10, 0, Math.PI, false);
+ @assert ctx.isPointInPath(50, 10) === false;
+ @assert ctx.isPointInPath(50, 20) === false;
+ @assert ctx.isPointInPath(50, 30) === true;
+ @assert ctx.isPointInPath(50, 40) === false;
+ @assert ctx.isPointInPath(30, 20) === false;
+ @assert ctx.isPointInPath(70, 20) === false;
+ @assert ctx.isPointInPath(30, 30) === false;
+ @assert ctx.isPointInPath(70, 30) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.bigarc
+ desc: isPointInPath() works on unclosed arcs larger than 2pi
+ opera: {bug: 320937}
+ code: |
+ ctx.arc(50, 25, 10, 0, 7, false);
+ @assert ctx.isPointInPath(50, 10) === false;
+ @assert ctx.isPointInPath(50, 20) === true;
+ @assert ctx.isPointInPath(50, 30) === true;
+ @assert ctx.isPointInPath(50, 40) === false;
+ @assert ctx.isPointInPath(30, 20) === false;
+ @assert ctx.isPointInPath(70, 20) === false;
+ @assert ctx.isPointInPath(30, 30) === false;
+ @assert ctx.isPointInPath(70, 30) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.bezier
+ desc: isPointInPath() works on Bezier curves
+ code: |
+ ctx.moveTo(25, 25);
+ ctx.bezierCurveTo(50, -50, 50, 100, 75, 25);
+ @assert ctx.isPointInPath(25, 20) === false;
+ @assert ctx.isPointInPath(25, 30) === false;
+ @assert ctx.isPointInPath(30, 20) === true;
+ @assert ctx.isPointInPath(30, 30) === false;
+ @assert ctx.isPointInPath(40, 2) === false;
+ @assert ctx.isPointInPath(40, 20) === true;
+ @assert ctx.isPointInPath(40, 30) === false;
+ @assert ctx.isPointInPath(40, 47) === false;
+ @assert ctx.isPointInPath(45, 20) === true;
+ @assert ctx.isPointInPath(45, 30) === false;
+ @assert ctx.isPointInPath(55, 20) === false;
+ @assert ctx.isPointInPath(55, 30) === true;
+ @assert ctx.isPointInPath(60, 2) === false;
+ @assert ctx.isPointInPath(60, 20) === false;
+ @assert ctx.isPointInPath(60, 30) === true;
+ @assert ctx.isPointInPath(60, 47) === false;
+ @assert ctx.isPointInPath(70, 20) === false;
+ @assert ctx.isPointInPath(70, 30) === true;
+ @assert ctx.isPointInPath(75, 20) === false;
+ @assert ctx.isPointInPath(75, 30) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.winding
+ desc: isPointInPath() uses the non-zero winding number rule
+ code: |
+ // Create a square ring, using opposite windings to make a hole in the centre
+ ctx.moveTo(0, 0);
+ ctx.lineTo(50, 0);
+ ctx.lineTo(50, 50);
+ ctx.lineTo(0, 50);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(10, 10);
+ ctx.lineTo(10, 40);
+ ctx.lineTo(40, 40);
+ ctx.lineTo(40, 10);
+ ctx.lineTo(10, 10);
+ @assert ctx.isPointInPath(5, 5) === true;
+ @assert ctx.isPointInPath(25, 5) === true;
+ @assert ctx.isPointInPath(45, 5) === true;
+ @assert ctx.isPointInPath(5, 25) === true;
+ @assert ctx.isPointInPath(25, 25) === false;
+ @assert ctx.isPointInPath(45, 25) === true;
+ @assert ctx.isPointInPath(5, 45) === true;
+ @assert ctx.isPointInPath(25, 45) === true;
+ @assert ctx.isPointInPath(45, 45) === true;
+ t.done();
+
+- name: 2d.path.isPointInPath.transform.1
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.translate(50, 0);
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInPath(-40, 10) === false;
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(49, 10) === false;
+ @assert ctx.isPointInPath(51, 10) === true;
+ @assert ctx.isPointInPath(69, 10) === true;
+ @assert ctx.isPointInPath(71, 10) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.transform.2
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.rect(50, 0, 20, 20);
+ ctx.translate(50, 0);
+ @assert ctx.isPointInPath(-40, 10) === false;
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(49, 10) === false;
+ @assert ctx.isPointInPath(51, 10) === true;
+ @assert ctx.isPointInPath(69, 10) === true;
+ @assert ctx.isPointInPath(71, 10) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.transform.3
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.scale(-1, 1);
+ ctx.rect(-70, 0, 20, 20);
+ @assert ctx.isPointInPath(-40, 10) === false;
+ @assert ctx.isPointInPath(10, 10) === false;
+ @assert ctx.isPointInPath(49, 10) === false;
+ @assert ctx.isPointInPath(51, 10) === true;
+ @assert ctx.isPointInPath(69, 10) === true;
+ @assert ctx.isPointInPath(71, 10) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.transform.4
+ desc: isPointInPath() handles transformations correctly
+ code: |
+ ctx.translate(50, 0);
+ ctx.rect(50, 0, 20, 20);
+ ctx.translate(0, 50);
+ @assert ctx.isPointInPath(60, 10) === false;
+ @assert ctx.isPointInPath(110, 10) === true;
+ @assert ctx.isPointInPath(110, 60) === false;
+ t.done();
+
+- name: 2d.path.isPointInPath.nonfinite
+ desc: isPointInPath() returns false for non-finite arguments
+ code: |
+ ctx.rect(-100, -50, 200, 100);
+ @assert ctx.isPointInPath(Infinity, 0) === false;
+ @assert ctx.isPointInPath(-Infinity, 0) === false;
+ @assert ctx.isPointInPath(NaN, 0) === false;
+ @assert ctx.isPointInPath(0, Infinity) === false;
+ @assert ctx.isPointInPath(0, -Infinity) === false;
+ @assert ctx.isPointInPath(0, NaN) === false;
+ @assert ctx.isPointInPath(NaN, NaN) === false;
+ t.done();
+
+- name: 2d.path.isPointInStroke.basic
+ desc: detects whether point is in the area contained by the stroke of the path
+ code: |
+ ctx.strokeStyle = '#0f0';
+ ctx.beginPath();
+ ctx.rect(0, 0, 20, 20);
+ @assert ctx.isPointInStroke(0, 0) === true;
+ @assert ctx.isPointInStroke(30, 10) === false;
+
+ var path = new Path2D();
+ path.rect(20, 20, 100, 100);
+ @assert ctx.isPointInStroke(path, 20, 20) === true;
+ @assert ctx.isPointInStroke(path, 120, 20) === true;
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/pixel-manipulation.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/pixel-manipulation.yaml
new file mode 100644
index 0000000000..86f7940add
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/pixel-manipulation.yaml
@@ -0,0 +1,735 @@
+- name: 2d.imageData.create2.basic
+ desc: createImageData(sw, sh) exists and returns something
+ code: |
+ @assert ctx.createImageData(1, 1) !== null;
+ t.done();
+
+- name: 2d.imageData.create1.basic
+ desc: createImageData(imgdata) exists and returns something
+ code: |
+ @assert ctx.createImageData(ctx.createImageData(1, 1)) !== null;
+ t.done();
+
+- name: 2d.imageData.create2.initial
+ desc: createImageData(sw, sh) returns transparent black data of the right size
+ code: |
+ var imgdata = ctx.createImageData(10, 20);
+ @assert imgdata.data.length === imgdata.width*imgdata.height*4;
+ @assert imgdata.width < imgdata.height;
+ @assert imgdata.width > 0;
+ var isTransparentBlack = true;
+ for (var i = 0; i < imgdata.data.length; ++i)
+ if (imgdata.data[i] !== 0)
+ isTransparentBlack = false;
+ @assert isTransparentBlack;
+ t.done();
+
+- name: 2d.imageData.create1.initial
+ desc: createImageData(imgdata) returns transparent black data of the right size
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata1 = ctx.getImageData(0, 0, 10, 20);
+ var imgdata2 = ctx.createImageData(imgdata1);
+ @assert imgdata2.data.length === imgdata1.data.length;
+ @assert imgdata2.width === imgdata1.width;
+ @assert imgdata2.height === imgdata1.height;
+ var isTransparentBlack = true;
+ for (var i = 0; i < imgdata2.data.length; ++i)
+ if (imgdata2.data[i] !== 0)
+ isTransparentBlack = false;
+ @assert isTransparentBlack;
+ t.done();
+
+- name: 2d.imageData.create2.large
+ desc: createImageData(sw, sh) works for sizes much larger than the canvas
+ code: |
+ var imgdata = ctx.createImageData(1000, 2000);
+ @assert imgdata.data.length === imgdata.width*imgdata.height*4;
+ @assert imgdata.width < imgdata.height;
+ @assert imgdata.width > 0;
+ var isTransparentBlack = true;
+ for (var i = 0; i < imgdata.data.length; i += 7813) // check ~1024 points (assuming normal scaling)
+ if (imgdata.data[i] !== 0)
+ isTransparentBlack = false;
+ @assert isTransparentBlack;
+ t.done();
+
+- name: 2d.imageData.create2.negative
+ desc: createImageData(sw, sh) takes the absolute magnitude of the size arguments
+ code: |
+ var imgdata1 = ctx.createImageData(10, 20);
+ var imgdata2 = ctx.createImageData(-10, 20);
+ var imgdata3 = ctx.createImageData(10, -20);
+ var imgdata4 = ctx.createImageData(-10, -20);
+ @assert imgdata1.data.length === imgdata2.data.length;
+ @assert imgdata2.data.length === imgdata3.data.length;
+ @assert imgdata3.data.length === imgdata4.data.length;
+ t.done();
+
+- name: 2d.imageData.create2.zero
+ desc: createImageData(sw, sh) throws INDEX_SIZE_ERR if size is zero
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(10, 0);
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(0, 10);
+ @assert throws INDEX_SIZE_ERR ctx.createImageData(0, 0);
+ t.done();
+
+- name: 2d.imageData.create2.nonfinite
+ desc: createImageData() throws TypeError if arguments are not finite
+ code: |
+ @nonfinite @assert throws TypeError ctx.createImageData(<10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ var posinfobj = { valueOf: function() { return Infinity; } },
+ neginfobj = { valueOf: function() { return -Infinity; } },
+ nanobj = { valueOf: function() { return -Infinity; } };
+ @nonfinite @assert throws TypeError ctx.createImageData(<10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>);
+ t.done();
+
+- name: 2d.imageData.create1.zero
+ desc: createImageData(null) throws TypeError
+ code: |
+ @assert throws TypeError ctx.createImageData(null);
+ t.done();
+
+- name: 2d.imageData.create2.round
+ desc: createImageData(w, h) is rounded the same as getImageData(0, 0, w, h)
+ code: |
+ var imgdata1 = ctx.createImageData(10.01, 10.99);
+ var imgdata2 = ctx.getImageData(0, 0, 10.01, 10.99);
+ @assert imgdata1.width === imgdata2.width;
+ @assert imgdata1.height === imgdata2.height;
+ t.done();
+
+- name: 2d.imageData.get.basic
+ desc: getImageData() exists and returns something
+ code: |
+ @assert ctx.getImageData(0, 0, 100, 50) !== null;
+ t.done();
+
+- name: 2d.imageData.get.zero
+ desc: getImageData() throws INDEX_SIZE_ERR if size is zero
+ code: |
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 10, 0);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 0, 10);
+ @assert throws INDEX_SIZE_ERR ctx.getImageData(1, 1, 0, 0);
+ t.done();
+
+- name: 2d.imageData.get.nonfinite
+ desc: getImageData() throws TypeError if arguments are not finite
+ code: |
+ @nonfinite @assert throws TypeError ctx.getImageData(<10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ var posinfobj = { valueOf: function() { return Infinity; } },
+ neginfobj = { valueOf: function() { return -Infinity; } },
+ nanobj = { valueOf: function() { return -Infinity; } };
+ @nonfinite @assert throws TypeError ctx.getImageData(<10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>, <10 posinfobj neginfobj nanobj>);
+ t.done();
+
+- name: 2d.imageData.get.source.outside
+ desc: getImageData() returns transparent black outside the canvas
+ code: |
+ ctx.fillStyle = '#08f';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata1 = ctx.getImageData(-10, 5, 1, 1);
+ @assert imgdata1.data[0] === 0;
+ @assert imgdata1.data[1] === 0;
+ @assert imgdata1.data[2] === 0;
+ @assert imgdata1.data[3] === 0;
+ var imgdata2 = ctx.getImageData(10, -5, 1, 1);
+ @assert imgdata2.data[0] === 0;
+ @assert imgdata2.data[1] === 0;
+ @assert imgdata2.data[2] === 0;
+ @assert imgdata2.data[3] === 0;
+ var imgdata3 = ctx.getImageData(200, 5, 1, 1);
+ @assert imgdata3.data[0] === 0;
+ @assert imgdata3.data[1] === 0;
+ @assert imgdata3.data[2] === 0;
+ @assert imgdata3.data[3] === 0;
+ var imgdata4 = ctx.getImageData(10, 60, 1, 1);
+ @assert imgdata4.data[0] === 0;
+ @assert imgdata4.data[1] === 0;
+ @assert imgdata4.data[2] === 0;
+ @assert imgdata4.data[3] === 0;
+ var imgdata5 = ctx.getImageData(100, 10, 1, 1);
+ @assert imgdata5.data[0] === 0;
+ @assert imgdata5.data[1] === 0;
+ @assert imgdata5.data[2] === 0;
+ @assert imgdata5.data[3] === 0;
+ var imgdata6 = ctx.getImageData(0, 10, 1, 1);
+ @assert imgdata6.data[0] === 0;
+ @assert imgdata6.data[1] === 136;
+ @assert imgdata6.data[2] === 255;
+ @assert imgdata6.data[3] === 255;
+ var imgdata7 = ctx.getImageData(-10, 10, 20, 20);
+ @assert imgdata7.data[ 0*4+0] === 0;
+ @assert imgdata7.data[ 0*4+1] === 0;
+ @assert imgdata7.data[ 0*4+2] === 0;
+ @assert imgdata7.data[ 0*4+3] === 0;
+ @assert imgdata7.data[ 9*4+0] === 0;
+ @assert imgdata7.data[ 9*4+1] === 0;
+ @assert imgdata7.data[ 9*4+2] === 0;
+ @assert imgdata7.data[ 9*4+3] === 0;
+ @assert imgdata7.data[10*4+0] === 0;
+ @assert imgdata7.data[10*4+1] === 136;
+ @assert imgdata7.data[10*4+2] === 255;
+ @assert imgdata7.data[10*4+3] === 255;
+ @assert imgdata7.data[19*4+0] === 0;
+ @assert imgdata7.data[19*4+1] === 136;
+ @assert imgdata7.data[19*4+2] === 255;
+ @assert imgdata7.data[19*4+3] === 255;
+ @assert imgdata7.data[20*4+0] === 0;
+ @assert imgdata7.data[20*4+1] === 0;
+ @assert imgdata7.data[20*4+2] === 0;
+ @assert imgdata7.data[20*4+3] === 0;
+ t.done();
+
+- name: 2d.imageData.get.source.negative
+ desc: getImageData() works with negative width and height, and returns top-to-bottom
+ left-to-right
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(20, 10, 60, 10);
+ var imgdata1 = ctx.getImageData(85, 25, -10, -10);
+ @assert imgdata1.data[0] === 255;
+ @assert imgdata1.data[1] === 255;
+ @assert imgdata1.data[2] === 255;
+ @assert imgdata1.data[3] === 255;
+ @assert imgdata1.data[imgdata1.data.length-4+0] === 0;
+ @assert imgdata1.data[imgdata1.data.length-4+1] === 0;
+ @assert imgdata1.data[imgdata1.data.length-4+2] === 0;
+ @assert imgdata1.data[imgdata1.data.length-4+3] === 255;
+ var imgdata2 = ctx.getImageData(0, 0, -1, -1);
+ @assert imgdata2.data[0] === 0;
+ @assert imgdata2.data[1] === 0;
+ @assert imgdata2.data[2] === 0;
+ @assert imgdata2.data[3] === 0;
+ t.done();
+
+- name: 2d.imageData.get.source.size
+ desc: getImageData() returns bigger ImageData for bigger source rectangle
+ code: |
+ var imgdata1 = ctx.getImageData(0, 0, 10, 10);
+ var imgdata2 = ctx.getImageData(0, 0, 20, 20);
+ @assert imgdata2.width > imgdata1.width;
+ @assert imgdata2.height > imgdata1.height;
+ t.done();
+
+- name: 2d.imageData.get.nonpremul
+ desc: getImageData() returns non-premultiplied colors
+ code: |
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(10, 10, 10, 10);
+ @assert imgdata.data[0] > 200;
+ @assert imgdata.data[1] > 200;
+ @assert imgdata.data[2] > 200;
+ @assert imgdata.data[3] > 100;
+ @assert imgdata.data[3] < 200;
+ t.done();
+
+- name: 2d.imageData.get.range
+ desc: getImageData() returns values in the range [0, 255]
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(20, 10, 60, 10);
+ var imgdata1 = ctx.getImageData(10, 5, 1, 1);
+ @assert imgdata1.data[0] === 0;
+ var imgdata2 = ctx.getImageData(30, 15, 1, 1);
+ @assert imgdata2.data[0] === 255;
+ t.done();
+
+- name: 2d.imageData.get.clamp
+ desc: getImageData() clamps colors to the range [0, 255]
+ code: |
+ ctx.fillStyle = 'rgb(-100, -200, -300)';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = 'rgb(256, 300, 400)';
+ ctx.fillRect(20, 10, 60, 10);
+ var imgdata1 = ctx.getImageData(10, 5, 1, 1);
+ @assert imgdata1.data[0] === 0;
+ @assert imgdata1.data[1] === 0;
+ @assert imgdata1.data[2] === 0;
+ var imgdata2 = ctx.getImageData(30, 15, 1, 1);
+ @assert imgdata2.data[0] === 255;
+ @assert imgdata2.data[1] === 255;
+ @assert imgdata2.data[2] === 255;
+ t.done();
+
+- name: 2d.imageData.get.length
+ desc: getImageData() returns a correctly-sized Uint8ClampedArray
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data.length === imgdata.width*imgdata.height*4;
+ t.done();
+
+- name: 2d.imageData.get.order.cols
+ desc: getImageData() returns leftmost columns first
+ code: |
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 2, 50);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[0] === 0;
+ @assert imgdata.data[Math.round(imgdata.width/2*4)] === 255;
+ @assert imgdata.data[Math.round((imgdata.height/2)*imgdata.width*4)] === 0;
+ t.done();
+
+- name: 2d.imageData.get.order.rows
+ desc: getImageData() returns topmost rows first
+ code: |
+ ctx.fillStyle = '#fff';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 2);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[0] === 0;
+ @assert imgdata.data[Math.floor(imgdata.width/2*4)] === 0;
+ @assert imgdata.data[(imgdata.height/2)*imgdata.width*4] === 255;
+ t.done();
+
+- name: 2d.imageData.get.order.rgb
+ desc: getImageData() returns R then G then B
+ code: |
+ ctx.fillStyle = '#48c';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[0] === 0x44;
+ @assert imgdata.data[1] === 0x88;
+ @assert imgdata.data[2] === 0xCC;
+ @assert imgdata.data[3] === 255;
+ @assert imgdata.data[4] === 0x44;
+ @assert imgdata.data[5] === 0x88;
+ @assert imgdata.data[6] === 0xCC;
+ @assert imgdata.data[7] === 255;
+ t.done();
+
+- name: 2d.imageData.get.order.alpha
+ desc: getImageData() returns A in the fourth component
+ code: |
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
+ ctx.fillRect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert imgdata.data[3] < 200;
+ @assert imgdata.data[3] > 100;
+ t.done();
+
+- name: 2d.imageData.get.unaffected
+ desc: getImageData() is not affected by context state
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50)
+ ctx.save();
+ ctx.translate(50, 0);
+ ctx.globalAlpha = 0.1;
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#f00';
+ ctx.rect(0, 0, 5, 5);
+ ctx.clip();
+ var imgdata = ctx.getImageData(0, 0, 50, 50);
+ ctx.restore();
+ ctx.putImageData(imgdata, 50, 0);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ t.done();
+
+
+- name: 2d.imageData.object.properties
+ desc: ImageData objects have the right properties
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @assert typeof(imgdata.width) === 'number';
+ @assert typeof(imgdata.height) === 'number';
+ @assert typeof(imgdata.data) === 'object';
+ t.done();
+
+- name: 2d.imageData.object.readonly
+ desc: ImageData objects properties are read-only
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ var w = imgdata.width;
+ var h = imgdata.height;
+ var d = imgdata.data;
+ imgdata.width = 123;
+ imgdata.height = 123;
+ imgdata.data = [100,100,100,100];
+ @assert imgdata.width === w;
+ @assert imgdata.height === h;
+ @assert imgdata.data === d;
+ @assert imgdata.data[0] === 0;
+ @assert imgdata.data[1] === 0;
+ @assert imgdata.data[2] === 0;
+ @assert imgdata.data[3] === 0;
+ t.done();
+
+- name: 2d.imageData.object.set
+ desc: ImageData.data can be modified
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ @assert imgdata.data[0] === 100;
+ imgdata.data[0] = 200;
+ @assert imgdata.data[0] === 200;
+ t.done();
+
+- name: 2d.imageData.object.undefined
+ desc: ImageData.data converts undefined to 0
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = undefined;
+ @assert imgdata.data[0] === 0;
+ t.done();
+
+- name: 2d.imageData.object.nan
+ desc: ImageData.data converts NaN to 0
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = NaN;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = "cheese";
+ @assert imgdata.data[0] === 0;
+ t.done();
+
+- name: 2d.imageData.object.string
+ desc: ImageData.data converts strings to numbers with ToNumber
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = "110";
+ @assert imgdata.data[0] === 110;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = "0x78";
+ @assert imgdata.data[0] === 120;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = " +130e0 ";
+ @assert imgdata.data[0] === 130;
+ t.done();
+
+- name: 2d.imageData.object.clamp
+ desc: ImageData.data clamps numbers to [0, 255]
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 100;
+ imgdata.data[0] = 300;
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -100;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = 200+Math.pow(2, 32);
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -200-Math.pow(2, 32);
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = Math.pow(10, 39);
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -Math.pow(10, 39);
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = -Infinity;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 100;
+ imgdata.data[0] = Infinity;
+ @assert imgdata.data[0] === 255;
+ t.done();
+
+- name: 2d.imageData.object.round
+ desc: ImageData.data rounds numbers with round-to-zero
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ imgdata.data[0] = 0.499;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 0.5;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = 0.501;
+ @assert imgdata.data[0] === 1;
+ imgdata.data[0] = 1.499;
+ @assert imgdata.data[0] === 1;
+ imgdata.data[0] = 1.5;
+ @assert imgdata.data[0] === 2;
+ imgdata.data[0] = 1.501;
+ @assert imgdata.data[0] === 2;
+ imgdata.data[0] = 2.5;
+ @assert imgdata.data[0] === 2;
+ imgdata.data[0] = 3.5;
+ @assert imgdata.data[0] === 4;
+ imgdata.data[0] = 252.5;
+ @assert imgdata.data[0] === 252;
+ imgdata.data[0] = 253.5;
+ @assert imgdata.data[0] === 254;
+ imgdata.data[0] = 254.5;
+ @assert imgdata.data[0] === 254;
+ imgdata.data[0] = 256.5;
+ @assert imgdata.data[0] === 255;
+ imgdata.data[0] = -0.5;
+ @assert imgdata.data[0] === 0;
+ imgdata.data[0] = -1.5;
+ @assert imgdata.data[0] === 0;
+ t.done();
+
+- name: 2d.imageData.put.null
+ desc: putImageData() with null imagedata throws TypeError
+ code: |
+ @assert throws TypeError ctx.putImageData(null, 0, 0);
+ t.done();
+
+- name: 2d.imageData.put.nonfinite
+ desc: putImageData() throws TypeError if arguments are not finite
+ code: |
+ var imgdata = ctx.getImageData(0, 0, 10, 10);
+ @nonfinite @assert throws TypeError ctx.putImageData(<imgdata>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ @nonfinite @assert throws TypeError ctx.putImageData(<imgdata>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>, <10 Infinity -Infinity NaN>);
+ t.done();
+
+- name: 2d.imageData.put.basic
+ desc: putImageData() puts image data from getImageData() onto the canvas
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.created
+ desc: putImageData() puts image data from createImageData() onto the canvas
+ code: |
+ var imgdata = ctx.createImageData(100, 50);
+ for (var i = 0; i < imgdata.data.length; i += 4) {
+ imgdata.data[i] = 0;
+ imgdata.data[i+1] = 255;
+ imgdata.data[i+2] = 0;
+ imgdata.data[i+3] = 255;
+ }
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.wrongtype
+ desc: putImageData() does not accept non-ImageData objects
+ code: |
+ var imgdata = { width: 1, height: 1, data: [255, 0, 0, 255] };
+ @assert throws TypeError ctx.putImageData(imgdata, 0, 0);
+ @assert throws TypeError ctx.putImageData("cheese", 0, 0);
+ @assert throws TypeError ctx.putImageData(42, 0, 0);
+ t.done();
+
+- name: 2d.imageData.put.cross
+ desc: putImageData() accepts image data got from a different canvas
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#0f0';
+ ctx2.fillRect(0, 0, 100, 50)
+ var imgdata = ctx2.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.alpha
+ desc: putImageData() puts non-solid image data correctly
+ code: |
+ ctx.fillStyle = 'rgba(0, 255, 0, 0.25)';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,64;
+ t.done();
+
+- name: 2d.imageData.put.modified
+ desc: putImageData() puts modified image data correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(45, 20, 10, 10)
+ var imgdata = ctx.getImageData(45, 20, 10, 10);
+ for (var i = 0, len = imgdata.width*imgdata.height*4; i < len; i += 4)
+ {
+ imgdata.data[i] = 0;
+ imgdata.data[i+1] = 255;
+ }
+ ctx.putImageData(imgdata, 45, 20);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.dirty.zero
+ desc: putImageData() with zero-sized dirty rectangle puts nothing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 0, 0, 0, 0, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.dirty.rect1
+ desc: putImageData() only modifies areas inside the dirty rectangle, using width
+ and height
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 20, 20)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(40, 20, 20, 20)
+ ctx.putImageData(imgdata, 40, 20, 0, 0, 20, 20);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 35,25 ==~ 0,255,0,255;
+ @assert pixel 65,25 ==~ 0,255,0,255;
+ @assert pixel 50,15 ==~ 0,255,0,255;
+ @assert pixel 50,45 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.dirty.rect2
+ desc: putImageData() only modifies areas inside the dirty rectangle, using x and
+ y
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(60, 30, 20, 20)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(40, 20, 20, 20)
+ ctx.putImageData(imgdata, -20, -10, 60, 30, 20, 20);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 35,25 ==~ 0,255,0,255;
+ @assert pixel 65,25 ==~ 0,255,0,255;
+ @assert pixel 50,15 ==~ 0,255,0,255;
+ @assert pixel 50,45 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.dirty.negative
+ desc: putImageData() handles negative-sized dirty rectangles correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 20, 20)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(40, 20, 20, 20)
+ ctx.putImageData(imgdata, 40, 20, 20, 20, -20, -20);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 35,25 ==~ 0,255,0,255;
+ @assert pixel 65,25 ==~ 0,255,0,255;
+ @assert pixel 50,15 ==~ 0,255,0,255;
+ @assert pixel 50,45 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.dirty.outside
+ desc: putImageData() handles dirty rectangles outside the canvas correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.putImageData(imgdata, 100, 20, 20, 20, -20, -20);
+ ctx.putImageData(imgdata, 200, 200, 0, 0, 100, 50);
+ ctx.putImageData(imgdata, 40, 20, -30, -20, 30, 20);
+ ctx.putImageData(imgdata, -30, 20, 0, 0, 30, 20);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 98,15 ==~ 0,255,0,255;
+ @assert pixel 98,25 ==~ 0,255,0,255;
+ @assert pixel 98,45 ==~ 0,255,0,255;
+ @assert pixel 1,5 ==~ 0,255,0,255;
+ @assert pixel 1,25 ==~ 0,255,0,255;
+ @assert pixel 1,45 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.unchanged
+ desc: putImageData(getImageData(...), ...) has no effect
+ code: |
+ var i = 0;
+ for (var y = 0; y < 16; ++y) {
+ for (var x = 0; x < 16; ++x, ++i) {
+ ctx.fillStyle = 'rgba(' + i + ',' + (Math.floor(i*1.5) % 256) + ',' + (Math.floor(i*23.3) % 256) + ',' + (i/256) + ')';
+ ctx.fillRect(x, y, 1, 1);
+ }
+ }
+ var imgdata1 = ctx.getImageData(0.1, 0.2, 15.8, 15.9);
+ var olddata = [];
+ for (var i = 0; i < imgdata1.data.length; ++i)
+ olddata[i] = imgdata1.data[i];
+ ctx.putImageData(imgdata1, 0.1, 0.2);
+ var imgdata2 = ctx.getImageData(0.1, 0.2, 15.8, 15.9);
+ for (var i = 0; i < imgdata2.data.length; ++i) {
+ @assert olddata[i] === imgdata2.data[i];
+ }
+ t.done();
+
+- name: 2d.imageData.put.unaffected
+ desc: putImageData() is not affected by context state
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.globalAlpha = 0.1;
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#f00';
+ ctx.shadowBlur = 1;
+ ctx.translate(100, 50);
+ ctx.scale(0.1, 0.1);
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.clip
+ desc: putImageData() is not affected by clipping regions
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50)
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.putImageData(imgdata, 0, 0);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.imageData.put.path
+ desc: putImageData() does not affect the current path
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50)
+ ctx.rect(0, 0, 100, 50);
+ var imgdata = ctx.getImageData(0, 0, 100, 50);
+ ctx.putImageData(imgdata, 0, 0);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/shadows.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/shadows.yaml
new file mode 100644
index 0000000000..06e2681c72
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/shadows.yaml
@@ -0,0 +1,947 @@
+- name: 2d.shadow.attributes.shadowBlur.initial
+ code: |
+ @assert ctx.shadowBlur === 0;
+ t.done();
+
+- name: 2d.shadow.attributes.shadowBlur.valid
+ code: |
+ ctx.shadowBlur = 1;
+ @assert ctx.shadowBlur === 1;
+ ctx.shadowBlur = 0.5;
+ @assert ctx.shadowBlur === 0.5;
+ ctx.shadowBlur = 1e6;
+ @assert ctx.shadowBlur === 1e6;
+ ctx.shadowBlur = 0;
+ @assert ctx.shadowBlur === 0;
+ t.done();
+
+- name: 2d.shadow.attributes.shadowBlur.invalid
+ code: |
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = -2;
+ @assert ctx.shadowBlur === 1;
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = Infinity;
+ @assert ctx.shadowBlur === 1;
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = -Infinity;
+ @assert ctx.shadowBlur === 1;
+ ctx.shadowBlur = 1;
+ ctx.shadowBlur = NaN;
+ @assert ctx.shadowBlur === 1;
+ t.done();
+
+- name: 2d.shadow.attributes.shadowOffset.initial
+ code: |
+ @assert ctx.shadowOffsetX === 0;
+ @assert ctx.shadowOffsetY === 0;
+ t.done();
+
+- name: 2d.shadow.attributes.shadowOffset.valid
+ code: |
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+ ctx.shadowOffsetX = 0.5;
+ ctx.shadowOffsetY = 0.25;
+ @assert ctx.shadowOffsetX === 0.5;
+ @assert ctx.shadowOffsetY === 0.25;
+ ctx.shadowOffsetX = -0.5;
+ ctx.shadowOffsetY = -0.25;
+ @assert ctx.shadowOffsetX === -0.5;
+ @assert ctx.shadowOffsetY === -0.25;
+ ctx.shadowOffsetX = 0;
+ ctx.shadowOffsetY = 0;
+ @assert ctx.shadowOffsetX === 0;
+ @assert ctx.shadowOffsetY === 0;
+ ctx.shadowOffsetX = 1e6;
+ ctx.shadowOffsetY = 1e6;
+ @assert ctx.shadowOffsetX === 1e6;
+ @assert ctx.shadowOffsetY === 1e6;
+ t.done();
+
+- name: 2d.shadow.attributes.shadowOffset.invalid
+ code: |
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = Infinity;
+ ctx.shadowOffsetY = Infinity;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = -Infinity;
+ ctx.shadowOffsetY = -Infinity;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+ ctx.shadowOffsetX = 1;
+ ctx.shadowOffsetY = 2;
+ ctx.shadowOffsetX = NaN;
+ ctx.shadowOffsetY = NaN;
+ @assert ctx.shadowOffsetX === 1;
+ @assert ctx.shadowOffsetY === 2;
+ t.done();
+
+- name: 2d.shadow.attributes.shadowColor.initial
+ code: |
+ @assert ctx.shadowColor === 'rgba(0, 0, 0, 0)';
+ t.done();
+
+- name: 2d.shadow.attributes.shadowColor.valid
+ code: |
+ ctx.shadowColor = 'lime';
+ @assert ctx.shadowColor === '#00ff00';
+ ctx.shadowColor = 'RGBA(0,255, 0,0)';
+ @assert ctx.shadowColor === 'rgba(0, 255, 0, 0)';
+ t.done();
+
+- name: 2d.shadow.attributes.shadowColor.invalid
+ code: |
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = 'bogus';
+ @assert ctx.shadowColor === '#00ff00';
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = 'red bogus';
+ @assert ctx.shadowColor === '#00ff00';
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = ctx;
+ @assert ctx.shadowColor === '#00ff00';
+ ctx.shadowColor = '#00ff00';
+ ctx.shadowColor = undefined;
+ @assert ctx.shadowColor === '#00ff00';
+ t.done();
+
+- name: 2d.shadow.enable.off.1
+ desc: Shadows are not drawn when only shadowColor is set
+ code: |
+ ctx.shadowColor = '#f00';
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.enable.off.2
+ desc: Shadows are not drawn when only shadowColor is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#f00';
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.enable.blur
+ desc: Shadows are drawn if shadowBlur is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowBlur = 0.1;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.enable.x
+ desc: Shadows are drawn if shadowOffsetX is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 0.1;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.enable.y
+ desc: Shadows are drawn if shadowOffsetY is set
+ code: |
+ ctx.globalCompositeOperation = 'destination-atop';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 0.1;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.offset.positiveX
+ desc: Shadows can be offset with positive x
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(0, 0, 50, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.offset.negativeX
+ desc: Shadows can be offset with negative x
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = -50;
+ ctx.fillRect(50, 0, 50, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.offset.positiveY
+ desc: Shadows can be offset with positive y
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 25;
+ ctx.fillRect(0, 0, 100, 25);
+ @assert pixel 50,12 == 0,255,0,255;
+ @assert pixel 50,37 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.offset.negativeY
+ desc: Shadows can be offset with negative y
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = -25;
+ ctx.fillRect(0, 25, 100, 25);
+ @assert pixel 50,12 == 0,255,0,255;
+ @assert pixel 50,37 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.outside
+ desc: Shadows of shapes outside the visible area can be offset onto the visible
+ area
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 100;
+ ctx.fillRect(-100, 0, 25, 50);
+ ctx.shadowOffsetX = -100;
+ ctx.fillRect(175, 0, 25, 50);
+ ctx.shadowOffsetX = 0;
+ ctx.shadowOffsetY = 100;
+ ctx.fillRect(25, -100, 50, 25);
+ ctx.shadowOffsetY = -100;
+ ctx.fillRect(25, 125, 50, 25);
+ @assert pixel 12,25 == 0,255,0,255;
+ @assert pixel 87,25 == 0,255,0,255;
+ @assert pixel 50,12 == 0,255,0,255;
+ @assert pixel 50,37 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.clip.1
+ desc: Shadows of clipped shapes are still drawn within the clipping region
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(50, 0, 50, 50);
+ ctx.clip();
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.restore();
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.clip.2
+ desc: Shadows are not drawn outside the clipping region
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.restore();
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.clip.3
+ desc: Shadows of clipped shapes are still drawn within the clipping region
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.save();
+ ctx.beginPath();
+ ctx.rect(0, 0, 50, 50);
+ ctx.clip();
+ ctx.fillStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 50;
+ ctx.fillRect(-50, 0, 50, 50);
+ ctx.restore();
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.stroke.basic
+ desc: Shadows are drawn for strokes
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.beginPath();
+ ctx.lineWidth = 50;
+ ctx.moveTo(0, -25);
+ ctx.lineTo(100, -25);
+ ctx.stroke();
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.stroke.cap.1
+ desc: Shadows are not drawn for areas outside stroke caps
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.beginPath();
+ ctx.lineWidth = 50;
+ ctx.lineCap = 'butt';
+ ctx.moveTo(-50, -25);
+ ctx.lineTo(0, -25);
+ ctx.moveTo(100, -25);
+ ctx.lineTo(150, -25);
+ ctx.stroke();
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.stroke.cap.2
+ desc: Shadows are drawn for stroke caps
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.beginPath();
+ ctx.lineWidth = 50;
+ ctx.lineCap = 'square';
+ ctx.moveTo(25, -25);
+ ctx.lineTo(75, -25);
+ ctx.stroke();
+ @assert pixel 1,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.stroke.join.1
+ desc: Shadows are not drawn for areas outside stroke joins
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 100;
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'bevel';
+ ctx.beginPath();
+ ctx.moveTo(-200, -50);
+ ctx.lineTo(-150, -50);
+ ctx.lineTo(-151, -100);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.stroke.join.2
+ desc: Shadows are drawn for stroke joins
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetX = 100;
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.beginPath();
+ ctx.moveTo(-200, -50);
+ ctx.lineTo(-150, -50);
+ ctx.lineTo(-151, -100);
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.stroke.join.3
+ desc: Shadows are drawn for stroke joins respecting miter limit
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#f00';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 100;
+ ctx.lineWidth = 200;
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 0.1;
+ ctx.beginPath();
+ ctx.moveTo(-200, -50);
+ ctx.lineTo(-150, -50);
+ ctx.lineTo(-151, -100); // (not an exact right angle, to avoid some other bug in Firefox 3)
+ ctx.stroke();
+ @assert pixel 1,1 == 0,255,0,255;
+ @assert pixel 48,48 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,48 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.image.basic
+ desc: Shadows are drawn for images
+ images:
+ - red.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 0, -50);
+ @assert pixel 50,25 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.image.transparent.1
+ desc: Shadows are not drawn for transparent images
+ images:
+ - transparent.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/transparent.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 0, -50);
+ @assert pixel 50,25 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.image.transparent.2
+ desc: Shadows are not drawn for transparent parts of images
+ images:
+ - redtransparent.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/redtransparent.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 50, -50);
+ ctx.shadowColor = '#f00';
+ ctx.drawImage(bitmap, -50, -50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.image.alpha
+ desc: Shadows are drawn correctly for partially-transparent images
+ images:
+ - transparent50.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/transparent50.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 0, -50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.image.section
+ desc: Shadows are not drawn for areas outside image source rectangles
+ images:
+ - redtransparent.png
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#f00';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/redtransparent.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 50, 0, 50, 50, 0, -50, 50, 50);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.image.scale
+ desc: Shadows are drawn correctly for scaled images
+ images:
+ - redtransparent.png
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/redtransparent.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ ctx.drawImage(bitmap, 0, 0, 100, 50, -10, -50, 240, 50);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.canvas.basic
+ desc: Shadows are drawn for canvases
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.drawImage(offscreenCanvas2, 0, -50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.canvas.transparent.1
+ desc: Shadows are not drawn for transparent canvases
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.drawImage(offscreenCanvas2, 0, -50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.canvas.transparent.2
+ desc: Shadows are not drawn for transparent parts of canvases
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = '#f00';
+ ctx2.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.drawImage(offscreenCanvas2, 50, -50);
+ ctx.shadowColor = '#f00';
+ ctx.drawImage(offscreenCanvas2, -50, -50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.canvas.alpha
+ desc: Shadows are drawn correctly for partially-transparent canvases
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var ctx2 = offscreenCanvas2.getContext('2d');
+ ctx2.fillStyle = 'rgba(255, 0, 0, 0.5)';
+ ctx2.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.drawImage(offscreenCanvas2, 0, -50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ t.done();
+
+- name: 2d.shadow.pattern.basic
+ desc: Shadows are drawn for fill patterns
+ images:
+ - red.png
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/red.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.pattern.transparent.1
+ desc: Shadows are not drawn for transparent fill patterns
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ images:
+ - transparent.png
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/transparent.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.pattern.transparent.2
+ desc: Shadows are not drawn for transparent parts of fill patterns
+ images:
+ - redtransparent.png
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/redtransparent.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.pattern.alpha
+ desc: Shadows are drawn correctly for partially-transparent fill patterns
+ images:
+ - transparent50.png
+ code: |
+ var promise = new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", '/images/transparent50.png');
+ xhr.responseType = 'blob';
+ xhr.send();
+ xhr.onload = function() {
+ resolve(xhr.response);
+ };
+ });
+ promise.then(function(response) {
+ createImageBitmap(response).then(bitmap => {
+ var pattern = ctx.createPattern(bitmap, 'repeat');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.fillStyle = pattern;
+ ctx.fillRect(0, -50, 100, 50);
+ }, t_fail);
+ }).then(t_pass, t_fail);
+
+- name: 2d.shadow.gradient.basic
+ desc: Shadows are drawn for gradient fills
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, '#f00');
+ gradient.addColorStop(1, '#f00');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#0f0';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.gradient.transparent.1
+ desc: Shadows are not drawn for transparent gradient fills
+ # http://bugs.webkit.org/show_bug.cgi?id=15266
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, 'rgba(0,0,0,0)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0)');
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetY = 50;
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.gradient.transparent.2
+ desc: Shadows are not drawn for transparent parts of gradient fills
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, '#f00');
+ gradient.addColorStop(0.499, '#f00');
+ gradient.addColorStop(0.5, 'rgba(0,0,0,0)');
+ gradient.addColorStop(1, 'rgba(0,0,0,0)');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, 0, 50, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.gradient.alpha
+ desc: Shadows are drawn correctly for partially-transparent gradient fills
+ code: |
+ var gradient = ctx.createLinearGradient(0, 0, 100, 0);
+ gradient.addColorStop(0, 'rgba(255,0,0,0.5)');
+ gradient.addColorStop(1, 'rgba(255,0,0,0.5)');
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#00f';
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ t.done();
+
+- name: 2d.shadow.transform.1
+ desc: Shadows take account of transformations
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.translate(100, 100);
+ ctx.fillRect(-100, -150, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.transform.2
+ desc: Shadow offsets are not affected by transformations
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowOffsetY = 50;
+ ctx.shadowColor = '#0f0';
+ ctx.rotate(Math.PI)
+ ctx.fillRect(-100, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.alpha.1
+ desc: Shadow color alpha components are used
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = 'rgba(255, 0, 0, 0.01)';
+ ctx.shadowOffsetY = 50;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255 +/- 4;
+ t.done();
+
+- name: 2d.shadow.alpha.2
+ desc: Shadow color alpha components are used
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.shadowColor = 'rgba(0, 0, 255, 0.5)';
+ ctx.shadowOffsetY = 50;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ t.done();
+
+- name: 2d.shadow.alpha.3
+ desc: Shadows are affected by globalAlpha
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00'; // (work around broken Firefox globalAlpha caching)
+ ctx.shadowColor = '#00f';
+ ctx.shadowOffsetY = 50;
+ ctx.globalAlpha = 0.5;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ t.done();
+
+- name: 2d.shadow.alpha.4
+ desc: Shadows with alpha components are correctly affected by globalAlpha
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00'; // (work around broken Firefox globalAlpha caching)
+ ctx.shadowColor = 'rgba(0, 0, 255, 0.707)';
+ ctx.shadowOffsetY = 50;
+ ctx.globalAlpha = 0.707;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ t.done();
+
+- name: 2d.shadow.alpha.5
+ desc: Shadows of shapes with alpha components are drawn correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = 'rgba(64, 0, 0, 0.5)';
+ ctx.shadowColor = '#00f';
+ ctx.shadowOffsetY = 50;
+ ctx.fillRect(0, -50, 100, 50);
+ @assert pixel 50,25 ==~ 127,0,127,255;
+ t.done();
+
+- name: 2d.shadow.composite.1
+ desc: Shadows are drawn using globalCompositeOperation
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = 'xor';
+ ctx.shadowColor = '#f00';
+ ctx.shadowOffsetX = 100;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, 0, 200, 50);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.composite.2
+ desc: Shadows are drawn using globalCompositeOperation
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = 'xor';
+ ctx.shadowColor = '#f00';
+ ctx.shadowBlur = 1;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-10, -10, 120, 70);
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
+
+- name: 2d.shadow.composite.3
+ desc: Areas outside shadows are drawn correctly with destination-out
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.globalCompositeOperation = 'destination-out';
+ ctx.shadowColor = '#f00';
+ ctx.shadowBlur = 10;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(200, 0, 100, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 50,25 ==~ 0,255,0,255;
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/text.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/text.yaml
new file mode 100644
index 0000000000..80c54ca6b6
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/text.yaml
@@ -0,0 +1,1478 @@
+- name: 2d.text.font.parse.basic
+ code: |
+ ctx.font = '20px serif';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20PX SERIF';
+ @assert ctx.font === '20px serif'; @moz-todo
+ t.done();
+
+- name: 2d.text.font.parse.tiny
+ code: |
+ ctx.font = '1px sans-serif';
+ @assert ctx.font === '1px sans-serif';
+ t.done();
+
+- name: 2d.text.font.parse.complex
+ code: |
+ ctx.font = 'small-caps italic 400 12px/2 Unknown Font, sans-serif';
+ @assert ctx.font === 'italic small-caps 12px "Unknown Font", sans-serif'; @moz-todo
+ t.done();
+
+- name: 2d.text.font.parse.family
+ code: |
+ ctx.font = '20px cursive,fantasy,monospace,sans-serif,serif,UnquotedFont,"QuotedFont\\\\\\","';
+ @assert ctx.font === '20px cursive, fantasy, monospace, sans-serif, serif, UnquotedFont, "QuotedFont\\\\\\","';
+ t.done();
+
+ # TODO:
+ # 2d.text.font.parse.size.absolute
+ # xx-small x-small small medium large x-large xx-large
+ # 2d.text.font.parse.size.relative
+ # smaller larger
+ # 2d.text.font.parse.size.length.relative
+ # em ex px
+ # 2d.text.font.parse.size.length.absolute
+ # in cm mm pt pc
+
+- name: 2d.text.font.parse.system
+ desc: System fonts must be computed to explicit values
+ code: |
+ ctx.font = 'message-box';
+ @assert ctx.font !== 'message-box';
+ t.done();
+
+- name: 2d.text.font.parse.invalid
+ code: |
+ ctx.font = '20px serif';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'bogus';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'inherit';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '10px {bogus}';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '10px initial';
+ @assert ctx.font === '20px serif'; @moz-todo
+
+ ctx.font = '20px serif';
+ ctx.font = '10px default';
+ @assert ctx.font === '20px serif'; @moz-todo
+
+ ctx.font = '20px serif';
+ ctx.font = '10px inherit';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '10px revert';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'var(--x)';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = 'var(--x, 10px serif)';
+ @assert ctx.font === '20px serif';
+
+ ctx.font = '20px serif';
+ ctx.font = '1em serif; background: green; margin: 10px';
+ @assert ctx.font === '20px serif';
+ t.done();
+
+- name: 2d.text.font.default
+ code: |
+ @assert ctx.font === '10px sans-serif';
+ t.done();
+
+- name: 2d.text.font.relative_size
+ code: |
+ ctx.font = '1em sans-serif';
+ @assert ctx.font === '10px sans-serif';
+ t.done();
+
+- name: 2d.text.align.valid
+ code: |
+ ctx.textAlign = 'start';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'end';
+ @assert ctx.textAlign === 'end';
+
+ ctx.textAlign = 'left';
+ @assert ctx.textAlign === 'left';
+
+ ctx.textAlign = 'right';
+ @assert ctx.textAlign === 'right';
+
+ ctx.textAlign = 'center';
+ @assert ctx.textAlign === 'center';
+ t.done();
+
+- name: 2d.text.align.invalid
+ code: |
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'bogus';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'END';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'end ';
+ @assert ctx.textAlign === 'start';
+
+ ctx.textAlign = 'start';
+ ctx.textAlign = 'end\0';
+ @assert ctx.textAlign === 'start';
+ t.done();
+
+- name: 2d.text.align.default
+ code: |
+ @assert ctx.textAlign === 'start';
+ t.done();
+
+
+- name: 2d.text.baseline.valid
+ code: |
+ ctx.textBaseline = 'top';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'hanging';
+ @assert ctx.textBaseline === 'hanging';
+
+ ctx.textBaseline = 'middle';
+ @assert ctx.textBaseline === 'middle';
+
+ ctx.textBaseline = 'alphabetic';
+ @assert ctx.textBaseline === 'alphabetic';
+
+ ctx.textBaseline = 'ideographic';
+ @assert ctx.textBaseline === 'ideographic';
+
+ ctx.textBaseline = 'bottom';
+ @assert ctx.textBaseline === 'bottom';
+ t.done();
+
+- name: 2d.text.baseline.invalid
+ code: |
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'bogus';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'MIDDLE';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'middle ';
+ @assert ctx.textBaseline === 'top';
+
+ ctx.textBaseline = 'top';
+ ctx.textBaseline = 'middle\0';
+ @assert ctx.textBaseline === 'top';
+ t.done();
+
+- name: 2d.text.baseline.default
+ code: |
+ @assert ctx.textBaseline === 'alphabetic';
+ t.done();
+
+- name: 2d.text.draw.fill.basic
+ desc: fillText draws filled text
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('PASS', 5, 35);
+ t.done();
+ expected: &passfill |
+ size 100 50
+ cr.set_source_rgb(0, 0, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ cr.set_source_rgb(0, 1, 0)
+ cr.select_font_face("Arial")
+ cr.set_font_size(35)
+ cr.translate(5, 35)
+ cr.text_path("PASS")
+ cr.fill()
+
+- name: 2d.text.draw.fill.unaffected
+ desc: fillText does not start a new path or subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('FAIL', 5, 35);
+
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.text.draw.fill.rtl
+ desc: fillText respects Right-To-Left Override characters
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.strokeStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('\u202eFAIL \xa0 \xa0 SSAP', 5, 35);
+ t.done();
+ expected: *passfill
+- name: 2d.text.draw.fill.maxWidth.large
+ desc: fillText handles maxWidth correctly
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('PASS', 5, 35, 200);
+ t.done();
+ expected: *passfill
+- name: 2d.text.draw.fill.maxWidth.small
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', -100, 35, 90);
+ _assertGreen(ctx, 100, 50);
+ t.done();
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.zero
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', 5, 35, 0);
+ _assertGreen(ctx, 100, 50);
+ t.done();
+ expected: green
+ timeout: long
+
+- name: 2d.text.draw.fill.maxWidth.negative
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', 5, 35, -1);
+ _assertGreen(ctx, 100, 50);
+ t.done();
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.NaN
+ desc: fillText handles maxWidth correctly
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.font = '35px Arial, sans-serif';
+ ctx.fillText('fail fail fail fail fail', 5, 35, NaN);
+ _assertGreen(ctx, 100, 50);
+ t.done();
+ expected: green
+
+- name: 2d.text.draw.stroke.basic
+ desc: strokeText draws stroked text
+ manual:
+ code: |
+ ctx.fillStyle = '#000';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.strokeStyle = '#0f0';
+ ctx.fillStyle = '#f00';
+ ctx.lineWidth = 1;
+ ctx.font = '35px Arial, sans-serif';
+ ctx.strokeText('PASS', 5, 35);
+ t.done();
+ expected: |
+ size 100 50
+ cr.set_source_rgb(0, 0, 0)
+ cr.rectangle(0, 0, 100, 50)
+ cr.fill()
+ cr.set_source_rgb(0, 1, 0)
+ cr.select_font_face("Arial")
+ cr.set_font_size(35)
+ cr.set_line_width(1)
+ cr.translate(5, 35)
+ cr.text_path("PASS")
+ cr.stroke()
+
+- name: 2d.text.draw.stroke.unaffected
+ desc: strokeText does not start a new path or subpath
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+
+ ctx.moveTo(0, 0);
+ ctx.lineTo(100, 0);
+
+ ctx.font = '35px Arial, sans-serif';
+ ctx.strokeStyle = '#f00';
+ ctx.strokeText('FAIL', 5, 35);
+
+ ctx.lineTo(100, 50);
+ ctx.lineTo(0, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 5,45 == 0,255,0,255;
+ t.done();
+ expected: green
+
+- name: 2d.text.draw.kern.consistent
+ desc: Stroked and filled text should have exactly the same kerning so it overlaps
+ manual:
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.strokeStyle = '#0f0';
+ ctx.lineWidth = 3;
+ ctx.font = '20px Arial, sans-serif';
+ ctx.fillText('VAVAVAVAVAVAVA', -50, 25);
+ ctx.fillText('ToToToToToToTo', -50, 45);
+ ctx.strokeText('VAVAVAVAVAVAVA', -50, 25);
+ ctx.strokeText('ToToToToToToTo', -50, 45);
+ t.done();
+ expected: green
+
+# CanvasTest is:
+# A = (0, 0) to (1em, 0.75em) (above baseline)
+# B = (0, 0) to (1em, -0.25em) (below baseline)
+# C = (0, -0.25em) to (1em, 0.75em) (the em square) plus some Xs above and below
+# D = (0, -0.25em) to (1em, 0.75em) (the em square) plus some Xs left and right
+# E = (0, -0.25em) to (1em, 0.75em) (the em square)
+# space = empty, 1em wide
+#
+# At 50px, "E" will fill the canvas vertically
+# At 67px, "A" will fill the canvas vertically
+#
+# Ideographic baseline is 0.125em above alphabetic
+# Mathematical baseline is 0.375em above alphabetic
+# Hanging baseline is 0.500em above alphabetic
+
+- name: 2d.text.draw.fill.maxWidth.fontface
+ desc: fillText works on @font-face fonts
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillText('EEEE', -50, 37.5, 40);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.fill.maxWidth.bound
+ desc: fillText handles maxWidth based on line size, not bounding box size
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('DD', 0, 37.5, 100);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.fontface
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '67px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('AA', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.fontface.repeat
+ desc: Draw with the font immediately, then wait a bit until and draw again. (This
+ crashes some version of WebKit.)
+ fonts:
+ - CanvasTest
+ fonthack: 0
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.font = '67px CanvasTest';
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('AA', 0, 50);
+ ctx.fillText('AA', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.fontface.notinpage
+ desc: '@font-face fonts should work even if they are not used in the page'
+ fonts:
+ - CanvasTest
+ fonthack: 0
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '67px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('AA', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.baseline.top
+ desc: textBaseline top is the top of the em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'top';
+ ctx.fillText('CC', 0, 0);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.baseline.bottom
+ desc: textBaseline bottom is the bottom of the em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'bottom';
+ ctx.fillText('CC', 0, 50);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.baseline.middle
+ desc: textBaseline middle is the middle of the em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'middle';
+ ctx.fillText('CC', 0, 25);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.baseline.alphabetic
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'alphabetic';
+ ctx.fillText('CC', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.baseline.ideographic
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'ideographic';
+ ctx.fillText('CC', 0, 31.25);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 95,45 ==~ 0,255,0,255; @moz-todo
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.baseline.hanging
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textBaseline = 'hanging';
+ ctx.fillText('CC', 0, 12.5);
+ @assert pixel 5,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 95,5 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.left
+ desc: textAlign left is the left of the first em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'left';
+ ctx.fillText('DD', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.right
+ desc: textAlign right is the right of the last em square (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'right';
+ ctx.fillText('DD', 100, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.start.ltr
+ desc: textAlign start with ltr is the left edge
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'start';
+ ctx.fillText('DD', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.start.rtl
+ desc: textAlign start with rtl is the right edge
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'rtl';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'start';
+ ctx.fillText('DD', 100, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.end.ltr
+ desc: textAlign end with ltr is the right edge
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'end';
+ ctx.fillText('DD', 100, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.end.rtl
+ desc: textAlign end with rtl is the left edge
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'rtl';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'end';
+ ctx.fillText('DD', 0, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.align.center
+ desc: textAlign center is the center of the em squares (not the bounding box)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'center';
+ ctx.fillText('DD', 50, 37.5);
+ @assert pixel 5,5 ==~ 0,255,0,255;
+ @assert pixel 95,5 ==~ 0,255,0,255;
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ @assert pixel 5,45 ==~ 0,255,0,255;
+ @assert pixel 95,45 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+
+- name: 2d.text.draw.space.basic
+ desc: U+0020 is rendered the correct size (1em wide)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E EE', -100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.space
+ desc: Space characters are converted to U+0020, and collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E EE', -100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.other
+ desc: Space characters are converted to U+0020, and collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E \x09\x0a\x0c\x0d \x09\x0a\x0c\x0dEE', -100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.nonspace
+ desc: Non-space characters are not converted to U+0020 and collapsed
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText('E\x0b EE', -150, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.start
+ desc: Space characters at the start of a line are collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillText(' EE', 0, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255; @moz-todo
+ @assert pixel 75,25 ==~ 0,255,0,255;
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.draw.space.collapse.end
+ desc: Space characters at the end of a line are collapsed (per CSS)
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.textAlign = 'right';
+ ctx.fillText('EE ', 100, 37.5);
+ @assert pixel 25,25 ==~ 0,255,0,255;
+ @assert pixel 75,25 ==~ 0,255,0,255; @moz-todo
+ }).then(t_pass, t_fail);
+ expected: green
+
+- name: 2d.text.measure.width.basic
+ desc: The width of character is same as font used for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ @assert ctx.measureText('A').width === 50;
+ @assert ctx.measureText('AA').width === 100;
+ @assert ctx.measureText('ABCD').width === 200;
+
+ ctx.font = '100px CanvasTest';
+ @assert ctx.measureText('A').width === 100;
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.measure.width.empty
+ desc: The empty string has zero width for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ @assert ctx.measureText("").width === 0;
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.measure.width.space
+ desc: Space characters are converted to U+0020 and collapsed (per CSS) for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ @assert ctx.measureText('A B').width === 150;
+ @assert ctx.measureText('A B').width === 200;
+ @assert ctx.measureText('A \x09\x0a\x0c\x0d \x09\x0a\x0c\x0dB').width === 150; @moz-todo
+ @assert ctx.measureText('A \x0b B').width >= 200;
+
+ @assert ctx.measureText(' AB').width === 100; @moz-todo
+ @assert ctx.measureText('AB ').width === 100; @moz-todo
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.measure.advances
+ desc: Testing width advances for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ // Some platforms may return '-0'.
+ @assert Math.abs(ctx.measureText('Hello').advances[0]) === 0;
+ // Different platforms may render text slightly different.
+ @assert ctx.measureText('Hello').advances[1] >= 36;
+ @assert ctx.measureText('Hello').advances[2] >= 58;
+ @assert ctx.measureText('Hello').advances[3] >= 70;
+ @assert ctx.measureText('Hello').advances[4] >= 80;
+
+ var tm = ctx.measureText('Hello');
+ @assert ctx.measureText('Hello').advances[0] === tm.advances[0];
+ @assert ctx.measureText('Hello').advances[1] === tm.advances[1];
+ @assert ctx.measureText('Hello').advances[2] === tm.advances[2];
+ @assert ctx.measureText('Hello').advances[3] === tm.advances[3];
+ @assert ctx.measureText('Hello').advances[4] === tm.advances[4];
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.measure.actualBoundingBox
+ desc: Testing actualBoundingBox for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ ctx.baseline = 'alphabetic'
+ // Some platforms may return '-0'.
+ @assert Math.abs(ctx.measureText('A').actualBoundingBoxLeft) === 0;
+ // Different platforms may render text slightly different.
+ @assert ctx.measureText('A').actualBoundingBoxRight >= 50;
+ @assert ctx.measureText('A').actualBoundingBoxAscent >= 35;
+ @assert Math.abs(ctx.measureText('A').actualBoundingBoxDescent) === 0;
+
+ @assert ctx.measureText('D').actualBoundingBoxLeft >= 48;
+ @assert ctx.measureText('D').actualBoundingBoxLeft <= 52;
+ @assert ctx.measureText('D').actualBoundingBoxRight >= 75;
+ @assert ctx.measureText('D').actualBoundingBoxRight <= 80;
+ @assert ctx.measureText('D').actualBoundingBoxAscent >= 35;
+ @assert ctx.measureText('D').actualBoundingBoxAscent <= 40;
+ @assert ctx.measureText('D').actualBoundingBoxDescent >= 12;
+ @assert ctx.measureText('D').actualBoundingBoxDescent <= 15;
+
+ @assert Math.abs(ctx.measureText('ABCD').actualBoundingBoxLeft) === 0;
+ @assert ctx.measureText('ABCD').actualBoundingBoxRight >= 200;
+ @assert ctx.measureText('ABCD').actualBoundingBoxAscent >= 85;
+ @assert ctx.measureText('ABCD').actualBoundingBoxDescent >= 37;
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.measure.fontBoundingBox
+ desc: Testing fontBoundingBox for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert ctx.measureText('A').fontBoundingBoxAscent === 85;
+ @assert ctx.measureText('A').fontBoundingBoxDescent === 39;
+
+ @assert ctx.measureText('ABCD').fontBoundingBoxAscent === 85;
+ @assert ctx.measureText('ABCD').fontBoundingBoxDescent === 39;
+ }).then(t_pass, t_fail);
+- name: 2d.text.measure.emHeights
+ desc: Testing emHeights for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert ctx.measureText('A').emHeightAscent === 37.5;
+ @assert ctx.measureText('A').emHeightDescent === 12.5;
+ @assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 50;
+
+ @assert ctx.measureText('ABCD').emHeightAscent === 37.5;
+ @assert ctx.measureText('ABCD').emHeightDescent === 12.5;
+ @assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 50;
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.measure.baselines
+ desc: Testing baselines for OffscreenCanvas
+ fonts:
+ - CanvasTest
+ code: |
+ var f = new FontFace("CanvasTest", "url('/fonts/CanvasTest.ttf')");
+ let fonts = (self.fonts ? self.fonts : document.fonts);
+ f.load();
+ fonts.add(f);
+ fonts.ready.then(function() {
+ ctx.font = '50px CanvasTest';
+ ctx.direction = 'ltr';
+ ctx.align = 'left'
+ @assert Math.abs(ctx.measureText('A').getBaselines().alphabetic) === 0;
+ @assert ctx.measureText('A').getBaselines().ideographic === -39;
+ @assert ctx.measureText('A').getBaselines().hanging === 68;
+
+ @assert Math.abs(ctx.measureText('ABCD').getBaselines().alphabetic) === 0;
+ @assert ctx.measureText('ABCD').getBaselines().ideographic === -39;
+ @assert ctx.measureText('ABCD').getBaselines().hanging === 68;
+ }).then(t_pass, t_fail);
+
+- name: 2d.text.drawing.style.spacing
+ desc: Testing letter spacing and word spacing
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+
+ ctx.letterSpacing = '3px';
+ @assert ctx.letterSpacing === '3px';
+ @assert ctx.wordSpacing === '0px';
+
+ ctx.wordSpacing = '5px';
+ @assert ctx.letterSpacing === '3px';
+ @assert ctx.wordSpacing === '5px';
+
+ ctx.letterSpacing = '-1px';
+ ctx.wordSpacing = '-1px';
+ @assert ctx.letterSpacing === '-1px';
+ @assert ctx.wordSpacing === '-1px';
+
+ ctx.letterSpacing = '1PX';
+ ctx.wordSpacing = '1EM';
+ @assert ctx.letterSpacing === '1px';
+ @assert ctx.wordSpacing === '1em';
+ t.done();
+
+- name: 2d.text.drawing.style.nonfinite.spacing
+ desc: Testing letter spacing and word spacing with nonfinite inputs
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+
+ function test_word_spacing(value) {
+ ctx.wordSpacing = value;
+ ctx.letterSpacing = value;
+ @assert ctx.wordSpacing === '0px';
+ @assert ctx.letterSpacing === '0px';
+ }
+ @nonfinite test_word_spacing(<0 NaN Infinity -Infinity>);
+
+ t.done();
+
+- name: 2d.text.drawing.style.invalid.spacing
+ desc: Testing letter spacing and word spacing with invalid units
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+
+ function test_word_spacing(value) {
+ ctx.wordSpacing = value;
+ ctx.letterSpacing = value;
+ @assert ctx.wordSpacing === '0px';
+ @assert ctx.letterSpacing === '0px';
+ }
+ @nonfinite test_word_spacing(< '0s' '1min' '1deg' '1pp'>);
+
+ t.done();
+
+- name: 2d.text.drawing.style.letterSpacing.measure
+ desc: Testing letter spacing and word spacing
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ var width_normal = ctx.measureText('Hello World').width;
+
+ function test_letter_spacing(value, difference_spacing, epsilon) {
+ ctx.letterSpacing = value;
+ @assert ctx.letterSpacing === value;
+ @assert ctx.wordSpacing === '0px';
+ width_with_letter_spacing = ctx.measureText('Hello World').width;
+ assert_approx_equals(width_with_letter_spacing, width_normal + difference_spacing, epsilon, "letter spacing doesn't work.");
+ }
+
+ // The first value is the letter Spacing to be set, the second value the
+ // change in length of string 'Hello World', note that there are 11 letters
+ // in 'hello world', so the length difference is always letterSpacing * 11.
+ // and the third value is the acceptable differencee for the length change,
+ // note that unit such as 1cm/1mm doesn't map to an exact pixel value.
+ test_cases = [['3px', 33, 0],
+ ['5px', 55, 0],
+ ['-2px', -22, 0],
+ ['1em', 110, 0],
+ ['1in', 1056, 0],
+ ['-0.1cm', -41.65, 0.2],
+ ['-0.6mm', -24,95, 0.2]]
+
+ for (const test_case of test_cases) {
+ test_letter_spacing(test_case[0], test_case[1], test_case[2]);
+ }
+ t.done();
+
+- name: 2d.text.drawing.style.wordSpacing.measure
+ desc: Testing if word spacing is working properly
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ var width_normal = ctx.measureText('Hello World, again').width;
+
+ function test_word_spacing(value, difference_spacing, epsilon) {
+ ctx.wordSpacing = value;
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === value;
+ width_with_word_spacing = ctx.measureText('Hello World, again').width;
+ assert_approx_equals(width_with_word_spacing, width_normal + difference_spacing, epsilon, "word spacing doesn't work.");
+ }
+
+ // The first value is the word Spacing to be set, the second value the
+ // change in length of string 'Hello World', note that there are 2 words
+ // in 'Hello World, again', so the length difference is always wordSpacing * 2.
+ // and the third value is the acceptable differencee for the length change,
+ // note that unit such as 1cm/1mm doesn't map to an exact pixel value.
+ test_cases = [['3px', 6, 0],
+ ['5px', 10, 0],
+ ['-2px', -4, 0],
+ ['1em', 20, 0],
+ ['1in', 192, 0],
+ ['-0.1cm', -7.57, 0.2],
+ ['-0.6mm', -4.54, 0.2]]
+
+ for (const test_case of test_cases) {
+ test_word_spacing(test_case[0], test_case[1], test_case[2]);
+ }
+ t.done();
+
+- name: 2d.text.drawing.style.letterSpacing.change.font
+ desc: Set letter spacing and word spacing to font dependent value and verify it works after font change.
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ // Get the width for 'Hello World' at default size, 10px.
+ var width_normal = ctx.measureText('Hello World').width;
+
+ ctx.letterSpacing = '1em';
+ @assert ctx.letterSpacing === '1em';
+ // 1em = 10px. Add 10px after each letter in "Hello World",
+ // makes it 110px longer.
+ var width_with_spacing = ctx.measureText('Hello World').width;
+ @assert width_with_spacing === width_normal + 110;
+
+ // Changing font to 20px. Without resetting the spacing, 1em letterSpacing
+ // is now 20px, so it's suppose to be 220px longer without any letterSpacing set.
+ ctx.font = '20px serif';
+ width_with_spacing = ctx.measureText('Hello World').width;
+ // Now calculate the reference spacing for "Hello World" with no spacing.
+ ctx.letterSpacing = '0em';
+ width_normal = ctx.measureText('Hello World').width;
+ @assert width_with_spacing === width_normal + 220;
+ t.done();
+
+- name: 2d.text.drawing.style.wordSpacing.change.font
+ desc: Set word spacing and word spacing to font dependent value and verify it works after font change.
+ code: |
+ @assert ctx.letterSpacing === '0px';
+ @assert ctx.wordSpacing === '0px';
+ // Get the width for 'Hello World, again' at default size, 10px.
+ var width_normal = ctx.measureText('Hello World, again').width;
+
+ ctx.wordSpacing = '1em';
+ @assert ctx.wordSpacing === '1em';
+ // 1em = 10px. Add 10px after each word in "Hello World, again",
+ // makes it 20px longer.
+ var width_with_spacing = ctx.measureText('Hello World, again').width;
+ @assert width_with_spacing === width_normal + 20;
+
+ // Changing font to 20px. Without resetting the spacing, 1em wordSpacing
+ // is now 20px, so it's suppose to be 40px longer without any wordSpacing set.
+ ctx.font = '20px serif';
+ width_with_spacing = ctx.measureText('Hello World, again').width;
+ // Now calculate the reference spacing for "Hello World, again" with no spacing.
+ ctx.wordSpacing = '0em';
+ width_normal = ctx.measureText('Hello World, again').width;
+ @assert width_with_spacing === width_normal + 40;
+ t.done();
+
+- name: 2d.text.drawing.style.fontKerning
+ desc: Testing basic functionalities of fontKerning for canvas
+ code: |
+ @assert ctx.fontKerning === "auto";
+ ctx.fontKerning = "normal";
+ @assert ctx.fontKerning === "normal";
+ width_normal = ctx.measureText("TAWATAVA").width;
+ ctx.fontKerning = "none";
+ @assert ctx.fontKerning === "none";
+ width_none = ctx.measureText("TAWATAVA").width;
+ @assert width_normal < width_none;
+ t.done();
+
+- name: 2d.text.drawing.style.fontKerning.with.uppercase
+ desc: Testing basic functionalities of fontKerning for canvas
+ code: |
+ @assert ctx.fontKerning === "auto";
+ ctx.fontKerning = "Normal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "normal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "noRmal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "NoRMal";
+ @assert ctx.fontKerning === "normal";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "NORMAL";
+ @assert ctx.fontKerning === "normal";
+
+ ctx.fontKerning = "None";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "none";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "nOne";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "nonE";
+ @assert ctx.fontKerning === "none";
+ ctx.fontKerning = "auto";
+ ctx.fontKerning = "NONE";
+ @assert ctx.fontKerning === "none";
+ t.done();
+
+- name: 2d.text.drawing.style.fontVariant.settings
+ desc: Testing basic functionalities of fontKerning for canvas
+ code: |
+ // Setting fontVariantCaps with lower cases
+ @assert ctx.fontVariantCaps === "normal";
+
+ ctx.fontVariantCaps = "normal";
+ @assert ctx.fontVariantCaps === "normal";
+
+ ctx.fontVariantCaps = "small-caps";
+ @assert ctx.fontVariantCaps === "small-caps";
+
+ ctx.fontVariantCaps = "all-small-caps";
+ @assert ctx.fontVariantCaps === "all-small-caps";
+
+ ctx.fontVariantCaps = "petite-caps";
+ @assert ctx.fontVariantCaps === "petite-caps";
+
+ ctx.fontVariantCaps = "all-petite-caps";
+ @assert ctx.fontVariantCaps === "all-petite-caps";
+
+ ctx.fontVariantCaps = "unicase";
+ @assert ctx.fontVariantCaps === "unicase";
+
+ ctx.fontVariantCaps = "titling-caps";
+ @assert ctx.fontVariantCaps === "titling-caps";
+
+ // Setting fontVariantCaps with lower cases and upper cases word.
+ ctx.fontVariantCaps = "nORmal";
+ @assert ctx.fontVariantCaps === "normal";
+
+ ctx.fontVariantCaps = "smaLL-caps";
+ @assert ctx.fontVariantCaps === "small-caps";
+
+ ctx.fontVariantCaps = "all-small-CAPS";
+ @assert ctx.fontVariantCaps === "all-small-caps";
+
+ ctx.fontVariantCaps = "pEtitE-caps";
+ @assert ctx.fontVariantCaps === "petite-caps";
+
+ ctx.fontVariantCaps = "All-Petite-Caps";
+ @assert ctx.fontVariantCaps === "all-petite-caps";
+
+ ctx.fontVariantCaps = "uNIcase";
+ @assert ctx.fontVariantCaps === "unicase";
+
+ ctx.fontVariantCaps = "titling-CAPS";
+ @assert ctx.fontVariantCaps === "titling-caps";
+
+ // Setting fontVariantCaps with non-existing font variant.
+ ctx.fontVariantCaps = "abcd";
+ @assert ctx.fontVariantCaps === "titling-caps";
+ t.done();
+
+- name: 2d.text.drawing.style.textRendering.settings
+ desc: Testing basic functionalities of textRendering in Canvas
+ code: |
+ // Setting textRendering with lower cases
+ @assert ctx.textRendering === "auto";
+
+ ctx.textRendering = "auto";
+ @assert ctx.textRendering === "auto";
+
+ ctx.textRendering = "optimizespeed";
+ @assert ctx.textRendering === "optimizeSpeed";
+
+ ctx.textRendering = "optimizelegibility";
+ @assert ctx.textRendering === "optimizeLegibility";
+
+ ctx.textRendering = "geometricprecision";
+ @assert ctx.textRendering === "geometricPrecision";
+
+ // Setting textRendering with lower cases and upper cases word.
+ ctx.textRendering = "aUto";
+ @assert ctx.textRendering === "auto";
+
+ ctx.textRendering = "OPtimizeSpeed";
+ @assert ctx.textRendering === "optimizeSpeed";
+
+ ctx.textRendering = "OPtimizELEgibility";
+ @assert ctx.textRendering === "optimizeLegibility";
+
+ ctx.textRendering = "GeometricPrecision";
+ @assert ctx.textRendering === "geometricPrecision";
+
+ // Setting textRendering with non-existing font variant.
+ ctx.textRendering = "abcd";
+ @assert ctx.textRendering === "geometricPrecision";
+ t.done();
+
+- name: 2d.text.drawing.style.measure.rtl.text
+ desc: Measurement should follow canvas direction instead text direction
+ code: |
+ metrics = ctx.measureText('اَلْعَرَبِيَّةُ');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+ t.done();
+
+- name: 2d.text.drawing.style.measure.textAlign
+ desc: Measurement should be related to textAlignment
+ code: |
+ ctx.textAlign = "right";
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft > metrics.actualBoundingBoxRight;
+
+ ctx.textAlign = "left"
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+ t.done();
+
+- name: 2d.text.drawing.style.measure.direction
+ desc: Measurement should follow text direction
+ code: |
+ ctx.direction = "ltr";
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft < metrics.actualBoundingBoxRight;
+
+ ctx.direction = "rtl";
+ metrics = ctx.measureText('hello');
+ @assert metrics.actualBoundingBoxLeft > metrics.actualBoundingBoxRight;
+ t.done();
+
+# TODO: shadows, alpha, composite, clip
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-canvas-state.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-canvas-state.yaml
new file mode 100644
index 0000000000..afb4cf956c
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-canvas-state.yaml
@@ -0,0 +1,92 @@
+- name: 2d.state.saverestore.transformation
+ desc: save()/restore() affects the current transformation matrix
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.translate(200, 0);
+ ctx.restore();
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(-200, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.state.saverestore.clip
+ desc: save()/restore() affects the clipping path
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.rect(0, 0, 1, 1);
+ ctx.clip();
+ ctx.restore();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.state.saverestore.path
+ desc: save()/restore() does not affect the current path
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.rect(0, 0, 100, 50);
+ ctx.restore();
+ ctx.fillStyle = '#0f0';
+ ctx.fill();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.state.saverestore.bitmap
+ desc: save()/restore() does not affect the current bitmap
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.restore();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.state.saverestore.stack
+ desc: save()/restore() can be nested as a stack
+ code: |
+ ctx.lineWidth = 1;
+ ctx.save();
+ ctx.lineWidth = 2;
+ ctx.save();
+ ctx.lineWidth = 3;
+ @assert ctx.lineWidth === 3;
+ ctx.restore();
+ @assert ctx.lineWidth === 2;
+ ctx.restore();
+ @assert ctx.lineWidth === 1;
+ t.done();
+
+- name: 2d.state.saverestore.stackdepth
+ desc: save()/restore() stack depth is not unreasonably limited
+ code: |
+ var limit = 512;
+ for (var i = 1; i < limit; ++i)
+ {
+ ctx.save();
+ ctx.lineWidth = i;
+ }
+ for (var i = limit-1; i > 0; --i)
+ {
+ @assert ctx.lineWidth === i;
+ ctx.restore();
+ }
+ t.done();
+
+- name: 2d.state.saverestore.underflow
+ desc: restore() with an empty stack has no effect
+ code: |
+ for (var i = 0; i < 16; ++i)
+ ctx.restore();
+ ctx.lineWidth = 0.5;
+ ctx.restore();
+ @assert ctx.lineWidth === 0.5;
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-offscreen-canvas.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-offscreen-canvas.yaml
new file mode 100644
index 0000000000..0bef18bf9d
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/the-offscreen-canvas.yaml
@@ -0,0 +1,275 @@
+- name: 2d.canvas.reference
+ desc: canvas refers back to its canvas
+ code: |
+ @assert ctx.canvas === canvas;
+ t.done();
+
+- name: 2d.canvas.readonly
+ desc: canvas is readonly
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ var d = ctx.canvas;
+ @assert offscreenCanvas2 !== d;
+ ctx.canvas = offscreenCanvas2;
+ @assert ctx.canvas === d;
+ t.done();
+
+- name: 2d.getcontext.exists
+ desc: The 2D context is implemented
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert offscreenCanvas2.getContext('2d') !== null;
+ t.done();
+
+- name: 2d.getcontext.extraargs.create
+ desc: The 2D context doesn't throw with extra getContext arguments (new context)
+ code: |
+ @assert (new OffscreenCanvas(100, 50)).getContext('2d', false, {}, [], 1, "2") !== null;
+ @assert (new OffscreenCanvas(100, 50)).getContext('2d', 123) !== null;
+ @assert (new OffscreenCanvas(100, 50)).getContext('2d', "test") !== null;
+ @assert (new OffscreenCanvas(100, 50)).getContext('2d', undefined) !== null;
+ @assert (new OffscreenCanvas(100, 50)).getContext('2d', null) !== null;
+ @assert (new OffscreenCanvas(100, 50)).getContext('2d', Symbol.hasInstance) !== null;
+ t.done();
+
+- name: 2d.getcontext.extraargs.cache
+ desc: The 2D context doesn't throw with extra getContext arguments (cached)
+ code: |
+ @assert canvas.getContext('2d', false, {}, [], 1, "2") !== null;
+ @assert canvas.getContext('2d', 123) !== null;
+ @assert canvas.getContext('2d', "test") !== null;
+ @assert canvas.getContext('2d', undefined) !== null;
+ @assert canvas.getContext('2d', null) !== null;
+ @assert canvas.getContext('2d', Symbol.hasInstance) !== null;
+ t.done();
+
+- name: 2d.getcontext.unique
+ desc: getContext('2d') returns the same object
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert offscreenCanvas2.getContext('2d') === offscreenCanvas2.getContext('2d');
+ t.done();
+
+- name: 2d.getcontext.shared
+ desc: getContext('2d') returns objects which share canvas state
+ code: |
+ var ctx2 = canvas.getContext('2d');
+ ctx.fillStyle = '#f00';
+ ctx2.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: context.emptystring
+ desc: getContext with empty string returns null
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext("");
+ t.done();
+
+- name: context.unrecognised.badname
+ desc: getContext with unrecognised context name returns null
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext('This is not an implemented context in any real browser');
+ t.done();
+
+- name: context.unrecognised.badsuffix
+ desc: Context name "2d" plus a suffix is unrecognised
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext("2d#");
+ t.done();
+
+- name: context.unrecognised.nullsuffix
+ desc: Context name "2d" plus a "\0" suffix is unrecognised
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext("2d\0");
+ t.done();
+
+- name: context.unrecognised.unicode
+ desc: Context name which kind of looks like "2d" is unrecognised
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext("2\uFF44");
+ t.done();
+
+- name: context.casesensitive
+ desc: Context name "2D" is unrecognised; matching is case sensitive
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext('2D');
+ t.done();
+
+- name: context.arguments.missing
+ code: |
+ var offscreenCanvas2 = new OffscreenCanvas(100, 50);
+ @assert throws TypeError offscreenCanvas2.getContext(); @moz-todo
+ t.done();
+
+
+- name: initial.color
+ desc: Initial state is transparent black
+ code: |
+ @assert pixel 20,20 == 0,0,0,0;
+ t.done();
+
+- name: initial.reset.different
+ desc: Changing size resets canvas to transparent black
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ @assert pixel 20,20 == 255,0,0,255;
+ canvas.width = 50;
+ @assert pixel 20,20 == 0,0,0,0;
+ t.done();
+
+- name: initial.reset.same
+ desc: Setting size (not changing the value) resets canvas to transparent black
+ code: |
+ canvas.width = 100;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 50, 50);
+ @assert pixel 20,20 == 255,0,0,255;
+ canvas.width = 100;
+ @assert pixel 20,20 == 0,0,0,0;
+ t.done();
+
+- name: initial.reset.path
+ desc: Resetting the canvas state resets the current path
+ code: |
+ canvas.width = 100;
+ ctx.rect(0, 0, 100, 50);
+ canvas.width = 100;
+ ctx.fillStyle = '#f00';
+ ctx.fill();
+ @assert pixel 20,20 == 0,0,0,0;
+ t.done();
+
+- name: initial.reset.clip
+ desc: Resetting the canvas state resets the current clip region
+ code: |
+ canvas.width = 100;
+ ctx.rect(0, 0, 1, 1);
+ ctx.clip();
+ canvas.width = 100;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 20,20 == 0,255,0,255;
+ t.done();
+
+- name: initial.reset.transform
+ desc: Resetting the canvas state resets the current transformation matrix
+ code: |
+ canvas.width = 100;
+ ctx.scale(0.1, 0.1);
+ canvas.width = 100;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 20,20 == 0,255,0,255;
+ t.done();
+
+- name: initial.reset.gradient
+ desc: Resetting the canvas state does not invalidate any existing gradients
+ code: |
+ canvas.width = 50;
+ var g = ctx.createLinearGradient(0, 0, 100, 0);
+ g.addColorStop(0, '#0f0');
+ g.addColorStop(1, '#0f0');
+ canvas.width = 100;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: initial.reset.pattern
+ desc: Resetting the canvas state does not invalidate any existing patterns
+ code: |
+ canvas.width = 30;
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 30, 50);
+ var p = ctx.createPattern(canvas, 'repeat-x');
+ canvas.width = 100;
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = p;
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: size.attributes.idl.set.zero
+ desc: Setting width/height IDL attributes to 0
+ code: |
+ canvas.width = 0;
+ canvas.height = 0;
+ @assert canvas.width === 0;
+ @assert canvas.height === 0;
+ t.done();
+
+- name: size.attributes.idl
+ desc: Getting/setting width/height IDL attributes
+ webidl:
+ - es-unsigned-long
+ code: |
+ canvas.width = "100";
+ canvas.height = "100";
+ @assert canvas.width === 100;
+ @assert canvas.height === 100;
+ canvas.width = "+1.5e2";
+ canvas.height = "0x96";
+ @assert canvas.width === 150;
+ @assert canvas.height === 150;
+ canvas.width = 301.999;
+ canvas.height = 301.001;
+ @assert canvas.width === 301;
+ @assert canvas.height === 301;
+ @assert throws TypeError canvas.width = "400x";
+ @assert throws TypeError canvas.height = "foo";
+ @assert canvas.width === 301;
+ @assert canvas.height === 301;
+ t.done();
+
+- name: size.attributes.default
+ desc: Default width/height when attributes are missing
+ code: |
+ @assert canvas.width === 100;
+ @assert canvas.height === 50;
+ t.done();
+
+- name: size.attributes.reflect.setidl
+ desc: Setting IDL attributes updates IDL and content attributes
+ code: |
+ canvas.width = 120;
+ canvas.height = 60;
+ @assert canvas.width === 120;
+ @assert canvas.height === 60;
+ t.done();
+
+- name: size.attributes.reflect.setidlzero
+ desc: Setting IDL attributes to 0 updates IDL and content attributes
+ code: |
+ canvas.width = 0;
+ canvas.height = 0;
+ @assert canvas.width === 0;
+ @assert canvas.height === 0;
+ t.done();
+
+- name: size.large
+ notes: Not sure how reasonable this is, but the spec doesn't say there's an upper
+ limit on the size.
+ code: |
+ var n = 2147483647; // 2^31 - 1, which should be supported by any sensible definition of "long"
+ canvas.width = n;
+ canvas.height = n;
+ @assert canvas.width === n;
+ @assert canvas.height === n;
+ t.done();
+
+- name: 2d.text.setFont.mathFont
+ desc: crbug.com/1212190, make sure offscreencanvas doesn't crash with Math Font
+ code: |
+ ctx.font = "math serif";
+ t.done();
diff --git a/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/transformations.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/transformations.yaml
new file mode 100644
index 0000000000..efc00222bf
--- /dev/null
+++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/transformations.yaml
@@ -0,0 +1,320 @@
+- name: 2d.transformation.order
+ desc: Transformations are applied in the right order
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(2, 1);
+ ctx.rotate(Math.PI / 2);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, -50, 50, 50);
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.scale.basic
+ desc: scale() works
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(2, 4);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 12.5);
+ @assert pixel 90,40 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.scale.zero
+ desc: scale() with a scale factor of zero works
+ code: |
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.translate(50, 0);
+ ctx.scale(0, 1);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.restore();
+ ctx.save();
+ ctx.translate(0, 25);
+ ctx.scale(1, 0);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.restore();
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.scale.negative
+ desc: scale() with negative scale factors works
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.save();
+ ctx.scale(-1, 1);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-50, 0, 50, 50);
+ ctx.restore();
+ ctx.save();
+ ctx.scale(1, -1);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(50, -50, 50, 50);
+ ctx.restore();
+ @assert pixel 25,25 == 0,255,0,255;
+ @assert pixel 75,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.scale.large
+ desc: scale() with large scale factors works
+ notes: Not really that large at all, but it hits the limits in Firefox.
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(1e5, 1e5);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 1, 1);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.scale.nonfinite
+ desc: scale() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(100, 10);
+ @nonfinite ctx.scale(<0.1 Infinity -Infinity NaN>, <0.1 Infinity -Infinity NaN>);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.scale.multiple
+ desc: Multiple scale()s combine
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.scale(Math.sqrt(2), Math.sqrt(2));
+ ctx.scale(Math.sqrt(2), Math.sqrt(2));
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 25);
+ @assert pixel 90,40 == 0,255,0,255;
+ t.done();
+
+
+- name: 2d.transformation.rotate.zero
+ desc: rotate() by 0 does nothing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rotate(0);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.rotate.radians
+ desc: rotate() uses radians
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rotate(Math.PI); // should fail obviously if this is 3.1 degrees
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.rotate.direction
+ desc: rotate() is clockwise
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rotate(Math.PI / 2);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, -100, 50, 100);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.rotate.wrap
+ desc: rotate() wraps large positive values correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rotate(Math.PI * (1 + 4096)); // == pi (mod 2*pi)
+ // We need about pi +/- 0.001 in order to get correct-looking results
+ // 32-bit floats can store pi*4097 with precision 2^-10, so that should
+ // be safe enough on reasonable implementations
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,2 == 0,255,0,255;
+ @assert pixel 98,47 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.rotate.wrapnegative
+ desc: rotate() wraps large negative values correctly
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.rotate(-Math.PI * (1 + 4096));
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ @assert pixel 98,2 == 0,255,0,255;
+ @assert pixel 98,47 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.rotate.nonfinite
+ desc: rotate() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(100, 10);
+ @nonfinite ctx.rotate(<0.1 Infinity -Infinity NaN>);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.translate.basic
+ desc: translate() works
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(100, 50);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -50, 100, 50);
+ @assert pixel 90,40 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.translate.nonfinite
+ desc: translate() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(100, 10);
+ @nonfinite ctx.translate(<0.1 Infinity -Infinity NaN>, <0.1 Infinity -Infinity NaN>);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+
+- name: 2d.transformation.transform.identity
+ desc: transform() with the identity matrix does nothing
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.transform(1,0, 0,1, 0,0);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.transform.skewed
+ desc: transform() with skewy matrix transforms correctly
+ code: |
+ // Create green with a red square ring inside it
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(20, 10, 60, 30);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(40, 20, 20, 10);
+ // Draw a skewed shape to fill that gap, to make sure it is aligned correctly
+ ctx.transform(1,4, 2,3, 5,6);
+ // Post-transform coordinates:
+ // [[20,10],[80,10],[80,40],[20,40],[20,10],[40,20],[40,30],[60,30],[60,20],[40,20],[20,10]];
+ // Hence pre-transform coordinates:
+ var pts=[[-7.4,11.2],[-43.4,59.2],[-31.4,53.2],[4.6,5.2],[-7.4,11.2],
+ [-15.4,25.2],[-11.4,23.2],[-23.4,39.2],[-27.4,41.2],[-15.4,25.2],
+ [-7.4,11.2]];
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var i = 0; i < pts.length; ++i)
+ ctx.lineTo(pts[i][0], pts[i][1]);
+ ctx.fill();
+ @assert pixel 21,11 == 0,255,0,255;
+ @assert pixel 79,11 == 0,255,0,255;
+ @assert pixel 21,39 == 0,255,0,255;
+ @assert pixel 79,39 == 0,255,0,255;
+ @assert pixel 39,19 == 0,255,0,255;
+ @assert pixel 61,19 == 0,255,0,255;
+ @assert pixel 39,31 == 0,255,0,255;
+ @assert pixel 61,31 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.transform.multiply
+ desc: transform() multiplies the CTM
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.transform(1,2, 3,4, 5,6);
+ ctx.transform(-2,1, 3/2,-1/2, 1,-2);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.transform.nonfinite
+ desc: transform() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(100, 10);
+ @nonfinite ctx.transform(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();
+
+
+- name: 2d.transformation.setTransform.skewed
+ code: |
+ // Create green with a red square ring inside it
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(20, 10, 60, 30);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(40, 20, 20, 10);
+ // Draw a skewed shape to fill that gap, to make sure it is aligned correctly
+ ctx.setTransform(1,4, 2,3, 5,6);
+ // Post-transform coordinates:
+ // [[20,10],[80,10],[80,40],[20,40],[20,10],[40,20],[40,30],[60,30],[60,20],[40,20],[20,10]];
+ // Hence pre-transform coordinates:
+ var pts=[[-7.4,11.2],[-43.4,59.2],[-31.4,53.2],[4.6,5.2],[-7.4,11.2],
+ [-15.4,25.2],[-11.4,23.2],[-23.4,39.2],[-27.4,41.2],[-15.4,25.2],
+ [-7.4,11.2]];
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var i = 0; i < pts.length; ++i)
+ ctx.lineTo(pts[i][0], pts[i][1]);
+ ctx.fill();
+ @assert pixel 21,11 == 0,255,0,255;
+ @assert pixel 79,11 == 0,255,0,255;
+ @assert pixel 21,39 == 0,255,0,255;
+ @assert pixel 79,39 == 0,255,0,255;
+ @assert pixel 39,19 == 0,255,0,255;
+ @assert pixel 61,19 == 0,255,0,255;
+ @assert pixel 39,31 == 0,255,0,255;
+ @assert pixel 61,31 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.setTransform.multiple
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.setTransform(1/2,0, 0,1/2, 0,0);
+ ctx.setTransform(2,0, 0,2, 0,0);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(0, 0, 50, 25);
+ @assert pixel 75,35 == 0,255,0,255;
+ t.done();
+
+- name: 2d.transformation.setTransform.nonfinite
+ desc: setTransform() with Infinity/NaN is ignored
+ code: |
+ ctx.fillStyle = '#f00';
+ ctx.fillRect(0, 0, 100, 50);
+ ctx.translate(100, 10);
+ @nonfinite ctx.setTransform(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
+ ctx.fillStyle = '#0f0';
+ ctx.fillRect(-100, -10, 100, 50);
+ @assert pixel 50,25 == 0,255,0,255;
+ t.done();