4 |
5 | PangoFontDescription *get_pango_font_description(unsigned char *filepath);
6 | bool register_font(unsigned char *filepath);
7 | bool deregister_font(unsigned char *filepath);
8 |
--------------------------------------------------------------------------------
/test/fixtures/159-crash1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/159-crash1.jpg
--------------------------------------------------------------------------------
/test/fixtures/bmp/1-bit.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/1-bit.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/24-bit.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/24-bit.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/32-bit.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/32-bit.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/4-bit.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/4-bit.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/bomb.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/bomb.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/min.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/min.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/negative-height.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/negative-height.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/palette.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/palette.bmp
--------------------------------------------------------------------------------
/test/fixtures/bmp/v3-header.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/bmp/v3-header.bmp
--------------------------------------------------------------------------------
/test/fixtures/checkers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/checkers.png
--------------------------------------------------------------------------------
/test/fixtures/chrome.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/chrome.jpg
--------------------------------------------------------------------------------
/test/fixtures/clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/clock.png
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f1.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f2.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f3.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f4.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f5.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f6.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f7.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-f8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-f8.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-fi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-fi.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-fm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-fm.jpg
--------------------------------------------------------------------------------
/test/fixtures/exif-orientation-fn.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/exif-orientation-fn.jpg
--------------------------------------------------------------------------------
/test/fixtures/existing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/existing.png
--------------------------------------------------------------------------------
/test/fixtures/face.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/face.jpeg
--------------------------------------------------------------------------------
/test/fixtures/grayscale.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/grayscale.jpg
--------------------------------------------------------------------------------
/test/fixtures/halved-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/halved-1.jpeg
--------------------------------------------------------------------------------
/test/fixtures/halved-2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/halved-2.jpeg
--------------------------------------------------------------------------------
/test/fixtures/newcontent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/newcontent.png
--------------------------------------------------------------------------------
/test/fixtures/quadrants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/quadrants.png
--------------------------------------------------------------------------------
/test/fixtures/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/star.png
--------------------------------------------------------------------------------
/test/fixtures/state.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/state.png
--------------------------------------------------------------------------------
/test/fixtures/tree.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/test/fixtures/ycck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Automattic/node-canvas/1234a86f65b2318906334ce0790cd12401c67a25/test/fixtures/ycck.jpg
--------------------------------------------------------------------------------
/test/fontParser.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | 'use strict'
4 |
5 | /**
6 | * Module dependencies.
7 | */
8 | const assert = require('assert')
9 | const {Canvas} = require('..');
10 |
11 | const tests = [
12 | '20px Arial',
13 | { size: 20, families: ['arial'] },
14 | '20pt Arial',
15 | { size: 26.666667461395264, families: ['arial'] },
16 | '20.5pt Arial',
17 | { size: 27.333334147930145, families: ['arial'] },
18 | '20% Arial',
19 | { size: 3.1999999284744263, families: ['arial'] },
20 | '20mm Arial',
21 | { size: 75.59999942779541, families: ['arial'] },
22 | '20px serif',
23 | { size: 20, families: ['serif'] },
24 | '20px sans-serif',
25 | { size: 20, families: ['sans-serif'] },
26 | '20px monospace',
27 | { size: 20, families: ['monospace'] },
28 | '50px Arial, sans-serif',
29 | { size: 50, families: ['arial', 'sans-serif'] },
30 | 'bold italic 50px Arial, sans-serif',
31 | { style: 1, weight: 700, size: 50, families: ['arial', 'sans-serif'] },
32 | '50px Helvetica , Arial, sans-serif',
33 | { size: 50, families: ['helvetica', 'arial', 'sans-serif'] },
34 | '50px "Helvetica Neue", sans-serif',
35 | { size: 50, families: ['Helvetica Neue', 'sans-serif'] },
36 | '50px "Helvetica Neue", "foo bar baz" , sans-serif',
37 | { size: 50, families: ['Helvetica Neue', 'foo bar baz', 'sans-serif'] },
38 | "50px 'Helvetica Neue'",
39 | { size: 50, families: ['Helvetica Neue'] },
40 | 'italic 20px Arial',
41 | { size: 20, style: 1, families: ['arial'] },
42 | 'oblique 20px Arial',
43 | { size: 20, style: 2, families: ['arial'] },
44 | 'normal 20px Arial',
45 | { size: 20, families: ['arial'] },
46 | '300 20px Arial',
47 | { size: 20, weight: 300, families: ['arial'] },
48 | '800 20px Arial',
49 | { size: 20, weight: 800, families: ['arial'] },
50 | 'bolder 20px Arial',
51 | { size: 20, weight: 700, families: ['arial'] },
52 | 'lighter 20px Arial',
53 | { size: 20, weight: 100, families: ['arial'] },
54 | 'normal normal normal 16px Impact',
55 | { size: 16, families: ['impact'] },
56 | 'italic small-caps bolder 16px cursive',
57 | { size: 16, style: 1, variant: 1, weight: 700, families: ['cursive'] },
58 | '20px "new century schoolbook", serif',
59 | { size: 20, families: ['new century schoolbook', 'serif'] },
60 | '20px "Arial bold 300"', // synthetic case with weight keyword inside family
61 | { size: 20, families: ['Arial bold 300'] },
62 | `50px "Helvetica 'Neue'", "foo \\"bar\\" baz" , "Someone's weird \\'edge\\' case", sans-serif`,
63 | { size: 50, families: [`Helvetica 'Neue'`, 'foo "bar" baz', `Someone's weird 'edge' case`, 'sans-serif'] },
64 | 'Helvetica, sans',
65 | undefined,
66 | '123px thefont/123abc',
67 | undefined,
68 | '123px /\tnormal thefont',
69 | {size: 123, families: ['thefont']},
70 | '12px/1.2whoops arial',
71 | undefined,
72 | 'bold bold 12px thefont',
73 | undefined,
74 | 'italic italic 12px Arial',
75 | undefined,
76 | 'small-caps bold italic small-caps 12px Arial',
77 | undefined,
78 | 'small-caps bold oblique 12px \'A\'ri\\61l',
79 | {size: 12, style: 2, weight: 700, variant: 1, families: ['Arial']},
80 | '12px/34% "The\\\n Word"',
81 | {size: 12, families: ['The Word']},
82 | '',
83 | undefined,
84 | 'normal normal normal 1%/normal a , \'b\'',
85 | {size: 0.1599999964237213, families: ['a', 'b']},
86 | 'normalnormalnormal 1px/normal a',
87 | undefined,
88 | '12px _the_font',
89 | {size: 12, families: ['_the_font']},
90 | '9px 7 birds',
91 | undefined,
92 | '2em "Courier',
93 | undefined,
94 | `2em \\'Courier\\"`,
95 | {size: 32, families: ['\'courier"']},
96 | '1px \\10abcde',
97 | {size: 1, families: [String.fromCodePoint(parseInt('10abcd', 16)) + 'e']},
98 | '3E+2 1e-1px yay',
99 | {weight: 300, size: 0.1, families: ['yay']}
100 | ];
101 |
102 | describe('Font parser', function () {
103 | for (let i = 0; i < tests.length; i++) {
104 | const str = tests[i++]
105 | it(str, function () {
106 | const expected = tests[i]
107 | const actual = Canvas.parseFont(str)
108 |
109 | if (expected) {
110 | if (expected.style == null) expected.style = 0
111 | if (expected.weight == null) expected.weight = 400
112 | if (expected.variant == null) expected.variant = 0
113 | }
114 |
115 | assert.deepEqual(actual, expected)
116 | })
117 | }
118 | })
119 |
--------------------------------------------------------------------------------
/test/imageData.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-env mocha */
2 |
3 | 'use strict'
4 |
5 | const {createImageData} = require('../')
6 | const {ImageData} = require('../')
7 |
8 | const assert = require('assert')
9 |
10 | describe('ImageData', function () {
11 | it('Prototype and ctor are well-shaped, don\'t hit asserts on accessors (GH-803)', function () {
12 | assert.throws(function () { ImageData.prototype.width }, /invalid argument/i)
13 | })
14 |
15 | it('stringifies as [object ImageData]', function () {
16 | const imageData = createImageData(2, 3)
17 | assert.strictEqual(imageData.toString(), '[object ImageData]')
18 | })
19 |
20 | it('gives class string as `ImageData`', function () {
21 | const imageData = createImageData(2, 3)
22 | assert.strictEqual(Object.prototype.toString.call(imageData), '[object ImageData]')
23 | })
24 |
25 | it('should throw with invalid numeric arguments', function () {
26 | assert.throws(() => { createImageData(0, 0) }, /width is zero/)
27 | assert.throws(() => { createImageData(1, 0) }, /height is zero/)
28 | assert.throws(() => { createImageData(0) }, TypeError)
29 | })
30 |
31 | it('should construct with width and height', function () {
32 | const imageData = createImageData(2, 3)
33 |
34 | assert.strictEqual(imageData.width, 2)
35 | assert.strictEqual(imageData.height, 3)
36 |
37 | assert.ok(imageData.data instanceof Uint8ClampedArray)
38 | assert.strictEqual(imageData.data.length, 24)
39 | })
40 |
41 | it('should throw with invalid typed array', function () {
42 | assert.throws(() => { createImageData(new Uint8ClampedArray(0), 0) }, /input data has a zero byte length/)
43 | assert.throws(() => { createImageData(new Uint8ClampedArray(3), 0) }, /source width is zero/)
44 | // Note: Some errors thrown by browsers are not thrown by node-canvas
45 | // because our ImageData can support different BPPs.
46 | })
47 |
48 | it('should construct with Uint8ClampedArray', function () {
49 | let data, imageData
50 |
51 | data = new Uint8ClampedArray(2 * 3 * 4)
52 | imageData = createImageData(data, 2)
53 | assert.strictEqual(imageData.width, 2)
54 | assert.strictEqual(imageData.height, 3)
55 | assert(imageData.data instanceof Uint8ClampedArray)
56 | assert.strictEqual(imageData.data.length, 24)
57 |
58 | data = new Uint8ClampedArray(3 * 4 * 4)
59 | imageData = createImageData(data, 3, 4)
60 | assert.strictEqual(imageData.width, 3)
61 | assert.strictEqual(imageData.height, 4)
62 | assert(imageData.data instanceof Uint8ClampedArray)
63 | assert.strictEqual(imageData.data.length, 48)
64 | })
65 |
66 | it('should construct with Uint16Array', function () {
67 | let data = new Uint16Array(2 * 3 * 2)
68 | let imagedata = createImageData(data, 2)
69 | assert.strictEqual(imagedata.width, 2)
70 | assert.strictEqual(imagedata.height, 3)
71 | assert(imagedata.data instanceof Uint16Array)
72 | assert.strictEqual(imagedata.data.length, 12)
73 |
74 | data = new Uint16Array(3 * 4 * 2)
75 | imagedata = createImageData(data, 3, 4)
76 | assert.strictEqual(imagedata.width, 3)
77 | assert.strictEqual(imagedata.height, 4)
78 | assert(imagedata.data instanceof Uint16Array)
79 | assert.strictEqual(imagedata.data.length, 24)
80 | })
81 | })
82 |
--------------------------------------------------------------------------------
/test/public/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | node-canvas
5 |
6 |
7 |
8 |
9 | node-canvas
10 |
11 |
12 | The tests below assert visual and api integrity by running the exact same code utilizing the client canvas api, as well as node-canvas.
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/public/app.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('load', runTests)
2 |
3 | function create (type, attrs, children) {
4 | const element = Object.assign(document.createElement(type), attrs)
5 |
6 | if (children) {
7 | children.forEach(function (child) { element.appendChild(child) })
8 | }
9 |
10 | return element
11 | }
12 |
13 | function pdfLink (name) {
14 | return create('a', {
15 | href: '/pdf?name=' + encodeURIComponent(name),
16 | target: '_blank',
17 | textContent: 'PDF'
18 | })
19 | }
20 |
21 | function localRendering (name, callback) {
22 | const canvas = create('canvas', { width: 200, height: 200, title: name })
23 | const tests = window.tests
24 | const ctx = canvas.getContext('2d', { alpha: true })
25 | const initialFillStyle = ctx.fillStyle
26 | ctx.fillStyle = 'white'
27 | ctx.fillRect(0, 0, 200, 200)
28 | ctx.fillStyle = initialFillStyle
29 | if (tests[name].length === 2) {
30 | tests[name](ctx, callback)
31 | } else {
32 | tests[name](ctx)
33 | callback(null)
34 | }
35 | return canvas
36 | }
37 |
38 | function getDifference (canvas, image, outputCanvas) {
39 | const imgCanvas = create('canvas', { width: 200, height: 200 })
40 | const ctx = imgCanvas.getContext('2d', { alpha: true })
41 | const output = outputCanvas.getContext('2d', { alpha: true }).getImageData(0, 0, 200, 200)
42 | ctx.drawImage(image, 0, 0, 200, 200)
43 | const imageDataCanvas = ctx.getImageData(0, 0, 200, 200).data
44 | const imageDataGolden = canvas.getContext('2d', { alpha: true }).getImageData(0, 0, 200, 200).data
45 | window.pixelmatch(imageDataCanvas, imageDataGolden, output.data, 200, 200, {
46 | includeAA: false,
47 | threshold: 0.15
48 | })
49 | outputCanvas.getContext('2d', { alpha: true }).putImageData(output, 0, 0)
50 | return outputCanvas
51 | }
52 |
53 | function clearTests () {
54 | const table = document.getElementById('tests')
55 | if (table) document.body.removeChild(table)
56 | }
57 |
58 | function runTests () {
59 | clearTests()
60 |
61 | const testNames = Object.keys(window.tests)
62 |
63 | const table = create('table', { id: 'tests' }, [
64 | create('thead', {}, [
65 | create('th', { textContent: 'node-canvas' }),
66 | create('th', { textContent: 'browser canvas' }),
67 | create('th', { textContent: 'visual diffs' }),
68 | create('th', { textContent: '' })
69 | ]),
70 | create('tbody', {}, testNames.map(function (name) {
71 | const img = create('img')
72 | const canvasOuput = create('canvas', { width: 200, height: 200, title: name })
73 | const canvas = localRendering(name, function () {
74 | img.onload = function () {
75 | getDifference(canvas, img, canvasOuput)
76 | }
77 | img.src = '/render?name=' + encodeURIComponent(name)
78 | })
79 | return create('tr', {}, [
80 | create('td', {}, [img]),
81 | create('td', {}, [canvas]),
82 | create('td', {}, [canvasOuput]),
83 | create('td', {}, [create('h3', { textContent: name }), pdfLink(name)])
84 | ])
85 | }))
86 | ])
87 |
88 | document.body.appendChild(table)
89 | }
90 |
--------------------------------------------------------------------------------
/test/public/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 40px 50px;
3 | font: 13px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | p {
7 | margin: 15px 5px;
8 | }
9 |
10 | a {
11 | color: #00C5F7;
12 | }
13 |
14 | canvas, img {
15 | padding: 5px;
16 | border: 1px solid #eee;
17 | }
18 |
19 | p.msg {
20 | width: 400px;
21 | }
22 |
23 | #tests {
24 | width: 100%;
25 | margin-top: 35px;
26 | }
27 |
28 | table tr td:nth-child(1),
29 | table tr td:nth-child(2),
30 | table tr td:nth-child(3) {
31 | width: 200px;
32 | }
33 |
34 | table tr td:nth-child(4) {
35 | padding: 0 45px;
36 | }
37 |
38 | table tr td p {
39 | margin: 5px 0;
40 | }
41 |
42 | table th {
43 | background: white;
44 | position: -webkit-sticky;
45 | position: sticky;
46 | top: 0;
47 | }
48 |
--------------------------------------------------------------------------------
/test/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const express = require('express')
3 |
4 | const Canvas = require('../')
5 | const tests = require('./public/tests')
6 |
7 | const app = express()
8 | const port = parseInt(process.argv[2] || '4000', 10)
9 |
10 | function renderTest (canvas, name, cb) {
11 | if (!tests[name]) {
12 | throw new Error('Unknown test: ' + name)
13 | }
14 |
15 | const ctx = canvas.getContext('2d', { pixelFormat: 'RGBA32' })
16 | const initialFillStyle = ctx.fillStyle
17 | ctx.fillStyle = 'white'
18 | ctx.fillRect(0, 0, 200, 200)
19 | ctx.fillStyle = initialFillStyle
20 | if (tests[name].length === 2) {
21 | tests[name](ctx, cb)
22 | } else {
23 | tests[name](ctx)
24 | cb(null)
25 | }
26 | }
27 |
28 | app.use(express.static(path.join(__dirname, 'fixtures')))
29 | app.use(express.static(path.join(__dirname, 'public')))
30 |
31 | app.get('/', function (req, res) {
32 | res.sendFile(path.join(__dirname, 'public', 'app.html'))
33 | })
34 |
35 | app.get('/pixelmatch.js', function (req, res) {
36 | res.sendFile(path.join(__dirname, '../node_modules/pixelmatch/', 'index.js'))
37 | })
38 |
39 | app.get('/render', function (req, res, next) {
40 | const canvas = Canvas.createCanvas(200, 200)
41 |
42 | renderTest(canvas, req.query.name, function (err) {
43 | if (err) return next(err)
44 |
45 | res.writeHead(200, { 'Content-Type': 'image/png' })
46 | canvas.pngStream().pipe(res)
47 | })
48 | })
49 |
50 | app.get('/pdf', function (req, res, next) {
51 | const canvas = Canvas.createCanvas(200, 200, 'pdf')
52 |
53 | renderTest(canvas, req.query.name, function (err) {
54 | if (err) return next(err)
55 |
56 | res.writeHead(200, { 'Content-Type': 'application/pdf' })
57 | canvas.pdfStream().pipe(res)
58 | })
59 | })
60 |
61 | app.listen(port, function () {
62 | console.log('👉 http://localhost:%d/', port)
63 | })
64 |
--------------------------------------------------------------------------------
/test/wpt/generate.js:
--------------------------------------------------------------------------------
1 | // This file is a port of gentestutils.py from
2 | // https://github.com/web-platform-tests/wpt/tree/master/html/canvas/tools
3 |
4 | const yaml = require("js-yaml");
5 | const fs = require("fs");
6 |
7 | const yamlFiles = fs.readdirSync(__dirname).filter(f => f.endsWith(".yaml"));
8 | // Files that should be skipped:
9 | const SKIP_FILES = new Set("meta.yaml");
10 | // Tests that should be skipped (e.g. because they cause hangs or V8 crashes):
11 | const SKIP_TESTS = new Set([
12 | "2d.imageData.create2.negative",
13 | "2d.imageData.create2.zero",
14 | "2d.imageData.create2.nonfinite",
15 | "2d.imageData.create1.zero",
16 | "2d.imageData.create2.double",
17 | "2d.imageData.get.source.outside",
18 | "2d.imageData.get.source.negative",
19 | "2d.imageData.get.double",
20 | "2d.imageData.get.large.crash", // expected
21 | ]);
22 |
23 | function expandNonfinite(method, argstr, tail) {
24 | // argstr is ", ..." (where usually
25 | // 'invalid' is Infinity/-Infinity/NaN)
26 | const args = [];
27 | for (const arg of argstr.split(', ')) {
28 | const [, a] = arg.match(/<(.*)>/);
29 | args.push(a.split(' '));
30 | }
31 | const calls = [];
32 | // Start with the valid argument list
33 | const call = [];
34 | for (let i = 0; i < args.length; i++) {
35 | call.push(args[i][0]);
36 | }
37 | // For each argument alone, try setting it to all its invalid values:
38 | for (let i = 0; i < args.length; i++) {
39 | for (let j = 1; j < args[i].length; j++) {
40 | const c2 = [...call]
41 | c2[i] = args[i][j];
42 | calls.push(c2);
43 | }
44 | }
45 | // For all combinations of >= 2 arguments, try setting them to their first
46 | // invalid values. (Don't do all invalid values, because the number of
47 | // combinations explodes.)
48 | const f = (c, start, depth) => {
49 | for (let i = start; i < args.length; i++) {
50 | if (args[i].length > 1) {
51 | const a = args[i][1]
52 | const c2 = [...c]
53 | c2[i] = a
54 | if (depth > 0)
55 | calls.push(c2)
56 | f(c2, i+1, depth+1)
57 | }
58 | }
59 | };
60 | f(call, 0, 0);
61 |
62 | return calls.map(c => `${method}(${c.join(", ")})${tail}`).join("\n\t\t");
63 | }
64 |
65 | function simpleEscapeJS(str) {
66 | return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
67 | }
68 |
69 | function escapeJS(str) {
70 | str = simpleEscapeJS(str)
71 | str = str.replace(/\[(\w+)\]/g, '[\\""+($1)+"\\"]') // kind of an ugly hack, for nicer failure-message output
72 | return str
73 | }
74 |
75 | /** @type {string} test */
76 | function convert(test) {
77 | let code = test.code;
78 | if (!code) return "";
79 | // Indent it
80 | code = code.trim().replace(/^/gm, "\t\t");
81 |
82 | code = code.replace(/@nonfinite ([^(]+)\(([^)]+)\)(.*)/g, (match, g1, g2, g3) => {
83 | return expandNonfinite(g1, g2, g3);
84 | });
85 |
86 | code = code.replace(/@assert pixel (\d+,\d+) == (\d+,\d+,\d+,\d+);/g,
87 | "_assertPixel(canvas, $1, $2);");
88 |
89 | code = code.replace(/@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+);/g,
90 | "_assertPixelApprox(canvas, $1, $2);");
91 |
92 | code = code.replace(/@assert pixel (\d+,\d+) ==~ (\d+,\d+,\d+,\d+) \+\/- (\d+);/g,
93 | "_assertPixelApprox(canvas, $1, $2, $3);");
94 |
95 | code = code.replace(/@assert throws (\S+_ERR) (.*);/g,
96 | 'assert.throws(function() { $2; }, /$1/);');
97 |
98 | code = code.replace(/@assert throws (\S+Error) (.*);/g,
99 | 'assert.throws(function() { $2; }, $1);');
100 |
101 | code = code.replace(/@assert (.*) === (.*);/g, (match, g1, g2) => {
102 | return `assert.strictEqual(${g1}, ${g2}, "${escapeJS(g1)}", "${escapeJS(g2)}")`;
103 | });
104 |
105 | code = code.replace(/@assert (.*) !== (.*);/g, (match, g1, g2) => {
106 | return `assert.notStrictEqual(${g1}, ${g2}, "${escapeJS(g1)}", "${escapeJS(g2)}");`;
107 | });
108 |
109 | code = code.replace(/@assert (.*) =~ (.*);/g, (match, g1, g2) => {
110 | return `assert.match(${g1}, ${g2});`;
111 | });
112 |
113 | code = code.replace(/@assert (.*);/g, (match, g1) => {
114 | return `assert(${g1}, "${escapeJS(g1)}");`;
115 | });
116 |
117 | code = code.replace(/ @moz-todo/g, "");
118 |
119 | code = code.replace(/@moz-UniversalBrowserRead;/g, "");
120 |
121 | if (code.includes("@"))
122 | throw new Error("@ found in code; generation failed");
123 |
124 | const name = test.name.replace(/"/g, /\"/);
125 |
126 | const skip = SKIP_TESTS.has(name) ? ".skip" : "";
127 |
128 | return `
129 | it${skip}("${name}", function () {${test.desc ? `\n\t\t// ${test.desc}` : ""}
130 | const canvas = createCanvas(100, 50);
131 | const ctx = canvas.getContext("2d");
132 | const t = new Test();
133 |
134 | ${code}
135 | });
136 | `
137 | }
138 |
139 |
140 | for (const filename of yamlFiles) {
141 | if (SKIP_FILES.has(filename))
142 | continue;
143 |
144 | let tests;
145 | try {
146 | const content = fs.readFileSync(`${__dirname}/${filename}`, "utf8");
147 | tests = yaml.load(content, {
148 | filename,
149 | // schema: yaml.DEFAULT_SCHEMA
150 | });
151 | } catch (ex) {
152 | console.error(ex.toString());
153 | continue;
154 | }
155 |
156 | const out = fs.createWriteStream(`${__dirname}/generated/${filename.replace(".yaml", ".js")}`);
157 |
158 | out.write(`// THIS FILE WAS AUTO-GENERATED. DO NOT EDIT BY HAND.
159 |
160 | const assert = require('assert');
161 | const path = require('path');
162 |
163 | const {
164 | createCanvas,
165 | CanvasRenderingContext2D,
166 | ImageData,
167 | Image,
168 | DOMMatrix,
169 | DOMPoint,
170 | CanvasPattern,
171 | CanvasGradient
172 | } = require('../../..');
173 |
174 | const window = {
175 | CanvasRenderingContext2D,
176 | ImageData,
177 | Image,
178 | DOMMatrix,
179 | DOMPoint,
180 | Uint8ClampedArray,
181 | CanvasPattern,
182 | CanvasGradient
183 | };
184 |
185 | const document = {
186 | createElement(type, ...args) {
187 | if (type !== "canvas")
188 | throw new Error(\`createElement(\${type}) not supported\`);
189 | return createCanvas(...args);
190 | }
191 | };
192 |
193 | function _getPixel(canvas, x, y) {
194 | const ctx = canvas.getContext('2d');
195 | const imgdata = ctx.getImageData(x, y, 1, 1);
196 | return [ imgdata.data[0], imgdata.data[1], imgdata.data[2], imgdata.data[3] ];
197 | }
198 |
199 | function _assertApprox(actual, expected, epsilon=0, msg="") {
200 | assert(typeof actual === "number", "actual should be a number but got a \${typeof type_actual}");
201 |
202 | // The epsilon math below does not place nice with NaN and Infinity
203 | // But in this case Infinity = Infinity and NaN = NaN
204 | if (isFinite(actual) || isFinite(expected)) {
205 | assert(Math.abs(actual - expected) <= epsilon,
206 | \`expected \${actual} to equal \${expected} +/- \${epsilon}. \${msg}\`);
207 | } else {
208 | assert.strictEqual(actual, expected);
209 | }
210 | }
211 |
212 | function _assertPixel(canvas, x, y, r, g, b, a, pos, color) {
213 | const c = _getPixel(canvas, x,y);
214 | assert.strictEqual(c[0], r, 'Red channel of the pixel at (' + x + ', ' + y + ')');
215 | assert.strictEqual(c[1], g, 'Green channel of the pixel at (' + x + ', ' + y + ')');
216 | assert.strictEqual(c[2], b, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
217 | assert.strictEqual(c[3], a, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
218 | }
219 |
220 | function _assertPixelApprox(canvas, x, y, r, g, b, a, pos, color, tolerance) {
221 | const c = _getPixel(canvas, x,y);
222 | _assertApprox(c[0], r, tolerance, 'Red channel of the pixel at (' + x + ', ' + y + ')');
223 | _assertApprox(c[1], g, tolerance, 'Green channel of the pixel at (' + x + ', ' + y + ')');
224 | _assertApprox(c[2], b, tolerance, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
225 | _assertApprox(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
226 | }
227 |
228 | function assert_throws_js(Type, fn) {
229 | assert.throws(fn, Type);
230 | }
231 |
232 | // Used by font tests to allow fonts to load.
233 | function deferTest() {}
234 |
235 | class Test {
236 | // Two cases of this in the tests, look unnecessary.
237 | done() {}
238 | // Used by font tests to allow fonts to load.
239 | step_func_done(func) { func(); }
240 | // Used for image onload callback.
241 | step_func(func) { func(); }
242 | }
243 |
244 | function step_timeout(result, time) {
245 | // Nothing; code needs to be converted for this to work.
246 | }
247 |
248 | describe("WPT: ${filename.replace(".yaml", "")}", function () {
249 | `);
250 |
251 | for (const test of tests) {
252 | out.write(convert(test));
253 | }
254 |
255 | out.write(`});
256 | `)
257 |
258 | out.end();
259 | }
260 |
--------------------------------------------------------------------------------
/test/wpt/generated/meta.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS AUTO-GENERATED. DO NOT EDIT BY HAND.
2 |
3 | const assert = require('assert');
4 | const path = require('path');
5 |
6 | const {
7 | createCanvas,
8 | CanvasRenderingContext2D,
9 | ImageData,
10 | Image,
11 | DOMMatrix,
12 | DOMPoint,
13 | CanvasPattern,
14 | CanvasGradient
15 | } = require('../../..');
16 |
17 | const window = {
18 | CanvasRenderingContext2D,
19 | ImageData,
20 | Image,
21 | DOMMatrix,
22 | DOMPoint,
23 | Uint8ClampedArray,
24 | CanvasPattern,
25 | CanvasGradient
26 | };
27 |
28 | const document = {
29 | createElement(type, ...args) {
30 | if (type !== "canvas")
31 | throw new Error(`createElement(${type}) not supported`);
32 | return createCanvas(...args);
33 | }
34 | };
35 |
36 | function _getPixel(canvas, x, y) {
37 | const ctx = canvas.getContext('2d');
38 | const imgdata = ctx.getImageData(x, y, 1, 1);
39 | return [ imgdata.data[0], imgdata.data[1], imgdata.data[2], imgdata.data[3] ];
40 | }
41 |
42 | function _assertApprox(actual, expected, epsilon=0, msg="") {
43 | assert(typeof actual === "number", "actual should be a number but got a ${typeof type_actual}");
44 |
45 | // The epsilon math below does not place nice with NaN and Infinity
46 | // But in this case Infinity = Infinity and NaN = NaN
47 | if (isFinite(actual) || isFinite(expected)) {
48 | assert(Math.abs(actual - expected) <= epsilon,
49 | `expected ${actual} to equal ${expected} +/- ${epsilon}. ${msg}`);
50 | } else {
51 | assert.strictEqual(actual, expected);
52 | }
53 | }
54 |
55 | function _assertPixel(canvas, x, y, r, g, b, a, pos, color) {
56 | const c = _getPixel(canvas, x,y);
57 | assert.strictEqual(c[0], r, 'Red channel of the pixel at (' + x + ', ' + y + ')');
58 | assert.strictEqual(c[1], g, 'Green channel of the pixel at (' + x + ', ' + y + ')');
59 | assert.strictEqual(c[2], b, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
60 | assert.strictEqual(c[3], a, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
61 | }
62 |
63 | function _assertPixelApprox(canvas, x, y, r, g, b, a, pos, color, tolerance) {
64 | const c = _getPixel(canvas, x,y);
65 | _assertApprox(c[0], r, tolerance, 'Red channel of the pixel at (' + x + ', ' + y + ')');
66 | _assertApprox(c[1], g, tolerance, 'Green channel of the pixel at (' + x + ', ' + y + ')');
67 | _assertApprox(c[2], b, tolerance, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
68 | _assertApprox(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
69 | }
70 |
71 | function assert_throws_js(Type, fn) {
72 | assert.throws(fn, Type);
73 | }
74 |
75 | // Used by font tests to allow fonts to load.
76 | function deferTest() {}
77 |
78 | class Test {
79 | // Two cases of this in the tests, look unnecessary.
80 | done() {}
81 | // Used by font tests to allow fonts to load.
82 | step_func_done(func) { func(); }
83 | // Used for image onload callback.
84 | step_func(func) { func(); }
85 | }
86 |
87 | function step_timeout(result, time) {
88 | // Nothing; code needs to be converted for this to work.
89 | }
90 |
91 | describe("WPT: meta", function () {
92 | });
93 |
--------------------------------------------------------------------------------
/test/wpt/generated/the-canvas-state.js:
--------------------------------------------------------------------------------
1 | // THIS FILE WAS AUTO-GENERATED. DO NOT EDIT BY HAND.
2 |
3 | const assert = require('assert');
4 | const path = require('path');
5 |
6 | const {
7 | createCanvas,
8 | CanvasRenderingContext2D,
9 | ImageData,
10 | Image,
11 | DOMMatrix,
12 | DOMPoint,
13 | CanvasPattern,
14 | CanvasGradient
15 | } = require('../../..');
16 |
17 | const window = {
18 | CanvasRenderingContext2D,
19 | ImageData,
20 | Image,
21 | DOMMatrix,
22 | DOMPoint,
23 | Uint8ClampedArray,
24 | CanvasPattern,
25 | CanvasGradient
26 | };
27 |
28 | const document = {
29 | createElement(type, ...args) {
30 | if (type !== "canvas")
31 | throw new Error(`createElement(${type}) not supported`);
32 | return createCanvas(...args);
33 | }
34 | };
35 |
36 | function _getPixel(canvas, x, y) {
37 | const ctx = canvas.getContext('2d');
38 | const imgdata = ctx.getImageData(x, y, 1, 1);
39 | return [ imgdata.data[0], imgdata.data[1], imgdata.data[2], imgdata.data[3] ];
40 | }
41 |
42 | function _assertApprox(actual, expected, epsilon=0, msg="") {
43 | assert(typeof actual === "number", "actual should be a number but got a ${typeof type_actual}");
44 |
45 | // The epsilon math below does not place nice with NaN and Infinity
46 | // But in this case Infinity = Infinity and NaN = NaN
47 | if (isFinite(actual) || isFinite(expected)) {
48 | assert(Math.abs(actual - expected) <= epsilon,
49 | `expected ${actual} to equal ${expected} +/- ${epsilon}. ${msg}`);
50 | } else {
51 | assert.strictEqual(actual, expected);
52 | }
53 | }
54 |
55 | function _assertPixel(canvas, x, y, r, g, b, a, pos, color) {
56 | const c = _getPixel(canvas, x,y);
57 | assert.strictEqual(c[0], r, 'Red channel of the pixel at (' + x + ', ' + y + ')');
58 | assert.strictEqual(c[1], g, 'Green channel of the pixel at (' + x + ', ' + y + ')');
59 | assert.strictEqual(c[2], b, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
60 | assert.strictEqual(c[3], a, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
61 | }
62 |
63 | function _assertPixelApprox(canvas, x, y, r, g, b, a, pos, color, tolerance) {
64 | const c = _getPixel(canvas, x,y);
65 | _assertApprox(c[0], r, tolerance, 'Red channel of the pixel at (' + x + ', ' + y + ')');
66 | _assertApprox(c[1], g, tolerance, 'Green channel of the pixel at (' + x + ', ' + y + ')');
67 | _assertApprox(c[2], b, tolerance, 'Blue channel of the pixel at (' + x + ', ' + y + ')');
68 | _assertApprox(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')');
69 | }
70 |
71 | function assert_throws_js(Type, fn) {
72 | assert.throws(fn, Type);
73 | }
74 |
75 | // Used by font tests to allow fonts to load.
76 | function deferTest() {}
77 |
78 | class Test {
79 | // Two cases of this in the tests, look unnecessary.
80 | done() {}
81 | // Used by font tests to allow fonts to load.
82 | step_func_done(func) { func(); }
83 | // Used for image onload callback.
84 | step_func(func) { func(); }
85 | }
86 |
87 | function step_timeout(result, time) {
88 | // Nothing; code needs to be converted for this to work.
89 | }
90 |
91 | describe("WPT: the-canvas-state", function () {
92 |
93 | it("2d.state.saverestore.transformation", function () {
94 | // save()/restore() affects the current transformation matrix
95 | const canvas = createCanvas(100, 50);
96 | const ctx = canvas.getContext("2d");
97 | const t = new Test();
98 |
99 | ctx.fillStyle = '#0f0';
100 | ctx.fillRect(0, 0, 100, 50);
101 | ctx.save();
102 | ctx.translate(200, 0);
103 | ctx.restore();
104 | ctx.fillStyle = '#f00';
105 | ctx.fillRect(-200, 0, 100, 50);
106 | _assertPixel(canvas, 50,25, 0,255,0,255);
107 | });
108 |
109 | it("2d.state.saverestore.clip", function () {
110 | // save()/restore() affects the clipping path
111 | const canvas = createCanvas(100, 50);
112 | const ctx = canvas.getContext("2d");
113 | const t = new Test();
114 |
115 | ctx.fillStyle = '#f00';
116 | ctx.fillRect(0, 0, 100, 50);
117 | ctx.save();
118 | ctx.rect(0, 0, 1, 1);
119 | ctx.clip();
120 | ctx.restore();
121 | ctx.fillStyle = '#0f0';
122 | ctx.fillRect(0, 0, 100, 50);
123 | _assertPixel(canvas, 50,25, 0,255,0,255);
124 | });
125 |
126 | it("2d.state.saverestore.path", function () {
127 | // save()/restore() does not affect the current path
128 | const canvas = createCanvas(100, 50);
129 | const ctx = canvas.getContext("2d");
130 | const t = new Test();
131 |
132 | ctx.fillStyle = '#f00';
133 | ctx.fillRect(0, 0, 100, 50);
134 | ctx.save();
135 | ctx.rect(0, 0, 100, 50);
136 | ctx.restore();
137 | ctx.fillStyle = '#0f0';
138 | ctx.fill();
139 | _assertPixel(canvas, 50,25, 0,255,0,255);
140 | });
141 |
142 | it("2d.state.saverestore.bitmap", function () {
143 | // save()/restore() does not affect the current bitmap
144 | const canvas = createCanvas(100, 50);
145 | const ctx = canvas.getContext("2d");
146 | const t = new Test();
147 |
148 | ctx.fillStyle = '#f00';
149 | ctx.fillRect(0, 0, 100, 50);
150 | ctx.save();
151 | ctx.fillStyle = '#0f0';
152 | ctx.fillRect(0, 0, 100, 50);
153 | ctx.restore();
154 | _assertPixel(canvas, 50,25, 0,255,0,255);
155 | });
156 |
157 | it("2d.state.saverestore.stack", function () {
158 | // save()/restore() can be nested as a stack
159 | const canvas = createCanvas(100, 50);
160 | const ctx = canvas.getContext("2d");
161 | const t = new Test();
162 |
163 | ctx.lineWidth = 1;
164 | ctx.save();
165 | ctx.lineWidth = 2;
166 | ctx.save();
167 | ctx.lineWidth = 3;
168 | assert.strictEqual(ctx.lineWidth, 3, "ctx.lineWidth", "3")
169 | ctx.restore();
170 | assert.strictEqual(ctx.lineWidth, 2, "ctx.lineWidth", "2")
171 | ctx.restore();
172 | assert.strictEqual(ctx.lineWidth, 1, "ctx.lineWidth", "1")
173 | });
174 |
175 | it("2d.state.saverestore.stackdepth", function () {
176 | // save()/restore() stack depth is not unreasonably limited
177 | const canvas = createCanvas(100, 50);
178 | const ctx = canvas.getContext("2d");
179 | const t = new Test();
180 |
181 | var limit = 512;
182 | for (var i = 1; i < limit; ++i)
183 | {
184 | ctx.save();
185 | ctx.lineWidth = i;
186 | }
187 | for (var i = limit-1; i > 0; --i)
188 | {
189 | assert.strictEqual(ctx.lineWidth, i, "ctx.lineWidth", "i")
190 | ctx.restore();
191 | }
192 | });
193 |
194 | it("2d.state.saverestore.underflow", function () {
195 | // restore() with an empty stack has no effect
196 | const canvas = createCanvas(100, 50);
197 | const ctx = canvas.getContext("2d");
198 | const t = new Test();
199 |
200 | for (var i = 0; i < 16; ++i)
201 | ctx.restore();
202 | ctx.lineWidth = 0.5;
203 | ctx.restore();
204 | assert.strictEqual(ctx.lineWidth, 0.5, "ctx.lineWidth", "0.5")
205 | });
206 | });
207 |
--------------------------------------------------------------------------------
/test/wpt/the-canvas-element.yaml:
--------------------------------------------------------------------------------
1 | - name: 2d.getcontext.exists
2 | desc: The 2D context is implemented
3 | testing:
4 | - context.2d
5 | code: |
6 | @assert canvas.getContext('2d') !== null;
7 |
8 | - name: 2d.getcontext.invalid.args
9 | desc: Calling getContext with invalid arguments.
10 | testing:
11 | - context.2d
12 | code: |
13 | @assert canvas.getContext('') === null;
14 | @assert canvas.getContext('2d#') === null;
15 | @assert canvas.getContext('This is clearly not a valid context name.') === null;
16 | @assert canvas.getContext('2d\0') === null;
17 | @assert canvas.getContext('2\uFF44') === null;
18 | @assert canvas.getContext('2D') === null;
19 | @assert throws TypeError canvas.getContext();
20 | @assert canvas.getContext('null') === null;
21 | @assert canvas.getContext('undefined') === null;
22 |
23 | - name: 2d.getcontext.extraargs.create
24 | desc: The 2D context doesn't throw with extra getContext arguments (new context)
25 | testing:
26 | - context.2d.extraargs
27 | code: |
28 | @assert document.createElement("canvas").getContext('2d', false, {}, [], 1, "2") !== null;
29 | @assert document.createElement("canvas").getContext('2d', 123) !== null;
30 | @assert document.createElement("canvas").getContext('2d', "test") !== null;
31 | @assert document.createElement("canvas").getContext('2d', undefined) !== null;
32 | @assert document.createElement("canvas").getContext('2d', null) !== null;
33 | @assert document.createElement("canvas").getContext('2d', Symbol.hasInstance) !== null;
34 |
35 | - name: 2d.getcontext.extraargs.cache
36 | desc: The 2D context doesn't throw with extra getContext arguments (cached)
37 | testing:
38 | - context.2d.extraargs
39 | code: |
40 | @assert canvas.getContext('2d', false, {}, [], 1, "2") !== null;
41 | @assert canvas.getContext('2d', 123) !== null;
42 | @assert canvas.getContext('2d', "test") !== null;
43 | @assert canvas.getContext('2d', undefined) !== null;
44 | @assert canvas.getContext('2d', null) !== null;
45 | @assert canvas.getContext('2d', Symbol.hasInstance) !== null;
46 |
47 | - name: 2d.type.exists
48 | desc: The 2D context interface is a property of 'window'
49 | notes: &bindings Defined in "Web IDL" (draft)
50 | testing:
51 | - context.2d.type
52 | code: |
53 | @assert window.CanvasRenderingContext2D;
54 |
55 | - name: 2d.type.prototype
56 | desc: window.CanvasRenderingContext2D.prototype are not [[Writable]] and not [[Configurable]],
57 | and its methods are [[Configurable]].
58 | notes: *bindings
59 | testing:
60 | - context.2d.type
61 | code: |
62 | @assert window.CanvasRenderingContext2D.prototype;
63 | @assert window.CanvasRenderingContext2D.prototype.fill;
64 | window.CanvasRenderingContext2D.prototype = null;
65 | @assert window.CanvasRenderingContext2D.prototype;
66 | delete window.CanvasRenderingContext2D.prototype;
67 | @assert window.CanvasRenderingContext2D.prototype;
68 | window.CanvasRenderingContext2D.prototype.fill = 1;
69 | @assert window.CanvasRenderingContext2D.prototype.fill === 1;
70 | delete window.CanvasRenderingContext2D.prototype.fill;
71 | @assert window.CanvasRenderingContext2D.prototype.fill === undefined;
72 |
73 | - name: 2d.type.replace
74 | desc: Interface methods can be overridden
75 | notes: *bindings
76 | testing:
77 | - context.2d.type
78 | code: |
79 | var fillRect = window.CanvasRenderingContext2D.prototype.fillRect;
80 | window.CanvasRenderingContext2D.prototype.fillRect = function (x, y, w, h)
81 | {
82 | this.fillStyle = '#0f0';
83 | fillRect.call(this, x, y, w, h);
84 | };
85 | ctx.fillStyle = '#f00';
86 | ctx.fillRect(0, 0, 100, 50);
87 | @assert pixel 50,25 == 0,255,0,255;
88 | expected: green
89 |
90 | - name: 2d.type.extend
91 | desc: Interface methods can be added
92 | notes: *bindings
93 | testing:
94 | - context.2d.type
95 | code: |
96 | window.CanvasRenderingContext2D.prototype.fillRectGreen = function (x, y, w, h)
97 | {
98 | this.fillStyle = '#0f0';
99 | this.fillRect(x, y, w, h);
100 | };
101 | ctx.fillStyle = '#f00';
102 | ctx.fillRectGreen(0, 0, 100, 50);
103 | @assert pixel 50,25 == 0,255,0,255;
104 | expected: green
105 |
106 | - name: 2d.getcontext.unique
107 | desc: getContext('2d') returns the same object
108 | testing:
109 | - context.unique
110 | code: |
111 | @assert canvas.getContext('2d') === canvas.getContext('2d');
112 |
113 | - name: 2d.getcontext.shared
114 | desc: getContext('2d') returns objects which share canvas state
115 | testing:
116 | - context.unique
117 | code: |
118 | var ctx2 = canvas.getContext('2d');
119 | ctx.fillStyle = '#f00';
120 | ctx2.fillStyle = '#0f0';
121 | ctx.fillRect(0, 0, 100, 50);
122 | @assert pixel 50,25 == 0,255,0,255;
123 | expected: green
124 |
125 | - name: 2d.scaled
126 | desc: CSS-scaled canvases get drawn correctly
127 | canvas: 'width="50" height="25" style="width: 100px; height: 50px"'
128 | manual:
129 | code: |
130 | ctx.fillStyle = '#00f';
131 | ctx.fillRect(0, 0, 50, 25);
132 | ctx.fillStyle = '#0ff';
133 | ctx.fillRect(0, 0, 25, 10);
134 | expected: |
135 | size 100 50
136 | cr.set_source_rgb(0, 0, 1)
137 | cr.rectangle(0, 0, 100, 50)
138 | cr.fill()
139 | cr.set_source_rgb(0, 1, 1)
140 | cr.rectangle(0, 0, 50, 20)
141 | cr.fill()
142 |
143 | - name: 2d.canvas.reference
144 | desc: CanvasRenderingContext2D.canvas refers back to its canvas
145 | testing:
146 | - 2d.canvas
147 | code: |
148 | @assert ctx.canvas === canvas;
149 |
150 | - name: 2d.canvas.readonly
151 | desc: CanvasRenderingContext2D.canvas is readonly
152 | testing:
153 | - 2d.canvas.attribute
154 | code: |
155 | var c = document.createElement('canvas');
156 | var d = ctx.canvas;
157 | @assert c !== d;
158 | ctx.canvas = c;
159 | @assert ctx.canvas === d;
160 |
161 | - name: 2d.canvas.context
162 | desc: checks CanvasRenderingContext2D prototype
163 | testing:
164 | - 2d.path.contexttypexxx.basic
165 | code: |
166 | @assert Object.getPrototypeOf(CanvasRenderingContext2D.prototype) === Object.prototype;
167 | @assert Object.getPrototypeOf(ctx) === CanvasRenderingContext2D.prototype;
168 | t.done();
169 |
170 |
--------------------------------------------------------------------------------
/test/wpt/the-canvas-state.yaml:
--------------------------------------------------------------------------------
1 | - name: 2d.state.saverestore.transformation
2 | desc: save()/restore() affects the current transformation matrix
3 | testing:
4 | - 2d.state.transformation
5 | code: |
6 | ctx.fillStyle = '#0f0';
7 | ctx.fillRect(0, 0, 100, 50);
8 | ctx.save();
9 | ctx.translate(200, 0);
10 | ctx.restore();
11 | ctx.fillStyle = '#f00';
12 | ctx.fillRect(-200, 0, 100, 50);
13 | @assert pixel 50,25 == 0,255,0,255;
14 | expected: green
15 |
16 | - name: 2d.state.saverestore.clip
17 | desc: save()/restore() affects the clipping path
18 | testing:
19 | - 2d.state.clip
20 | code: |
21 | ctx.fillStyle = '#f00';
22 | ctx.fillRect(0, 0, 100, 50);
23 | ctx.save();
24 | ctx.rect(0, 0, 1, 1);
25 | ctx.clip();
26 | ctx.restore();
27 | ctx.fillStyle = '#0f0';
28 | ctx.fillRect(0, 0, 100, 50);
29 | @assert pixel 50,25 == 0,255,0,255;
30 | expected: green
31 |
32 | - name: 2d.state.saverestore.path
33 | desc: save()/restore() does not affect the current path
34 | testing:
35 | - 2d.state.path
36 | code: |
37 | ctx.fillStyle = '#f00';
38 | ctx.fillRect(0, 0, 100, 50);
39 | ctx.save();
40 | ctx.rect(0, 0, 100, 50);
41 | ctx.restore();
42 | ctx.fillStyle = '#0f0';
43 | ctx.fill();
44 | @assert pixel 50,25 == 0,255,0,255;
45 | expected: green
46 |
47 | - name: 2d.state.saverestore.bitmap
48 | desc: save()/restore() does not affect the current bitmap
49 | testing:
50 | - 2d.state.bitmap
51 | code: |
52 | ctx.fillStyle = '#f00';
53 | ctx.fillRect(0, 0, 100, 50);
54 | ctx.save();
55 | ctx.fillStyle = '#0f0';
56 | ctx.fillRect(0, 0, 100, 50);
57 | ctx.restore();
58 | @assert pixel 50,25 == 0,255,0,255;
59 | expected: green
60 |
61 | - name: 2d.state.saverestore.stack
62 | desc: save()/restore() can be nested as a stack
63 | testing:
64 | - 2d.state.save
65 | - 2d.state.restore
66 | code: |
67 | ctx.lineWidth = 1;
68 | ctx.save();
69 | ctx.lineWidth = 2;
70 | ctx.save();
71 | ctx.lineWidth = 3;
72 | @assert ctx.lineWidth === 3;
73 | ctx.restore();
74 | @assert ctx.lineWidth === 2;
75 | ctx.restore();
76 | @assert ctx.lineWidth === 1;
77 |
78 | - name: 2d.state.saverestore.stackdepth
79 | desc: save()/restore() stack depth is not unreasonably limited
80 | testing:
81 | - 2d.state.save
82 | - 2d.state.restore
83 | code: |
84 | var limit = 512;
85 | for (var i = 1; i < limit; ++i)
86 | {
87 | ctx.save();
88 | ctx.lineWidth = i;
89 | }
90 | for (var i = limit-1; i > 0; --i)
91 | {
92 | @assert ctx.lineWidth === i;
93 | ctx.restore();
94 | }
95 |
96 | - name: 2d.state.saverestore.underflow
97 | desc: restore() with an empty stack has no effect
98 | testing:
99 | - 2d.state.restore.underflow
100 | code: |
101 | for (var i = 0; i < 16; ++i)
102 | ctx.restore();
103 | ctx.lineWidth = 0.5;
104 | ctx.restore();
105 | @assert ctx.lineWidth === 0.5;
106 |
107 |
108 |
--------------------------------------------------------------------------------
/util/has_lib.js:
--------------------------------------------------------------------------------
1 | const query = process.argv[2]
2 | const fs = require('fs')
3 | const childProcess = require('child_process')
4 |
5 | const SYSTEM_PATHS = [
6 | '/lib',
7 | '/usr/lib',
8 | '/usr/lib64',
9 | '/usr/local/lib',
10 | '/opt/local/lib',
11 | '/opt/homebrew/lib',
12 | '/usr/lib/x86_64-linux-gnu',
13 | '/usr/lib/i386-linux-gnu',
14 | '/usr/lib/arm-linux-gnueabihf',
15 | '/usr/lib/arm-linux-gnueabi',
16 | '/usr/lib/aarch64-linux-gnu'
17 | ]
18 |
19 | /**
20 | * Checks for lib using ldconfig if present, or searching SYSTEM_PATHS
21 | * otherwise.
22 | * @param {string} lib - library name, e.g. 'jpeg' in 'libjpeg64.so' (see first line)
23 | * @return {boolean} exists
24 | */
25 | function hasSystemLib (lib) {
26 | const libName = 'lib' + lib + '.+(so|dylib)'
27 | const libNameRegex = new RegExp(libName)
28 |
29 | // Try using ldconfig on linux systems
30 | if (hasLdconfig()) {
31 | try {
32 | if (childProcess.execSync('ldconfig -p 2>/dev/null | grep -E "' + libName + '"').length) {
33 | return true
34 | }
35 | } catch (err) {
36 | // noop -- proceed to other search methods
37 | }
38 | }
39 |
40 | // Try checking common library locations
41 | return SYSTEM_PATHS.some(function (systemPath) {
42 | try {
43 | const dirListing = fs.readdirSync(systemPath)
44 | return dirListing.some(function (file) {
45 | return libNameRegex.test(file)
46 | })
47 | } catch (err) {
48 | return false
49 | }
50 | })
51 | }
52 |
53 | /**
54 | * Checks for ldconfig on the path and /sbin
55 | * @return {boolean} exists
56 | */
57 | function hasLdconfig () {
58 | try {
59 | // Add /sbin to path as ldconfig is located there on some systems -- e.g.
60 | // Debian (and it can still be used by unprivileged users):
61 | childProcess.execSync('export PATH="$PATH:/sbin"')
62 | process.env.PATH = '...'
63 | // execSync throws on nonzero exit
64 | childProcess.execSync('hash ldconfig 2>/dev/null')
65 | return true
66 | } catch (err) {
67 | return false
68 | }
69 | }
70 |
71 | /**
72 | * Checks for freetype2 with --cflags-only-I
73 | * @return Boolean exists
74 | */
75 | function hasFreetype () {
76 | try {
77 | if (childProcess.execSync('pkg-config cairo --cflags-only-I 2>/dev/null | grep freetype2').length) {
78 | return true
79 | }
80 | } catch (err) {
81 | // noop
82 | }
83 | return false
84 | }
85 |
86 | /**
87 | * Checks for lib using pkg-config.
88 | * @param {string} lib - library name
89 | * @return {boolean} exists
90 | */
91 | function hasPkgconfigLib (lib) {
92 | try {
93 | // execSync throws on nonzero exit
94 | childProcess.execSync('pkg-config --exists "' + lib + '" 2>/dev/null')
95 | return true
96 | } catch (err) {
97 | return false
98 | }
99 | }
100 |
101 | function main (query) {
102 | switch (query) {
103 | case 'gif':
104 | case 'cairo':
105 | return hasSystemLib(query)
106 | case 'pango':
107 | return hasPkgconfigLib(query)
108 | case 'freetype':
109 | return hasFreetype()
110 | case 'jpeg':
111 | return hasPkgconfigLib('libjpeg')
112 | case 'rsvg':
113 | return hasPkgconfigLib('librsvg-2.0')
114 | default:
115 | throw new Error('Unknown library: ' + query)
116 | }
117 | }
118 |
119 | process.stdout.write(main(query).toString())
120 |
--------------------------------------------------------------------------------
/util/win_jpeg_lookup.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const paths = ['C:/libjpeg-turbo']
3 |
4 | if (process.arch === 'x64') {
5 | paths.unshift('C:/libjpeg-turbo64')
6 | }
7 |
8 | paths.forEach(function (path) {
9 | if (exists(path)) {
10 | process.stdout.write(path)
11 | process.exit()
12 | }
13 | })
14 |
15 | function exists (path) {
16 | try {
17 | return fs.lstatSync(path).isDirectory()
18 | } catch (e) {
19 | return false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------