diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/html/canvas/tools | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/canvas/tools')
37 files changed, 20516 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/canvas/tools/PRESUBMIT.py b/testing/web-platform/tests/html/canvas/tools/PRESUBMIT.py new file mode 100644 index 0000000000..048e96b701 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/PRESUBMIT.py @@ -0,0 +1,22 @@ +# Copyright 2023 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Presubmit script for t/b/web_tests/external/wpt/html/canvas/tools. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +USE_PYTHON3 = True + + +def CommonChecks(input_api, output_api): + return input_api.canned_checks.RunPylint(input_api, output_api) + + +def CheckChangeOnUpload(input_api, output_api): + return CommonChecks(input_api, output_api) + + +def CheckChangeOnCommit(input_api, output_api): + return CommonChecks(input_api, output_api) diff --git a/testing/web-platform/tests/html/canvas/tools/gentest.py b/testing/web-platform/tests/html/canvas/tools/gentest.py new file mode 100644 index 0000000000..bca7b9ecfc --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/gentest.py @@ -0,0 +1,8 @@ +from gentestutils import genTestUtils +from gentestutilsunion import genTestUtils_union + +genTestUtils('../element', '../element', 'templates.yaml', + 'name2dir-canvas.yaml', False) +genTestUtils('../offscreen', '../offscreen', 'templates.yaml', + 'name2dir-offscreen.yaml', True) +genTestUtils_union('templates-new.yaml', 'name2dir.yaml') diff --git a/testing/web-platform/tests/html/canvas/tools/gentest_union.py b/testing/web-platform/tests/html/canvas/tools/gentest_union.py new file mode 100644 index 0000000000..62c1cde6a1 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/gentest_union.py @@ -0,0 +1,3 @@ +from gentestutilsunion import genTestUtils_union + +genTestUtils_union('templates-new.yaml', 'name2dir-canvas.yaml') diff --git a/testing/web-platform/tests/html/canvas/tools/gentestutils.py b/testing/web-platform/tests/html/canvas/tools/gentestutils.py new file mode 100644 index 0000000000..27412e40df --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/gentestutils.py @@ -0,0 +1,367 @@ +# Current code status: +# +# This was originally written by Philip Taylor for use at +# http://philip.html5.org/tests/canvas/suite/tests/ +# +# It has been adapted for use with the Web Platform Test Suite suite at +# https://github.com/web-platform-tests/wpt/ +# +# The original version had a number of now-removed features (multiple versions +# of each test case of varying verbosity, Mozilla mochitests, semi-automated +# test harness). It also had a different directory structure. + +# To update or add test cases: +# +# * Modify the tests*.yaml files. +# - 'name' is an arbitrary hierarchical name to help categorise tests. +# - 'desc' is a rough description of what behaviour the test aims to test. +# - 'code' is JavaScript code to execute, with some special commands starting +# with '@' +# - 'expected' is what the final canvas output should be: a string 'green' or +# 'clear' (100x50 images in both cases), or a string 'size 100 50' (or any +# other size) followed by Python code using Pycairo to generate the image. +# +# * Run "./build.sh". +# This requires a few Python modules which might not be ubiquitous. +# It will usually emit some warnings, which ideally should be fixed but can +# generally be safely ignored. +# +# * Test the tests, add new ones to Git, remove deleted ones from Git, etc. + +from typing import List, Mapping + +import re +import importlib +import os +import pathlib +import sys + +try: + import cairocffi as cairo # type: ignore +except ImportError: + import cairo + +try: + # Compatible and lots faster. + import syck as yaml # type: ignore +except ImportError: + import yaml + + +class Error(Exception): + """Base class for all exceptions raised by this module""" + + +class InvalidTestDefinitionError(Error): + """Raised on invalid test definition.""" + + +def _simpleEscapeJS(string: str) -> str: + return string.replace('\\', '\\\\').replace('"', '\\"') + + +def _escapeJS(string: str) -> str: + string = _simpleEscapeJS(string) + # Kind of an ugly hack, for nicer failure-message output. + string = re.sub(r'\[(\w+)\]', r'[\\""+(\1)+"\\"]', string) + return string + + +def _expand_nonfinite(method: str, argstr: str, tail: str) -> str: + """ + >>> print _expand_nonfinite('f', '<0 a>, <0 b>', ';') + f(a, 0); + f(0, b); + f(a, b); + >>> print _expand_nonfinite('f', '<0 a>, <0 b c>, <0 d>', ';') + f(a, 0, 0); + f(0, b, 0); + f(0, c, 0); + f(0, 0, d); + f(a, b, 0); + f(a, b, d); + f(a, 0, d); + f(0, b, d); + """ + # argstr is "<valid-1 invalid1-1 invalid2-1 ...>, ..." (where usually + # 'invalid' is Infinity/-Infinity/NaN). + args = [] + for arg in argstr.split(', '): + match = re.match('<(.*)>', arg) + if match is None: + raise InvalidTestDefinitionError( + f"Expected arg to match format '<(.*)>', but was: {arg}") + a = match.group(1) + args.append(a.split(' ')) + calls = [] + # Start with the valid argument list. + call = [args[j][0] for j in range(len(args))] + # For each argument alone, try setting it to all its invalid values: + for i in range(len(args)): + for a in args[i][1:]: + c2 = call[:] + c2[i] = a + calls.append(c2) + # For all combinations of >= 2 arguments, try setting them to their + # first invalid values. (Don't do all invalid values, because the + # number of combinations explodes.) + def f(c: List[str], start: int, depth: int) -> None: + for i in range(start, len(args)): + if len(args[i]) > 1: + a = args[i][1] + c2 = c[:] + c2[i] = a + if depth > 0: + calls.append(c2) + f(c2, i + 1, depth + 1) + + f(call, 0, 0) + + return '\n'.join('%s(%s)%s' % (method, ', '.join(c), tail) for c in calls) + + +def _get_test_sub_dir(name: str, name_to_sub_dir: Mapping[str, str]) -> str: + for prefix in sorted(name_to_sub_dir.keys(), key=len, reverse=True): + if name.startswith(prefix): + return name_to_sub_dir[prefix] + raise InvalidTestDefinitionError( + 'Test "%s" has no defined target directory mapping' % name) + + +def _expand_test_code(code: str) -> str: + code = re.sub(r'@nonfinite ([^(]+)\(([^)]+)\)(.*)', lambda m: + _expand_nonfinite(m.group(1), m.group(2), m.group(3)), + code) # Must come before '@assert throws'. + + code = re.sub(r'@assert pixel (\d+,\d+) == (\d+,\d+,\d+,\d+);', + r'_assertPixel(canvas, \1, \2);', code) + + code = re.sub(r'@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+);', + r'_assertPixelApprox(canvas, \1, \2, 2);', code) + + code = re.sub(r'@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+) \+/- (\d+);', + r'_assertPixelApprox(canvas, \1, \2, \3);', code) + + code = re.sub(r'@assert throws (\S+_ERR) (.*);', + r'assert_throws_dom("\1", function() { \2; });', code) + + code = re.sub(r'@assert throws (\S+Error) (.*);', + r'assert_throws_js(\1, function() { \2; });', code) + + code = re.sub( + r'@assert (.*) === (.*);', lambda m: '_assertSame(%s, %s, "%s", "%s");' + % (m.group(1), m.group(2), _escapeJS(m.group(1)), _escapeJS(m.group(2)) + ), code) + + code = re.sub( + r'@assert (.*) !== (.*);', lambda m: + '_assertDifferent(%s, %s, "%s", "%s");' % (m.group(1), m.group( + 2), _escapeJS(m.group(1)), _escapeJS(m.group(2))), code) + + code = re.sub( + r'@assert (.*) =~ (.*);', lambda m: 'assert_regexp_match(%s, %s);' % ( + m.group(1), m.group(2)), code) + + code = re.sub( + r'@assert (.*);', lambda m: '_assert(%s, "%s");' % (m.group( + 1), _escapeJS(m.group(1))), code) + + code = re.sub(r' @moz-todo', '', code) + + code = re.sub(r'@moz-UniversalBrowserRead;', '', code) + + assert ('@' not in code) + + return code + + +_CANVAS_SIZE_REGEX = re.compile(r'(?P<width>.*), (?P<height>.*)', + re.MULTILINE | re.DOTALL) + + +def _get_canvas_size(test: Mapping[str, str]): + size = test.get('size', '100, 50') + match = _CANVAS_SIZE_REGEX.match(size) + if not match: + raise InvalidTestDefinitionError( + 'Invalid canvas size "%s" in test %s. Expected a string matching ' + 'this pattern: "%%s, %%s" %% (width, height)' % + (size, test['name'])) + return match.group('width'), match.group('height') + + +def _generate_test(test: Mapping[str, str], templates: Mapping[str, str], + sub_dir: str, test_output_dir: str, image_output_dir: str, + is_offscreen_canvas: bool): + name = test['name'] + + if test.get('expected', '') == 'green' and re.search( + r'@assert pixel .* 0,0,0,0;', test['code']): + print('Probable incorrect pixel test in %s' % name) + + code = _expand_test_code(test['code']) + + expectation_html = '' + if 'expected' in test and test['expected'] is not None: + expected = test['expected'] + expected_img = None + if expected == 'green': + expected_img = '/images/green-100x50.png' + elif expected == 'clear': + expected_img = '/images/clear-100x50.png' + else: + if ';' in expected: + print('Found semicolon in %s' % name) + expected = re.sub( + r'^size (\d+) (\d+)', + r'surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \1, \2)' + r'\ncr = cairo.Context(surface)', expected) + + expected += ("\nsurface.write_to_png('%s.png')\n" % + os.path.join(image_output_dir, sub_dir, name)) + eval(compile(expected, '<test %s>' % name, 'exec'), {}, + {'cairo': cairo}) + expected_img = '%s.png' % name + + if expected_img: + expectation_html = ( + '<p class="output expectedtext">Expected output:<p>' + '<img src="%s" class="output expected" id="expected" ' + 'alt="">' % expected_img) + + canvas = ' ' + test['canvas'] if 'canvas' in test else '' + width, height = _get_canvas_size(test) + + notes = '<p class="notes">%s' % test['notes'] if 'notes' in test else '' + + timeout = ('\n<meta name="timeout" content="%s">' % + test['timeout'] if 'timeout' in test else '') + timeout_js = ('// META: timeout=%s\n' % test['timeout'] + if 'timeout' in test else '') + + images = '' + for src in test.get('images', []): + img_id = src.split('/')[-1] + if '/' not in src: + src = '../images/%s' % src + images += '<img src="%s" id="%s" class="resource">\n' % (src, img_id) + for src in test.get('svgimages', []): + img_id = src.split('/')[-1] + if '/' not in src: + src = '../images/%s' % src + images += ('<svg><image xlink:href="%s" id="%s" class="resource">' + '</svg>\n' % (src, img_id)) + images = images.replace('../images/', '/images/') + + fonts = '' + fonthack = '' + for font in test.get('fonts', []): + fonts += ('@font-face {\n font-family: %s;\n' + ' src: url("/fonts/%s.ttf");\n}\n' % (font, font)) + # Browsers require the font to actually be used in the page. + if test.get('fonthack', 1): + fonthack += ('<span style="font-family: %s; position: ' + 'absolute; visibility: hidden">A</span>\n' % font) + if fonts: + fonts = '<style>\n%s</style>\n' % fonts + + fallback = test.get('fallback', + '<p class="fallback">FAIL (fallback content)</p>') + + desc = test.get('desc', '') + escaped_desc = _simpleEscapeJS(desc) + + attributes = test.get('attributes', '') + if attributes: + context_args = "'2d', %s" % attributes.strip() + attributes = ', ' + attributes.strip() + else: + context_args = "'2d'" + + template_params = { + 'name': name, + 'desc': desc, + 'escaped_desc': escaped_desc, + 'notes': notes, + 'images': images, + 'fonts': fonts, + 'fonthack': fonthack, + 'timeout': timeout, + 'timeout_js': timeout_js, + 'canvas': canvas, + 'width': width, + 'height': height, + 'expected': expectation_html, + 'code': code, + 'fallback': fallback, + 'attributes': attributes, + 'context_args': context_args + } + + test_path = os.path.join(test_output_dir, sub_dir, name) + if 'manual' in test: + test_path += '-manual' + + if is_offscreen_canvas: + pathlib.Path(f'{test_path}.html').write_text( + templates['offscreen'] % template_params, 'utf-8') + pathlib.Path(f'{test_path}.worker.js').write_text( + templates['worker'] % template_params, 'utf-8') + else: + pathlib.Path(f'{test_path}.html').write_text( + templates['element'] % template_params, 'utf-8') + + +def genTestUtils(TESTOUTPUTDIR: str, IMAGEOUTPUTDIR: str, TEMPLATEFILE: str, + NAME2DIRFILE: str, ISOFFSCREENCANVAS: bool) -> None: + # Run with --test argument to run unit tests + if len(sys.argv) > 1 and sys.argv[1] == '--test': + doctest = importlib.import_module('doctest') + doctest.testmod() + sys.exit() + + templates = yaml.safe_load(pathlib.Path(TEMPLATEFILE).read_text()) + name_to_sub_dir = yaml.safe_load(pathlib.Path(NAME2DIRFILE).read_text()) + + tests = [] + test_yaml_directory = 'yaml/element' + if ISOFFSCREENCANVAS: + test_yaml_directory = 'yaml/offscreen' + TESTSFILES = [ + os.path.join(test_yaml_directory, f) + for f in os.listdir(test_yaml_directory) if f.endswith('.yaml') + ] + for t in sum( + [yaml.safe_load(pathlib.Path(f).read_text()) for f in TESTSFILES], []): + if 'DISABLED' in t: + continue + if 'meta' in t: + eval(compile(t['meta'], '<meta test>', 'exec'), {}, + {'tests': tests}) + else: + tests.append(t) + + # Ensure the test output directories exist. + testdirs = [TESTOUTPUTDIR, IMAGEOUTPUTDIR] + for sub_dir in set(name_to_sub_dir.values()): + testdirs.append('%s/%s' % (TESTOUTPUTDIR, sub_dir)) + for d in testdirs: + try: + os.mkdir(d) + except FileExistsError: + pass # Ignore if it already exists. + + used_tests = {} + for test in tests: + name = test['name'] + print('\r(%s)' % name, ' ' * 32, '\t') + + if name in used_tests: + print('Test %s is defined twice' % name) + used_tests[name] = 1 + + sub_dir = _get_test_sub_dir(name, name_to_sub_dir) + _generate_test(test, templates, sub_dir, TESTOUTPUTDIR, IMAGEOUTPUTDIR, + ISOFFSCREENCANVAS) + + print() diff --git a/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py b/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py new file mode 100644 index 0000000000..bf5fdeee50 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/gentestutilsunion.py @@ -0,0 +1,617 @@ +# Current code status: +# +# This was originally written by Philip Taylor for use at +# http://philip.html5.org/tests/canvas/suite/tests/ +# +# It has been adapted for use with the Web Platform Test Suite suite at +# https://github.com/web-platform-tests/wpt/ +# +# The original version had a number of now-removed features (multiple versions +# of each test case of varying verbosity, Mozilla mochitests, semi-automated +# test harness). It also had a different directory structure. + +# To update or add test cases: +# +# * Modify the tests*.yaml files. +# - 'name' is an arbitrary hierarchical name to help categorise tests. +# - 'desc' is a rough description of what behaviour the test aims to test. +# - 'code' is JavaScript code to execute, with some special commands starting +# with '@'. +# - 'expected' is what the final canvas output should be: a string 'green' or +# 'clear' (100x50 images in both cases), or a string 'size 100 50' (or any +# other size) followed by Python code using Pycairo to generate the image. +# +# * Run "./build.sh". +# This requires a few Python modules which might not be ubiquitous. +# It will usually emit some warnings, which ideally should be fixed but can +# generally be safely ignored. +# +# * Test the tests, add new ones to Git, remove deleted ones from Git, etc. + +from typing import Any, List, Mapping, MutableMapping, Optional, Tuple + +import re +import collections +import dataclasses +import enum +import importlib +import itertools +import os +import pathlib +import sys +import textwrap + +try: + import cairocffi as cairo # type: ignore +except ImportError: + import cairo + +try: + # Compatible and lots faster. + import syck as yaml # type: ignore +except ImportError: + import yaml + + +class Error(Exception): + """Base class for all exceptions raised by this module""" + + +class InvalidTestDefinitionError(Error): + """Raised on invalid test definition.""" + + +def _simpleEscapeJS(string: str) -> str: + return string.replace('\\', '\\\\').replace('"', '\\"') + + +def _escapeJS(string: str) -> str: + string = _simpleEscapeJS(string) + # Kind of an ugly hack, for nicer failure-message output. + string = re.sub(r'\[(\w+)\]', r'[\\""+(\1)+"\\"]', string) + return string + + +def _unroll(text: str) -> str: + """Unrolls text with all possible permutations of the parameter lists. + + Example: + >>> print _unroll('f = {<a | b>: <1 | 2 | 3>};') + // a + f = {a: 1}; + f = {a: 2}; + f = {a: 3}; + // b + f = {b: 1}; + f = {b: 2}; + f = {b: 3}; + """ + patterns = [] # type: List[Tuple[str, List[str]]] + while match := re.search(r'<([^>]+)>', text): + key = f'@unroll_pattern_{len(patterns)}' + values = text[match.start(1):match.end(1)] + text = text[:match.start(0)] + key + text[match.end(0):] + patterns.append((key, [value.strip() for value in values.split('|')])) + + def unroll_patterns(text: str, + patterns: List[Tuple[str, List[str]]], + label: Optional[str] = None) -> List[str]: + if not patterns: + return [text] + patterns = patterns.copy() + key, values = patterns.pop(0) + return (['// ' + label] if label else []) + list( + itertools.chain.from_iterable( + unroll_patterns(text.replace(key, value), patterns, value) + for value in values)) + + result = '\n'.join(unroll_patterns(text, patterns)) + return result + + +def _expand_nonfinite(method: str, argstr: str, tail: str) -> str: + """ + >>> print _expand_nonfinite('f', '<0 a>, <0 b>', ';') + f(a, 0); + f(0, b); + f(a, b); + >>> print _expand_nonfinite('f', '<0 a>, <0 b c>, <0 d>', ';') + f(a, 0, 0); + f(0, b, 0); + f(0, c, 0); + f(0, 0, d); + f(a, b, 0); + f(a, b, d); + f(a, 0, d); + f(0, b, d); + """ + # argstr is "<valid-1 invalid1-1 invalid2-1 ...>, ..." (where usually + # 'invalid' is Infinity/-Infinity/NaN). + args = [] + for arg in argstr.split(', '): + match = re.match('<(.*)>', arg) + if match is None: + raise InvalidTestDefinitionError( + f"Expected arg to match format '<(.*)>', but was: {arg}") + a = match.group(1) + args.append(a.split(' ')) + calls = [] + # Start with the valid argument list. + call = [args[j][0] for j in range(len(args))] + # For each argument alone, try setting it to all its invalid values: + for i in range(len(args)): + for a in args[i][1:]: + c2 = call[:] + c2[i] = a + calls.append(c2) + # For all combinations of >= 2 arguments, try setting them to their + # first invalid values. (Don't do all invalid values, because the + # number of combinations explodes.) + def f(c: List[str], start: int, depth: int) -> None: + for i in range(start, len(args)): + if len(args[i]) > 1: + a = args[i][1] + c2 = c[:] + c2[i] = a + if depth > 0: + calls.append(c2) + f(c2, i + 1, depth + 1) + + f(call, 0, 0) + + return '\n'.join('%s(%s)%s' % (method, ', '.join(c), tail) for c in calls) + + +def _get_test_sub_dir(name: str, name_to_sub_dir: Mapping[str, str]) -> str: + for prefix in sorted(name_to_sub_dir.keys(), key=len, reverse=True): + if name.startswith(prefix): + return name_to_sub_dir[prefix] + raise InvalidTestDefinitionError( + 'Test "%s" has no defined target directory mapping' % name) + + +def _remove_extra_newlines(text: str) -> str: + """Remove newlines if a backslash is found at end of line.""" + # Lines ending with '\' gets their newline character removed. + text = re.sub(r'\\\n', '', text, flags=re.MULTILINE | re.DOTALL) + + # Lines ending with '\-' gets their newline and any leading white spaces on + # the following line removed. + text = re.sub(r'\\-\n\s*', '', text, flags=re.MULTILINE | re.DOTALL) + return text + +def _expand_test_code(code: str) -> str: + code = _remove_extra_newlines(code) + + # Unroll expressions with a cross-product-style parameter expansion. + code = re.sub(r'@unroll ([^;]*;)', lambda m: _unroll(m.group(1)), code) + + code = re.sub(r'@nonfinite ([^(]+)\(([^)]+)\)(.*)', lambda m: + _expand_nonfinite(m.group(1), m.group(2), m.group(3)), + code) # Must come before '@assert throws'. + + code = re.sub(r'@assert pixel (\d+,\d+) == (\d+,\d+,\d+,\d+);', + r'_assertPixel(canvas, \1, \2);', code) + + code = re.sub(r'@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+);', + r'_assertPixelApprox(canvas, \1, \2, 2);', code) + + code = re.sub(r'@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+) \+/- (\d+);', + r'_assertPixelApprox(canvas, \1, \2, \3);', code) + + code = re.sub(r'@assert throws (\S+_ERR) (.*);', + r'assert_throws_dom("\1", function() { \2; });', code) + + code = re.sub(r'@assert throws (\S+Error) (.*);', + r'assert_throws_js(\1, function() { \2; });', code) + + code = re.sub( + r'@assert (.*) === (.*);', lambda m: '_assertSame(%s, %s, "%s", "%s");' + % (m.group(1), m.group(2), _escapeJS(m.group(1)), _escapeJS(m.group(2)) + ), code) + + code = re.sub( + r'@assert (.*) !== (.*);', lambda m: + '_assertDifferent(%s, %s, "%s", "%s");' % (m.group(1), m.group( + 2), _escapeJS(m.group(1)), _escapeJS(m.group(2))), code) + + code = re.sub( + r'@assert (.*) =~ (.*);', lambda m: 'assert_regexp_match(%s, %s);' % ( + m.group(1), m.group(2)), code) + + code = re.sub( + r'@assert (.*);', lambda m: '_assert(%s, "%s");' % (m.group( + 1), _escapeJS(m.group(1))), code) + + code = re.sub(r' @moz-todo', '', code) + + code = re.sub(r'@moz-UniversalBrowserRead;', '', code) + + assert ('@' not in code) + + return code + + +class CanvasType(str, enum.Enum): + HTML_CANVAS = 'htmlcanvas' + OFFSCREEN_CANVAS = 'offscreencanvas' + + +def _get_enabled_canvas_types(test: Mapping[str, Any]) -> List[CanvasType]: + return [CanvasType(t.lower()) for t in test.get('canvasType', CanvasType)] + + +@dataclasses.dataclass +class TestConfig: + out_dir: str + image_out_dir: str + enabled: bool + + +_CANVAS_SIZE_REGEX = re.compile(r'(?P<width>.*), (?P<height>.*)', + re.MULTILINE | re.DOTALL) + + +def _get_canvas_size(test: Mapping[str, Any]): + size = test.get('size', '100, 50') + match = _CANVAS_SIZE_REGEX.match(size) + if not match: + raise InvalidTestDefinitionError( + 'Invalid canvas size "%s" in test %s. Expected a string matching ' + 'this pattern: "%%s, %%s" %% (width, height)' % + (size, test['name'])) + return match.group('width'), match.group('height') + + +def _write_reference_test(is_js_ref: bool, templates: Mapping[str, str], + template_params: MutableMapping[str, str], + ref_code: str, canvas_path: Optional[str], + offscreen_path: Optional[str]): + ref_code = ref_code.strip() + ref_code = textwrap.indent(ref_code, ' ') if is_js_ref else ref_code + ref_template_name = 'element_ref_test' if is_js_ref else 'html_ref_test' + + code = template_params['code'] + template_params['code'] = textwrap.indent(code, ' ') + if canvas_path: + pathlib.Path(f'{canvas_path}.html').write_text( + templates['element_ref_test'] % template_params, 'utf-8') + if offscreen_path: + pathlib.Path(f'{offscreen_path}.html').write_text( + templates['offscreen_ref_test'] % template_params, 'utf-8') + template_params['code'] = textwrap.indent(code, ' ') + pathlib.Path(f'{offscreen_path}.w.html').write_text( + templates['worker_ref_test'] % template_params, 'utf-8') + + template_params['code'] = ref_code + template_params['links'] = '' + template_params['fuzzy'] = '' + if canvas_path: + pathlib.Path(f'{canvas_path}-expected.html').write_text( + templates[ref_template_name] % template_params, 'utf-8') + if offscreen_path: + pathlib.Path(f'{offscreen_path}-expected.html').write_text( + templates[ref_template_name] % template_params, 'utf-8') + + +def _write_testharness_test(templates: Mapping[str, str], + template_params: MutableMapping[str, str], + canvas_path: Optional[str], + offscreen_path: Optional[str]): + # Create test cases for canvas and offscreencanvas. + code = template_params['code'] + template_params['code'] = textwrap.indent(code, ' ') + if canvas_path: + pathlib.Path(f'{canvas_path}.html').write_text( + templates['element'] % template_params, 'utf-8') + + if offscreen_path: + offscreen_template = templates['offscreen'] + worker_template = templates['worker'] + + if ('then(t_pass, t_fail);' in code): + offscreen_template = offscreen_template.replace('t.done();\n', '') + worker_template = worker_template.replace('t.done();\n', '') + + pathlib.Path(f'{offscreen_path}.html').write_text( + offscreen_template % template_params, 'utf-8') + pathlib.Path(f'{offscreen_path}.worker.js').write_text( + worker_template % template_params, 'utf-8') + + +def _expand_template(template: str, template_params: Mapping[str, str]) -> str: + # Remove whole line comments. + template = re.sub(r'^ *#.*?\n', '', template, flags=re.MULTILINE) + # Remove trailing line comments. + template = re.sub(r' *#.*?$', '', template, flags=re.MULTILINE) + + # Unwrap lines ending with a backslash. + template = _remove_extra_newlines(template) + + content_without_nested_if = r'((?:(?!{%\s*(?:if|else|endif)[^%]*%}).)*?)' + + # Resolve {% if <cond> %}<content>{% else %}<alternate content>{% endif %} + if_else_regex = re.compile( + r'{%\s*if\s*([^\s%]+)\s*%}' + # {% if <cond> %} + content_without_nested_if + # content + r'{%\s*else\s*%}' + # {% else %} + content_without_nested_if + # alternate + r'{%\s*endif\s*%}', # {% endif %} + flags=re.MULTILINE | re.DOTALL) + while match := if_else_regex.search(template): + condition, content, alternate = match.groups() + substitution = content if template_params[condition] else alternate + template = ( + template[:match.start(0)] + substitution + template[match.end(0):]) + + # Resolve {% if <cond> %}<content>{% endif %} + if_regex = re.compile( + r'{%\s*if\s*([^\s%]+)\s*%}' + # {% if <cond> %} + content_without_nested_if + # content + r'{%\s*endif\s*%}', # {% endif %} + flags=re.MULTILINE | re.DOTALL) + while match := if_regex.search(template): + condition, content = match.groups() + substitution = content if template_params[condition] else '' + template = ( + template[:match.start(0)] + substitution + template[match.end(0):]) + + return template + + +def _expand_templates(templates: Mapping[str, str], + params: Mapping[str, str]) -> Mapping[str, str]: + return { + name: _expand_template(template, params) + for name, template in templates.items() + } + + +def _generate_test(test: Mapping[str, Any], templates: Mapping[str, str], + sub_dir: str, html_canvas_cfg: TestConfig, + offscreen_canvas_cfg: TestConfig) -> None: + name = test['name'] + + if test.get('expected', '') == 'green' and re.search( + r'@assert pixel .* 0,0,0,0;', test['code']): + print('Probable incorrect pixel test in %s' % name) + + code_canvas = _expand_test_code(test['code']).strip() + + expectation_html = '' + if 'expected' in test and test['expected'] is not None: + expected = test['expected'] + expected_img = None + if expected == 'green': + expected_img = '/images/green-100x50.png' + elif expected == 'clear': + expected_img = '/images/clear-100x50.png' + else: + if ';' in expected: + print('Found semicolon in %s' % name) + expected = re.sub( + r'^size (\d+) (\d+)', + r'surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \1, \2)' + r'\ncr = cairo.Context(surface)', expected) + + expected_canvas = ( + expected + "\nsurface.write_to_png('%s.png')\n" % + os.path.join(html_canvas_cfg.image_out_dir, sub_dir, name)) + eval(compile(expected_canvas, '<test %s>' % name, 'exec'), {}, + {'cairo': cairo}) + + expected_offscreencanvas = ( + expected + "\nsurface.write_to_png('%s.png')\n" % os.path.join( + offscreen_canvas_cfg.image_out_dir, sub_dir, name)) + eval(compile(expected_offscreencanvas, '<test %s>' % name, 'exec'), + {}, {'cairo': cairo}) + + expected_img = '%s.png' % name + + if expected_img: + expectation_html = ( + '<p class="output expectedtext">Expected output:<p>' + '<img src="%s" class="output expected" id="expected" ' + 'alt="">' % expected_img) + + canvas = ' ' + test['canvas'] if 'canvas' in test else '' + width, height = _get_canvas_size(test) + + notes = '<p class="notes">%s' % test['notes'] if 'notes' in test else '' + + links = f'<link rel="match" href="{name}-expected.html">\n' + fuzzy = ('<meta name=fuzzy content="%s">\n' % + test['fuzzy'] if 'fuzzy' in test else '') + timeout = ('<meta name="timeout" content="%s">\n' % + test['timeout'] if 'timeout' in test else '') + timeout_js = ('// META: timeout=%s\n' % test['timeout'] + if 'timeout' in test else '') + + images = '' + for src in test.get('images', []): + img_id = src.split('/')[-1] + if '/' not in src: + src = '../images/%s' % src + images += '<img src="%s" id="%s" class="resource">\n' % (src, img_id) + for src in test.get('svgimages', []): + img_id = src.split('/')[-1] + if '/' not in src: + src = '../images/%s' % src + images += ('<svg><image xlink:href="%s" id="%s" class="resource">' + '</svg>\n' % (src, img_id)) + images = images.replace('../images/', '/images/') + + fonts = '' + fonthack = '' + for font in test.get('fonts', []): + fonts += ('@font-face {\n font-family: %s;\n' + ' src: url("/fonts/%s.ttf");\n}\n' % (font, font)) + # Browsers require the font to actually be used in the page. + if test.get('fonthack', 1): + fonthack += ('<span style="font-family: %s; position: ' + 'absolute; visibility: hidden">A</span>\n' % font) + if fonts: + fonts = '<style>\n%s</style>\n' % fonts + + fallback = test.get('fallback', + '<p class="fallback">FAIL (fallback content)</p>') + + desc = test.get('desc', '') + escaped_desc = _simpleEscapeJS(desc) + + attributes = test.get('attributes', '') + if attributes: + context_args = "'2d', %s" % attributes.strip() + attributes = ', ' + attributes.strip() + else: + context_args = "'2d'" + + is_promise_test = False + if 'test_type' in test: + if test['test_type'] == 'promise': + is_promise_test = True + else: + raise InvalidTestDefinitionError( + f'Test {name}\' test_type is invalid, it only accepts ' + '"promise" now for creating promise test type in the template ' + 'file.') + + template_params = { + 'name': name, + 'desc': desc, + 'escaped_desc': escaped_desc, + 'notes': notes, + 'images': images, + 'fonts': fonts, + 'fonthack': fonthack, + 'timeout': timeout, + 'timeout_js': timeout_js, + 'fuzzy': fuzzy, + 'links': links, + 'canvas': canvas, + 'width': width, + 'height': height, + 'expected': expectation_html, + 'code': code_canvas, + 'fallback': fallback, + 'attributes': attributes, + 'context_args': context_args, + 'promise_test': is_promise_test + } + + canvas_path = os.path.join(html_canvas_cfg.out_dir, sub_dir, name) + offscreen_path = os.path.join(offscreen_canvas_cfg.out_dir, sub_dir, name) + if 'manual' in test: + canvas_path += '-manual' + offscreen_path += '-manual' + + js_reference = test.get('reference') + html_reference = test.get('html_reference') + if js_reference is not None and html_reference is not None: + raise InvalidTestDefinitionError( + f'Test {name} is invalid, "reference" and "html_reference" can\'t ' + 'both be specified at the same time.') + + templates = _expand_templates(templates, template_params) + + ref_code = js_reference or html_reference + if ref_code is not None: + _write_reference_test( + js_reference is not None, templates, template_params, ref_code, + canvas_path if html_canvas_cfg.enabled else None, + offscreen_path if offscreen_canvas_cfg.enabled else None) + else: + _write_testharness_test( + templates, template_params, + canvas_path if html_canvas_cfg.enabled else None, + offscreen_path if offscreen_canvas_cfg.enabled else None) + + +def genTestUtils_union(TEMPLATEFILE: str, NAME2DIRFILE: str) -> None: + CANVASOUTPUTDIR = '../element' + CANVASIMAGEOUTPUTDIR = '../element' + OFFSCREENCANVASOUTPUTDIR = '../offscreen' + OFFSCREENCANVASIMAGEOUTPUTDIR = '../offscreen' + + # Run with --test argument to run unit tests. + if len(sys.argv) > 1 and sys.argv[1] == '--test': + doctest = importlib.import_module('doctest') + doctest.testmod() + sys.exit() + + templates = yaml.safe_load(pathlib.Path(TEMPLATEFILE).read_text()) + name_to_sub_dir = yaml.safe_load(pathlib.Path(NAME2DIRFILE).read_text()) + + tests = [] + test_yaml_directory = 'yaml-new' + TESTSFILES = [ + os.path.join(test_yaml_directory, f) + for f in os.listdir(test_yaml_directory) if f.endswith('.yaml') + ] + for t in sum( + [yaml.safe_load(pathlib.Path(f).read_text()) for f in TESTSFILES], []): + if 'DISABLED' in t: + continue + if 'meta' in t: + eval(compile(t['meta'], '<meta test>', 'exec'), {}, + {'tests': tests}) + else: + tests.append(t) + + # Ensure the test output directories exist. + testdirs = [ + CANVASOUTPUTDIR, OFFSCREENCANVASOUTPUTDIR, CANVASIMAGEOUTPUTDIR, + OFFSCREENCANVASIMAGEOUTPUTDIR + ] + for sub_dir in set(name_to_sub_dir.values()): + testdirs.append('%s/%s' % (CANVASOUTPUTDIR, sub_dir)) + testdirs.append('%s/%s' % (OFFSCREENCANVASOUTPUTDIR, sub_dir)) + for d in testdirs: + try: + os.mkdir(d) + except FileExistsError: + pass # Ignore if it already exists, + + used_tests = collections.defaultdict(set) + for original_test in tests: + variants = original_test.get('variants', {'': dict()}) + for variant_name, variant_params in variants.items(): + test = original_test.copy() + if variant_name or variant_params: + test['name'] += '.' + variant_name + test['code'] = test['code'] % variant_params + if 'reference' in test: + test['reference'] = test['reference'] % variant_params + if 'html_reference' in test: + test['html_reference'] = ( + test['html_reference'] % variant_params) + test.update(variant_params) + + name = test['name'] + print('\r(%s)' % name, ' ' * 32, '\t') + + enabled_canvas_types = _get_enabled_canvas_types(test) + + already_tested = used_tests[name].intersection( + enabled_canvas_types) + if already_tested: + raise InvalidTestDefinitionError( + f'Test {name} is defined twice for types {already_tested}') + used_tests[name].update(enabled_canvas_types) + + sub_dir = _get_test_sub_dir(name, name_to_sub_dir) + _generate_test( + test, + templates, + sub_dir, + html_canvas_cfg=TestConfig( + out_dir=CANVASOUTPUTDIR, + image_out_dir=CANVASIMAGEOUTPUTDIR, + enabled=CanvasType.HTML_CANVAS in enabled_canvas_types), + offscreen_canvas_cfg=TestConfig( + out_dir=OFFSCREENCANVASOUTPUTDIR, + image_out_dir=OFFSCREENCANVASIMAGEOUTPUTDIR, + enabled=CanvasType.OFFSCREEN_CANVAS in + enabled_canvas_types)) + + print() diff --git a/testing/web-platform/tests/html/canvas/tools/name2dir-canvas.yaml b/testing/web-platform/tests/html/canvas/tools/name2dir-canvas.yaml new file mode 100644 index 0000000000..c3818ebc76 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/name2dir-canvas.yaml @@ -0,0 +1,54 @@ +2d.transformation: "transformations" +2d.color.space: 'wide-gamut-canvas' +2d.composite: "compositing" +2d.coordinatespace: "conformance-requirements" +2d.missingargs: "conformance-requirements" +2d.type.delete: "conformance-requirements" +2d.voidreturn: "conformance-requirements" +2d.drawImage: "drawing-images-to-the-canvas" +2d.clearRect: "drawing-rectangles-to-the-canvas" +2d.fillRect: "drawing-rectangles-to-the-canvas" +2d.strokeRect: "drawing-rectangles-to-the-canvas" +2d.text.draw: "drawing-text-to-the-canvas" +2d.text.draw.space.basic: "drawing-text-to-the-canvas" +2d.text.draw.space.collapse: "drawing-text-to-the-canvas" +2d.text.measure: "drawing-text-to-the-canvas" +2d.fillStyle: "fill-and-stroke-styles" +2d.gradient: "fill-and-stroke-styles" +2d.pattern: "fill-and-stroke-styles" +2d.strokeStyle: "fill-and-stroke-styles" +2d.path: "path-objects" +2d.imageData: "pixel-manipulation" +2d.reset: "reset" +2d.shadow: "shadows" +2d.filter: "filters" +2d.text.align: "text-styles" +2d.text.baseline: "text-styles" +2d.text.font: "text-styles" +2d.text.draw.baseline: "text-styles" +2d.text.draw.space: "text-styles" +2d.text.measure.width.space: "text-styles" +2d.text.draw.space.collapse.end: "text-styles" +2d.text.draw.space.collapse.other: "text-styles" +2d.text.draw.space.collapse.space: "text-styles" +2d.text.draw.space.collapse.start: "text-styles" +2d.state: "the-canvas-state" +2d.scrollPathIntoView: "scroll" +2d.video: "video" +2d.canvas: "../../../html/semantics/embedded-content/the-canvas-element" +2d.getcontext: "../../../html/semantics/embedded-content/the-canvas-element" +2d.scaled: "../../../html/semantics/embedded-content/the-canvas-element" +2d.type: "../../../html/semantics/embedded-content/the-canvas-element" +context: "../../../html/semantics/embedded-content/the-canvas-element" +fallback: "../../../html/semantics/embedded-content/the-canvas-element" +initial: "../../../html/semantics/embedded-content/the-canvas-element" +security: "../../../html/semantics/embedded-content/the-canvas-element" +size: "../../../html/semantics/embedded-content/the-canvas-element" +toBlob: "../../../html/semantics/embedded-content/the-canvas-element" +toDataURL: "../../../html/semantics/embedded-content/the-canvas-element" +type: "../../../html/semantics/embedded-content/the-canvas-element" +2d.offscreencanvas: "the-offscreen-canvas" +2d.offscreencanva.getcontext: "the-offscreen-canvas" +2d.offscreencanva.context: "the-offscreen-canvas" +2d.offscreencanva.initial: "the-offscreen-canvas" +2d.offscreencanva.size: "the-offscreen-canvas" diff --git a/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml b/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml new file mode 100644 index 0000000000..807b09507b --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/name2dir-offscreen.yaml @@ -0,0 +1,25 @@ +2d.state: "the-canvas-state" +2d.transformation: "transformations" +2d.color.space: "wide-gamut-canvas" +2d.composite: "compositing" +2d.fillStyle: "fill-and-stroke-styles" +2d.gradient: "fill-and-stroke-styles" +2d.pattern: "fill-and-stroke-styles" +2d.strokeStyle: "fill-and-stroke-styles" +2d.shadow: "shadows" +2d.filter: "filters" +2d.clearRect: "drawing-rectangles-to-the-canvas" +2d.fillRect: "drawing-rectangles-to-the-canvas" +2d.strokeRect: "drawing-rectangles-to-the-canvas" +2d.drawImage: "drawing-images-to-the-canvas" +2d.imageData: "pixel-manipulation" +2d.path: "path-objects" +2d.text: "text" +2d.coordinatespace: "conformance-requirements" +2d.missingargs: "conformance-requirements" +2d.voidreturn: "conformance-requirements" +2d.canvas: "the-offscreen-canvas" +2d.getcontext: "the-offscreen-canvas" +context: "the-offscreen-canvas" +initial: "the-offscreen-canvas" +size: "the-offscreen-canvas" diff --git a/testing/web-platform/tests/html/canvas/tools/name2dir.yaml b/testing/web-platform/tests/html/canvas/tools/name2dir.yaml new file mode 100644 index 0000000000..d6871f2852 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/name2dir.yaml @@ -0,0 +1,43 @@ +2d.transformation: "transformations" +2d.color.space: 'wide-gamut-canvas' +2d.composite: "compositing" +2d.conformance.requirements: "conformance-requirements" +2d.drawImage: "drawing-images-to-the-canvas" +2d.clearRect: "drawing-rectangles-to-the-canvas" +2d.fillRect: "drawing-rectangles-to-the-canvas" +2d.strokeRect: "drawing-rectangles-to-the-canvas" +2d.text.draw: "drawing-text-to-the-canvas" +2d.text.draw.space.basic: "drawing-text-to-the-canvas" +2d.text.draw.space.collapse: "drawing-text-to-the-canvas" +2d.text.measure: "drawing-text-to-the-canvas" +2d.fillStyle: "fill-and-stroke-styles" +2d.gradient: "fill-and-stroke-styles" +2d.pattern: "fill-and-stroke-styles" +2d.strokeStyle: "fill-and-stroke-styles" +2d.line: "line-styles" +2d.path: "path-objects" +2d.imageData: "pixel-manipulation" +2d.reset: "reset" +2d.shadow: "shadows" +2d.filter: "filters" +2d.layer: "layers" +2d.text.align: "text-styles" +2d.text.baseline: "text-styles" +2d.text.font: "text-styles" +2d.text.draw.baseline: "text-styles" +2d.text.draw.space: "text-styles" +2d.text.measure.width.space: "text-styles" +2d.text.draw.space.collapse.end: "text-styles" +2d.text.draw.space.collapse.other: "text-styles" +2d.text.draw.space.collapse.space: "text-styles" +2d.text.draw.space.collapse.start: "text-styles" +2d.state: "the-canvas-state" +2d.scrollPathIntoView: "scroll" +2d.video: "video" +2d.canvas: "../../../html/semantics/embedded-content/the-canvas-element" +2d.canvas.getcontext: "../../../html/semantics/embedded-content/the-canvas-element" +2d.canvas.scaled: "../../../html/semantics/embedded-content/the-canvas-element" +2d.canvas.type: "../../../html/semantics/embedded-content/the-canvas-element" +2d.canvas.initial: "../../../html/semantics/embedded-content/the-canvas-element" +2d.canvas.size: "../../../html/semantics/embedded-content/the-canvas-element" +2d.canvas.type: "../../../html/semantics/embedded-content/the-canvas-element" diff --git a/testing/web-platform/tests/html/canvas/tools/templates-new.yaml b/testing/web-platform/tests/html/canvas/tools/templates-new.yaml new file mode 100644 index 0000000000..7d8ebfccf5 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/templates-new.yaml @@ -0,0 +1,237 @@ +offscreen: | + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + <title>OffscreenCanvas test: %(name)s</title> + %(timeout)s\ + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/html/canvas/resources/canvas-tests.js"></script> + + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + + %(notes)s + <script>\ + + ## Promise vs. async test header: + {% if promise_test %}\ + promise_test(async t => { + {% else %}\ + var t = async_test("%(escaped_desc)s"); + var t_pass = t.done.bind(t); + var t_fail = t.step_func(function(reason) { + throw reason; + }); + t.step(function() { + {% endif %}\ + + ## Test body: + var canvas = new OffscreenCanvas(%(width)s, %(height)s); + var ctx = canvas.getContext(%(context_args)s); + + %(code)s\ + + ## Promise vs. async test footer: + {% if promise_test %}\ + + }, "%(desc)s"); + {% else %}\ + t.done(); + + }); + {% endif %}\ + </script> + +worker: | + %(timeout_js)s\ + // DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. + // OffscreenCanvas test in a worker:%(name)s + // Description:%(desc)s + // Note:%(notes)s + + importScripts("/resources/testharness.js"); + importScripts("/html/canvas/resources/canvas-tests.js"); + + ## Promise vs. async test header: + {% if promise_test %}\ + promise_test(async t => { + {% else %}\ + var t = async_test("%(escaped_desc)s"); + var t_pass = t.done.bind(t); + var t_fail = t.step_func(function(reason) { + throw reason; + }); + t.step(function() { + {% endif %}\ + + ## Test body: + var canvas = new OffscreenCanvas(%(width)s, %(height)s); + var ctx = canvas.getContext(%(context_args)s); + + %(code)s + t.done();\ + + ## Promise vs. async test footer: + {% if promise_test %}\ + }, "%(desc)s"); + {% else %}\ + }); + {% endif %}\ + done(); + +element: | + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + <title>Canvas test: %(name)s</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/html/canvas/resources/canvas-tests.js"></script> + <link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> + %(fonts)s\ + <body class="show_output"> + + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + + %(notes)s + %(fonthack)s\ + <p class="output">Actual output:</p> + <canvas id="c" class="output" width="%(width)s" height="%(height)s"%(canvas)s>\- + %(fallback)s\ + </canvas> + %(expected)s + <ul id="d"></ul>\ + + <script>\ + + ## Promise vs. async test header: + {% if promise_test %}\ + promise_test(async t => { + + var canvas = document.getElementById('c'); + var ctx = canvas.getContext('2d'%(attributes)s); + {% else %}\ + var t = async_test("%(escaped_desc)s"); + _addTest(function(canvas, ctx) { + {% endif %}\ + + ## Test body: + %(code)s + + ## Promise vs. async test footer: + {% if promise_test %}\ + }, "%(desc)s"); + {% else %}\ + }%(attributes)s); + {% endif %}\ + \ + </script> + %(images)s + +offscreen_ref_test: |- + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + {% if promise_test %}\ + <html class="reftest-wait"> + {% endif %}\ + %(links)s\ + %(fuzzy)s\ + %(timeout)s\ + <title>Canvas test: %(name)s</title> + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + %(fonts)s%(fonthack)s%(notes)s<canvas id="canvas" width="%(width)s" height="%(height)s"%(canvas)s> + %(fallback)s + </canvas> + <script{% if promise_test %} type="module"{% endif %}> + const canvas = new OffscreenCanvas(%(width)s, %(height)s); + const ctx = canvas.getContext(%(context_args)s); + + %(code)s + + const outputCanvas = document.getElementById("canvas"); + outputCanvas.getContext(%(context_args)s).drawImage(canvas, 0, 0); + {% if promise_test %}\ + document.documentElement.classList.remove("reftest-wait"); + {% endif %}\ + </script> + %(images)s\ + {% if promise_test %}</html>{% endif %} + +worker_ref_test: | + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + <html class="reftest-wait"> + %(links)s\ + %(fuzzy)s\ + %(timeout)s\ + <title>Canvas test: %(name)s</title> + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + %(fonts)s%(fonthack)s%(notes)s<canvas id="canvas" width="%(width)s" height="%(height)s"%(canvas)s> + %(fallback)s + </canvas> + <script id='myWorker' type='text/worker'> + self.onmessage = {% if promise_test %}async {% endif %}function(e) { + const canvas = new OffscreenCanvas(%(width)s, %(height)s); + const ctx = canvas.getContext('2d'); + + %(code)s + + const bitmap = canvas.transferToImageBitmap(); + self.postMessage(bitmap, bitmap); + }; + </script> + <script> + const blob = new Blob([document.getElementById('myWorker').textContent]); + const worker = new Worker(URL.createObjectURL(blob)); + worker.addEventListener('message', msg => { + const outputCtx = document.getElementById("canvas").getContext('2d'); + outputCtx.drawImage(msg.data, 0, 0); + document.documentElement.classList.remove("reftest-wait"); + }); + worker.postMessage(null); + </script> + %(images)s\ + </html> + +element_ref_test: |- + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + {% if promise_test %}\ + <html class="reftest-wait"> + {% endif %}\ + %(links)s\ + %(fuzzy)s\ + %(timeout)s\ + <title>Canvas test: %(name)s</title> + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + %(fonts)s%(fonthack)s%(notes)s<canvas id="canvas" width="%(width)s" height="%(height)s"%(canvas)s> + %(fallback)s + </canvas> + <script{% if promise_test %} type="module"{% endif %}> + const canvas = document.getElementById("canvas"); + const ctx = canvas.getContext(%(context_args)s); + + %(code)s + {% if promise_test %}\ + document.documentElement.classList.remove("reftest-wait"); + {% endif %}\ + </script> + %(images)s\ + {% if promise_test %}</html>{% endif %} + + +html_ref_test: |- + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + %(links)s\ + %(fuzzy)s\ + %(timeout)s\ + <title>Canvas test: %(name)s</title> + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + %(fonts)s%(fonthack)s%(notes)s + %(code)s + %(images)s diff --git a/testing/web-platform/tests/html/canvas/tools/templates.yaml b/testing/web-platform/tests/html/canvas/tools/templates.yaml new file mode 100644 index 0000000000..cb11f76db6 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/templates.yaml @@ -0,0 +1,79 @@ +offscreen: | + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + <title>OffscreenCanvas test: %(name)s</title>%(timeout)s + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/html/canvas/resources/canvas-tests.js"></script> + + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + + %(notes)s + <script> + var t = async_test("%(escaped_desc)s"); + var t_pass = t.done.bind(t); + var t_fail = t.step_func(function(reason) { + throw reason; + }); + t.step(function() { + + var canvas = new OffscreenCanvas(%(width)s, %(height)s); + var ctx = canvas.getContext(%(context_args)s); + + %(code)s + }); + </script> + + +worker: | + %(timeout_js)s// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. + // OffscreenCanvas test in a worker:%(name)s + // Description:%(desc)s + // Note:%(notes)s + + importScripts("/resources/testharness.js"); + importScripts("/html/canvas/resources/canvas-tests.js"); + + var t = async_test("%(escaped_desc)s"); + var t_pass = t.done.bind(t); + var t_fail = t.step_func(function(reason) { + throw reason; + }); + t.step(function() { + + var canvas = new OffscreenCanvas(%(width)s, %(height)s); + var ctx = canvas.getContext(%(context_args)s); + + %(code)s + }); + done(); + + +element: | + <!DOCTYPE html> + <!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> + <title>Canvas test: %(name)s</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/html/canvas/resources/canvas-tests.js"></script> + <link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> + %(fonts)s<body class="show_output"> + + <h1>%(name)s</h1> + <p class="desc">%(desc)s</p> + + %(notes)s + %(fonthack)s<p class="output">Actual output:</p> + <canvas id="c" class="output" width="%(width)s" height="%(height)s"%(canvas)s>%(fallback)s</canvas> + %(expected)s + <ul id="d"></ul> + <script> + var t = async_test("%(escaped_desc)s"); + _addTest(function(canvas, ctx) { + + %(code)s + + }%(attributes)s); + </script> + %(images)s diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/color_space.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/color_space.yaml new file mode 100644 index 0000000000..ba5d93e0b0 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/color_space.yaml @@ -0,0 +1,329 @@ +- name: 2d.color.space.p3.to.p3 + desc: test getImageData with display-p3 and uint8 from display p3 uint8 canvas + attributes: | + {colorSpace: "display-p3"} + code: | + var color_style = 'rgb(50, 100, 150)'; + // [0.24304, 0.38818, 0.57227, 1.0] * 255 = [62, 99, 146, 255] + var pixel_expected = [62, 99, 146, 255]; + var epsilon = 2; + ctx.fillStyle = color_style; + ctx.fillRect(0, 0, 10, 10); + + var pixel = ctx.getImageData(5, 5, 1, 1, {colorSpace: "display-p3", storageFormat: "uint8"}).data; + @assert pixel.length === pixel_expected.length; + assert_approx_equals(pixel[0], pixel_expected[0], 2); + assert_approx_equals(pixel[1], pixel_expected[1], 2); + assert_approx_equals(pixel[2], pixel_expected[2], 2); + assert_approx_equals(pixel[3], pixel_expected[3], 2); + +- name: 2d.color.space.p3.to.srgb + desc: test getImageData with srsb and uint8 from display p3 uint8 canvas + attributes: | + {colorSpace: "display-p3"} + code: | + var color_style = 'rgb(50, 100, 150)'; + var pixel_expected = [50, 100, 150, 255]; + var epsilon = 2; + ctx.fillStyle = color_style; + ctx.fillRect(0, 0, 10, 10); + + var pixel = ctx.getImageData(5, 5, 1, 1, {colorSpace: "srgb", storageFormat: "uint8"}).data; + @assert pixel.length === pixel_expected.length; + assert_approx_equals(pixel[0], pixel_expected[0], 2); + assert_approx_equals(pixel[1], pixel_expected[1], 2); + assert_approx_equals(pixel[2], pixel_expected[2], 2); + assert_approx_equals(pixel[3], pixel_expected[3], 2); + +- name: 2d.color.space.p3.toBlob.p3.canvas + desc: test if toblob returns p3 data from p3 color space canvas + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + ctx.fillStyle = "rgba(155, 27, 27, 1)"; + ctx.fillRect(0, 0, 1, 1); + ctx.fillStyle = "rgba(27, 155, 27, 0)"; + ctx.fillRect(1, 0, 1, 1); + ctx.fillStyle = "rgba(27, 27, 155, 0.5)"; + ctx.fillRect(0, 1, 1, 1); + ctx.fillStyle = "rgba(27, 27, 27, 0.5)"; + ctx.fillRect(1, 1, 1, 1); + expectedPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + + var image = new Image(); + image.onload = t.step_func_done(function() { + var dstCanvas = document.createElement("canvas"); + dstCanvas.width = 2; + dstCanvas.height = 2; + var ctx = dstCanvas.getContext('2d', {colorSpace: "display-p3"}); + ctx.drawImage(image, 0, 0); + var actualPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + assert_array_approx_equals(actualPixels, expectedPixels, 2); + }); + + canvas.toBlob(function(blob) { + var urlCreator = window.URL || window.webkitURL; + image.src = urlCreator.createObjectURL(blob); + }, 'image/png', 1); + +- name: 2d.color.space.p3.toDataURL.p3.canvas + desc: test if toDataURL returns p3 data from canvas with p3 color space + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + ctx.fillStyle = "rgba(155, 27, 27, 1)"; + ctx.fillRect(0, 0, 1, 1); + ctx.fillStyle = "rgba(27, 155, 27, 0)"; + ctx.fillRect(1, 0, 1, 1); + ctx.fillStyle = "rgba(27, 27, 155, 0.5)"; + ctx.fillRect(0, 1, 1, 1); + ctx.fillStyle = "rgba(27, 27, 27, 0.5)"; + ctx.fillRect(1, 1, 1, 1); + expectedPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + + var image = new Image(); + image.onload = t.step_func_done(function() { + var dstCanvas = document.createElement("canvas"); + dstCanvas.width = 2; + dstCanvas.height = 2; + var ctx = dstCanvas.getContext('2d', {colorSpace: "display-p3"}); + ctx.drawImage(image, 0, 0); + var actualPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + assert_array_approx_equals(actualPixels, expectedPixels, 2); + }); + image.src = canvas.toDataURL(); + +- name: 2d.color.space.p3.toDataURL.jpeg.p3.canvas + desc: test if toDataURL('image/jpeg') returns p3 data from canvas with p3 color space + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + ctx.fillStyle = "rgba(155, 27, 27, 1)"; + ctx.fillRect(0, 0, 1, 1); + ctx.fillStyle = "rgba(27, 155, 27, 0)"; + ctx.fillRect(1, 0, 1, 1); + ctx.fillStyle = "rgba(27, 27, 155, 0.5)"; + ctx.fillRect(0, 1, 1, 1); + ctx.fillStyle = "rgba(27, 27, 27, 0.5)"; + ctx.fillRect(1, 1, 1, 1); + expectedPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + + var image = new Image(); + image.onload = t.step_func_done(function() { + var dstCanvas = document.createElement("canvas"); + dstCanvas.width = 2; + dstCanvas.height = 2; + var ctx = dstCanvas.getContext('2d', {colorSpace: "display-p3"}); + ctx.drawImage(image, 0, 0); + var actualPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + assert_array_approx_equals(actualPixels, expectedPixels, 2); + }); + image.src = canvas.toDataURL("image/jpeg"); + +- name: 2d.color.space.p3.toBlob.with.putImageData + desc: Use putImageData to put some p3 data in canvas and test if toBlob returns the same data + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + canvas.width = 2; + canvas.height = 2; + + // Create an ImageData using createImageData and populate its data array. + var image_data = ctx.createImageData(canvas.width, canvas.height, {colorSpace: "display-p3"}); + var color_data = [[255, 100, 150, 1.0], [255, 100, 150, 0.5], + [255, 100, 150, 0.5], [255, 100, 150, 0]]; + var data = image_data.data; + for (var i = 0; i < data.length / 4; ++i) { + data[4*i + 0] = color_data[i][0]; + data[4*i + 1] = color_data[i][1]; + data[4*i + 2] = color_data[i][2]; + data[4*i + 3] = color_data[i][3]; + } + ctx.putImageData(image_data, 0, 0); + expectedPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + + var image = new Image(); + image.onload = t.step_func_done(function() { + var dstCanvas = document.createElement("canvas"); + dstCanvas.width = 2; + dstCanvas.height = 2; + var ctx = dstCanvas.getContext('2d', {colorSpace: "display-p3"}); + ctx.drawImage(image, 0, 0); + var actualPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + assert_array_approx_equals(actualPixels, expectedPixels, 2); + }); + canvas.toBlob(function(blob) { + var urlCreator = window.URL || window.webkitURL; + image.src = urlCreator.createObjectURL(blob); + }, 'image/png', 1); + +- name: 2d.color.space.p3.toDataURL.with.putImageData + desc: Use putImageData to put some p3 data in canvas and test if toDataURL returns the same data + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + canvas.width = 2; + canvas.height = 2; + + // Create an ImageData using createImageData and populate its data array. + var image_data = ctx.createImageData(canvas.width, canvas.height, {colorSpace: "display-p3"}); + var color_data = [[255, 100, 150, 1.0], [255, 100, 150, 0.5], + [255, 100, 150, 0.5], [255, 100, 150, 0]]; + var data = image_data.data; + for (var i = 0; i < data.length / 4; ++i) { + data[4*i + 0] = color_data[i][0]; + data[4*i + 1] = color_data[i][1]; + data[4*i + 2] = color_data[i][2]; + data[4*i + 3] = color_data[i][3]; + } + ctx.putImageData(image_data, 0, 0); + expectedPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + + var image = new Image(); + image.onload = t.step_func_done(function() { + var dstCanvas = document.createElement("canvas"); + dstCanvas.width = 2; + dstCanvas.height = 2; + var ctx = dstCanvas.getContext('2d', {colorSpace: "display-p3"}); + ctx.drawImage(image, 0, 0); + var actualPixels = ctx.getImageData(0, 0, 2, 2, {colorSpace: "display-p3"}).data; + assert_array_approx_equals(actualPixels, expectedPixels, 2); + }); + image.src = canvas.toDataURL(); + +- name: 2d.color.space.p3.fillText + desc: Test if fillText can be used with a solid display-p3 color + attributes: | + {colorSpace: "display-p3"} + canvasType: ['HTMLCanvas'] + code: | + deferTest(); + + const fullRedInP3 = [255, 0, 0, 255]; + const sRGBRedInP3 = [234, 51, 35, 255]; + + canvas.width = 100; + canvas.height = 100; + + let f = new FontFace("Ahem", "url(/fonts/Ahem.ttf)"); + document.fonts.add(f); + f.load().then(function() { + t.step(function() { + ctx.font = "40px Ahem"; + + ctx.fillStyle = "#f00"; + ctx.fillText("A", 0, 50); + + ctx.fillStyle = "black"; + ctx.fillStyle = "color(display-p3 100% 0 0)"; + ctx.fillText("A", 50, 50); + + let pixels = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: "display-p3" }).data; + let pixelAt = function(x, y) { + let offset = (y * canvas.width + x) * 4; + return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]]; + }; + + assert_array_equals(pixelAt(25, 25), sRGBRedInP3); + assert_array_equals(pixelAt(75, 25), fullRedInP3); + + t.done(); + }); + }); + +- name: 2d.color.space.p3.strokeText + desc: Test if strokeText can be used with a solid display-p3 color + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + deferTest(); + + const fullRedInP3 = [255, 0, 0, 255]; + const sRGBRedInP3 = [234, 51, 35, 255]; + + canvas.width = 100; + canvas.height = 100; + + let f = new FontFace("Ahem", "url(/fonts/Ahem.ttf)"); + document.fonts.add(f); + f.load().then(function() { + t.step(function() { + ctx.font = "40px Ahem"; + + ctx.strokeStyle = "#f00"; + ctx.lineWidth = 20; + ctx.strokeText("A", 0, 50); + + ctx.strokeStyle = "black"; + ctx.strokeStyle = "color(display-p3 100% 0 0)"; + ctx.strokeText("A", 50, 50); + + let pixels = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: "display-p3" }).data; + let pixelAt = function(x, y) { + let offset = (y * canvas.width + x) * 4; + return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]]; + }; + + assert_array_equals(pixelAt(25, 25), sRGBRedInP3); + assert_array_equals(pixelAt(75, 25), fullRedInP3); + + t.done(); + }); + }); + +- name: 2d.color.space.p3.fillText.shadow + desc: Test if fillText can be used with a display-p3 shadow color + attributes: | + {colorSpace: "display-p3"} + canvasType: + ['HTMLCanvas'] + code: | + deferTest(); + + const fullRedInP3 = [255, 0, 0, 255]; + const sRGBRedInP3 = [234, 51, 35, 255]; + + canvas.width = 100; + canvas.height = 100; + + let f = new FontFace("Ahem", "url(/fonts/Ahem.ttf)"); + document.fonts.add(f); + f.load().then(function() { + t.step(function() { + ctx.font = "40px Ahem"; + + ctx.fillStyle = "black"; + ctx.shadowBlur = 4; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 50; + ctx.shadowColor = "#f00"; + ctx.fillText("A", 0, 0); + + ctx.shadowColor = "black"; + ctx.shadowColor = "color(display-p3 100% 0 0)"; + ctx.fillText("A", 50, 0); + + let pixels = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: "display-p3" }).data; + let pixelAt = function(x, y) { + let offset = (y * canvas.width + x) * 4; + return [pixels[offset], pixels[offset + 1], pixels[offset + 2], pixels[offset + 3]]; + }; + + assert_array_equals(pixelAt(25, 25), sRGBRedInP3); + assert_array_equals(pixelAt(75, 25), fullRedInP3); + + t.done(); + }); + }); diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/compositing.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/compositing.yaml new file mode 100644 index 0000000000..0051f07db2 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/compositing.yaml @@ -0,0 +1,232 @@ +- name: 2d.composite.globalAlpha.range + code: | + ctx.globalAlpha = 0.5; + // This may not set it to exactly 0.5 if it is rounded/quantised, so + // remember for future comparisons. + var a = ctx.globalAlpha; + @assert ctx.globalAlpha === a; + 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 + code: | + ctx.globalAlpha = 0.5; + // This may not set it to exactly 0.5 if it is rounded/quantised, so + // remember for future comparisons. + var a = ctx.globalAlpha; + 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 + code: | + @assert ctx.globalAlpha === 1.0; + +- name: 2d.composite.globalAlpha.fill + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 ==~ 2,253,0,255; + expected: green + +- name: 2d.composite.globalAlpha.canvas + canvasType: ['HTMLCanvas'] + 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); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + ctx.drawImage(canvas2, 0, 0); + @assert pixel 50,25 ==~ 2,253,0,255; + expected: green + +- name: 2d.composite.globalAlpha.canvaspattern + canvasType: ['HTMLCanvas'] + 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'); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 ==~ 2,253,0,255; + expected: green + +- name: 2d.composite.globalAlpha.canvascopy + canvasType: ['HTMLCanvas'] + 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 + + +- name: 2d.composite.operation.get + 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 + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'nonexistent'; + @assert ctx.globalCompositeOperation === 'xor'; + +- name: 2d.composite.operation.darker + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'darker'; + @assert ctx.globalCompositeOperation === 'xor'; + +- name: 2d.composite.operation.over + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'over'; + @assert ctx.globalCompositeOperation === 'xor'; + +- name: 2d.composite.operation.clear + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'clear'; + @assert ctx.globalCompositeOperation === 'clear'; + +- name: 2d.composite.operation.highlight + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'highlight'; + @assert ctx.globalCompositeOperation === 'xor'; + +- name: 2d.composite.operation.nullsuffix + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'source-over\0'; + @assert ctx.globalCompositeOperation === 'xor'; + +- name: 2d.composite.operation.casesensitive + code: | + ctx.globalCompositeOperation = 'xor'; + ctx.globalCompositeOperation = 'Source-over'; + @assert ctx.globalCompositeOperation === 'xor'; + +- name: 2d.composite.operation.default + code: | + @assert ctx.globalCompositeOperation === 'source-over'; + + +- name: 2d.composite.globalAlpha.image + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0); + @assert pixel 50,25 ==~ 2,253,0,255; + expected: green + +- name: 2d.composite.globalAlpha.canvas + canvasType: ['OffscreenCanvas'] + code: | + var offscreenCanvas2 = new OffscreenCanvas(100, 50); + var ctx2 = offscreenCanvas2.getContext('2d'); + ctx2.fillStyle = '#f00'; + ctx2.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + ctx.drawImage(offscreenCanvas2, 0, 0); + @assert pixel 50,25 ==~ 2,253,0,255; + +- name: 2d.composite.globalAlpha.imagepattern + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.fillStyle = ctx.createPattern(bitmap, 'no-repeat'); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 ==~ 2,253,0,255; + expected: green + +- name: 2d.composite.globalAlpha.canvaspattern + canvasType: ['OffscreenCanvas'] + code: | + var offscreenCanvas2 = new OffscreenCanvas(100, 50); + var ctx2 = offscreenCanvas2.getContext('2d'); + ctx2.fillStyle = '#f00'; + ctx2.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.fillStyle = ctx.createPattern(offscreenCanvas2, 'no-repeat'); + // Avoiding any potential alpha = 0 optimisations. + ctx.globalAlpha = 0.01; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 ==~ 2,253,0,255; + +- name: 2d.composite.globalAlpha.canvascopy + canvasType: ['OffscreenCanvas'] + code: | + var offscreenCanvas2 = new OffscreenCanvas(100, 50); + var ctx2 = offscreenCanvas2.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(offscreenCanvas2, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,130; diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/conformance_requirements.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/conformance_requirements.yaml new file mode 100644 index 0000000000..3483d115f4 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/conformance_requirements.yaml @@ -0,0 +1,178 @@ +- name: 2d.conformance.requirements.delete + desc: window.CanvasRenderingContext2D is Configurable + notes: &bindings Defined in "Web IDL" (draft) + canvasType: ['HTMLCanvas'] + code: | + @assert window.CanvasRenderingContext2D !== undefined; + @assert delete window.CanvasRenderingContext2D === true; + @assert window.CanvasRenderingContext2D === undefined; + +- name: 2d.conformance.requirements.basics + desc: void methods return undefined + notes: *bindings + 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(canvas, 0, 0, 1, 1, 0, 0, 0, 0) === undefined; + @assert ctx.createLinearGradient(0, 0, 0, 0).addColorStop(0, 'white') === undefined; + +- name: 2d.conformance.requirements.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.conformance.requirements.drawings + desc: void methods return undefined + images: + - yellow.png + canvasType: ['HTMLCanvas'] + code: | + @assert ctx.drawImage(document.getElementById('yellow.png'), 0, 0, 1, 1, 0, 0, 0, 0) === undefined; + diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml new file mode 100644 index 0000000000..e1837342e6 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-images-to-the-canvas.yaml @@ -0,0 +1,711 @@ +- name: 2d.drawImage.canvas + canvasType: ['HTMLCanvas'] + 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.drawImage(canvas2, 0, 0); + + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + + ctx.drawImage(document.createElement('canvas'), 0, 0); + + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.self.1 + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 50, 50); + ctx.fillStyle = '#f00'; + ctx.fillRect(50, 0, 50, 50); + ctx.drawImage(canvas, 50, 0); + + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.self.2 + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 1, 100, 49); + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 1); + ctx.drawImage(canvas, 0, 1); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 2); + + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.null + code: | + @assert throws TypeError ctx.drawImage(null, 0, 0); + +- name: 2d.drawImage.wrongtype + desc: Incorrect image types in drawImage do not match any defined overloads, so + WebIDL throws a TypeError + code: | + @assert throws TypeError ctx.drawImage(undefined, 0, 0); + @assert throws TypeError ctx.drawImage(0, 0, 0); + @assert throws TypeError ctx.drawImage("", 0, 0); + +- name: 2d.drawImage.wrongtype.paragraph + desc: Incorrect image types in drawImage do not match any defined overloads, so + WebIDL throws a TypeError + notes: &bindings Defined in "Web IDL" (draft) + canvasType: ['HTMLCanvas'] + code: | + @assert throws TypeError ctx.drawImage(document.createElement('p'), 0, 0); + +- name: 2d.drawImage.incomplete.nosrc + canvasType: ['HTMLCanvas'] + mozilla: {throws: !!null ''} + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + var img = new Image(); + ctx.drawImage(img, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.incomplete.immediate + canvasType: ['HTMLCanvas'] + images: + - red.png + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + 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: + ctx.drawImage(img, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; @moz-todo + expected: green + +- name: 2d.drawImage.incomplete.reload + canvasType: ['HTMLCanvas'] + images: + - yellow.png + - red.png + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + 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: + ctx.drawImage(img, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; @moz-todo + expected: green + +- name: 2d.drawImage.incomplete.emptysrc + canvasType: ['HTMLCanvas'] + images: + - red.png + mozilla: {throws: !!null ''} + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + var img = document.getElementById('red.png'); + img.src = ""; + ctx.drawImage(img, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.incomplete.removedsrc + canvasType: ['HTMLCanvas'] + images: + - red.png + mozilla: {throws: !!null ''} + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + var img = document.getElementById('red.png'); + img.removeAttribute('src'); + ctx.drawImage(img, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.nonexistent + canvasType: ['HTMLCanvas'] + images: + - not-found-at-all.png + code: | + var img = document.getElementById('not-found-at-all.png'); + @assert throws INVALID_STATE_ERR ctx.drawImage(img, 0, 0); + +- name: 2d.drawImage.zerocanvas + desc: drawImage with zero-sized canvas as the source shoud throw exception + canvasType: ['HTMLCanvas'] + code: | + var canvas2 = document.createElement('canvas'); + canvas2.width = 0; + canvas2.height = 50; + @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0); + + canvas2.width = 50; + canvas2.height = 0; + @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0); + + canvas2.width = 0; + canvas2.height = 0; + @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0); + +- name: 2d.drawImage.animated.gif + desc: drawImage() of an animated GIF draws the first frame + canvasType: ['HTMLCanvas'] + images: + - anim-gr.gif + code: | + deferTest(); + step_timeout(t.step_func_done(function () { + ctx.drawImage(document.getElementById('anim-gr.gif'), 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + }), 500); + expected: green + +- name: 2d.drawImage.animated.apng + desc: drawImage() of an APNG with no poster frame draws the first frame + canvasType: ['HTMLCanvas'] + images: + - anim-gr.png + code: | + deferTest(); + step_timeout(t.step_func_done(function () { + ctx.drawImage(document.getElementById('anim-gr.png'), 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + }), 500); + expected: green + +# TODO: drawImage shadows + +- name: 2d.drawImage.3arg + test_type: promise + code: | + const response_red = await fetch('/images/red.png'); + const blob_red = await response_red.blob(); + const bitmap_red = await createImageBitmap(blob_red); + + const response_green = await fetch('/images/green.png'); + const blob_green = await response_green.blob(); + const bitmap_green = await createImageBitmap(blob_green); + + ctx.drawImage(bitmap_green, 0, 0); + ctx.drawImage(bitmap_red, -100, 0); + ctx.drawImage(bitmap_red, 100, 0); + ctx.drawImage(bitmap_red, 0, -50); + ctx.drawImage(bitmap_red, 0, 50); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + +- name: 2d.drawImage.5arg + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response_red = await fetch('/images/red.png'); + const blob_red = await response_red.blob(); + const bitmap_red = await createImageBitmap(blob_red); + + const response_green = await fetch('/images/green.png'); + const blob_green = await response_green.blob(); + const bitmap_green = await createImageBitmap(blob_green); + + ctx.drawImage(bitmap_green, 50, 0, 50, 50); + ctx.drawImage(bitmap_red, 0, 0, 50, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 50, 50); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + +- name: 2d.drawImage.9arg.basic + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/green.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0, 100, 50, 0, 0, 100, 50); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.9arg.sourcepos + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/rgrg-256x256.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 140, 20, 100, 50, 0, 0, 100, 50); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.9arg.sourcesize + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/rgrg-256x256.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 0, 0, 256, 256, 0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 51, 26); + ctx.fillRect(49, 24, 51, 26); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + @assert pixel 20,20 ==~ 0,255,0,255; + @assert pixel 80,20 ==~ 0,255,0,255; + @assert pixel 20,30 ==~ 0,255,0,255; + @assert pixel 80,30 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.9arg.destpos + test_type: promise + code: | + const response_red = await fetch('/images/red.png'); + const blob_red = await response_red.blob(); + const bitmap_red = await createImageBitmap(blob_red); + + const response_green = await fetch('/images/green.png'); + const blob_green = await response_green.blob(); + const bitmap_green = await createImageBitmap(blob_green); + + ctx.drawImage(bitmap_green, 0, 0, 100, 50, 0, 0, 100, 50); + ctx.drawImage(bitmap_green, 0, 0, 100, 50, -100, 0, 100, 50); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, 100, 0, 100, 50); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, 0, -50, 100, 50); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, 0, 50, 100, 50); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + +- name: 2d.drawImage.9arg.destsize + test_type: promise + code: | + const response_red = await fetch('/images/red.png'); + const blob_red = await response_red.blob(); + const bitmap_red = await createImageBitmap(blob_red); + + const response_green = await fetch('/images/green.png'); + const blob_green = await response_green.blob(); + const bitmap_green = await createImageBitmap(blob_green); + + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.drawImage(bitmap_green, 1, 1, 1, 1, 0, 0, 100, 50); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, -50, 0, 50, 50); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, 100, 0, 50, 50); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, 0, -25, 100, 25); + ctx.drawImage(bitmap_red, 0, 0, 100, 50, 0, 50, 100, 25); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + +- name: 2d.drawImage.canvas + canvasType: ['OffscreenCanvas'] + code: | + var offscreenCanvas2 = new OffscreenCanvas(100, 50); + var ctx2 = offscreenCanvas2.getContext('2d'); + ctx2.fillStyle = '#0f0'; + ctx2.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#f00'; + ctx.drawImage(offscreenCanvas2, 0, 0); + @assert pixel 0,0 ==~ 0,255,0,255; + @assert pixel 99,0 ==~ 0,255,0,255; + @assert pixel 0,49 ==~ 0,255,0,255; + @assert pixel 99,49 ==~ 0,255,0,255; + +- name: 2d.drawImage.zerocanvas + canvasType: ['OffscreenCanvas'] + code: | + var offscreenCanvas2 = new OffscreenCanvas(0, 10); + @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0); + + offscreenCanvas2.width = 10; + offscreenCanvas2.height = 0; + @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0); + + offscreenCanvas2.width = 0; + offscreenCanvas2.height = 0; + @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0); + +- name: 2d.drawImage.floatsource + test_type: promise + code: | + const response = await fetch('/images/green.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 10.1, 10.1, 0.1, 0.1, 0, 0, 100, 50); + @assert pixel 50,25 ==~ 0,255,0,255; + Expected: green + +- name: 2d.drawImage.zerosource + desc: drawImage with zero-sized source rectangle draws nothing without exception + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 10, 10, 0, 1, 0, 0, 100, 50); + ctx.drawImage(bitmap, 10, 10, 1, 0, 0, 0, 100, 50); + ctx.drawImage(bitmap, 10, 10, 0, 0, 0, 0, 100, 50); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.zerosource.image + desc: drawImage with zero-sized source rectangle from image draws nothing without exception + test_type: promise + canvasType: ['HTMLCanvas'] + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + + function loadImage(src) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = (err) => reject(err); + img.src = src; + }); + } + const img1 = await loadImage('/images/red-zerowidth.svg'); + const img2 = await loadImage('/images/red-zeroheight.svg'); + const img3 = await loadImage('/images/red-zerosize.svg'); + + ctx.drawImage(img1, 0, 0, 100, 50); + ctx.drawImage(img2, 0, 0, 100, 50); + ctx.drawImage(img3, 0, 0, 100, 50); + _assertPixel(canvas, 50, 25, 0, 255, 0, 255); + expected: green + +- name: 2d.drawImage.zerosource.image + desc: drawImage with zero-sized source rectangle from image draws nothing without exception + test_type: promise + canvasType: ['OffscreenCanvas'] + 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-zerowidth.svg'); + 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); + _assertPixel(canvas, 50,25, 0,255,0,255); + }); + }); + expected: green + +- name: 2d.drawImage.negativesource + desc: Negative source width/height represents the correct rectangle + mozilla: {throws: !!null ''} + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/ggrr-256x256.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 100, 78, -100, 50, 0, 0, 50, 50); + ctx.drawImage(bitmap, 100, 128, -100, -50, 50, 0, 50, 50); + @assert pixel 1,1 ==~ 0,255,0,255; + @assert pixel 1,48 ==~ 0,255,0,255; + @assert pixel 98,1 ==~ 0,255,0,255; + @assert pixel 98,48 ==~ 0,255,0,255; + @assert pixel 48,1 ==~ 0,255,0,255; + @assert pixel 48,48 ==~ 0,255,0,255; + @assert pixel 51,1 ==~ 0,255,0,255; + @assert pixel 51,48 ==~ 0,255,0,255; + @assert pixel 25,25 ==~ 0,255,0,255; + @assert pixel 75,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.negativedest + desc: Negative destination width/height represents the correct rectangle + mozilla: {throws: !!null ''} + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/ggrr-256x256.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 100, 78, 50, 50, 0, 50, 50, -50); + ctx.drawImage(bitmap, 100, 128, 50, -50, 100, 50, -50, -50); + @assert pixel 1,1 ==~ 0,255,0,255; + @assert pixel 1,48 ==~ 0,255,0,255; + @assert pixel 98,1 ==~ 0,255,0,255; + @assert pixel 98,48 ==~ 0,255,0,255; + @assert pixel 48,1 ==~ 0,255,0,255; + @assert pixel 48,48 ==~ 0,255,0,255; + @assert pixel 51,1 ==~ 0,255,0,255; + @assert pixel 51,48 ==~ 0,255,0,255; + @assert pixel 25,25 ==~ 0,255,0,255; + @assert pixel 75,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.negativedir + desc: Negative dimensions do not affect the direction of the image + mozilla: {throws: !!null ''} + test_type: promise + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/ggrr-256x256.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + ctx.drawImage(bitmap, 0, 178, 50, -100, 0, 0, 50, 100); + ctx.drawImage(bitmap, 0, 78, 50, 100, 50, 100, 50, -100); + @assert pixel 1,1 ==~ 0,255,0,255; + @assert pixel 1,48 ==~ 0,255,0,255; + @assert pixel 98,1 ==~ 0,255,0,255; + @assert pixel 98,48 ==~ 0,255,0,255; + @assert pixel 48,1 ==~ 0,255,0,255; + @assert pixel 48,48 ==~ 0,255,0,255; + @assert pixel 51,1 ==~ 0,255,0,255; + @assert pixel 51,48 ==~ 0,255,0,255; + @assert pixel 25,25 ==~ 0,255,0,255; + @assert pixel 75,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.outsidesource + DISABLED: fix this to match the current spec (transparent black outside source) + canvasType: ['OffscreenCanvas'] + code: | + const response_red = await fetch('/images/red.png'); + const blob_red = await response_red.blob(); + const bitmap_red = await createImageBitmap(blob_red); + + const response_green = await fetch('/images/green.png'); + const blob_green = await response_green.blob(); + const bitmap_green = await createImageBitmap(blob_green); + ctx.drawImage(bitmap_green, 10.5, 10.5, 89.5, 39.5, 0, 0, 100, 50); + ctx.drawImage(bitmap_green, 5.5, 5.5, -5.5, -5.5, 0, 0, 100, 50); + ctx.drawImage(bitmap_green, 100, 50, -5, -5, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, -0.001, 0, 100, 50, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 0, -0.001, 100, 50, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 0, 0, 100.001, 50, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 0, 0, 100, 50.001, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 50, 0, 50.001, 50, 0, 0, 100, 50); @moz-todo + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 0, 0, -5, 5, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 0, 0, 5, -5, 0, 0, 100, 50); + @assert throws INDEX_SIZE_ERR ctx.drawImage(bitmap1, 110, 60, -20, -20, 0, 0, 100, 50); + @assert pixel 50,25 ==~ 0,255,0,255; @moz-todo + expected: green + +- name: 2d.drawImage.broken + test_type: promise + code: | + const response = await fetch('/images/broken.png'); + const blob = await response.blob(); + + await promise_rejects_dom(t, 'InvalidStateError', createImageBitmap(blob), 'The source image could not be decoded.'); + expected: green + +- name: 2d.drawImage.svg + desc: drawImage() of an SVG image + test_type: promise + canvasType: ['HTMLCanvas'] + code: | + const img = new Image(); + const imageLoadPromise = new Promise((resolve, reject) => { + img.onload = () => resolve(); + img.onerror = (err) => reject(err); + }); + img.src = '/images/green.svg'; + await imageLoadPromise; + + ctx.drawImage(img, 0, 0); + _assertPixelApprox(canvas, 50, 25, 0, 255, 0, 255, 2); + expected: green + + +- name: 2d.drawImage.svg + desc: drawImage() of an SVG image + test_type: promise + canvasType: ['OffscreenCanvas'] + code: | + var promise = new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", '/images/green.svg'); + xhr.responseType = 'blob'; + xhr.send(); + xhr.onload = function() { + resolve(xhr.response); + }; + }); + promise.then(function(response) { + createImageBitmap(response).then(bitmap => { + ctx.drawImage(bitmap, 0, 0); + _assertPixelApprox(canvas, 50,25, 0,255,0,255, 2); + }); + }); + expected: green + +- name: 2d.drawImage.animated.poster + desc: drawImage() of an APNG draws the poster frame + test_type: promise + code: | + const response = await fetch('/images/anim-poster-gr.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; @moz-todo + expected: green + +- name: 2d.drawImage.path + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.rect(0, 0, 100, 50); + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0); + ctx.fill(); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.transform + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.translate(100, 0); + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.alpha + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.globalAlpha = 0; + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.clip + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.rect(-10, -10, 1, 1); + ctx.clip(); + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.rect(-10, -10, 1, 1); + ctx.clip(); + ctx.drawImage(bitmap, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.composite + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.globalCompositeOperation = 'destination-over'; + const response = await fetch('/images/red.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, 0, 0); + @assert pixel 50,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.nowrap + desc: Stretched images do not get pixels wrapping around the edges + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/redtransparent.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + ctx.drawImage(bitmap, -1950, 0, 2000, 50); + @assert pixel 45,25 ==~ 0,255,0,255; + @assert pixel 50,25 ==~ 0,255,0,255; + @assert pixel 55,25 ==~ 0,255,0,255; + expected: green + +- name: 2d.drawImage.nonfinite + desc: drawImage() with Infinity/NaN is ignored + test_type: promise + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + const response = await fetch('/images/redtransparent.png'); + const blob = await response.blob(); + const bitmap = await createImageBitmap(blob); + + @nonfinite ctx.drawImage(<bitmap>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>); + @nonfinite ctx.drawImage(<bitmap>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>); + @nonfinite ctx.drawImage(<bitmap>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>); + @assert pixel 50,25 == 0,255,0,255; + expected: green diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-rectangles-to-the-canvas.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-rectangles-to-the-canvas.yaml new file mode 100644 index 0000000000..408e932abe --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/drawing-rectangles-to-the-canvas.yaml @@ -0,0 +1,469 @@ +- name: 2d.clearRect.basic + desc: clearRect clears to transparent black + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.clearRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.clearRect.path + desc: clearRect does not affect the current path + code: | + ctx.fillStyle = '#0f0'; + ctx.beginPath(); + ctx.rect(0, 0, 100, 50); + ctx.clearRect(0, 0, 16, 16); + ctx.fill(); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.clearRect.zero + desc: clearRect of zero pixels has no effect + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.clearRect(0, 0, 100, 0); + ctx.clearRect(0, 0, 0, 50); + ctx.clearRect(0, 0, 0, 0); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.clearRect.negative + desc: clearRect of negative sizes works + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.clearRect(0, 0, 50, 25); + ctx.clearRect(100, 0, -50, 25); + ctx.clearRect(0, 50, 50, -25); + ctx.clearRect(100, 50, -50, -25); + @assert pixel 25,12 == 0,0,0,0; + @assert pixel 75,12 == 0,0,0,0; + @assert pixel 25,37 == 0,0,0,0; + @assert pixel 75,37 == 0,0,0,0; + expected: clear + +- name: 2d.clearRect.transform + desc: clearRect is affected by transforms + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.scale(10, 10); + ctx.translate(0, 5); + ctx.clearRect(0, -5, 10, 5); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.clearRect.globalalpha + desc: clearRect is not affected by globalAlpha + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.globalAlpha = 0.1; + ctx.clearRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.clearRect.globalcomposite + desc: clearRect is not affected by globalCompositeOperation + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.globalCompositeOperation = 'destination-atop'; + ctx.clearRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.clearRect.clip + desc: clearRect is affected by clipping regions + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.beginPath(); + ctx.rect(0, 0, 16, 16); + ctx.clip(); + ctx.clearRect(0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 16, 16); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.clearRect.shadow + desc: clearRect does not draw shadows + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.shadowColor = '#f00'; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 50; + ctx.clearRect(0, -50, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.clearRect.nonfinite + desc: clearRect() with Infinity/NaN is ignored + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + @nonfinite ctx.clearRect(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>); + @assert pixel 50,25 == 0,255,0,255; + expected: green + + +- name: 2d.fillRect.basic + desc: fillRect works + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillRect.path + desc: fillRect does not affect the current path + code: | + ctx.beginPath(); + ctx.rect(0, 0, 100, 50); + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 16, 16); + ctx.fillStyle = '#0f0'; + ctx.fill(); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillRect.zero + desc: fillRect of zero pixels has no effect + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 0); + ctx.fillRect(0, 0, 0, 50); + ctx.fillRect(0, 0, 0, 0); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillRect.negative + desc: fillRect of negative sizes works + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 50, 25); + ctx.fillRect(100, 0, -50, 25); + ctx.fillRect(0, 50, 50, -25); + ctx.fillRect(100, 50, -50, -25); + @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; + expected: green + +- name: 2d.fillRect.transform + desc: fillRect is affected by transforms + code: | + ctx.scale(10, 10); + ctx.translate(0, 5); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, -5, 10, 5); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +# don't bother testing globalalpha, globalcomposite because they're already heavily used by other test cases + +- name: 2d.fillRect.clip + desc: fillRect is affected by clipping regions + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.beginPath(); + ctx.rect(0, 0, 16, 16); + ctx.clip(); + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 16, 16); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillRect.shadow + desc: fillRect draws shadows + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.shadowColor = '#0f0'; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 50; + ctx.fillRect(0, -50, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillRect.nonfinite + desc: fillRect() with Infinity/NaN is ignored + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#f00'; + @nonfinite ctx.fillRect(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>); + @assert pixel 50,25 == 0,255,0,255; + expected: green + + +- name: 2d.strokeRect.basic + desc: strokeRect works + code: | + ctx.strokeStyle = '#0f0'; + ctx.lineWidth = 50; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.path + desc: strokeRect does not affect the current path + code: | + ctx.beginPath(); + ctx.rect(0, 0, 100, 50); + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 5; + ctx.strokeRect(0, 0, 16, 16); + ctx.fillStyle = '#0f0'; + ctx.fill(); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.zero.1 + desc: strokeRect of 0x0 pixels draws nothing + code: | + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 250; + ctx.strokeRect(50, 25, 0, 0); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.strokeRect.zero.2 + desc: strokeRect of 0x0 pixels draws nothing, including caps and joins + code: | + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 250; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.strokeRect(50, 25, 0, 0); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.strokeRect.zero.3 + desc: strokeRect of Nx0 pixels draws a straight line + code: | + ctx.strokeStyle = '#0f0'; + ctx.lineWidth = 50; + ctx.strokeRect(0, 25, 100, 0); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.zero.4 + desc: strokeRect of Nx0 pixels draws a closed line with no caps + code: | + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 250; + ctx.lineCap = 'round'; + ctx.strokeRect(100, 25, 100, 0); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.strokeRect.zero.5 + desc: strokeRect of Nx0 pixels draws a closed line with joins + code: | + ctx.strokeStyle = '#0f0'; + ctx.lineWidth = 250; + ctx.lineJoin = 'round'; + ctx.strokeRect(100, 25, 100, 0); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.negative + desc: strokeRect of negative sizes works + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.strokeStyle = '#0f0'; + ctx.lineWidth = 25; + ctx.strokeRect(12, 12, 26, 1); + ctx.strokeRect(88, 12, -26, 1); + ctx.strokeRect(12, 38, 26, -1); + ctx.strokeRect(88, 38, -26, -1); + @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; + expected: green + +- name: 2d.strokeRect.transform + desc: fillRect is affected by transforms + code: | + ctx.scale(10, 10); + ctx.translate(0, 5); + ctx.strokeStyle = '#0f0'; + ctx.lineWidth = 5; + ctx.strokeRect(2.5, -2.6, 5, 0.2); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.globalalpha + desc: strokeRect is affected by globalAlpha + code: | + ctx.globalAlpha = 0; + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 50; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.strokeRect.globalcomposite + desc: strokeRect is not affected by globalCompositeOperation + code: | + ctx.globalCompositeOperation = 'source-in'; + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 50; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,0,0,0; + expected: clear + +- name: 2d.strokeRect.clip + desc: strokeRect is affected by clipping regions + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.beginPath(); + ctx.rect(0, 0, 16, 16); + ctx.clip(); + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 50; + ctx.strokeRect(0, 0, 100, 50); + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 16, 16); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.shadow + desc: strokeRect draws shadows + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, 100, 50); + ctx.fillStyle = '#f00'; + ctx.shadowColor = '#0f0'; + ctx.shadowBlur = 0; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 50; + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 50; + ctx.strokeRect(0, -75, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeRect.nonfinite + desc: strokeRect() with Infinity/NaN is ignored + code: | + ctx.fillStyle = '#0f0'; + ctx.fillRect(0, 0, 100, 50); + ctx.strokeStyle = '#f00'; + ctx.lineWidth = 150; + @nonfinite ctx.strokeRect(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <100 Infinity -Infinity NaN>, <50 Infinity -Infinity NaN>); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillStyle.colorObject + desc: ctx.fillStyle works with color objects + canvasType: + ['HTMLCanvas'] + code: | + ctx.fillStyle = {r: 1, g: 0, b: 0}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 255,0,0,255; + ctx.fillStyle = {r: 0, g: 0, b: 1}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,255,255; + ctx.fillStyle = {r: 0.2, g: 0.4, b: 0.6}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 51,102,153,255; + ctx.fillStyle = {r: 0, g: 1, b: 0}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + ctx.fillStyle = {r: -1, g: 0, b: 0}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,0,255; + ctx.fillStyle = {r: 0, g: 2, b: 0}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.fillStyle.colorObject.transparency + desc: ctx.fillStyle with color objects has transparency + canvasType: + ['HTMLCanvas'] + code: | + ctx.fillStyle = {r: 0, g: 1, b: 0, a: 0}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,0,0; + ctx.clearRect(0, 0, 100, 50); + ctx.fillStyle = {r: 0, g: 1, b: 0, a: -1}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,0,0,0; + ctx.clearRect(0, 0, 100, 50); + ctx.fillStyle = {r: 0, g: 1, b: 0, a: 0.5}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,255,0,128; + ctx.clearRect(0, 0, 100, 50); + ctx.fillStyle = {r: 0, g: 1, b: 0, a: 1}; + ctx.fillRect(0, 0, 100, 50); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeStyle.colorObject + desc: ctx.strokeStyle works with color objects + canvasType: + ['HTMLCanvas'] + code: | + ctx.lineWidth = 50; + ctx.strokeStyle = {r: 1, g: 0, b: 0}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 255,0,0,255; + ctx.strokeStyle = {r: 0, g: 0, b: 1}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,0,255,255; + ctx.strokeStyle = {r: 0.2, g: 0.4, b: 0.6}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 51,102,153,255; + ctx.strokeStyle = {r: 0, g: 1, b: 0}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,255,0,255; + ctx.strokeStyle = {r: -1, g: 0, b: 0}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,0,0,255; + ctx.strokeStyle = {r: 0, g: 2, b: 0}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,255,0,255; + expected: green + +- name: 2d.strokeStyle.colorObject.transparency + desc: ctx.strokeStyle with color objects has transparency + canvasType: + ['HTMLCanvas'] + code: | + ctx.lineWidth = 50; + ctx.strokeStyle = {r: 0, g: 1, b: 0, a: 0}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,0,0,0; + ctx.strokeStyle = {r: 0, g: 1, b: 0, a: -1}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,0,0,0; + ctx.clearRect(0, 0, 100, 50); + ctx.strokeStyle = {r: 0, g: 1, b: 0, a: 0.5}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,255,0,128; + ctx.clearRect(0, 0, 100, 50); + ctx.strokeStyle = {r: 0, g: 1, b: 0, a: 1}; + ctx.strokeRect(25, 24, 50, 2); + @assert pixel 50,25 == 0,255,0,255; + expected: green diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml new file mode 100644 index 0000000000..dd84f913f9 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/filters.yaml @@ -0,0 +1,639 @@ +- name: 2d.filter.value + desc: test if ctx.filter works correctly + code: | + @assert ctx.filter == 'none'; + ctx.filter = 'blur(5px)'; + @assert ctx.filter == 'blur(5px)'; + ctx.save(); + ctx.filter = 'none'; + @assert ctx.filter == 'none'; + ctx.restore(); + @assert ctx.filter == 'blur(5px)'; + + ctx.filter = 'blur(10)'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = 'blur 10px'; + @assert ctx.filter == 'blur(5px)'; + + ctx.filter = 'inherit'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = 'initial'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = 'unset'; + @assert ctx.filter == 'blur(5px)'; + + ctx.filter = ''; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = null; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = undefined; + @assert ctx.filter == 'blur(5px)'; + + ctx.filter = 'blur( 5px)'; + assert_equals(ctx.filter, 'blur( 5px)'); + +- name: 2d.filter.canvasFilterObject.tentative + desc: Test CanvasFilter() object + canvasType: + ['HTMLCanvas'] + code: | + @assert ctx.filter == 'none'; + ctx.filter = 'blur(5px)'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: 5}); + @assert ctx.filter.toString() == '[object CanvasFilter]'; + ctx.filter = new CanvasFilter([ + {filter: 'gaussianBlur', stdDeviation: 5}, + {filter: 'gaussianBlur', stdDeviation: 10} + ]); + @assert ctx.filter.toString() == '[object CanvasFilter]'; + var canvas2 = document.createElement('canvas'); + var ctx2 = canvas2.getContext('2d'); + ctx2.filter = ctx.filter; + @assert ctx.filter.toString() == '[object CanvasFilter]'; + ctx.filter = 'blur(5px)'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = 'none'; + @assert ctx.filter == 'none'; + ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: 5}); + ctx.filter = 'this string is not a filter and should do nothing'; + @assert ctx.filter.toString() == '[object CanvasFilter]'; + +- name: 2d.filter.canvasFilterObject.tentative + desc: Test CanvasFilter() object + canvasType: ['OffscreenCanvas'] + code: | + @assert ctx.filter == 'none'; + ctx.filter = 'blur(5px)'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: 5}); + @assert ctx.filter.toString() == '[object CanvasFilter]'; + ctx.filter = new CanvasFilter([ + {filter: 'gaussianBlur', stdDeviation: 5}, + {filter: 'gaussianBlur', stdDeviation: 10} + ]); + @assert ctx.filter.toString() == '[object CanvasFilter]'; + var canvas2 = new OffscreenCanvas(100, 50); + var ctx2 = canvas2.getContext('2d'); + ctx2.filter = ctx.filter; + @assert ctx.filter.toString() == '[object CanvasFilter]'; + ctx.filter = 'blur(5px)'; + @assert ctx.filter == 'blur(5px)'; + ctx.filter = 'none'; + @assert ctx.filter == 'none'; + ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: 5}); + ctx.filter = 'this string is not a filter and should do nothing'; + @assert ctx.filter.toString() == '[object CanvasFilter]'; + +- name: 2d.filter.canvasFilterObject.blur.exceptions.tentative + desc: Test exceptions on CanvasFilter() blur.object + code: | + @assert throws TypeError ctx.filter = new CanvasFilter({filter: 'gaussianBlur'}); + @assert throws TypeError ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: undefined}); + @assert throws TypeError ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: 'foo'}); + @assert throws TypeError ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: [1,2]}); + @assert throws TypeError ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: NaN}); + @assert throws TypeError ctx.filter = new CanvasFilter({filter: 'gaussianBlur', stdDeviation: {}}); + +- name: 2d.filter.canvasFilterObject.colorMatrix.tentative + desc: Test the functionality of ColorMatrix filters in CanvasFilter objects + code: | + @assert throws TypeError new CanvasFilter({filter: 'colorMatrix', values: undefined}); + @assert throws TypeError new CanvasFilter({filter: 'colorMatrix', values: 'foo'}); + @assert throws TypeError new CanvasFilter({filter: 'colorMatrix', values: null}); + @assert throws TypeError new CanvasFilter({filter: 'colorMatrix', values: [1, 2, 3]}); + @assert throws TypeError new CanvasFilter({filter: 'colorMatrix', values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 'a']}); + @assert throws TypeError new CanvasFilter({filter: 'colorMatrix', values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, Infinity]}); + ctx.fillStyle = '#f00'; + ctx.filter = new CanvasFilter({filter: 'colorMatrix', type: 'hueRotate', values: 0}); + ctx.fillRect(0, 0, 100, 50); + @assert pixel 10,10 ==~ 255,0,0,255; + ctx.filter = new CanvasFilter({filter: 'colorMatrix', type: 'hueRotate', values: 90}); + ctx.fillRect(0, 0, 100, 50); + @assert pixel 10,10 ==~ 0,91,0,255; + ctx.filter = new CanvasFilter({filter: 'colorMatrix', type: 'hueRotate', values: 180}); + ctx.fillRect(0, 0, 100, 50); + @assert pixel 10,10 ==~ 0,109,109,255; + ctx.filter = new CanvasFilter({filter: 'colorMatrix', type: 'hueRotate', values: 270}); + ctx.fillRect(0, 0, 100, 50); + @assert pixel 10,10 ==~ 109,18,255,255; + ctx.filter = new CanvasFilter({filter: 'colorMatrix', type: 'saturate', values: 0.5}); + ctx.fillRect(0, 0, 100, 50); + @assert pixel 10,10 ==~ 155,27,27,255; + ctx.clearRect(0, 0, 100, 50); + ctx.filter = new CanvasFilter({filter: 'colorMatrix', type: 'luminanceToAlpha'}); + ctx.fillRect(0, 0, 100, 50); + @assert pixel 10,10 ==~ 0,0,0,54; + ctx.filter = new CanvasFilter({filter: 'colorMatrix', values: [ + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]}); + ctx.fillRect(0, 0, 50, 25); + ctx.fillStyle = '#0f0'; + ctx.fillRect(50, 0, 50, 25); + ctx.fillStyle = '#00f'; + ctx.fillRect(0, 25, 50, 25); + ctx.fillStyle = '#fff'; + ctx.fillRect(50, 25, 50, 25); + @assert pixel 10,10 ==~ 0,255,0,255; + @assert pixel 60,10 ==~ 0,255,0,255; + @assert pixel 10,30 ==~ 0,255,0,255; + @assert pixel 60,30 ==~ 0,255,0,255; + +- name: 2d.filter.canvasFilterObject.convolveMatrix.exceptions.tentative + desc: Test exceptions on CanvasFilter() convolveMatrix + code: | + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix'}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', divisor: 2}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: null}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: 1}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[1, 0], [0]]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[1, 'a'], [0]]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[1, 0], 0]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[1, 0], [0, Infinity]]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: []}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [1]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [1, 2, 3, 4]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[], []]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[1, 2], []]}); + @assert throws TypeError new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[], [1, 2]]}); + // This should not throw an error + ctx.filter = new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[]]}); + ctx.filter = new CanvasFilter({filter: 'convolveMatrix', kernelMatrix: [[1]]}); + +- name: 2d.filter.canvasFilterObject.componentTransfer.linear.tentative + desc: Test pixels on CanvasFilter() componentTransfer with linear type + code: | + // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement + function getColor(inputColor, slopes, intercepts) { + return [ + Math.max(0, Math.min(1, inputColor[0]/255 * slopes[0] + intercepts[0])) * 255, + Math.max(0, Math.min(1, inputColor[1]/255 * slopes[1] + intercepts[1])) * 255, + Math.max(0, Math.min(1, inputColor[2]/255 * slopes[2] + intercepts[2])) * 255, + ]; + } + + const slopes = [0.5, 1.2, -0.2]; + const intercepts = [0.25, 0, 0.5]; + ctx.filter = new CanvasFilter({filter: 'componentTransfer', + funcR: {type: 'linear', slope: slopes[0], intercept: intercepts[0]}, + funcG: {type: 'linear', slope: slopes[1], intercept: intercepts[1]}, + funcB: {type: 'linear', slope: slopes[2], intercept: intercepts[2]}, + }); + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (const color of inputColors) { + let outputColor = getColor(color, slopes, intercepts); + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(0, 0, 10, 10); + _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + } + +- name: 2d.filter.canvasFilterObject.componentTransfer.identity.tentative + desc: Test pixels on CanvasFilter() componentTransfer with identity type + code: | + ctx.filter = new CanvasFilter({filter: 'componentTransfer', + funcR: {type: 'identity'}, + funcG: {type: 'identity'}, + funcB: {type: 'identity'}, + }); + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (const color of inputColors) { + ctx.fillStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, 1)`, + ctx.fillRect(0, 0, 10, 10); + _assertPixel(canvas, 5, 5, color[0],color[1],color[2],255); + } + +- name: 2d.filter.canvasFilterObject.componentTransfer.gamma.tentative + desc: Test pixels on CanvasFilter() componentTransfer with gamma type + code: | + // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement + function getColor(inputColor, amplitude, exponent, offset) { + return [ + Math.max(0, Math.min(1, Math.pow(inputColor[0]/255, exponent[0]) * amplitude[0] + offset[0])) * 255, + Math.max(0, Math.min(1, Math.pow(inputColor[1]/255, exponent[1]) * amplitude[1] + offset[1])) * 255, + Math.max(0, Math.min(1, Math.pow(inputColor[2]/255, exponent[2]) * amplitude[2] + offset[2])) * 255, + ]; + } + + const amplitudes = [2, 1.1, 0.5]; + const exponents = [5, 3, 1]; + const offsets = [0.25, 0, 0.5]; + ctx.filter = new CanvasFilter({filter: 'componentTransfer', + funcR: {type: 'gamma', amplitude: amplitudes[0], exponent: exponents[0], offset: offsets[0]}, + funcG: {type: 'gamma', amplitude: amplitudes[1], exponent: exponents[1], offset: offsets[1]}, + funcB: {type: 'gamma', amplitude: amplitudes[2], exponent: exponents[2], offset: offsets[2]}, + }); + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (const color of inputColors) { + let outputColor = getColor(color, amplitudes, exponents, offsets); + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(0, 0, 10, 10); + _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + } + +- name: 2d.filter.canvasFilterObject.componentTransfer.table.tentative + desc: Test pixels on CanvasFilter() componentTransfer with table type + code: | + // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement + function getTransformedValue(C, V) { + // Get the right interval + const n = V.length - 1; + const k = C == 1 ? n - 1 : Math.floor(C * n); + return V[k] + (C - k/n) * n * (V[k + 1] - V[k]); + } + + function getColor(inputColor, tableValues) { + const result = [0, 0, 0]; + for (const i in inputColor) { + const C = inputColor[i]/255; + const Cprime = getTransformedValue(C, tableValues[i]); + result[i] = Math.max(0, Math.min(1, Cprime)) * 255; + } + return result; + } + + tableValuesR = [0, 0, 1, 1]; + tableValuesG = [2, 0, 0.5, 3]; + tableValuesB = [1, -1, 5, 0]; + ctx.filter = new CanvasFilter({filter: 'componentTransfer', + funcR: {type: 'table', tableValues: tableValuesR}, + funcG: {type: 'table', tableValues: tableValuesG}, + funcB: {type: 'table', tableValues: tableValuesB}, + }); + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (const color of inputColors) { + let outputColor = getColor(color, [tableValuesR, tableValuesG, tableValuesB]); + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(0, 0, 10, 10); + _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + } + +- name: 2d.filter.canvasFilterObject.componentTransfer.discrete.tentative + desc: Test pixels on CanvasFilter() componentTransfer with discrete type + code: | + // From https://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement + function getTransformedValue(C, V) { + // Get the right interval + const n = V.length; + const k = C == 1 ? n - 1 : Math.floor(C * n); + return V[k]; + } + + function getColor(inputColor, tableValues) { + const result = [0, 0, 0]; + for (const i in inputColor) { + const C = inputColor[i]/255; + const Cprime = getTransformedValue(C, tableValues[i]); + result[i] = Math.max(0, Math.min(1, Cprime)) * 255; + } + return result; + } + + tableValuesR = [0, 0, 1, 1]; + tableValuesG = [2, 0, 0.5, 3]; + tableValuesB = [1, -1, 5, 0]; + ctx.filter = new CanvasFilter({filter: 'componentTransfer', + funcR: {type: 'discrete', tableValues: tableValuesR}, + funcG: {type: 'discrete', tableValues: tableValuesG}, + funcB: {type: 'discrete', tableValues: tableValuesB}, + }); + + const inputColors = [ + [255, 255, 255], + [0, 0, 0], + [127, 0, 34], + [252, 186, 3], + [50, 68, 87], + ]; + + for (const color of inputColors) { + let outputColor = getColor(color, [tableValuesR, tableValuesG, tableValuesB]); + ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; + ctx.fillRect(0, 0, 10, 10); + _assertPixelApprox(canvas, 5, 5, outputColor[0],outputColor[1],outputColor[2],255, 2); + } + +- name: 2d.filter.canvasFilterObject.dropShadow.tentative + desc: Test CanvasFilter() dropShadow object. + size: 520, 420 + code: | + ctx.fillStyle = 'teal'; + ctx.fillRect(0, 0, 520, 50); + ctx.fillRect(0, 100, 520, 50); + ctx.fillRect(0, 200, 520, 50); + ctx.fillRect(0, 300, 520, 50); + + ctx.fillStyle = 'crimson'; + + // Parameter defaults. + ctx.filter = new CanvasFilter({filter: 'dropShadow'}); + ctx.fillRect(10, 10, 80, 80); + + // All parameters specified. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 5, + floodColor: 'purple', floodOpacity: 0.7}); + ctx.fillRect(110, 10, 80, 80); + + // Named color. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'purple'}); + ctx.fillRect(10, 110, 80, 80); + + // System color. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'LinkText'}); + ctx.fillRect(110, 110, 80, 80); + + // Numerical color. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'rgba(20, 50, 130, 1)'}); + ctx.fillRect(210, 110, 80, 80); + + // Transparent floodColor. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'rgba(20, 50, 130, 0.7)'}); + ctx.fillRect(310, 110, 80, 80); + + // Transparent floodColor and floodOpacity. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 3, + floodColor: 'rgba(20, 50, 130, 0.7)', floodOpacity: 0.7}); + ctx.fillRect(410, 110, 80, 80); + + // No blur. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 0, + floodColor: 'purple'}); + ctx.fillRect(10, 210, 80, 80); + + // Single float blur. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: 5, + floodColor: 'purple'}); + ctx.fillRect(110, 210, 80, 80); + + // Single negative float blur. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: -5, + floodColor: 'purple'}); + ctx.fillRect(210, 210, 80, 80); + + // Two floats (X&Y) blur. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: [3, 5], + floodColor: 'purple'}); + ctx.fillRect(310, 210, 80, 80); + + // Two negative floats (X&Y) blur. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: 9, dy: 12, stdDeviation: [-3, -5], + floodColor: 'purple'}); + ctx.fillRect(410, 210, 80, 80); + + // Degenerate parameter values. + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: [-5], dy: [], stdDeviation: null, + floodColor: 'purple', floodOpacity: [2]}); + ctx.fillRect(10, 310, 80, 80); + + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: null, dy: '5', stdDeviation: [[-5], ['3']], + floodColor: 'purple', floodOpacity: '0.8'}); + ctx.fillRect(110, 310, 80, 80); + + ctx.filter = new CanvasFilter( + {filter: 'dropShadow', dx: true, dy: ['10'], stdDeviation: false, + floodColor: 'purple', floodOpacity: ['0.4']}); + ctx.fillRect(210, 310, 80, 80); + html_reference: | + <svg xmlns="http://www.w3.org/2000/svg" + width=520 height=420 + color-interpolation-filters="sRGB"> + <rect x=0 y=0 width=100% height=50 fill="teal" /> + <rect x=0 y=100 width=100% height=50 fill="teal" /> + <rect x=0 y=200 width=100% height=50 fill="teal" /> + <rect x=0 y=300 width=100% height=50 fill="teal" /> + + <rect x=10 y=10 width=80 height=80 fill="crimson" + filter="drop-shadow(2px 2px 2px black)"/> + <rect x=110 y=10 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 5px rgba(128, 0, 128, 0.7))"/> + + <rect x=10 y=110 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 3px purple)"/> + <rect x=110 y=110 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 3px LinkText)"/> + <rect x=210 y=110 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 3px rgba(20, 50, 130, 1))"/> + <rect x=310 y=110 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 3px rgba(20, 50, 130, 0.7))"/> + <rect x=410 y=110 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 3px rgba(20, 50, 130, 0.49))"/> + + <rect x=10 y=210 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 0px purple)"/> + <rect x=110 y=210 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 5px purple)"/> + <rect x=210 y=210 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 0px purple)"/> + <filter id="separable-filter" + x="-100%" y="-100%" width="300%" height="300%"> + <feDropShadow dx=9 dy=12 stdDeviation="3 5" flood-color="purple"/> + </filter> + <rect x=310 y=210 width=80 height=80 fill="crimson" + filter="url(#separable-filter)"/> + <rect x=410 y=210 width=80 height=80 fill="crimson" + filter="drop-shadow(9px 12px 0px purple)"/> + + <rect x=10 y=310 width=80 height=80 fill="crimson" + filter="drop-shadow(-5px 0px 0px purple)"/> + <filter id="separable-filter-degenerate" + x="-100%" y="-100%" width="300%" height="300%"> + <feDropShadow dx=0 dy=5 stdDeviation="0 3" + flood-color="rgba(128, 0, 128, 0.8)"/> + </filter> + <rect x=110 y=310 width=80 height=80 fill="crimson" + filter="url(#separable-filter-degenerate)"/> + <rect x=210 y=310 width=80 height=80 fill="crimson" + filter="drop-shadow(1px 10px 0px rgba(128, 0, 128, 0.4))"/> + </svg> + +- name: 2d.filter.canvasFilterObject.dropShadow.exceptions.tentative + desc: Test exceptions on CanvasFilter() dropShadow object + code: | + @unroll @assert new CanvasFilter({\- + filter: 'dropShadow', \- + <dx | dy | floodOpacity>: \- + <10 | -1 | 0.5 | null | true | false | [] | [20] | '30'>}); + @unroll @assert new CanvasFilter({\- + filter: 'dropShadow', \- + <stdDeviation>: \- + <10 | -1 | 0.5 | null | true | false | [] | [20] | '30' | \- + [10, -1] | [0.5, null] | [true, false] | [[], [20]] | \- + ['30', ['40']]>}); + @unroll @assert new CanvasFilter({\- + filter: 'dropShadow', \- + <floodColor>: \- + <'red' | 'canvas' | 'rgba(4, -3, 0.5, 1)' | '#aabbccdd' | '#abcd'>}); + + @unroll @assert throws TypeError new CanvasFilter({\- + filter: 'dropShadow', \- + <dx | dy | floodOpacity>: \- + <NaN | Infinity | -Infinity | undefined | 'test' | {} | [1, 2]>}); + @unroll @assert throws TypeError new CanvasFilter({\- + filter: 'dropShadow', \- + <stdDeviation>: \- + <NaN | Infinity | -Infinity | undefined | 'test' | {} | [1, 2, 3] | \- + [1, NaN] | [1, Infinity] | [1, -Infinity] | [1, undefined] | \- + [1, 'test'] | [1, {}] | [1, [2, 3]]>}); + @unroll @assert throws TypeError new CanvasFilter({\- + filter: 'dropShadow', \- + <floodColor>: \- + <'test' | 'rgba(NaN, 3, 2, 1)' | 10 | undefined | null | NaN>}); + +- name: 2d.filter.canvasFilterObject.turbulence.inputTypes.tentative + desc: Test exceptions on CanvasFilter() turbulence object + code: | + const errorTestCases = [ + {baseFrequency: {}}, + {baseFrequency: -1}, + {baseFrequency: [0, -1]}, + {baseFrequency: NaN}, + {baseFrequency: Infinity}, + {baseFrequency: undefined}, + {baseFrequency: -Infinity}, + {baseFrequency: 'test'}, + + {numOctaves: {}}, + {numOctaves: -1}, + {numOctaves: NaN}, + {numOctaves: Infinity}, + {numOctaves: undefined}, + {numOctaves: -Infinity}, + {numOctaves: [1, 1]}, + {numOctaves: 'test'}, + + {seed: {}}, + {seed: NaN}, + {seed: Infinity}, + {seed: undefined}, + {seed: -Infinity}, + {seed: [1, 1]}, + {seed: 'test'}, + + {stitchTiles: {}}, + {stitchTiles: NaN}, + {stitchTiles: Infinity}, + {stitchTiles: undefined}, + {stitchTiles: -Infinity}, + {stitchTiles: [1, 1]}, + {stitchTiles: 'test'}, + {stitchTiles: null}, + {stitchTiles: []}, + {stitchTiles: [10]}, + {stitchTiles: 30}, + {stitchTiles: false}, + {stitchTiles: true}, + {stitchTiles: '10'}, + {stitchTiles: -1}, + + {type: {}}, + {type: NaN}, + {type: Infinity}, + {type: undefined}, + {type: -Infinity}, + {type: [1, 1]}, + {type: 'test'}, + {type: null}, + {type: []}, + {type: [10]}, + {type: 30}, + {type: false}, + {type: true}, + {type: '10'}, + {type: -1}, + ] + + // null and [] = 0 when parsed as number + const workingTestCases = [ + {baseFrequency: null}, + {baseFrequency: []}, + {baseFrequency: [10]}, + {baseFrequency: [10, 3]}, + {baseFrequency: 30}, + {baseFrequency: false}, + {baseFrequency: true}, + {baseFrequency: '10'}, + + {numOctaves: null}, + {numOctaves: []}, + {numOctaves: [10]}, + {numOctaves: 30}, + {numOctaves: false}, + {numOctaves: true}, + {numOctaves: '10'}, + + {seed: null}, + {seed: []}, + {seed: [10]}, + {seed: 30}, + {seed: false}, + {seed: true}, + {seed: '10'}, + {seed: -1}, + + {stitchTiles: 'stitch'}, + {stitchTiles: 'noStitch'}, + + {type: 'fractalNoise'}, + {type: 'turbulence'}, + ] + + for (testCase of errorTestCases) { + const filterOptions = {...{filter: 'turbulence'}, ...testCase}; + @assert throws TypeError new CanvasFilter(filterOptions); + } + + for (testCase of workingTestCases) { + const filterOptions = {...{filter: 'turbulence'}, ...testCase}; + @assert new CanvasFilter(filterOptions) != null; + } diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml new file mode 100644 index 0000000000..fe1902c61b --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/layers.yaml @@ -0,0 +1,496 @@ +- name: 2d.layer.global-states + desc: Checks that layers correctly use global render states. + size: 200, 200 + code: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + + var circle = new Path2D(); + circle.arc(90, 90, 45, 0, 2 * Math.PI); + ctx.fill(circle); + + %(render-states)s + + ctx.beginLayer(); + + // Enable compositing in the layer to validate that draw calls in the layer + // won't individually composite with the background. + ctx.globalCompositeOperation = 'screen'; + + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); + + ctx.endLayer(); + reference: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + + var circle = new Path2D(); + circle.arc(90, 90, 45, 0, 2 * Math.PI); + ctx.fill(circle); + + %(render-states)s + + canvas2 = document.createElement("canvas"); + ctx2 = canvas2.getContext("2d"); + + ctx2.globalCompositeOperation = 'screen'; + ctx2.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx2.fillRect(50, 50, 75, 50); + ctx2.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx2.fillRect(70, 70, 75, 50); + + ctx.drawImage(canvas2, 0, 0); + variants: &global-state-variants + no-global-states: + render-states: // No global states. + alpha: + render-states: ctx.globalAlpha = 0.6; + blending: + render-states: ctx.globalCompositeOperation = 'multiply'; + composite: + render-states: ctx.globalCompositeOperation = 'source-in'; + shadow: + render-states: |- + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; + alpha.blending: + render-states: |- + ctx.globalAlpha = 0.6; + ctx.globalCompositeOperation = 'multiply'; + alpha.composite: + render-states: |- + ctx.globalAlpha = 0.6; + ctx.globalCompositeOperation = 'source-in'; + alpha.shadow: + render-states: |- + ctx.globalAlpha = 0.5; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; + alpha.blending.shadow: + render-states: |- + ctx.globalAlpha = 0.6; + ctx.globalCompositeOperation = 'multiply'; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; + alpha.composite.shadow: + render-states: |- + ctx.globalAlpha = 0.6; + ctx.globalCompositeOperation = 'source-in'; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; + blending.shadow: + render-states: |- + ctx.globalCompositeOperation = 'multiply'; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; + composite.shadow: + render-states: |- + ctx.globalCompositeOperation = 'source-in'; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'rgba(255, 165, 0, 0.5)'; + + +- name: 2d.layer.global-states.filter + desc: Checks that layers with filters correctly use global render states. + size: 200, 200 + code: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + + var circle = new Path2D(); + circle.arc(90, 90, 45, 0, 2 * Math.PI); + ctx.fill(circle); + + %(render-states)s + + ctx.beginLayer([ + {filter: 'colorMatrix', values: [0.393, 0.769, 0.189, 0, 0, + 0.349, 0.686, 0.168, 0, 0, + 0.272, 0.534, 0.131, 0, 0, + 0, 0, 0, 1, 0]}, + {filter: 'componentTransfer', + funcA: {type: "table", tableValues: [0, 0.7]}}, + {filter: 'dropShadow', dx: 5, dy: 5, floodColor: '#81e'}]); + + ctx.fillStyle = 'rgba(200, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 200, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); + + ctx.endLayer(); + reference: | + const svg = ` + <svg xmlns="http://www.w3.org/2000/svg" + width="200" height="200" + color-interpolation-filters="sRGB"> + <filter id="filter" x="-100%%" y="-100%%" width="300%%" height="300%%"> + <feColorMatrix + type="matrix" + values="0.393 0.769 0.189 0 0 + 0.349 0.686 0.168 0 0 + 0.272 0.534 0.131 0 0 + 0 0 0 1 0" /> + <feComponentTransfer> + <feFuncA type="table" tableValues="0 0.7"></feFuncA> + </feComponentTransfer> + <feDropShadow dx="5" dy="5" flood-color="#81e" /> + </filter> + <g filter="url(#filter)"> + <rect x="50" y="50" width="75" height="50" fill="rgba(200, 0, 0, 1)"/> + <rect x="70" y="70" width="75" height="50" fill="rgba(0, 200, 0, 1)"/> + </g> + </svg>`; + + const img = new Image(); + img.width = 200; + img.height = 200; + img.onload = () => { + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + + var circle = new Path2D(); + circle.arc(90, 90, 45, 0, 2 * Math.PI); + ctx.fill(circle); + + %(render-states)s + + ctx.drawImage(img, 0, 0); + }; + img.src = 'data:image/svg+xml;base64,' + btoa(svg); + variants: *global-state-variants + + +- name: 2d.layer.nested + desc: Tests nested canvas layers. + size: 200, 200 + code: | + var circle = new Path2D(); + circle.arc(90, 90, 40, 0, 2 * Math.PI); + ctx.fill(circle); + + ctx.globalCompositeOperation = 'source-in'; + + ctx.beginLayer(); + + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(60, 60, 75, 50); + + ctx.globalAlpha = 0.5; + + ctx.beginLayer(); + + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); + + ctx.endLayer(); + ctx.endLayer(); + reference: | + var circle = new Path2D(); + circle.arc(90, 90, 40, 0, 2 * Math.PI); + ctx.fill(circle); + + ctx.globalCompositeOperation = 'source-in'; + + canvas2 = document.createElement("canvas"); + ctx2 = canvas2.getContext("2d"); + + ctx2.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx2.fillRect(60, 60, 75, 50); + + ctx2.globalAlpha = 0.5; + + canvas3 = document.createElement("canvas"); + ctx3 = canvas3.getContext("2d"); + + ctx3.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx3.fillRect(50, 50, 75, 50); + ctx3.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx3.fillRect(70, 70, 75, 50); + + ctx2.drawImage(canvas3, 0, 0); + ctx.drawImage(canvas2, 0, 0); + + +- name: 2d.layer.restore-style + desc: Test that ensure layers restores style values upon endLayer. + size: 200, 200 + fuzzy: maxDifference=0-1; totalPixels=0-950 + code: | + ctx.fillStyle = 'rgba(0,0,255,1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.beginLayer(); + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(60, 60, 75, 50); + ctx.endLayer(); + + ctx.fillRect(70, 70, 75, 50); + reference: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.globalAlpha = 0.5; + + canvas2 = document.createElement("canvas"); + ctx2 = canvas2.getContext("2d"); + ctx2.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx2.fillRect(60, 60, 75, 50); + ctx.drawImage(canvas2, 0, 0); + + ctx.fillRect(70, 70, 75, 50); + + +- name: 2d.layer.unclosed + desc: Check that layers are rendered even if not closed. + size: 200, 200 + code: | + ctx.fillStyle = 'purple'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillRect(40, 40, 75, 50); + ctx.fillStyle = 'grey'; + ctx.fillRect(50, 50, 75, 50); + reference: | + ctx.fillStyle = 'purple'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillStyle = 'purple'; + ctx.fillRect(40, 40, 75, 50); + ctx.fillStyle = 'grey'; + ctx.fillRect(50, 50, 75, 50); + ctx.endLayer(); + + +- name: 2d.layer.render-opportunities + desc: Check that layers state stack is flushed and rebuilt on frame renders. + size: 200, 200 + code: | + ctx.fillStyle = 'purple'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillRect(40, 40, 75, 50); + ctx.fillStyle = 'grey'; + ctx.fillRect(50, 50, 75, 50); + + // Force a flush and restoration of the state stack: + %(flush_canvas)s + + ctx.fillRect(70, 70, 75, 50); + ctx.fillStyle = 'orange'; + ctx.fillRect(80, 80, 75, 50); + ctx.endLayer(); + + ctx.fillRect(80, 40, 75, 50); + reference: | + ctx.fillStyle = 'purple'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillStyle = 'purple'; + ctx.fillRect(40, 40, 75, 50); + ctx.fillStyle = 'grey'; + ctx.fillRect(50, 50, 75, 50); + ctx.endLayer(); + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillStyle = 'grey'; + ctx.fillRect(70, 70, 75, 50); + ctx.fillStyle = 'orange'; + ctx.fillRect(80, 80, 75, 50); + ctx.endLayer(); + + ctx.fillRect(80, 40, 75, 50); + variants: + convertToBlob: + test_type: "promise" + canvasType: ['OffscreenCanvas'] + flush_canvas: |- + await canvas.convertToBlob(); + createImageBitmap: + flush_canvas: createImageBitmap(canvas); + drawImage: + flush_canvas: |- + const canvas2 = new OffscreenCanvas(200, 200); + const ctx2 = canvas2.getContext('2d'); + ctx2.drawImage(canvas, 0, 0); + getImageData: + flush_canvas: ctx.getImageData(0, 0, 200, 200); + requestAnimationFrame: + canvasType: ['HTMLCanvas'] + test_type: "promise" + flush_canvas: |- + await new Promise(resolve => requestAnimationFrame(resolve)); + putImageData: + flush_canvas: |- + const canvas2 = new OffscreenCanvas(200, 200); + const ctx2 = canvas2.getContext('2d'); + ctx.putImageData(ctx2.getImageData(0, 0, 1, 1), 0, 0); + toBlob: + test_type: "promise" + canvasType: ['HTMLCanvas'] + flush_canvas: |- + await new Promise(resolve => canvas.toBlob(resolve)); + toDataURL: + canvasType: ['HTMLCanvas'] + flush_canvas: canvas.toDataURL(); + + +- name: 2d.layer.render-opportunities.transferToImageBitmap + desc: Checks that transferToImageBitmap flushes and rebuilds the state stack. + size: 200, 200 + canvasType: ['OffscreenCanvas'] + code: | + ctx.fillStyle = 'purple'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillRect(40, 40, 75, 50); + ctx.fillStyle = 'grey'; + ctx.fillRect(50, 50, 75, 50); + + // Force a flush and restoration of the state stack. + // `transferToImageBitmap` clears the frame but preserves render states. + canvas.transferToImageBitmap(); + + ctx.fillRect(70, 70, 75, 50); + ctx.fillStyle = 'orange'; + ctx.fillRect(80, 80, 75, 50); + ctx.endLayer(); + + ctx.fillRect(80, 40, 75, 50); + reference: | + ctx.fillStyle = 'purple'; + ctx.globalAlpha = 0.5; + + ctx.beginLayer({filter: 'dropShadow', dx: -2, dy: 2}); + ctx.fillStyle = 'grey'; + ctx.fillRect(70, 70, 75, 50); + ctx.fillStyle = 'orange'; + ctx.fillRect(80, 80, 75, 50); + ctx.endLayer(); + + ctx.fillRect(80, 40, 75, 50); + + +- name: 2d.layer.several-complex + desc: >- + Test to ensure beginlayer works for filter, alpha and shadow, even with + consecutive layers. + size: 500, 500 + fuzzy: maxDifference=0-3; totalPixels=0-6318 + code: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(50, 50, 95, 70); + + ctx.globalAlpha = 0.5; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'orange'; + + + for (let i = 0; i < 5; i++) { + ctx.beginLayer(); + + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(60 + i, 40 + i, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(80 + i, 60 + i, 75, 50); + + ctx.endLayer(); + } + reference: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(50, 50, 95, 70); + + ctx.globalAlpha = 0.5; + ctx.shadowOffsetX = -10; + ctx.shadowOffsetY = 10; + ctx.shadowColor = 'orange'; + + var canvas2 = [5]; + var ctx2 = [5]; + + for (let i = 0; i < 5; i++) { + canvas2[i] = document.createElement("canvas"); + ctx2[i] = canvas2[i].getContext("2d"); + ctx2[i].fillStyle = 'rgba(225, 0, 0, 1)'; + ctx2[i].fillRect(60, 40, 75, 50); + ctx2[i].fillStyle = 'rgba(0, 255, 0, 1)'; + ctx2[i].fillRect(80, 60, 75, 50); + + ctx.drawImage(canvas2[i], i, i); + } + + +- name: 2d.layer.endlayer.unmatched + desc: >- + A test to make sure an unmatched endLayer is a no-op and has no effect on + the code following it. + size: 200, 200 + code: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(60, 60, 75, 50); + + ctx.globalAlpha = 0.5; + + // This endlayer call should no-op. + ctx.endLayer(); + + ctx.beginLayer(); + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); + ctx.endLayer(); + reference: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(60, 60, 75, 50); + + ctx.globalAlpha = 0.5; + + ctx.beginLayer(); + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); + ctx.endLayer(); + + +- name: 2d.layer.endlayer.alone + desc: A test to make sure a single endLayer with no beginLayer is a no-op. + size: 200, 200 + code: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); + + ctx.endLayer(); + reference: | + ctx.fillStyle = 'rgba(0, 0, 255, 1)'; + ctx.fillRect(60, 60, 75, 50); + ctx.globalAlpha = 0.5; + + ctx.fillStyle = 'rgba(225, 0, 0, 1)'; + ctx.fillRect(50, 50, 75, 50); + ctx.fillStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(70, 70, 75, 50); diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/line-styles.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/line-styles.yaml new file mode 100644 index 0000000000..604f4f3659 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/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-new/path-objects.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/path-objects.yaml new file mode 100644 index 0000000000..eec142c769 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/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-new/pixel-manipulation.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/pixel-manipulation.yaml new file mode 100644 index 0000000000..b9bdf3d2bd --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/pixel-manipulation.yaml @@ -0,0 +1,1042 @@ +- 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 + canvasType: ['HTMLCanvas'] + 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 + canvasType: ['HTMLCanvas'] + 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| + canvasType: ['HTMLCanvas'] + 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| + canvasType: ['HTMLCanvas'] + 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.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.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; + +- name: 2d.imageData.create.and.resize + desc: Verify no crash when resizing an image bitmap to zero. + canvasType: ['HTMLCanvas'] + 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 + canvasType: ['HTMLCanvas'] + 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.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 + canvasType: ['HTMLCanvas'] + 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 + canvasType: ['HTMLCanvas'] + 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 ImageData(1, 1); + @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 + canvasType: ['HTMLCanvas'] + 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 + canvasType: ['HTMLCanvas'] + 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 + canvasType: ['HTMLCanvas'] + 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.cross + desc: putImageData() accepts image data got from a different canvas + canvasType: ['OffscreenCanvas'] + 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; + 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-new/reset.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/reset.yaml new file mode 100644 index 0000000000..4107166248 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/reset.yaml @@ -0,0 +1,15 @@ +- name: 2d.reset.basic + desc: reset clears to transparent black + canvasType: + ['HTMLCanvas'] + code: | + ctx.fillStyle = '#f00'; + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.reset(); + @assert pixel 0,0 == 0,0,0,0; + @assert pixel 50,25 == 0,0,0,0; + @assert pixel 25,50 == 0,0,0,0; + @assert pixel 100,50 == 0,0,0,0; + @assert pixel 0,50 == 0,0,0,0; + @assert pixel 100,0 == 0,0,0,0; + t.done();
\ No newline at end of file diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/scroll.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/scroll.yaml new file mode 100644 index 0000000000..dd088aa396 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/scroll.yaml @@ -0,0 +1,76 @@ +- name: 2d.scrollPathIntoView.basic + desc: scrollPathIntoView() works + canvasType: + ['HTMLCanvas'] + code: | + var div = document.createElement('div'); + div.style.cssText = 'width: 200vw; height: 200vh'; + document.body.appendChild(div); + canvas.style.cssText = 'position: absolute; top: 100px; left: 200px; border: none;'; + window.scrollTo(0, 0); + + ctx.beginPath(); + ctx.rect(4, 8, 16, 32); + ctx.scrollPathIntoView(); + var rect = canvas.getBoundingClientRect(); + @assert Math.round(rect.top) === -8; + @assert Math.round(rect.left) === 200; + +- name: 2d.scrollPathIntoView.verticalLR + desc: scrollPathIntoView() works in vertical-lr writing mode + canvasType: + ['HTMLCanvas'] + code: | + document.documentElement.style.cssText = 'writing-mode: vertical-lr'; + var div = document.createElement('div'); + div.style.cssText = 'width: 200vw; height: 200vh'; + document.body.appendChild(div); + canvas.style.cssText = 'position: absolute; top: 100px; left: 200px; border: none;'; + window.scrollTo(0, 0); + + ctx.beginPath(); + ctx.rect(4, 8, 16, 32); + ctx.scrollPathIntoView(); + var rect = canvas.getBoundingClientRect(); + @assert Math.round(rect.top) === 100; + @assert Math.round(rect.left) === -4; + +- name: 2d.scrollPathIntoView.verticalRL + desc: scrollPathIntoView() works in vertical-rl writing mode + canvasType: + ['HTMLCanvas'] + code: | + document.documentElement.style.cssText = 'writing-mode: vertical-rl'; + var div = document.createElement('div'); + div.style.cssText = 'width: 200vw; height: 200vh'; + document.body.appendChild(div); + canvas.style.cssText = 'position: absolute; top: 100px; right: 200px; border: none;'; + window.scrollTo(0, 0); + + ctx.beginPath(); + ctx.rect(4, 8, 16, 32); + ctx.scrollPathIntoView(); + var rect = canvas.getBoundingClientRect(); + var viewportWidth = document.scrollingElement.clientWidth; + var canvasWidth = canvas.width; + @assert Math.round(rect.top) === 100; + @assert Math.round(rect.right) === viewportWidth + (canvasWidth - 4 - 16); + +- name: 2d.scrollPathIntoView.path + desc: scrollPathIntoView() with path argument works + canvasType: + ['HTMLCanvas'] + code: | + var div = document.createElement('div'); + div.style.cssText = 'width: 200vw; height: 200vh'; + document.body.appendChild(div); + canvas.style.cssText = 'position: absolute; top: 100px; left: 200px; border: none;'; + window.scrollTo(0, 0); + + var path = new Path2D(); + path.rect(4, 8, 16, 32); + ctx.scrollPathIntoView(path); + var rect = canvas.getBoundingClientRect(); + @assert Math.round(rect.top) === -8; + @assert Math.round(rect.left) === 200; + diff --git a/testing/web-platform/tests/html/canvas/tools/yaml-new/transformations.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/transformations.yaml new file mode 100644 index 0000000000..0d2265be7a --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/transformations.yaml @@ -0,0 +1,356 @@ +- 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(); + + @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-new/video.yaml b/testing/web-platform/tests/html/canvas/tools/yaml-new/video.yaml new file mode 100644 index 0000000000..f9b48fb8da --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml-new/video.yaml @@ -0,0 +1,10 @@ +- name: 2d.video.invalid + desc: Verify test doesn't crash with invalid video. + canvasType: + ['HTMLCanvas'] + code: | + var v = document.createElement('video'); + v.play(); + // Test is deliberately not waiting for the 'playing' event to fire. + ctx.createPattern(v, 'repeat-x'); + ctx.drawImage(v, 0, 0); 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..3443ad35b3 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/drawing-text-to-the-canvas.yaml @@ -0,0 +1,970 @@ +- 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: 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: 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: 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: 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' 'initial' 'inherit' 'normal' 'none'>); + +- 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.1], + ['5px', 55, 0.1], + ['-2px', -22, 0.1], + ['1em', 110, 0.1], + ['1in', 1056, 0.1], + ['-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.1], + ['5px', 10, 0.1], + ['-2px', -4, 0.1], + ['1em', 20, 0.1], + ['1in', 192, 0.1], + ['-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_approx_equals(width_with_spacing, width_normal + 110, 0.1, "letterSpacing incorrect before font change"); + + // 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_approx_equals(width_with_spacing, width_normal + 220, 0.1, "letterSpacing incorrect after font change"); + + +- 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..9d35dde661 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml @@ -0,0 +1,1998 @@ +- 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; + ctx.fillRect(0, 0, 100, 50); + 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/meta.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/element/meta.yaml new file mode 100644 index 0000000000..e8c4250426 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/meta.yaml @@ -0,0 +1,539 @@ +- 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": "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": "size.attributes.setAttribute.%s" % name, + "desc": "Parsing of non-negative integers in setAttribute", + "size": '50, 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'), + ('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-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/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..407e346210 --- /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"' + 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..d6d8cd801b --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/element/the-canvas-element.yaml @@ -0,0 +1,143 @@ +- 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 + size: 50, 25 + canvas: '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/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..a0eca74922 --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml @@ -0,0 +1,1586 @@ +- 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; + ctx.fillRect(0, 0, 100, 50); + 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/meta.yaml b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/meta.yaml new file mode 100644 index 0000000000..43a67d54ab --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/meta.yaml @@ -0,0 +1,539 @@ +- 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'), + ('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; + 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/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..784a099aca --- /dev/null +++ b/testing/web-platform/tests/html/canvas/tools/yaml/offscreen/text.yaml @@ -0,0 +1,1536 @@ +- 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' 'initial' 'inherit' 'normal' 'none'>); + + t.done(); + +- name: 2d.text.drawing.style.letterSpacing.measure.absolute + desc: Testing letter spacing with absolute length + 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.1], + ['5px', 55, 0.1], + ['-2px', -22, 0.1], + ['1in', 1056, 0.1], + ['-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.letterSpacing.measure.relative + desc: Testing letter spacing with font-relative length + code: | + @assert ctx.letterSpacing === '0px'; + @assert ctx.wordSpacing === '0px'; + ctx.font = "10px monospace"; + var width_normal = ctx.measureText('Hello World').width; + var ch_width = width_normal / 11; + + 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. + test_cases = [['1em', 110, 0.1], + ['-0.1em', -11, 0.1], + ['1ch', 11 * ch_width, 0.1]] + + 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.absolute + desc: Testing if word spacing is working properly with absolute length + 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.1], + ['5px', 10, 0.1], + ['-2px', -4, 0.1], + ['1in', 192, 0.1], + ['-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.wordSpacing.measure.relative + desc: Testing if word spacing is working properly with font-relative length + code: | + @assert ctx.letterSpacing === '0px'; + @assert ctx.wordSpacing === '0px'; + ctx.font = "10px monospace"; + var width_normal = ctx.measureText('Hello World, again').width; + var ch_width = width_normal / 18; + + 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. + test_cases = [['1em', 20, 0.1], + ['-0.5em', -10, 0.1], + ['1ch', 2 * ch_width, 0.1]] + + 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(); |