describe('Canvas 2D emulation', () => {
let container;
beforeEach(async () => {
await LoadCanvasKit;
container = document.createElement('div');
container.innerHTML = `
`;
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
});
const expectColorCloseTo = (a, b) => {
expect(a.length).toEqual(4);
expect(b.length).toEqual(4);
for (let i=0; i<4; i++) {
expect(a[i]).toBeCloseTo(b[i], 3);
}
}
describe('color strings', () => {
const hex = (s) => {
return parseInt(s, 16);
}
it('parses hex color strings', () => {
const parseColor = CanvasKit.parseColorString;
expectColorCloseTo(parseColor('#FED'),
CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
expectColorCloseTo(parseColor('#FEDC'),
CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
expectColorCloseTo(parseColor('#fed'),
CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
expectColorCloseTo(parseColor('#fedc'),
CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), hex('CC')/255));
});
it('parses rgba color strings', () => {
const parseColor = CanvasKit.parseColorString;
expectColorCloseTo(parseColor('rgba(117, 33, 64, 0.75)'),
CanvasKit.Color(117, 33, 64, 0.75));
expectColorCloseTo(parseColor('rgb(117, 33, 64, 0.75)'),
CanvasKit.Color(117, 33, 64, 0.75));
expectColorCloseTo(parseColor('rgba(117,33,64)'),
CanvasKit.Color(117, 33, 64, 1.0));
expectColorCloseTo(parseColor('rgb(117,33, 64)'),
CanvasKit.Color(117, 33, 64, 1.0));
expectColorCloseTo(parseColor('rgb(117,33, 64, 32%)'),
CanvasKit.Color(117, 33, 64, 0.32));
expectColorCloseTo(parseColor('rgb(117,33, 64, 0.001)'),
CanvasKit.Color(117, 33, 64, 0.001));
expectColorCloseTo(parseColor('rgb(117,33,64,0)'),
CanvasKit.Color(117, 33, 64, 0.0));
});
it('parses named color strings', () => {
// Keep this one as the _testing version, because we don't include the large
// color map by default.
const parseColor = CanvasKit._testing.parseColor;
expectColorCloseTo(parseColor('grey'),
CanvasKit.Color(128, 128, 128, 1.0));
expectColorCloseTo(parseColor('blanchedalmond'),
CanvasKit.Color(255, 235, 205, 1.0));
expectColorCloseTo(parseColor('transparent'),
CanvasKit.Color(0, 0, 0, 0));
});
it('properly produces color strings', () => {
const colorToString = CanvasKit._testing.colorToString;
expect(colorToString(CanvasKit.Color(102, 51, 153, 1.0))).toEqual('#663399');
expect(colorToString(CanvasKit.Color(255, 235, 205, 0.5))).toEqual(
'rgba(255, 235, 205, 0.50000000)');
});
it('can multiply colors by alpha', () => {
const multiplyByAlpha = CanvasKit.multiplyByAlpha;
const testCases = [
{
inColor: CanvasKit.Color(102, 51, 153, 1.0),
inAlpha: 1.0,
outColor: CanvasKit.Color(102, 51, 153, 1.0),
},
{
inColor: CanvasKit.Color(102, 51, 153, 1.0),
inAlpha: 0.8,
outColor: CanvasKit.Color(102, 51, 153, 0.8),
},
{
inColor: CanvasKit.Color(102, 51, 153, 0.8),
inAlpha: 0.7,
outColor: CanvasKit.Color(102, 51, 153, 0.56),
},
{
inColor: CanvasKit.Color(102, 51, 153, 0.8),
inAlpha: 1000,
outColor: CanvasKit.Color(102, 51, 153, 1.0),
},
];
for (const tc of testCases) {
// Print out the test case if the two don't match.
expect(multiplyByAlpha(tc.inColor, tc.inAlpha))
.toEqual(tc.outColor, JSON.stringify(tc));
}
});
}); // end describe('color string parsing')
describe('fonts', () => {
it('can parse font sizes', () => {
const parseFontString = CanvasKit._testing.parseFontString;
const tests = [{
'input': '10px monospace',
'output': {
'style': '',
'variant': '',
'weight': '',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': '15pt Arial',
'output': {
'style': '',
'variant': '',
'weight': '',
'sizePx': 20,
'family': 'Arial',
}
},
{
'input': '1.5in Arial, san-serif ',
'output': {
'style': '',
'variant': '',
'weight': '',
'sizePx': 144,
'family': 'Arial, san-serif',
}
},
{
'input': '1.5em SuperFont',
'output': {
'style': '',
'variant': '',
'weight': '',
'sizePx': 24,
'family': 'SuperFont',
}
},
];
for (let i = 0; i < tests.length; i++) {
expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
}
});
it('can parse font attributes', () => {
const parseFontString = CanvasKit._testing.parseFontString;
const tests = [{
'input': 'bold 10px monospace',
'output': {
'style': '',
'variant': '',
'weight': 'bold',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': 'italic bold 10px monospace',
'output': {
'style': 'italic',
'variant': '',
'weight': 'bold',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': 'italic small-caps bold 10px monospace',
'output': {
'style': 'italic',
'variant': 'small-caps',
'weight': 'bold',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': 'small-caps bold 10px monospace',
'output': {
'style': '',
'variant': 'small-caps',
'weight': 'bold',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': 'italic 10px monospace',
'output': {
'style': 'italic',
'variant': '',
'weight': '',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': 'small-caps 10px monospace',
'output': {
'style': '',
'variant': 'small-caps',
'weight': '',
'sizePx': 10,
'family': 'monospace',
}
},
{
'input': 'normal bold 10px monospace',
'output': {
'style': 'normal',
'variant': '',
'weight': 'bold',
'sizePx': 10,
'family': 'monospace',
}
},
];
for (let i = 0; i < tests.length; i++) {
expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
}
});
});
const multipleCanvasTest = (testname, done, test) => {
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
skcanvas._config = 'software_canvas';
const realCanvas = document.getElementById('test');
realCanvas._config = 'html_canvas';
realCanvas.width = CANVAS_WIDTH;
realCanvas.height = CANVAS_HEIGHT;
if (!done) {
console.log('debugging canvaskit');
test(realCanvas);
test(skcanvas);
const png = skcanvas.toDataURL();
const img = document.createElement('img');
document.body.appendChild(img);
img.src = png;
debugger;
return;
}
let promises = [];
for (let canvas of [skcanvas, realCanvas]) {
test(canvas);
// canvas has .toDataURL (even though skcanvas is not a real Canvas)
// so this will work.
promises.push(reportCanvas(canvas, testname, canvas._config));
}
Promise.all(promises).then(() => {
skcanvas.dispose();
done();
}).catch(reportError(done));
}
describe('CanvasContext2D API', () => {
multipleCanvasGM('all_line_drawing_operations', (canvas) => {
const ctx = canvas.getContext('2d');
ctx.scale(3.0, 3.0);
ctx.moveTo(20, 5);
ctx.lineTo(30, 20);
ctx.lineTo(40, 10);
ctx.lineTo(50, 20);
ctx.lineTo(60, 0);
ctx.lineTo(20, 5);
ctx.moveTo(20, 80);
ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
ctx.moveTo(36, 148);
ctx.quadraticCurveTo(66, 188, 120, 136);
ctx.lineTo(36, 148);
ctx.rect(5, 170, 20, 25);
ctx.moveTo(150, 180);
ctx.arcTo(150, 100, 50, 200, 20);
ctx.lineTo(160, 160);
ctx.moveTo(20, 120);
ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
ctx.lineTo(20, 120);
ctx.moveTo(150, 5);
ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
ctx.lineWidth = 2;
ctx.stroke();
// Test edgecases and draw direction
ctx.beginPath();
ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
ctx.stroke();
ctx.beginPath();
ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
ctx.stroke();
});
multipleCanvasGM('all_matrix_operations', (canvas) => {
const ctx = canvas.getContext('2d');
ctx.rect(10, 10, 20, 20);
ctx.scale(2.0, 4.0);
ctx.rect(30, 10, 20, 20);
ctx.resetTransform();
ctx.rotate(Math.PI / 3);
ctx.rect(50, 10, 20, 20);
ctx.resetTransform();
ctx.translate(30, -2);
ctx.rect(70, 10, 20, 20);
ctx.resetTransform();
ctx.translate(60, 0);
ctx.rotate(Math.PI / 6);
ctx.transform(1.5, 0, 0, 0.5, 0, 0); // effectively scale
ctx.rect(90, 10, 20, 20);
ctx.resetTransform();
ctx.save();
ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
ctx.rect(110, 10, 20, 20);
ctx.lineTo(110, 0);
ctx.restore();
ctx.lineTo(220, 120);
ctx.scale(3.0, 3.0);
ctx.font = '6pt Noto Mono';
ctx.fillText('This text should be huge', 10, 80);
ctx.resetTransform();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(250, 30);
ctx.lineTo(250, 80);
ctx.scale(3.0, 3.0);
ctx.lineTo(280/3, 90/3);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.stroke();
});
multipleCanvasGM('shadows_and_save_restore', (canvas) => {
const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#000';
ctx.fillStyle = '#CCC';
ctx.shadowColor = 'rebeccapurple';
ctx.shadowBlur = 1;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = -8;
ctx.rect(10, 10, 30, 30);
ctx.save();
ctx.strokeStyle = '#C00';
ctx.fillStyle = '#00C';
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
ctx.stroke();
ctx.restore();
ctx.fill();
ctx.beginPath();
ctx.moveTo(36, 148);
ctx.quadraticCurveTo(66, 188, 120, 136);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.shadowColor = '#993366AA';
ctx.shadowOffsetX = 8;
ctx.shadowBlur = 5;
ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
ctx.rect(110, 10, 20, 20);
ctx.lineTo(110, 0);
ctx.resetTransform();
ctx.lineTo(220, 120);
ctx.stroke();
ctx.fillStyle = 'green';
ctx.font = '16pt Noto Mono';
ctx.fillText('This should be shadowed', 20, 80);
ctx.beginPath();
ctx.lineWidth = 6;
ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
ctx.scale(2, 1);
ctx.moveTo(10, 290)
ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
ctx.resetTransform();
ctx.shadowColor = '#993366AA';
ctx.scale(3, 1);
ctx.moveTo(10, 290)
ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
ctx.stroke();
});
multipleCanvasGM('global_dashed_rects', (canvas) => {
const ctx = canvas.getContext('2d');
ctx.scale(1.1, 1.1);
ctx.translate(10, 10);
// Shouldn't impact the fillRect calls
ctx.setLineDash([5, 3]);
ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
ctx.fillRect(20, 30, 100, 100);
ctx.globalAlpha = 0.81;
ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
ctx.fillRect(120, 30, 100, 100);
// This shouldn't do anything
ctx.globalAlpha = 0.1;
ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
ctx.globalAlpha = 0.9;
// Intentional no-op to check ordering
ctx.clearRect(220, 30, 100, 100);
ctx.fillRect(220, 30, 100, 100);
ctx.fillRect(320, 30, 100, 100);
ctx.clearRect(330, 40, 80, 80);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.setLineDash([5, 3]);
ctx.strokeRect(20, 150, 100, 100);
ctx.setLineDash([50, 30]);
ctx.strokeRect(125, 150, 100, 100);
ctx.lineDashOffset = 25;
ctx.strokeRect(230, 150, 100, 100);
ctx.setLineDash([2, 5, 9]);
ctx.strokeRect(335, 150, 100, 100);
ctx.setLineDash([5, 2]);
ctx.moveTo(336, 400);
ctx.quadraticCurveTo(366, 488, 120, 450);
ctx.lineTo(300, 400);
ctx.stroke();
ctx.font = '36pt Noto Mono';
ctx.strokeText('Dashed', 20, 350);
ctx.fillText('Not Dashed', 20, 400);
});
multipleCanvasGM('gradients_clip', (canvas) => {
const ctx = canvas.getContext('2d');
const rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
rgradient.addColorStop(0, 'red');
rgradient.addColorStop(.7, 'white');
rgradient.addColorStop(1, 'blue');
ctx.fillStyle = rgradient;
ctx.globalAlpha = 0.7;
ctx.fillRect(0,0,600,600);
ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.arc(300, 100, 90, 0, Math.PI*1.66);
ctx.closePath();
ctx.strokeStyle = 'yellow';
ctx.lineWidth = 5;
ctx.stroke();
ctx.save();
ctx.clip();
const lgradient = ctx.createLinearGradient(200, 20, 420, 40);
lgradient.addColorStop(0, 'green');
lgradient.addColorStop(.5, 'cyan');
lgradient.addColorStop(1, 'orange');
ctx.fillStyle = lgradient;
ctx.fillRect(200, 30, 200, 300);
ctx.restore();
ctx.fillRect(550, 550, 40, 40);
});
multipleCanvasGM('get_put_imagedata', (canvas) => {
const ctx = canvas.getContext('2d');
// Make a gradient so we see if the pixels copying worked
const grad = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
grad.addColorStop(0, 'yellow');
grad.addColorStop(1, 'red');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
const iData = ctx.getImageData(400, 100, 200, 150);
expect(iData.width).toBe(200);
expect(iData.height).toBe(150);
expect(iData.data.byteLength).toBe(200*150*4);
ctx.putImageData(iData, 10, 10);
ctx.putImageData(iData, 350, 350, 100, 75, 45, 40);
ctx.strokeRect(350, 350, 200, 150);
const box = ctx.createImageData(20, 40);
ctx.putImageData(box, 10, 300);
const biggerBox = ctx.createImageData(iData);
ctx.putImageData(biggerBox, 10, 350);
expect(biggerBox.width).toBe(iData.width);
expect(biggerBox.height).toBe(iData.height);
});
multipleCanvasGM('shadows_with_rotate_skbug_9947', (canvas) => {
const ctx = canvas.getContext('2d');
const angle = 240;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.save();
ctx.translate(80, 80);
ctx.rotate((angle * Math.PI) / 180);
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowColor = 'rgba(100,100,100,0.5)';
ctx.shadowBlur = 1;
ctx.fillStyle = 'black';
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.rect(-20, -20, 40, 40);
ctx.fill();
ctx.fillRect(30, 30, 40, 40);
ctx.strokeRect(30, -20, 40, 40);
ctx.fillText('text', -20, -30);
ctx.restore();
});
describe('using images', () => {
let skImageData = null;
let htmlImage = null;
const skPromise = fetch('/assets/mandrill_512.png')
.then((response) => response.arrayBuffer())
.then((buffer) => {
skImageData = buffer;
});
const realPromise = fetch('/assets/mandrill_512.png')
.then((response) => response.blob())
.then((blob) => createImageBitmap(blob))
.then((bitmap) => {
htmlImage = bitmap;
});
beforeEach(async () => {
await skPromise;
await realPromise;
});
multipleCanvasGM('draw_patterns', (canvas) => {
const ctx = canvas.getContext('2d');
let img = htmlImage;
if (canvas._config == 'software_canvas') {
img = canvas.decodeImage(skImageData);
}
ctx.fillStyle = '#EEE';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.lineWidth = 20;
ctx.scale(0.2, 0.4);
let pattern = ctx.createPattern(img, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 1500, 750);
pattern = ctx.createPattern(img, 'repeat-x');
ctx.fillStyle = pattern;
ctx.fillRect(1500, 0, 3000, 750);
ctx.globalAlpha = 0.7
pattern = ctx.createPattern(img, 'repeat-y');
ctx.fillStyle = pattern;
ctx.fillRect(0, 750, 1500, 1500);
ctx.strokeRect(0, 750, 1500, 1500);
pattern = ctx.createPattern(img, 'no-repeat');
ctx.fillStyle = pattern;
pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
ctx.fillRect(0, 0, 3000, 1500);
});
multipleCanvasGM('draw_image', (canvas) => {
let ctx = canvas.getContext('2d');
let img = htmlImage;
if (canvas._config == 'software_canvas') {
img = canvas.decodeImage(skImageData);
}
ctx.drawImage(img, 30, -200);
ctx.globalAlpha = 0.7
ctx.rotate(.1);
ctx.imageSmoothingQuality = 'medium';
ctx.drawImage(img, 200, 350, 150, 100);
ctx.rotate(-.2);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 100, 150, 400, 350, 10, 400, 150, 100);
});
}); // end describe('using images')
{
const drawPoint = (ctx, x, y, color) => {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
const IN = 'purple';
const OUT = 'orange';
const SCALE = 8;
// Check to see if these points are in or out on each of the
// test configurations.
const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
[6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
[25, 25], [26, 26], [27, 27]];
const tests = [
{
xOffset: 0,
yOffset: 0,
fillType: 'nonzero',
strokeWidth: 0,
testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
},
{
xOffset: 30,
yOffset: 0,
fillType: 'evenodd',
strokeWidth: 0,
testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
},
{
xOffset: 0,
yOffset: 30,
fillType: null,
strokeWidth: 1,
testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
},
{
xOffset: 30,
yOffset: 30,
fillType: null,
strokeWidth: 2,
testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
},
];
multipleCanvasGM('points_in_path_stroke', (canvas) => {
const ctx = canvas.getContext('2d');
ctx.font = '20px Noto Mono';
// Draw some visual aids
ctx.fillText('path-nonzero', 60, 30);
ctx.fillText('path-evenodd', 300, 30);
ctx.fillText('stroke-1px-wide', 60, 260);
ctx.fillText('stroke-2px-wide', 300, 260);
ctx.fillText('purple is IN, orange is OUT', 20, 560);
// Scale up to make single pixels easier to see
ctx.scale(SCALE, SCALE);
for (const test of tests) {
ctx.beginPath();
const xOffset = test.xOffset;
const yOffset = test.yOffset;
ctx.fillStyle = '#AAA';
ctx.lineWidth = test.strokeWidth;
ctx.rect(5+xOffset, 5+yOffset, 20, 20);
ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
if (test.fillType) {
ctx.fill(test.fillType);
} else {
ctx.stroke();
}
for (const pt of pts) {
let [x, y] = pt;
x += xOffset;
y += yOffset;
// naively apply transform when querying because the points queried
// ignore the CTM.
if (test.testFn(ctx, x, y)) {
drawPoint(ctx, x, y, IN);
} else {
drawPoint(ctx, x, y, OUT);
}
}
}
});
}
describe('loading custom fonts', () => {
const realFontLoaded = new FontFace('BungeeNonSystem', 'url(/assets/Bungee-Regular.ttf)', {
'family': 'BungeeNonSystem', //Make sure the canvas does not use the system font
'style': 'normal',
'weight': '400',
}).load().then((font) => {
document.fonts.add(font);
});
let fontBuffer = null;
const skFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
fontBuffer = buffer;
});
beforeEach(async () => {
await realFontLoaded;
await skFontLoaded;
});
multipleCanvasGM('custom_font', (canvas) => {
if (canvas.loadFont) {
canvas.loadFont(fontBuffer, {
'family': 'BungeeNonSystem',
'style': 'normal',
'weight': '400',
});
}
const ctx = canvas.getContext('2d');
ctx.font = '20px monospace';
ctx.fillText('20 px monospace', 10, 30);
ctx.font = '2.0em BungeeNonSystem';
ctx.fillText('2.0em Bungee filled', 10, 80);
ctx.strokeText('2.0em Bungee stroked', 10, 130);
ctx.font = '40pt monospace';
ctx.strokeText('40pt monospace', 10, 200);
// bold wasn't defined, so should fallback to just the 400 weight
ctx.font = 'bold 45px BungeeNonSystem';
ctx.fillText('45px Bungee filled', 10, 260);
});
}); // describe('loading custom fonts')
it('can read default properties', () => {
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
const realCanvas = document.getElementById('test');
realCanvas.width = CANVAS_WIDTH;
realCanvas.height = CANVAS_HEIGHT;
const skcontext = skcanvas.getContext('2d');
const realContext = realCanvas.getContext('2d');
// The skia canvas only comes with a monospace font by default
// Set the html canvas to be monospace too.
realContext.font = '10px monospace';
const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
'lineJoin', 'miterLimit', 'shadowOffsetY',
'shadowBlur', 'shadowColor', 'shadowOffsetX',
'globalAlpha', 'globalCompositeOperation',
'lineDashOffset', 'imageSmoothingEnabled',
'imageFilterQuality'];
// Compare all the default values of the properties of skcanvas
// to the default values on the properties of a real canvas.
for(let attr of toTest) {
expect(skcontext[attr]).toBe(realContext[attr], attr);
}
skcanvas.dispose();
});
}); // end describe('CanvasContext2D API')
describe('Path2D API', () => {
multipleCanvasGM('path2d_line_drawing_operations', (canvas) => {
const ctx = canvas.getContext('2d');
let clock;
let path;
if (canvas.makePath2D) {
clock = canvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
path = canvas.makePath2D();
} else {
clock = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z')
path = new Path2D();
}
path.moveTo(20, 5);
path.lineTo(30, 20);
path.lineTo(40, 10);
path.lineTo(50, 20);
path.lineTo(60, 0);
path.lineTo(20, 5);
path.moveTo(20, 80);
path.bezierCurveTo(90, 10, 160, 150, 190, 10);
path.moveTo(36, 148);
path.quadraticCurveTo(66, 188, 120, 136);
path.lineTo(36, 148);
path.rect(5, 170, 20, 25);
path.moveTo(150, 180);
path.arcTo(150, 100, 50, 200, 20);
path.lineTo(160, 160);
path.moveTo(20, 120);
path.arc(20, 120, 18, 0, 1.75 * Math.PI);
path.lineTo(20, 120);
path.moveTo(150, 5);
path.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
ctx.lineWidth = 2;
ctx.scale(3.0, 3.0);
ctx.stroke(path);
ctx.stroke(clock);
});
}); // end describe('Path2D API')
});