Source code

Revision control

Copy as Markdown

Other Tools

- 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 
 because \r\n gets converted to \n in the HTML parser.
htmlString = string.replace('\r', '
')
tests.append( {
"name": "2d.canvas.host.size.attributes.parse.%s" % name,
"desc": "Parsing of non-negative integers",
"size": '%s, %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": "2d.canvas.host.size.attributes.setAttribute.%s" % name,
"desc": "Parsing of non-negative integers in setAttribute",
"size": '50, 50',
"code": code,
"expected": expected
} )
- meta: |
# Composite operation tests
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'),
('clear', '0', '0'),
]
# 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-negative-saturation', 'hsl(120, -200%, 49.9%)', 127,127,127,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-negative-saturation', 'hsla(120, -200%, 49.9%, 1)', 127,127,127,255, ""),
('hsla-clamp-alpha-1', 'hsla(120, 100%, 50%, 2)', 0,255,0,255, ""),
('hsla-clamp-alpha-2', '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
('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
('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)