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