- 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 ['italic small-caps 12px "Unknown Font", sans-serif', 'italic small-caps 12px Unknown Font, sans-serif'].includes(ctx.font);
- name: 2d.text.font.parse.complex2
code: |
ctx.font = 'small-caps italic 400 12px/2 "Unknown Font #2", sans-serif';
@assert ctx.font === 'italic small-caps 12px "Unknown Font #2", sans-serif';
- 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"'
canvas_types: ['HtmlCanvas']
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
canvas_types: ['HtmlCanvas']
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
canvas_types: ['HtmlCanvas']
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.font.relative_size
canvas_types: ['OffscreenCanvas', 'Worker']
code: |
ctx.font = '1em sans-serif';
@assert ctx.font === '10px sans-serif';
- name: 2d.text.font.weight
code: |
ctx.font = 'italic 400 12px serif';
@assert ctx.font === 'italic 12px serif';
ctx.font = 'italic 300 12px serif';
@assert ctx.font === 'italic 300 12px 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)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- &load-font-variant-definition
HtmlCanvas:
append_variants_to_name: false
canvas_types: ['HtmlCanvas']
load_font: |-
await document.fonts.ready;
OffscreenCanvas:
append_variants_to_name: false
canvas_types: ['OffscreenCanvas', 'Worker']
load_font: |-
var f = new FontFace("{{ fonts[0] }}", "url('/fonts/{{ fonts[0] }}.ttf')");
f.load();
{% set root = 'self' if canvas_type == 'Worker' else 'document' %}
{{ root }}.fonts.add(f);
await {{ root }}.fonts.ready;
- name: 2d.text.draw.baseline.bottom
desc: textBaseline bottom is the bottom of the em square (not the bounding box)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.baseline.middle
desc: textBaseline middle is the middle of the em square (not the bounding box)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.baseline.alphabetic
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.baseline.ideographic
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.baseline.hanging
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.space.collapse.space
desc: Space characters are converted to U+0020, and are NOT collapsed
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
ctx.fillStyle = '#f00';
ctx.fillRect(0, 0, 100, 50);
ctx.fillStyle = '#0f0';
ctx.fillText('E EE', 0, 37.5);
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.space.collapse.other
desc: Space characters are converted to U+0020, and are NOT collapsed
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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', 0, 37.5);
@assert pixel 25,25 ==~ 0,255,0,255;
@assert pixel 75,25 ==~ 255,0,0,255;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.space.collapse.start
desc: Space characters at the start of a line are NOT collapsed
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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 ==~ 255,0,0,255; @moz-todo
@assert pixel 75,25 ==~ 0,255,0,255;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.space.collapse.end
desc: Space characters at the end of a line are NOT collapsed
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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 ==~ 255,0,0,255;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.measure.width.space
desc: Space characters are converted to U+0020 and NOT collapsed
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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 === 650;
@assert ctx.measureText('A \x0b B').width >= 200;
@assert ctx.measureText(' AB').width === 150;
@assert ctx.measureText('AB ').width === 150;
variants:
- *load-font-variant-definition
- 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;
- 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;
- 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;
- 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
- name: 2d.text.drawing.style.reset.fontKerning.none
desc: >-
crbug/338965374, fontKerning still works after setting font for a second
time.
code: |
ctx.font = '100px serif';
ctx.fontKerning = "none";
const width1 = ctx.measureText("AW").width;
ctx.font = '100px serif';
@assert ctx.fontKerning === "none";
const width2 = ctx.measureText("AW").width;
@assert width1 === width2;
- name: 2d.text.drawing.style.reset.fontKerning.none2
desc: FontKerning value still applies after font changes.
code: |
ctx.font = '10px serif';
ctx.fontKerning = "none";
ctx.font = '20px serif';
ctx.fillText("TATATA", 20, 30);
reference: |
ctx.font = '20px serif';
ctx.fontKerning = "none";
ctx.fillText("TATATA", 20, 30);
# 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
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.fill.maxWidth.bound
desc: fillText handles maxWidth based on line size, not bounding box size
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.fontface
fonts:
- CanvasTest
test_type: promise
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- 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.)
test_type: promise
fonts:
- CanvasTest
font_unused_in_dom: true
code: |
{{ load_font }}
ctx.fillStyle = '#f00';
ctx.fillRect(0, 0, 100, 50);
ctx.font = '67px CanvasTest';
ctx.fillStyle = '#0f0';
ctx.fillText('AA', 0, 50);
await new Promise(resolve => t.step_timeout(resolve, 500));
ctx.fillText('AA', 0, 50);
_assertPixelApprox(canvas, 5,5, 0,255,0,255, 2);
_assertPixelApprox(canvas, 95,5, 0,255,0,255, 2);
_assertPixelApprox(canvas, 25,25, 0,255,0,255, 2);
_assertPixelApprox(canvas, 75,25, 0,255,0,255, 2);
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.fontface.notinpage
desc: '@font-face fonts should work even if they are not used in the page'
test_type: promise
fonts:
- CanvasTest
font_unused_in_dom: true
code: |
{{ load_font }}
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
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.left
desc: textAlign left is the left of the first em square (not the bounding box)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.right
desc: textAlign right is the right of the last em square (not the bounding box)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.start.ltr
desc: textAlign start with ltr is the left edge
canvas: dir="ltr"
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
{% if canvas_type != 'HtmlCanvas' %}
ctx.direction = 'ltr';
{% endif %}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.start.rtl
desc: textAlign start with rtl is the right edge
canvas: dir="rtl"
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
{% if canvas_type != 'HtmlCanvas' %}
ctx.direction = 'rtl';
{% endif %}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.end.ltr
desc: textAlign end with ltr is the right edge
test_type: promise
canvas: dir="ltr"
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
{% if canvas_type != 'HtmlCanvas' %}
ctx.direction = 'ltr';
{% endif %}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.end.rtl
desc: textAlign end with rtl is the left edge
canvas: dir="rtl"
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
{% if canvas_type != 'HtmlCanvas' %}
ctx.direction = 'rtl';
{% endif %}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.align.center
desc: textAlign center is the center of the em squares (not the bounding box)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.space.basic
desc: U+0020 is rendered the correct size (1em wide)
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.draw.space.collapse.nonspace
desc: Non-space characters are not converted to U+0020 and collapsed
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
expected: green
variants:
- *load-font-variant-definition
- name: 2d.text.measure.width.basic
desc: The width of character is same as font used
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.width.empty
desc: The empty string has zero width
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
@assert ctx.measureText("").width === 0;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.actualBoundingBox
desc: Testing actualBoundingBox
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
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;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox
desc: Testing fontBoundingBox measurements
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '40px CanvasTest';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert ctx.measureText('A').fontBoundingBoxAscent === 30;
@assert ctx.measureText('A').fontBoundingBoxDescent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox.ahem
desc: Testing fontBoundingBox for font ahem
test_type: promise
fonts:
- Ahem
code: |
{{ load_font }}
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;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox-reduced-ascent
desc: Testing fontBoundingBox for OffscreenCanvas with reduced ascent metric
test_type: promise
fonts:
- CanvasTest-ascent256
code: |
{{ load_font }}
ctx.font = '40px CanvasTest-ascent256';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert ctx.measureText('A').fontBoundingBoxAscent === 10;
@assert ctx.measureText('A').fontBoundingBoxDescent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 10;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 10;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.fontBoundingBox-zero-descent
desc: Testing fontBoundingBox for OffscreenCanvas with zero descent metric
test_type: promise
fonts:
- CanvasTest-descent0
code: |
{{ load_font }}
ctx.font = '40px CanvasTest-descent0';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert ctx.measureText('A').fontBoundingBoxAscent === 30;
@assert ctx.measureText('A').fontBoundingBoxDescent === 0;
@assert ctx.measureText('ABCD').fontBoundingBoxAscent === 30;
@assert ctx.measureText('ABCD').fontBoundingBoxDescent === 0;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.emHeights
desc: Testing emHeights
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '40px CanvasTest';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert ctx.measureText('A').emHeightAscent === 30;
@assert ctx.measureText('A').emHeightDescent === 10;
@assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 40;
@assert ctx.measureText('ABCD').emHeightAscent === 30;
@assert ctx.measureText('ABCD').emHeightDescent === 10;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.emHeights-low-ascent
desc: Testing emHeights with reduced ascent metric
test_type: promise
fonts:
- CanvasTest-ascent256
code: |
{{ load_font }}
ctx.font = '40px CanvasTest-ascent256';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert ctx.measureText('A').emHeightAscent === 20;
@assert ctx.measureText('A').emHeightDescent === 20;
@assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 40;
@assert ctx.measureText('ABCD').emHeightAscent === 20;
@assert ctx.measureText('ABCD').emHeightDescent === 20;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.emHeights-zero-descent
desc: Testing emHeights with zero descent metric
test_type: promise
fonts:
- CanvasTest-descent0
code: |
{{ load_font }}
ctx.font = '40px CanvasTest-descent0';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert ctx.measureText('A').emHeightAscent === 40;
@assert ctx.measureText('A').emHeightDescent === 0;
@assert ctx.measureText('A').emHeightDescent + ctx.measureText('A').emHeightAscent === 40;
@assert ctx.measureText('ABCD').emHeightAscent === 40;
@assert ctx.measureText('ABCD').emHeightDescent === 0;
@assert ctx.measureText('ABCD').emHeightDescent + ctx.measureText('ABCD').emHeightAscent === 40;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.baselines
desc: Testing baselines
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
ctx.direction = 'ltr';
ctx.align = 'left'
@assert Math.abs(ctx.measureText('A').alphabeticBaseline) === 0;
@assert ctx.measureText('A').ideographicBaseline === 6.25;
@assert ctx.measureText('A').hangingBaseline === 25;
@assert Math.abs(ctx.measureText('ABCD').alphabeticBaseline) === 0;
@assert ctx.measureText('ABCD').ideographicBaseline === 6.25;
@assert ctx.measureText('ABCD').hangingBaseline === 25;
variants:
- *load-font-variant-definition
- name: 2d.text.measure.selection-rects.tentative
desc: >-
Check that TextMetrics::getSelectionRects() matches its DOM equivalent, with
direction {{ text_direction }}, text align {{ text_align }},
{{ letter_spacing }} letter spacing, and {{ variant_names[3] }}.
canvas_types: ['HtmlCanvas', 'OffscreenCanvas']
code: |
function placeAndSelectTextInDOM(text, from, to) {
const el = document.createElement("div");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = '{{ text_direction }}';
el.style.textAlign = '{{ text_align }}';
el.style.letterSpacing = '{{ letter_spacing }}';
document.body.appendChild(el);
let range = document.createRange();
range.setStart(el.childNodes[0], 0);
range.setEnd(el.childNodes[0], text.length);
const parent = range.getClientRects()[0];
let width = 0;
for (const rect of range.getClientRects()) {
width += rect.width;
}
range.setStart(el.childNodes[0], from);
range.setEnd(el.childNodes[0], to);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
let sel_rects = sel.getRangeAt(0).getClientRects();
document.body.removeChild(el);
// Offset to the alignment point determined by textAlign.
let text_align_dx;
switch (el.style.textAlign) {
case 'right':
text_align_dx = width;
break;
case 'center':
text_align_dx = width / 2;
break;
default:
text_align_dx = 0;
}
for(let i = 0 ; i < sel_rects.length ; ++i) {
sel_rects[i].x -= parent.x + text_align_dx;
sel_rects[i].y -= parent.y;
}
return sel_rects;
}
function checkRectListsMatch(list_a, list_b) {
@assert list_a.length === list_b.length;
for(let i = 0 ; i < list_a.length ; ++i) {
assert_approx_equals(list_a[i].x, list_b[i].x, 1.0);
assert_approx_equals(list_a[i].width, list_b[i].width, 1.0);
assert_approx_equals(list_a[i].height, list_b[i].height, 1.0);
// Y-position not tested here as getting the baseline for text in the
// DOM is not straightforward.
}
}
{% if use_directional_override %}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
{% endif %}
ctx.font = '50px sans-serif';
ctx.direction = '{{ text_direction }}';
ctx.textAlign = '{{ text_align }}';
ctx.letterSpacing = '{{ letter_spacing }}';
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
')(あ)(',
'-abcd_'
]
for (text of kTexts) {
{% if use_directional_override %}
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
{% endif %}
const tm = ctx.measureText(text);
// First character.
checkRectListsMatch(
tm.getSelectionRects(0, 1),
placeAndSelectTextInDOM(text, 0, 1)
);
// Last character.
checkRectListsMatch(
tm.getSelectionRects(text.length - 1, text.length),
placeAndSelectTextInDOM(text, text.length - 1, text.length)
);
// Whole string.
checkRectListsMatch(
tm.getSelectionRects(0, text.length),
placeAndSelectTextInDOM(text, 0, text.length)
);
// Intermediate string.
checkRectListsMatch(
tm.getSelectionRects(1, text.length - 1),
placeAndSelectTextInDOM(text, 1, text.length - 1)
);
// Invalid start > end string. Creates 0 width rectangle.
checkRectListsMatch(
tm.getSelectionRects(3, 2),
placeAndSelectTextInDOM(text, 3, 2)
);
checkRectListsMatch(
tm.getSelectionRects(1, 0),
placeAndSelectTextInDOM(text, 1, 0)
);
}
variants_layout: [single_file, single_file, single_file, single_file]
variants:
- direction-ltr:
text_direction: ltr
direction-rtl:
text_direction: rtl
- align-left:
text_align: left
align-center:
text_align: center
align-right:
text_align: right
- no-spacing:
letter_spacing: 0px
spacing:
letter_spacing: 10px
- no-directional-override:
use_directional_override: false
directional-override:
use_directional_override: true
- name: 2d.text.measure.selection-rects-baselines.tentative
desc: >-
Check that TextMetrics::getSelectionRects() works correctly with
textBaseline.
code: |
ctx.font = '50px sans-serif';
const kBaselines = [
"top",
"hanging",
"middle",
"alphabetic",
"ideographic",
"bottom",
];
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
')(あ)(',
'-abcd_'
]
for (const text of kTexts) {
for (const baseline of kBaselines) {
const tm = ctx.measureText(text);
// First character.
for (const r of tm.getSelectionRects(0, 1)) {
assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0);
assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0);
}
// Last character.
for (const r of tm.getSelectionRects(text.length - 1, text.length)) {
assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0);
assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0);
}
// Whole string.
for (const r of tm.getSelectionRects(0, text.length)) {
assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0);
assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0);
}
// Intermediate string.
for (const r of tm.getSelectionRects(1, text.length - 1)) {
assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0);
assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0);
}
// Invalid start > end string. Creates 0 width rectangle.
for (const r of tm.getSelectionRects(3, 2)) {
assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0);
assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0);
}
for (const r of tm.getSelectionRects(1, 0)) {
assert_approx_equals(r.top, -tm.fontBoundingBoxAscent, 1.0);
assert_approx_equals(r.bottom, tm.fontBoundingBoxDescent, 1.0);
}
}
}
- name: 2d.text.measure.selection-rects-exceptions.tentative
desc: >-
Check that TextMetrics::getSelectionRects() throws when using invalid
indexes.
code: |
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
')(あ)(',
'-abcd_'
]
for (const text of kTexts) {
const tm = ctx.measureText(text);
// Handled by the IDL binding.
assert_throws_js(TypeError, () => tm.getSelectionRects(-1, 0) );
assert_throws_js(TypeError, () => tm.getSelectionRects(0, -1) );
assert_throws_js(TypeError, () => tm.getSelectionRects(-1, -1) );
// Thrown in TextMetrics.
assert_throws_dom("IndexSizeError",
() => tm.getSelectionRects(text.length + 1, 0) );
assert_throws_dom("IndexSizeError",
() => tm.getSelectionRects(0, text.length + 1) );
assert_throws_dom("IndexSizeError",
() => tm.getSelectionRects(text.length + 1, text.length + 1) );
}
- name: 2d.text.measure.getActualBoundingBox.tentative
desc: >-
Test TextMetrics::getActualBoundingBox(), with text align {{ text_align }}
, and {{ letter_spacing }} letter spacing.
test_type: promise
fonts:
- CanvasTest
size: [800, 200]
code: |
// Use measureText to create a rect for the whole text
function getFullTextBoundingBox(text) {
const tm = ctx.measureText(text);
return {
x: -tm.actualBoundingBoxLeft,
y: -tm.actualBoundingBoxAscent,
width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
};
}
// Returns a string a replaces the characters in text that are not in the
// range [start, end) with spaces.
function buildTestString(text, start, end) {
let ret = '';
for (let i = 0; i < text.length; ++i) {
if (start <= i && i < end)
ret += text[i];
else
ret += ' ';
}
return ret;
}
function checkRectsMatch(rect_a, rect_b, text, start, end) {
assert_approx_equals(rect_a.x, rect_b.x, 1.0, `${text} :: x :: ${start} to ${end}`);
assert_approx_equals(rect_a.y, rect_b.y, 1.0, `${text} :: y :: ${start} to ${end}`);
assert_approx_equals(rect_a.width, rect_b.width, 1.0, `${text} :: width :: ${start} to ${end}`);
assert_approx_equals(rect_a.height, rect_b.height, 1.0, `${text} :: height :: ${start} to ${end}`);
}
function testForSubstring(text, start, end) {
const textMetrics = ctx.measureText(text);
const rect_from_api = textMetrics.getActualBoundingBox(start, end);
const rect_from_full_bounds = getFullTextBoundingBox(
buildTestString(text, start, end));
checkRectsMatch(rect_from_api, rect_from_full_bounds, buildTestString(text, start, end), start, end);
}
{{ load_font }}
ctx.textAlign = '{{ text_align }}';
ctx.letterSpacing = '{{ letter_spacing }}';
const kAligns = [
'left',
'center',
'right',
];
const kBaselines = [
'top',
'hanging',
'middle',
'alphabetic',
'ideographic',
'bottom',
];
ctx.font = '50px CanvasTest';
const text = 'ABCDE';
for (const align of kAligns) {
for (const baseline of kBaselines) {
ctx.textAlign = align;
ctx.textBaseline = baseline;
// Full string.
testForSubstring(text, 0, text.length);
// Intermediate string.
testForSubstring(text, 1, text.length - 1);
// First character.
testForSubstring(text, 0, 1);
// Intermediate character.
testForSubstring(text, 2, 3);
}
}
variants_layout:
[multi_files, single_file, single_file]
variants:
- *load-font-variant-definition
- align-left:
text_align: left
align-center:
text_align: center
align-right:
text_align: right
- no-spacing:
letter_spacing: 0px
spacing:
letter_spacing: 10px
- name: 2d.text.measure.getActualBoundingBox-full-text.tentative
desc: >-
Test TextMetrics::getActualBoundingBox() for the full length of the string
for some edge cases, with direction {{ text_direction }} and
{{ variant_names[1] }}
size: [800, 200]
code: |
// Use measureText to create a rect for the whole text
function getFullTextBoundingBox(text) {
const tm = ctx.measureText(text);
return {
x: -tm.actualBoundingBoxLeft,
y: -tm.actualBoundingBoxAscent,
width: tm.actualBoundingBoxLeft + tm.actualBoundingBoxRight,
height: tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent
};
}
function checkRectsMatch(rect_a, rect_b) {
assert_approx_equals(rect_a.x, rect_b.x, 1.0);
assert_approx_equals(rect_a.y, rect_b.y, 1.0);
assert_approx_equals(rect_a.width, rect_b.width, 1.0);
assert_approx_equals(rect_a.height, rect_b.height, 1.0);
}
{% if use_directional_override %}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
{% endif %}
const kAligns = [
'left',
'center',
'right',
];
const kBaselines = [
'top',
'hanging',
'middle',
'alphabetic',
'ideographic',
'bottom',
];
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
')(あ)(',
'-abcd '
]
ctx.font = '50px sans-serif';
ctx.direction = '{{ text_direction }}';
for (const align of kAligns) {
for (const baseline of kBaselines) {
ctx.textAlign = align;
ctx.textBaseline = baseline;
for (text of kTexts) {
{% if use_directional_override %}
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
{% endif %}
const tm = ctx.measureText(text);
const rect_from_api = tm.getActualBoundingBox(0, text.length);
const rect_from_full_bounds = getFullTextBoundingBox(text);
checkRectsMatch(rect_from_api, rect_from_full_bounds)
}
}
}
variants_layout: [single_file, single_file]
variants:
- direction-ltr:
text_direction: ltr
direction-rtl:
text_direction: rtl
- no-directional-override:
use_directional_override: false
directional-override:
use_directional_override: true
- name: 2d.text.measure.getActualBoundingBox-exceptions.tentative
desc: >-
Check that TextMetrics::getActualBoundingBox() throws when using invalid
indexes.
code: |
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
')(あ)(',
'-abcd_'
]
for (const text of kTexts) {
const tm = ctx.measureText(text);
// Handled by the IDL binding.
assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, 0) );
assert_throws_js(TypeError, () => tm.getActualBoundingBox(0, -1) );
assert_throws_js(TypeError, () => tm.getActualBoundingBox(-1, -1) );
// Thrown in TextMetrics.
assert_throws_dom("IndexSizeError",
() => tm.getActualBoundingBox(text.length, 0) );
assert_throws_dom("IndexSizeError",
() => tm.getActualBoundingBox(0, text.length + 1) );
assert_throws_dom("IndexSizeError",
() => tm.getActualBoundingBox(text.length, text.length + 1) );
}
- name: 2d.text.measure.caret-position.tentative
desc: >-
Check that TextMetrics::caretPositionFromPoint() matches its DOM equivalent,
where possible, with direction {{ text_direction }}, text align
{{ text_align }}, {{ letter_spacing }} letter spacing and
{{ variant_names[3] }}.
canvas_types: ['HtmlCanvas', 'OffscreenCanvas']
code: |
function alignOffset(offset, width) {
if ('{{ text_align }}' == 'center') {
offset -= width / 2;
} else if ('{{ text_align }}' == 'right' ||
('{{ text_direction }}' == 'ltr' && '{{ text_align }}' == 'end') ||
('{{ text_direction }}' == 'rtl' && '{{ text_align }}' == 'start')) {
offset -= width;
}
return offset;
}
{% if use_directional_override %}
function addDirectionalOverrideCharacters(text, direction_is_ltr) {
// source: www.w3.org/International/questions/qa-bidi-unicode-controls.en
const LTR_OVERRIDE = '\u202D';
const RTL_OVERRIDE = '\u202E';
const OVERRIDE_END = '\u202C';
if (direction_is_ltr) {
return LTR_OVERRIDE + text + OVERRIDE_END;
}
return RTL_OVERRIDE + text + OVERRIDE_END;
}
{% endif %}
function placeAndMeasureTextInDOM(text, text_width, point) {
const el = document.createElement("p");
el.innerHTML = text;
el.style.font = '50px sans-serif';
el.style.direction = '{{ text_direction }}';
el.style.letterSpacing = '{{ letter_spacing }}';
// Put the text top left to make offsets simpler.
el.style.padding = '0px';
el.style.margin = '0px';
el.style.position = 'absolute';
el.style.x = '0px';
el.style.y = '0px';
document.body.appendChild(el);
text_bound = el.getBoundingClientRect();
text_x = text_bound.x;
text_y = text_bound.y + text_bound.height / 2;
// Offset to the requested point determined by textAlign and direction.
let text_align_dx = 0;
if ('{{ text_align }}' == 'center') {
text_align_dx = text_width / 2;
} else if ('{{ text_align }}' == 'right' ||
('{{ text_direction }}' == 'ltr' && '{{ text_align }}' == 'end') ||
('{{ text_direction }}' == 'rtl' && '{{ text_align }}' == 'start')) {
text_align_dx = text_width;
}
position = document.caretPositionFromPoint(point + text_align_dx + text_x, text_y);
@assert position.offsetNode.parentNode === el;
document.body.removeChild(el);
return position.offset;
}
ctx.font = '50px sans-serif';
ctx.direction = '{{ text_direction }}';
ctx.textAlign = '{{ text_align }}';
ctx.letterSpacing = '{{ letter_spacing }}';
const kTexts = [
'UNAVAILABLE',
'🏁🎶🏁',
')(あ)(',
'--abcd__'
]
for (text of kTexts) {
{% if use_directional_override %}
text = addDirectionalOverrideCharacters(text, ctx.direction == 'ltr');
{% endif %}
const tm = ctx.measureText(text);
text_width = tm.width;
step = 30;
if ('{{letter_spacing}}' == '10px') {
step = 40;
}
offset = step;
adjusted_offset = alignOffset(offset, text_width);
tm_position = tm.caretPositionFromPoint(adjusted_offset);
doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
assert_equals(tm_position,
doc_position,
"for " + text + " offset " + offset);
offset = text_width / 2;
adjusted_offset = alignOffset(offset, text_width);
tm_position = tm.caretPositionFromPoint(adjusted_offset);
doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
assert_equals(tm_position,
doc_position,
"for " + text + " offset " + offset);
offset = text_width - step;
adjusted_offset = alignOffset(offset, text_width);
tm_position = tm.caretPositionFromPoint(adjusted_offset);
doc_position = placeAndMeasureTextInDOM(text, text_width, adjusted_offset);
assert_equals(tm_position,
doc_position,
"for " + text + " offset " + offset);
}
variants_layout: [single_file, single_file, single_file, single_file]
variants:
- direction-ltr:
text_direction: ltr
direction-rtl:
text_direction: rtl
- align-left:
text_align: left
align-center:
text_align: center
align-right:
text_align: right
align-start:
text_align: start
align-end:
text_align: end
- no-spacing:
letter_spacing: 0px
spacing:
letter_spacing: 10px
- no-directional-override:
use_directional_override: false
directional-override:
use_directional_override: true
- name: 2d.text.measure.caret-position-edges.tentative
desc: >-
Check that TextMetrics::caretPositionFromPoint() gives correct edges when
the requested point is outside the range, with direction {{ text_direction }}
and text align {{ text_align }}.
code: |
function computeExpected(text, text_width, offset) {
expected_position = 0;
if ('{{ text_align }}' == 'center' && offset == 0) {
return text.length / 2;
}
if ('{{ text_direction }}' == 'ltr') {
if (offset >= text_width) {
return text.length;
}
if (offset <= -text_width) {
return 0;
}
// offset must be 0.
if ('{{ text_align }}' == 'start' || '{{ text_align }}' == 'left') {
return 0;
} else {
return text.length;
}
} else {
if (offset >= text_width) {
return 0;
}
if (offset <= -text_width) {
return text.length;
}
// offset must be 0.
if ('{{ text_align }}' == 'start' || '{{ text_align }}' == 'right') {
return 0;
} else {
return text.length;
}
}
return expected_position;
}
ctx.font = '50px sans-serif';
ctx.direction = '{{ text_direction }}';
ctx.textAlign = '{{ text_align }}';
ctx.letterSpacing = '{{ letter_spacing }}';
// The leading and trailing '-' cause the string to always follow
// the specified direction, even though the interior will always be ltr.
const text = '-0123456789-';
// Points are multiples of the string width as reported by
// textMetrics.width.
const kPoints = [
-2,
-1,
0,
1,
2
]
const tm = ctx.measureText(text);
text_width = tm.width;
for (const multiple of kPoints) {
offset = multiple * text_width;
tm_position = tm.caretPositionFromPoint(offset);
expected_position = computeExpected(text, text_width, offset);
assert_equals(tm_position,
expected_position,
"for " + text + " multiple " + multiple);
}
variants_layout: [single_file, single_file]
variants:
- direction-ltr:
text_direction: ltr
direction-rtl:
text_direction: rtl
- align-left:
text_align: left
align-center:
text_align: center
align-right:
text_align: right
align-start:
text_align: start
align-end:
text_align: end
- name: 2d.text.measure.caret-position-edge-cases.tentative
desc: >-
Test the edge cases for caretPositionFromPoint, where the point is at the
edge of glyph and at the midpoint.
test_type: promise
fonts:
- CanvasTest
code: |
{{ load_font }}
ctx.font = '50px CanvasTest';
ctx.direction = 'ltr';
ctx.align = 'left'
ctx.baseline = 'alphabetic'
tm = ctx.measureText('A');
const a_width = tm.width;
tm = ctx.measureText('B');
const b_width = tm.width;
tm = ctx.measureText('C');
const c_width = tm.width;
const epsilon = 1.0e-4;
tm = ctx.measureText('ABC');
@assert tm.caretPositionFromPoint(0) == 0;
@assert tm.caretPositionFromPoint(a_width / 2) == 0;
@assert tm.caretPositionFromPoint(a_width / 2 + 1) == 1;
@assert tm.caretPositionFromPoint(a_width) == 1;
@assert tm.caretPositionFromPoint(a_width + b_width / 2) == 1;
@assert tm.caretPositionFromPoint(a_width + b_width / 2 + 1) == 2;
@assert tm.caretPositionFromPoint(a_width + b_width) == 2;
@assert tm.caretPositionFromPoint(a_width + b_width + c_width / 2) == 2;
@assert tm.caretPositionFromPoint(a_width + b_width + c_width / 2 + 1) == 3;
@assert tm.caretPositionFromPoint(a_width + b_width + c_width) == 3;
variants:
- *load-font-variant-definition
- name: 2d.text.drawing.style.absolute.spacing
desc: Testing letter spacing and word spacing with absolute length
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 = '10PX';
@assert ctx.letterSpacing === '1px';
@assert ctx.wordSpacing === '10px';
- name: 2d.text.drawing.style.font-relative.spacing
desc: Testing letter spacing and word spacing with font-relative length
code: |
@assert ctx.letterSpacing === '0px';
@assert ctx.wordSpacing === '0px';
ctx.letterSpacing = '1EX';
ctx.wordSpacing = '1EM';
@assert ctx.letterSpacing === '1ex';
@assert ctx.wordSpacing === '1em';
ctx.letterSpacing = '1ch';
ctx.wordSpacing = '1ic';
@assert ctx.letterSpacing === '1ch';
@assert ctx.wordSpacing === '1ic';
- 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 with different length units
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],
['-0.1em', -11, 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 word spacing with different length units
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],
['-0.5em', -10, 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, "letter-spacing error");
// 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, "letter-spacing error 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 === "auto";
ctx.fontKerning = "normal";
@assert ctx.fontKerning === "normal";
ctx.fontKerning = "Auto";
@assert ctx.fontKerning === "normal";
ctx.fontKerning = "auto";
ctx.fontKerning = "noRmal";
@assert ctx.fontKerning === "auto";
ctx.fontKerning = "auto";
ctx.fontKerning = "NoRMal";
@assert ctx.fontKerning === "auto";
ctx.fontKerning = "auto";
ctx.fontKerning = "NORMAL";
@assert ctx.fontKerning === "auto";
ctx.fontKerning = "None";
@assert ctx.fontKerning === "auto";
ctx.fontKerning = "none";
@assert ctx.fontKerning === "none";
ctx.fontKerning = "Auto";
@assert ctx.fontKerning === "none";
ctx.fontKerning = "auto";
ctx.fontKerning = "nOne";
@assert ctx.fontKerning === "auto";
ctx.fontKerning = "auto";
ctx.fontKerning = "nonE";
@assert ctx.fontKerning === "auto";
ctx.fontKerning = "auto";
ctx.fontKerning = "NONE";
@assert ctx.fontKerning === "auto";
- name: 2d.text.drawing.style.fontVariant.settings
desc: Testing basic functionalities of fontVariant 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 mixed-case values is not valid
ctx.fontVariantCaps = "nORmal";
@assert ctx.fontVariantCaps === "titling-caps";
ctx.fontVariantCaps = "normal";
@assert ctx.fontVariantCaps === "normal";
ctx.fontVariantCaps = "smaLL-caps";
@assert ctx.fontVariantCaps === "normal";
ctx.fontVariantCaps = "all-small-CAPS";
@assert ctx.fontVariantCaps === "normal";
ctx.fontVariantCaps = "pEtitE-caps";
@assert ctx.fontVariantCaps === "normal";
ctx.fontVariantCaps = "All-Petite-Caps";
@assert ctx.fontVariantCaps === "normal";
ctx.fontVariantCaps = "uNIcase";
@assert ctx.fontVariantCaps === "normal";
ctx.fontVariantCaps = "titling-CAPS";
@assert ctx.fontVariantCaps === "normal";
// Setting fontVariantCaps with non-existing font variant.
ctx.fontVariantCaps = "titling-caps";
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 correct case.
@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";
ctx.textRendering = "auto";
@assert ctx.textRendering === "auto";
// Setting textRendering with incorrect case is ignored.
ctx.textRendering = "OPtimizeSpeed";
@assert ctx.textRendering === "auto";
ctx.textRendering = "OPtimizELEgibility";
@assert ctx.textRendering === "auto";
ctx.textRendering = "GeometricPrecision";
@assert ctx.textRendering === "auto";
ctx.textRendering = "optimizespeed";
@assert ctx.textRendering === "auto";
ctx.textRendering = "optimizelegibility";
@assert ctx.textRendering === "auto";
ctx.textRendering = "geometricprecision";
@assert ctx.textRendering === "auto";
ctx.textRendering = "optimizeLegibility";
@assert ctx.textRendering === "optimizeLegibility";
ctx.textRendering = "AUTO";
@assert ctx.textRendering === "optimizeLegibility";
ctx.textRendering = "Auto";
@assert ctx.textRendering === "optimizeLegibility";
// Setting textRendering with non-existing font variant.
ctx.textRendering = "abcd";
@assert ctx.textRendering === "optimizeLegibility";
ctx.textRendering = "normal";
@assert ctx.textRendering === "optimizeLegibility";
ctx.textRendering = "";
@assert ctx.textRendering === "optimizeLegibility";
ctx.textRendering = "auto";
@assert ctx.textRendering === "auto";
- name: 2d.text.drawing.style.reset.TextRendering
desc: TextRendering stays the same after font change.
code: |
ctx.font = '20px serif';
ctx.textRendering = "optimizeSpeed";
@assert ctx.textRendering === "optimizeSpeed";
ctx.font = '10px serif';
@assert ctx.textRendering === "optimizeSpeed";
- name: 2d.text.drawing.style.fontStretch.settings
desc: Testing value setting of fontStretch in Canvas
code: |
// Setting fontStretch with lower cases
ctx.fontStretch = "ultra-condensed";
@assert ctx.fontStretch === "ultra-condensed";
ctx.fontStretch = "extra-condensed";
@assert ctx.fontStretch === "extra-condensed";
ctx.fontStretch = "condensed";
@assert ctx.fontStretch === "condensed";
ctx.fontStretch = "semi-condensed";
@assert ctx.fontStretch === "semi-condensed";
ctx.fontStretch = "normal";
@assert ctx.fontStretch === "normal";
ctx.fontStretch = "semi-expanded";
@assert ctx.fontStretch === "semi-expanded";
ctx.fontStretch = "expanded";
@assert ctx.fontStretch === "expanded";
ctx.fontStretch = "extra-expanded";
@assert ctx.fontStretch === "extra-expanded";
ctx.fontStretch = "ultra-expanded";
@assert ctx.fontStretch === "ultra-expanded";
// Setting fontStretch with lower cases and upper cases word,
// these values should be ignored.
ctx.fontStretch = "ulTra-condensed";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "Extra-condensed";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "cOndensed";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "Semi-Condensed";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "normaL";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "semi-Expanded";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "Expanded";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "eXtra-expanded";
@assert ctx.fontStretch === "ultra-expanded";
ctx.fontStretch = "abcd";
@assert ctx.fontStretch === "ultra-expanded";
- name: 2d.text.fontVariantCaps1
desc: Testing small caps setting in fontVariant
code: |
ctx.font = "32px serif";
ctx.fontVariantCaps = "small-caps";
// This should render the same as font = "small-caps 32px serif".
ctx.fillText("Hello World", 20, 100);
reference: |
ctx.font = "small-caps 32px serif";
ctx.fillText("Hello World", 20, 100);
- name: 2d.text.fontVariantCaps2
desc: Testing small caps setting in fontVariant
code: |
ctx.font = "small-caps 32px serif";
// "mismatch" test, to verify that small-caps does change the rendering.
smallCaps_len = ctx.measureText("Hello World").width;
ctx.font = "32px serif";
normalCaps_len = ctx.measureText("Hello World").width;
@assert smallCaps_len != normalCaps_len;
- name: 2d.text.fontVariantCaps3
desc: Testing small caps setting in fontVariant
code: |
ctx.font = "32px serif";
ctx.fontVariantCaps = "all-small-caps";
// This should render the same as using font = "small-caps 32px serif"
// with all the underlying text in lowercase.
ctx.fillText("Hello World", 20, 100);
reference: |
ctx.font = "small-caps 32px serif";
ctx.fillText("hello world", 20, 100);
- name: 2d.text.fontVariantCaps4
desc: Testing small caps setting in fontVariant
code: |
ctx.font = "small-caps 32px serif";
// fontVariantCaps overrides the small-caps setting from the font attribute
// (spec unclear, cf. https://github.com/whatwg/html/issues/8103)
ctx.fontVariantCaps = "all-small-caps";
ctx.fillText("Hello World", 20, 100);
reference: |
ctx.font = "small-caps 32px serif";
ctx.fillText("hello world", 20, 100);
- name: 2d.text.fontVariantCaps5
desc: Testing small caps setting in fontVariant
code: |
ctx.font = "small-caps 32px serif";
// fontVariantCaps 'normal' does not override the setting from the font attribute.
// (spec unclear, cf. https://github.com/whatwg/html/issues/8103)
ctx.fontVariantCaps = "normal";
ctx.fillText("Hello World", 20, 100);
reference: |
ctx.font = "small-caps 32px serif";
ctx.fillText("Hello World", 20, 100);
- name: 2d.text.fontVariantCaps6
desc: Testing small caps setting in fontVariant
code: |
// fontVariantCaps is reset when the font attribute is set.
// (spec unclear, cf. https://github.com/whatwg/html/issues/8103)
ctx.fontVariantCaps = "all-small-caps";
ctx.font = "32px serif";
ctx.fillText("Hello World", 20, 100);
reference: |
ctx.font = "32px serif";
ctx.fillText("Hello World", 20, 100);
- name: 2d.text.fontVariantCaps.after.reset.font
desc: Testing if the fontVariantCaps is reset after font change
size: [300, 300]
code: |
ctx.font = "32px serif";
ctx.fontVariantCaps = "small-caps";
ctx.font = "31px serif";
ctx.fillText("Hello World", 20, 40);
ctx.fontVariantCaps = "small-caps";
ctx.fillText("Hello World", 20, 80);
reference: |
ctx.font = "31px serif";
ctx.fillText("Hello World", 20, 40);
ctx.fontVariantCaps = "small-caps";
ctx.fillText("Hello World", 20, 80);
- name: 2d.text.setFont.mathFont
desc: crbug.com/1212190, make sure offscreencanvas doesn't crash with Math Font
code: |
ctx.font = "math serif";
- name: 2d.text.writingmode
desc: writing-mode in css should not change how text is rendered
canvas_types: ['HtmlCanvas']
code: |
canvas.style.writingMode = "vertical-rl";
canvas.style.fontFamily = "Arial";
ctx.font = "bold 64px Arial";
ctx.textBaseline = "top";
ctx.fillText("Happy", 0, 100);
reference: |
ctx.font = "bold 64px Arial";
ctx.textBaseline = "top";
ctx.fillText("Happy", 0, 100);
# TODO: shadows, alpha, composite, clip