blob: 4b8c3ef5e97769bd2dddf2c7c38230cef914e9fd [file] [log] [blame]
Kevin Lubicka0ba6122018-08-15 13:45:28 -04001jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
Kevin Lubick644d8e72018-08-09 13:58:04 -04002
3var dumpErrors = false;
4var container;
5
6function getViewBox(path) {
7 let bounds = path.getBounds();
8 return `${(bounds.fLeft-2)*.95} ${(bounds.fTop-2)*.95} ${(bounds.fRight+2)*1.05} ${(bounds.fBottom+2)*1.05}`;
9}
10
11function addSVG(testName, expectedPath, actualPath, message) {
12 if (!dumpErrors) {
13 return;
14 }
15 if (!container) {
16 let styleEl = document.createElement('style');
17 document.head.appendChild(styleEl);
18 let sheet = styleEl.sheet;
19 sheet.insertRule(`svg {
20 border: 1px solid #DDD;
21 max-width: 45%;
22 vertical-align: top;
23 }`, 0);
24
25 container = document.createElement('div');
26 document.body.appendChild(container);
27
28 }
29
30 let thisTest = document.createElement('div');
31 thisTest.innerHTML = `
32 <h2>Failed test ${testName}</h2>
33
34 <div>${message}</div>
35
36 <svg class='expected' viewBox='${getViewBox(expectedPath)}'>
37 <path stroke=black d="${expectedPath.toSVGString()}"></path>
38 </svg>
39
40 <svg class='actual' viewBox='${getViewBox(actualPath)}'>
41 <path stroke=black d="${actualPath.toSVGString()}"></path>
42 </svg>
43`;
44 container.appendChild(thisTest);
45
46}
47
48const TOLERANCE = 0.0001;
49
50function diffPaths(expected, actual) {
51 // Look through commands and see if they are within tolerance.
52 let eCmds = expected.toCmds(), aCmds = actual.toCmds();
53 if (eCmds.length !== aCmds.length) {
54 //console.log(`Expected: ${JSON.stringify(eCmds)} and Actual: ${JSON.stringify(aCmds)}`);
55 return `Different amount of verbs. Expected had ${eCmds.length}, Actual had ${aCmds.length}`;
56 }
57 for(let idx = 0; idx < eCmds.length; idx++){
58 let eCmd = eCmds[idx], aCmd = aCmds[idx];
59 if (eCmd.length !== aCmd.length) {
60 // Should never happen, means WASM code is returning bad ops.
61 return `Command index ${idx} differs in num arguments. Expected had ${eCmd.length}, Actual had ${aCmd.length}`;
62 }
63 let eVerb = eCmd[0], aVerb = aCmd[0];
64 if (eVerb !== aVerb) {
65 return `Command index ${idx} differs. Expected had ${eVerb}, Actual had ${aVerb}`;
66 }
67 for (let arg = 1; arg < eCmd.length; arg++) {
68 if (Math.abs(eCmd[arg] - aCmd[arg]) > TOLERANCE) {
69 return `Command index ${idx} has different argument for verb ${eVerb} at position ${arg}. Expected had ${eCmd[arg]}, Actual had ${aCmd[arg]}`
70 }
71 }
72 }
73 return null;
74}
75
76describe('PathKit\'s PathOps Behavior', function() {
77 // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
78 var PathKit = null;
79 var PATHOP_MAP = {};
80 var FILLTYPE_MAP = {};
Kevin Lubick97d6d982018-08-10 15:53:16 -040081 const LoadPathKit = new Promise(function(resolve, reject) {
Kevin Lubick644d8e72018-08-09 13:58:04 -040082 if (PathKit) {
83 resolve();
84 } else {
85 PathKitInit({
Kevin Lubick97d6d982018-08-10 15:53:16 -040086 locateFile: (file) => '/pathkit/'+file,
Kevin Lubick644d8e72018-08-09 13:58:04 -040087 }).then((_PathKit) => {
88 PathKit = _PathKit;
89 PATHOP_MAP = {
90 'kIntersect_SkPathOp':PathKit.PathOp.INTERSECT,
91 'kDifference_SkPathOp':PathKit.PathOp.DIFFERENCE,
92 'kUnion_SkPathOp': PathKit.PathOp.UNION,
93 'kXOR_SkPathOp': PathKit.PathOp.XOR,
94 'kXOR_PathOp': PathKit.PathOp.XOR,
95 'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE,
96 };
97 FILLTYPE_MAP = {
98 'kWinding_FillType':PathKit.FillType.WINDING,
99 'kEvenOdd_FillType':PathKit.FillType.EVENODD,
100 'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING,
101 'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD,
102 };
103 resolve();
104 });
105 }
106 });
107
108 function getFillType(str) {
109 let e = FILLTYPE_MAP[str];
110 expect(e).toBeTruthy(`Could not find FillType Enum for ${str}`);
111 return e;
112 }
113
114 function getPathOp(str) {
115 let e = PATHOP_MAP[str];
116 expect(e).toBeTruthy(`Could not find PathOp Enum for ${str}`);
117 return e;
118 }
119
120 function fromCmds(cmds) {
121 let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
122 return PathKit.FromCmds(ptr, len);
123 }
124
125 it('combines two paths with .op() and matches what we see from C++', function(done) {
126 LoadPathKit.then(() => {
127 // Test JSON created with:
128 // ./out/Clang/pathops_unittest -J ./pathkit/experimental/tests/PathOpsOp.json -m PathOpsOp$
129 fetch('/base/tests/PathOpsOp.json').then((r) => {
130 r.json().then((json)=>{
131 expect(json).toBeTruthy();
132 let testNames = Object.keys(json);
133 expect(testNames.length).toBe(351); // Remove if test data changes a lot.
134 testNames.sort();
135 for (testName of testNames) {
136 let test = json[testName];
137
138 let path1 = fromCmds(test.p1);
139 expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`);
140 path1.setFillType(getFillType(test.fillType1));
141
142 let path2 = fromCmds(test.p2);
143 expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`);
144 path2.setFillType(getFillType(test.fillType2));
145
146 let combined = path1.op(path2, getPathOp(test.op));
147
148 if (test.expectSuccess === 'no') {
149 expect(combined).toBeNull(`Test ${testName} should have not created output, but did`);
150 } else {
151 expect(combined).not.toBeNull();
152 let expected = fromCmds(test.out);
153 // Do a tolerant match.
154 let diff = diffPaths(expected, combined);
155 if (test.expectMatch === 'yes'){
Kevin Lubick97d6d982018-08-10 15:53:16 -0400156 // Check fill type
157 expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
158 // diff should be null if the paths are identical (modulo rounding)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400159 if (diff) {
160 expect(`[${testName}] ${diff}`).toBe('');
161 addSVG('[PathOps] ' + testName, expected, combined, diff);
162 }
163 } else if (test.expectMatch === 'flaky') {
164 // Don't worry about it, at least it didn't crash.
165 } else {
166 if (!diff) {
167 expect(`[${testName}] was expected to have paths that differed`).not.toBe('');
168 }
169 }
170 expected.delete();
171 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400172 // combined === path1, so we only have to delete one.
Kevin Lubick644d8e72018-08-09 13:58:04 -0400173 path1.delete();
174 path2.delete();
175 }
176 done();
177 });
178 });
179 });
180 });
181
182 it('simplifies a path with .simplify() and matches what we see from C++', function(done) {
183 LoadPathKit.then(() => {
184 // Test JSON created with:
185 // ./out/Clang/pathops_unittest -J ./pathkit/experimental/tests/PathOpsSimplify.json -m PathOpsSimplify$
186 fetch('/base/tests/PathOpsSimplify.json').then((r) => {
187 r.json().then((json)=>{
188 expect(json).toBeTruthy();
189 let testNames = Object.keys(json);
190 expect(testNames.length).toBe(457); // Remove if test data changes a lot.
191 testNames.sort();
192 for (testName of testNames) {
193 let test = json[testName];
194
195 let path = fromCmds(test.path);
196 expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`);
197 path.setFillType(getFillType(test.fillType));
198
199 let simplified = path.simplify();
200
201 if (test.expectSuccess === 'no') {
202 expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`);
203 } else {
204 expect(simplified).not.toBeNull();
205 let expected = fromCmds(test.out);
206 // Do a tolerant match.
207 let diff = diffPaths(expected, simplified);
208 if (test.expectMatch === 'yes'){
Kevin Lubick97d6d982018-08-10 15:53:16 -0400209 // Check fill type
210 expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
211 // diff should be null if the paths are identical (modulo rounding)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400212 if (diff) {
213 expect(`[${testName}] ${diff}`).toBe('');
214 addSVG('[Simplify] ' + testName, expected, simplified, diff);
215 }
216 } else if (test.expectMatch === 'flaky') {
217 // Don't worry about it, at least it didn't crash.
218 } else {
219 if (!diff) {
220 expect(`[${testName}] was expected to not match output`).not.toBe('');
221 }
222 }
223 expected.delete();
224 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400225 // simplified === path, so we only have to delete one.
Kevin Lubick644d8e72018-08-09 13:58:04 -0400226 path.delete();
227 }
228 done();
229 });
230 });
231 });
232 });
Kevin Lubick97d6d982018-08-10 15:53:16 -0400233});