blob: 6d505b7a30a75b60e84db4f53284eadc38edbaf5 [file] [log] [blame]
Kevin Lubick92c91712018-08-09 10:00:02 -04001
2
3describe('PathKit\'s Path2D API', function() {
Kevin Lubick92c91712018-08-09 10:00:02 -04004 // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
5 var PathKit = null;
Kevin Lubick97d6d982018-08-10 15:53:16 -04006 const LoadPathKit = new Promise(function(resolve, reject) {
Kevin Lubick92c91712018-08-09 10:00:02 -04007 if (PathKit) {
8 resolve();
9 } else {
10 PathKitInit({
Kevin Lubick97d6d982018-08-10 15:53:16 -040011 locateFile: (file) => '/pathkit/'+file,
Kevin Lubick275eaff2019-01-04 14:38:29 -050012 }).ready().then((_PathKit) => {
Kevin Lubick92c91712018-08-09 10:00:02 -040013 PathKit = _PathKit;
14 resolve();
15 });
16 }
17 });
18
Kevin Lubick97d6d982018-08-10 15:53:16 -040019 it('can do everything in the Path2D API w/o crashing', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -050020 LoadPathKit.then(catchException(done, () => {
Kevin Lubick92c91712018-08-09 10:00:02 -040021 // This is taken from example.html
22 let path = PathKit.NewPath();
23
24 path.moveTo(20, 5);
25 path.lineTo(30, 20);
26 path.lineTo(40, 10);
27 path.lineTo(50, 20);
28 path.lineTo(60, 0);
29 path.lineTo(20, 5);
30
31 path.moveTo(20, 80);
32 path.bezierCurveTo(90, 10, 160, 150, 190, 10);
33
34 path.moveTo(36, 148);
35 path.quadraticCurveTo(66, 188, 120, 136);
36 path.lineTo(36, 148);
37
38 path.rect(5, 170, 20, 20);
39
40 path.moveTo(150, 180);
41 path.arcTo(150, 100, 50, 200, 20);
42 path.lineTo(160, 160);
43
44 path.moveTo(20, 120);
45 path.arc(20, 120, 18, 0, 1.75 * Math.PI);
46 path.lineTo(20, 120);
47
48 let secondPath = PathKit.NewPath();
49 secondPath.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI, false);
50
51 path.addPath(secondPath);
52
53 let m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
54 m.a = 1; m.b = 0;
55 m.c = 0; m.d = 1;
56 m.e = 0; m.f = 20.5;
57
58 path.addPath(secondPath, m);
Kevin Lubick92c91712018-08-09 10:00:02 -040059
60 let canvas = document.createElement('canvas');
Kevin Lubick92c91712018-08-09 10:00:02 -040061 let canvasCtx = canvas.getContext('2d');
Kevin Lubicka0ba6122018-08-15 13:45:28 -040062 // Set canvas size and make it a bit bigger to zoom in on the lines
63 standardizedCanvasSize(canvasCtx);
64 canvasCtx.scale(3.0, 3.0);
Kevin Lubick92c91712018-08-09 10:00:02 -040065 canvasCtx.fillStyle = 'blue';
66 canvasCtx.stroke(path.toPath2D());
67
Kevin Lubickc6c48aa2018-08-17 15:00:43 -040068 let svgPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
69 svgPath.setAttribute('stroke', 'black');
70 svgPath.setAttribute('fill', 'rgba(255,255,255,0.0)');
71 svgPath.setAttribute('transform', 'scale(3.0, 3.0)');
72 svgPath.setAttribute('d', path.toSVGString());
73
74 let newSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
75 newSVG.appendChild(svgPath);
76 newSVG.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
77 newSVG.setAttribute('width', 600);
78 newSVG.setAttribute('height', 600);
79
Kevin Lubick92c91712018-08-09 10:00:02 -040080 path.delete();
81 secondPath.delete();
82
Kevin Lubicka0ba6122018-08-15 13:45:28 -040083 reportCanvas(canvas, 'path2D_api_example').then(() => {
Kevin Lubickc6c48aa2018-08-17 15:00:43 -040084 reportSVG(newSVG, 'path2D_api_example').then(() => {
85 done();
86 }).catch(reportError(done));
Kevin Lubicka0ba6122018-08-15 13:45:28 -040087 }).catch(reportError(done));
Kevin Lubicke71e9ef2018-11-05 07:51:40 -050088 }));
Kevin Lubick92c91712018-08-09 10:00:02 -040089 });
90
Kevin Lubick11194ab2018-08-17 13:52:56 -040091 it('can chain by returning the same object', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -050092 LoadPathKit.then(catchException(done, () => {
Kevin Lubick11194ab2018-08-17 13:52:56 -040093 let path = PathKit.NewPath();
94
95 let p1 = path.moveTo(20, 5)
96 .lineTo(30, 20)
97 .quadTo(66, 188, 120, 136)
98 .close();
99
100 // these should be the same object
101 expect(path === p1).toBe(true);
102 p1.delete();
103 try {
104 // This should throw an exception because
105 // the underlying path was already deleted.
106 path.delete();
107 expect('should not have gotten here').toBe(false);
108 } catch (e) {
109 // all is well
110 }
111 done();
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500112 }));
Kevin Lubick11194ab2018-08-17 13:52:56 -0400113 });
114
115 it('does not leak path objects when chaining', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500116 LoadPathKit.then(catchException(done, () => {
Kevin Lubick11194ab2018-08-17 13:52:56 -0400117 // By default, we have 16 MB of memory assigned to our PathKit
118 // library. This can be configured by -S TOTAL_MEMORY=NN
119 // and defaults to 16MB (we likely don't need to touch this).
120 // If there's a leak in here, we should OOM pretty quick.
121 // Testing showed around 50k is enough to see one if we leak a path,
122 // so run 250k times just to be safe.
123 for(let i = 0; i < 250000; i++) {
124 let path = PathKit.NewPath()
125 .moveTo(20, 5)
126 .lineTo(30, 20)
127 .quadTo(66, 188, 120, 136)
128 .close();
129 path.delete();
130 }
131 done();
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500132 }));
Kevin Lubick11194ab2018-08-17 13:52:56 -0400133 });
134
135 function drawTriangle() {
136 let path = PathKit.NewPath();
137 path.moveTo(0, 0);
138 path.lineTo(10, 0);
139 path.lineTo(10, 10);
140 path.close();
141 return path;
142 }
143
144 it('has multiple overloads of addPath', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500145 LoadPathKit.then(catchException(done, () => {
Kevin Lubick11194ab2018-08-17 13:52:56 -0400146 let basePath = PathKit.NewPath();
147 let otherPath = drawTriangle();
148 // These add path call can be chained.
149 // add it unchanged
150 basePath.addPath(otherPath)
151 // providing the 6 params of an SVG matrix to make it appear 20.5 px down
152 .addPath(otherPath, 1, 0, 0, 1, 0, 20.5)
153 // provide the full 9 matrix params to make it appear 30 px to the right
154 // and be 3 times as big.
155 .addPath(otherPath, 3, 0, 30,
156 0, 3, 0,
157 0, 0, 1);
158
Kevin Lubickc6c48aa2018-08-17 15:00:43 -0400159 reportPath(basePath, 'add_path_3x', done);
Kevin Lubick11194ab2018-08-17 13:52:56 -0400160 basePath.delete();
161 otherPath.delete();
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500162 }));
Kevin Lubick11194ab2018-08-17 13:52:56 -0400163 });
164
Kevin Lubick97d6d982018-08-10 15:53:16 -0400165 it('approximates arcs (conics) with quads', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500166 LoadPathKit.then(catchException(done, () => {
Kevin Lubick97d6d982018-08-10 15:53:16 -0400167 let path = PathKit.NewPath();
Kevin Lubicka0ba6122018-08-15 13:45:28 -0400168 path.moveTo(50, 120);
169 path.arc(50, 120, 45, 0, 1.75 * Math.PI);
170 path.lineTo(50, 120);
Kevin Lubick97d6d982018-08-10 15:53:16 -0400171
172 let canvas = document.createElement('canvas');
Kevin Lubick97d6d982018-08-10 15:53:16 -0400173 let canvasCtx = canvas.getContext('2d');
Kevin Lubicka0ba6122018-08-15 13:45:28 -0400174 standardizedCanvasSize(canvasCtx);
175 // The and.callThrough is important to make it actually
176 // draw the quadratics
177 spyOn(canvasCtx, 'quadraticCurveTo').and.callThrough();
Kevin Lubick97d6d982018-08-10 15:53:16 -0400178
179 canvasCtx.beginPath();
180 path.toCanvas(canvasCtx);
181 canvasCtx.stroke();
182 // No need to check the whole path, as that's more what the
183 // gold correctness tests are for (can account for changes we make
184 // to the approximation algorithms).
185 expect(canvasCtx.quadraticCurveTo).toHaveBeenCalled();
186 path.delete();
Kevin Lubicka0ba6122018-08-15 13:45:28 -0400187 reportCanvas(canvas, 'conics_quads_approx').then(() => {
188 done();
189 }).catch(reportError(done));
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500190 }));
Kevin Lubick97d6d982018-08-10 15:53:16 -0400191 });
192
193});