UNPKG

fugafacere

Version:

A pure-JS implementation of the W3C's Canvas-2D Context API that can run on top of either Expo Graphics or a browser WebGL context.

1,436 lines (1,303 loc) 296 kB
# Copyright (c) 2011 Philip Taylor # Released under the BSD license and W3C Test Suite License: see LICENSE.txt - name: 2d.getcontext.exists desc: The 2D context is implemented testing: - context.2d code: | @assert canvas.getContext('2d') !== null; - name: 2d.getcontext.extraargs desc: The 2D context ignores extra getContext arguments testing: - context.2d.extraargs code: | @assert canvas.getContext('2d', false, {}, [], 1, "2") !== null; - name: 2d.type.exists desc: The 2D context interface is a property of 'window' notes: &bindings Defined in "Web IDL" (draft) testing: - context.2d.type code: | @assert window.CanvasRenderingContext2D; - name: 2d.type.delete desc: window.CanvasRenderingContext2D is Configurable notes: *bindings testing: - context.2d.type code: | @assert window.CanvasRenderingContext2D !== undefined; @assert delete window.CanvasRenderingContext2D === true; @assert window.CanvasRenderingContext2D === undefined; - name: 2d.type.prototype desc: window.CanvasRenderingContext2D.prototype are not [[Writable]] and not [[Configurable]], and its methods are [[Configurable]]. notes: *bindings testing: - context.2d.type 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 testing: - context.2d.type 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 testing: - context.2d.type 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 testing: - context.unique code: | @assert canvas.getContext('2d') === canvas.getContext('2d'); - name: 2d.getcontext.shared desc: getContext('2d') returns objects which share canvas state testing: - context.unique 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.voidreturn desc: void methods return undefined notes: *bindings images: - yellow.png code: | @assert ctx.save() === undefined; @assert ctx.restore() === undefined; @assert ctx.scale(1, 1) === undefined; @assert ctx.rotate(0) === undefined; @assert ctx.translate(0, 0) === undefined; if (ctx.transform) { // (avoid spurious failures, since the aim here is not to test that all features are supported) @assert ctx.transform(1, 0, 0, 1, 0, 0) === undefined; } if (ctx.setTransform) { @assert ctx.setTransform(1, 0, 0, 1, 0, 0) === undefined; @assert ctx.setTransform() === undefined; } @assert ctx.clearRect(0, 0, 0, 0) === undefined; @assert ctx.fillRect(0, 0, 0, 0) === undefined; @assert ctx.strokeRect(0, 0, 0, 0) === undefined; @assert ctx.beginPath() === undefined; @assert ctx.closePath() === undefined; @assert ctx.moveTo(0, 0) === undefined; @assert ctx.lineTo(0, 0) === undefined; @assert ctx.quadraticCurveTo(0, 0, 0, 0) === undefined; @assert ctx.bezierCurveTo(0, 0, 0, 0, 0, 0) === undefined; @assert ctx.arcTo(0, 0, 0, 0, 1) === undefined; @assert ctx.rect(0, 0, 0, 0) === undefined; @assert ctx.arc(0, 0, 1, 0, 0, true) === undefined; @assert ctx.fill() === undefined; @assert ctx.stroke() === undefined; @assert ctx.clip() === undefined; if (ctx.fillText) { @assert ctx.fillText('test', 0, 0) === undefined; @assert ctx.strokeText('test', 0, 0) === undefined; } if (ctx.putImageData) { @assert ctx.putImageData(ctx.getImageData(0, 0, 1, 1), 0, 0) === undefined; } @assert ctx.drawImage(document.getElementById('yellow.png'), 0, 0, 1, 1, 0, 0, 0, 0) === undefined; @assert ctx.drawImage(canvas, 0, 0, 1, 1, 0, 0, 0, 0) === undefined; @assert ctx.createLinearGradient(0, 0, 0, 0).addColorStop(0, 'white') === undefined; - name: 2d.missingargs desc: Missing arguments cause TypeError code: | @assert throws TypeError ctx.scale(); @assert throws TypeError ctx.scale(1); @assert throws TypeError ctx.rotate(); @assert throws TypeError ctx.translate(); @assert throws TypeError ctx.translate(0); if (ctx.transform) { // (avoid spurious failures, since the aim here is not to test that all features are supported) @assert throws TypeError ctx.transform(); @assert throws TypeError ctx.transform(1); @assert throws TypeError ctx.transform(1, 0); @assert throws TypeError ctx.transform(1, 0, 0); @assert throws TypeError ctx.transform(1, 0, 0, 1); @assert throws TypeError ctx.transform(1, 0, 0, 1, 0); } if (ctx.setTransform) { @assert throws TypeError ctx.setTransform(1); @assert throws TypeError ctx.setTransform(1, 0); @assert throws TypeError ctx.setTransform(1, 0, 0); @assert throws TypeError ctx.setTransform(1, 0, 0, 1); @assert throws TypeError ctx.setTransform(1, 0, 0, 1, 0); } @assert throws TypeError ctx.createLinearGradient(); @assert throws TypeError ctx.createLinearGradient(0); @assert throws TypeError ctx.createLinearGradient(0, 0); @assert throws TypeError ctx.createLinearGradient(0, 0, 1); @assert throws TypeError ctx.createRadialGradient(); @assert throws TypeError ctx.createRadialGradient(0); @assert throws TypeError ctx.createRadialGradient(0, 0); @assert throws TypeError ctx.createRadialGradient(0, 0, 1); @assert throws TypeError ctx.createRadialGradient(0, 0, 1, 0); @assert throws TypeError ctx.createRadialGradient(0, 0, 1, 0, 0); @assert throws TypeError ctx.createPattern(canvas); @assert throws TypeError ctx.clearRect(); @assert throws TypeError ctx.clearRect(0); @assert throws TypeError ctx.clearRect(0, 0); @assert throws TypeError ctx.clearRect(0, 0, 0); @assert throws TypeError ctx.fillRect(); @assert throws TypeError ctx.fillRect(0); @assert throws TypeError ctx.fillRect(0, 0); @assert throws TypeError ctx.fillRect(0, 0, 0); @assert throws TypeError ctx.strokeRect(); @assert throws TypeError ctx.strokeRect(0); @assert throws TypeError ctx.strokeRect(0, 0); @assert throws TypeError ctx.strokeRect(0, 0, 0); @assert throws TypeError ctx.moveTo(); @assert throws TypeError ctx.moveTo(0); @assert throws TypeError ctx.lineTo(); @assert throws TypeError ctx.lineTo(0); @assert throws TypeError ctx.quadraticCurveTo(); @assert throws TypeError ctx.quadraticCurveTo(0); @assert throws TypeError ctx.quadraticCurveTo(0, 0); @assert throws TypeError ctx.quadraticCurveTo(0, 0, 0); @assert throws TypeError ctx.bezierCurveTo(); @assert throws TypeError ctx.bezierCurveTo(0); @assert throws TypeError ctx.bezierCurveTo(0, 0); @assert throws TypeError ctx.bezierCurveTo(0, 0, 0); @assert throws TypeError ctx.bezierCurveTo(0, 0, 0, 0); @assert throws TypeError ctx.bezierCurveTo(0, 0, 0, 0, 0); @assert throws TypeError ctx.arcTo(); @assert throws TypeError ctx.arcTo(0); @assert throws TypeError ctx.arcTo(0, 0); @assert throws TypeError ctx.arcTo(0, 0, 0); @assert throws TypeError ctx.arcTo(0, 0, 0, 0); @assert throws TypeError ctx.rect(); @assert throws TypeError ctx.rect(0); @assert throws TypeError ctx.rect(0, 0); @assert throws TypeError ctx.rect(0, 0, 0); @assert throws TypeError ctx.arc(); @assert throws TypeError ctx.arc(0); @assert throws TypeError ctx.arc(0, 0); @assert throws TypeError ctx.arc(0, 0, 1); @assert throws TypeError ctx.arc(0, 0, 1, 0); // (6th argument to arc is optional) if (ctx.isPointInPath) { @assert throws TypeError ctx.isPointInPath(); @assert throws TypeError ctx.isPointInPath(0); } if (ctx.drawFocusRing) { @assert throws TypeError ctx.drawFocusRing(); @assert throws TypeError ctx.drawFocusRing(canvas); @assert throws TypeError ctx.drawFocusRing(canvas, 0); } if (ctx.fillText) { @assert throws TypeError ctx.fillText(); @assert throws TypeError ctx.fillText('test'); @assert throws TypeError ctx.fillText('test', 0); @assert throws TypeError ctx.strokeText(); @assert throws TypeError ctx.strokeText('test'); @assert throws TypeError ctx.strokeText('test', 0); @assert throws TypeError ctx.measureText(); } @assert throws TypeError ctx.drawImage(); @assert throws TypeError ctx.drawImage(canvas); @assert throws TypeError ctx.drawImage(canvas, 0); // TODO: n >= 3 args on drawImage could be either a valid overload, // or too few for another overload, or too many for another // overload - what should happen? if (ctx.createImageData) { @assert throws TypeError ctx.createImageData(); @assert throws TypeError ctx.createImageData(1); } if (ctx.getImageData) { @assert throws TypeError ctx.getImageData(); @assert throws TypeError ctx.getImageData(0); @assert throws TypeError ctx.getImageData(0, 0); @assert throws TypeError ctx.getImageData(0, 0, 1); } if (ctx.putImageData) { var imgdata = ctx.getImageData(0, 0, 1, 1); @assert throws TypeError ctx.putImageData(); @assert throws TypeError ctx.putImageData(imgdata); @assert throws TypeError ctx.putImageData(imgdata, 0); } var g = ctx.createLinearGradient(0, 0, 0, 0); @assert throws TypeError g.addColorStop(); @moz-todo @assert throws TypeError g.addColorStop(0); @moz-todo - name: 2d.coordinatespace desc: Coordinate space goes from top-left to bottom-right notes: This should not be upside down. manual: We can't tell that getPixelData isn't using the wrong coordinate space too. testing: - 2d.coordinatespace code: | ctx.fillStyle = '#00f'; ctx.fillRect(0, 0, 100, 50); ctx.fillStyle = '#0ff'; ctx.fillRect(0, 0, 50, 25); @assert pixel 25,12 == 0,255,255,255; @assert pixel 75,12 == 0,0,255,255; @assert pixel 25,37 == 0,0,255,255; @assert pixel 75,37 == 0,0,255,255; 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, 25) cr.fill() - 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 testing: - 2d.canvas code: | @assert ctx.canvas === canvas; - name: 2d.canvas.readonly desc: CanvasRenderingContext2D.canvas is readonly testing: - 2d.canvas.attribute code: | var c = document.createElement('canvas'); var d = ctx.canvas; @assert c !== d; ctx.canvas = c; @assert ctx.canvas === d; - 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, 'testing': [ '2d.state.%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', 'testing': [ 'initial.reset' ], '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), } ) - name: 2d.state.saverestore.transformation desc: save()/restore() affects the current transformation matrix testing: - 2d.state.transformation 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 testing: - 2d.state.clip 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 testing: - 2d.state.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 testing: - 2d.state.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 testing: - 2d.state.save - 2d.state.restore 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 testing: - 2d.state.save - 2d.state.restore 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 testing: - 2d.state.restore.underflow code: | for (var i = 0; i < 16; ++i) ctx.restore(); ctx.lineWidth = 0.5; ctx.restore(); @assert ctx.lineWidth === 0.5; - name: 2d.transformation.order desc: Transformations are applied in the right order testing: - 2d.transformation.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 testing: - 2d.transformation.scale 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 testing: - 2d.transformation.scale 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 testing: - 2d.transformation.scale 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. testing: - 2d.transformation.scale 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 testing: - 2d.nonfinite 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 testing: - 2d.transformation.scale.multiple 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 testing: - 2d.transformation.rotate 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 testing: - 2d.transformation.rotate.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 testing: - 2d.transformation.rotate.direction 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 testing: - 2d.transformation.rotate 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 testing: - 2d.transformation.rotate 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 testing: - 2d.nonfinite 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 testing: - 2d.transformation.translate 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 testing: - 2d.nonfinite 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 testing: - 2d.transformation.transform 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 testing: - 2d.transformation.transform 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 testing: - 2d.transformation.transform.multiply 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 testing: - 2d.nonfinite 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 testing: - 2d.transformation.setTransform 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 testing: - 2d.transformation.setTransform.identity 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 testing: - 2d.nonfinite 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 - name: 2d.composite.globalAlpha.range testing: - 2d.composite.globalAlpha.range code: | ctx.globalAlpha = 0.5; var a = ctx.globalAlpha; // might not be exactly 0.5, if it is rounded/quantised, so remember for future comparisons ctx.globalAlpha = 1.1; @assert ctx.globalAlpha === a; ctx.globalAlpha = -0.1; @assert ctx.globalAlpha === a; ctx.globalAlpha = 0; @assert ctx.globalAlpha === 0; ctx.globalAlpha = 1; @assert ctx.globalAlpha === 1; - name: 2d.composite.globalAlpha.invalid testing: - 2d.composite.globalAlpha.range code: | ctx.globalAlpha = 0.5; var a = ctx.globalAlpha; // might not be exactly 0.5, if it is rounded/quantised, so remember for future comparisons ctx.globalAlpha = Infinity; @assert ctx.globalAlpha === a; ctx.globalAlpha = -Infinity; @assert ctx.globalAlpha === a; ctx.globalAlpha = NaN; @assert ctx.globalAlpha === a; - name: 2d.composite.globalAlpha.default testing: - 2d.composite.globalAlpha.default code: | @assert ctx.globalAlpha === 1.0; - name: 2d.composite.globalAlpha.fill testing: - 2d.composite.globalAlpha.shape code: | ctx.fillStyle = '#0f0'; ctx.fillRect(0, 0, 100, 50); ctx.globalAlpha = 0.01; // avoid any potential alpha=0 optimisations ctx.fillStyle = '#f00'; ctx.fillRect(0, 0, 100, 50); @assert pixel 50,25 ==~ 2,253,0,255; expected: green - name: 2d.composite.globalAlpha.image testing: - 2d.composite.globalAlpha.image images: - red.png code: | ctx.fillStyle = '#0f0'; ctx.fillRect(0, 0, 100, 50); ctx.globalAlpha = 0.01; // avoid any potential alpha=0 optimisations ctx.drawImage(document.getElementById('red.png'), 0, 0); @assert pixel 50,25 ==~ 2,253,0,255; expected: green - name: 2d.composite.globalAlpha.canvas testing: - 2d.composite.globalAlpha.image 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 = '#0f0'; ctx.fillRect(0, 0, 100, 50); ctx.globalAlpha = 0.01; // avoid any potential alpha=0 optimisations ctx.drawImage(canvas2, 0, 0); @assert pixel 50,25 ==~ 2,253,0,255; expected: green - name: 2d.composite.globalAlpha.imagepattern testing: - 2d.composite.globalAlpha.image images: - red.png code: | ctx.fillStyle = '#0f0'; ctx.fillRect(0, 0, 100, 50); ctx.fillStyle = ctx.createPattern(document.getElementById('red.png'), 'no-repeat'); ctx.globalAlpha = 0.01; // avoid any potential alpha=0 optimisations ctx.fillRect(0, 0, 100, 50); @assert pixel 50,25 ==~ 2,253,0,255; expected: green - name: 2d.composite.globalAlpha.canvaspattern testing: - 2d.composite.globalAlpha.image 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 = '#0f0'; ctx.fillRect(0, 0, 100, 50); ctx.fillStyle = ctx.createPattern(canvas2, 'no-repeat'); ctx.globalAlpha = 0.01; // avoid any potential alpha=0 optimisations ctx.fillRect(0, 0, 100, 50); @assert pixel 50,25 ==~ 2,253,0,255; expected: green - name: 2d.composite.globalAlpha.canvascopy testing: - 2d.composite.globalAlpha.image 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); ctx.fillStyle = '#f00'; ctx.fillRect(0, 0, 100, 50); ctx.globalCompositeOperation = 'copy' ctx.globalAlpha = 0.51; ctx.drawImage(canvas2, 0, 0); @assert pixel 50,25 ==~ 0,255,0,130; expected: green - 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), 'testing': [ '2d.composite.%s' % 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), 'testing': [ '2d.composite.%s' % 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), 'testing': [ '2d.composite.%s' % 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.', 'testing': [ '2d.composite.%s' % op ], '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.', 'testing': [ '2d.composite.%s' % op ], '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.', 'testing': [ '2d.composite.%s' % op ], '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.', 'testing': [ '2d.composite.%s' % op ], '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.', 'testing': [ '2d.composite.%s' % op ], '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' } ) - name: 2d.composite.operation.get testing: - 2d.composite.operation code: | var modes = ['source-atop', 'source-in', 'source-out', 'source-over', 'destination-atop', 'destination-in', 'destination-out', 'destination-over', 'lighter', 'copy', 'xor']; for (var i = 0; i < modes.length; ++i) { ctx.globalCompositeOperation = modes[i]; @assert ctx.globalCompositeOperation === modes[i]; } - name: 2d.composite.operation.unrecognised testing: - 2d.composite.operation.unrecognised code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'nonexistent'; @assert ctx.globalCompositeOperation === 'xor'; - name: 2d.composite.operation.darker testing: - 2d.composite.operation.unrecognised code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'darker'; @assert ctx.globalCompositeOperation === 'xor'; - name: 2d.composite.operation.over testing: - 2d.composite.operation.unrecognised code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'over'; @assert ctx.globalCompositeOperation === 'xor'; - name: 2d.composite.operation.clear testing: - 2d.composite.operation.unrecognised code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'clear'; @assert ctx.globalCompositeOperation === 'clear'; - name: 2d.composite.operation.highlight testing: - 2d.composite.operation.unrecognised code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'highlight'; @assert ctx.globalCompositeOperation === 'xor'; - name: 2d.composite.operation.nullsuffix testing: - 2d.composite.operation.exact code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'source-over\0'; @assert ctx.globalCompositeOperation === 'xor'; - name: 2d.composite.operation.casesensitive testing: - 2d.composite.operation.casesensitive code: | ctx.globalCompositeOperation = 'xor'; ctx.globalCompositeOperation = 'Source-over'; @assert ctx.globalCompositeOperation === 'xor'; - name: 2d.composite.operation.default testing: - 2d.composite.operation.default code: | @assert ctx.globalCompositeOperation === 'source-over'; - meta: | # Colour 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 colours are clamped to [0,255].'), ('rgb-clamp-2', 'rgb(-200%, 200%, -200%)', 0,255,0,255, 'Assumes colours are clamped to [0,255].'), ('rgb-clamp-3', 'rgb(-2147483649, 4294967298, -18446744073709551619)', 0,255,0,255, 'Assumes colours are clamped to [0,255].'), ('rgb-clamp-4', 'rgb(-'+big_float+', '+big_float+', -'+big_float+')', 0,255,0,255, 'Assumes colours are clamped to [0,255].'), ('rgb-clamp-5', 'rgb(-'+big_double+', '+big_double+', -'+big_double+')', 0,255,0,255, 'Assumes colours 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, instea