blob: 3a9228ed53cafdcd22958bbd5802dbdf05835652 [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 = {};
Kevin Lubick97d6d982018-08-10 15:53:16 -040080 const LoadPathKit = new Promise(function(resolve, reject) {
Kevin Lubick644d8e72018-08-09 13:58:04 -040081 if (PathKit) {
82 resolve();
83 } else {
84 PathKitInit({
Kevin Lubick97d6d982018-08-10 15:53:16 -040085 locateFile: (file) => '/pathkit/'+file,
Kevin Lubick644d8e72018-08-09 13:58:04 -040086 }).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'){
Kevin Lubick97d6d982018-08-10 15:53:16 -0400155 // Check fill type
156 expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
157 // diff should be null if the paths are identical (modulo rounding)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400158 if (diff) {
159 expect(`[${testName}] ${diff}`).toBe('');
160 addSVG('[PathOps] ' + testName, expected, combined, diff);
161 }
162 } else if (test.expectMatch === 'flaky') {
163 // Don't worry about it, at least it didn't crash.
164 } else {
165 if (!diff) {
166 expect(`[${testName}] was expected to have paths that differed`).not.toBe('');
167 }
168 }
169 expected.delete();
170 }
171 combined.delete();
172 path1.delete();
173 path2.delete();
174 }
175 done();
176 });
177 });
178 });
179 });
180
181 it('simplifies a path with .simplify() and matches what we see from C++', function(done) {
182 LoadPathKit.then(() => {
183 // Test JSON created with:
184 // ./out/Clang/pathops_unittest -J ./pathkit/experimental/tests/PathOpsSimplify.json -m PathOpsSimplify$
185 fetch('/base/tests/PathOpsSimplify.json').then((r) => {
186 r.json().then((json)=>{
187 expect(json).toBeTruthy();
188 let testNames = Object.keys(json);
189 expect(testNames.length).toBe(457); // Remove if test data changes a lot.
190 testNames.sort();
191 for (testName of testNames) {
192 let test = json[testName];
193
194 let path = fromCmds(test.path);
195 expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`);
196 path.setFillType(getFillType(test.fillType));
197
198 let simplified = path.simplify();
199
200 if (test.expectSuccess === 'no') {
201 expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`);
202 } else {
203 expect(simplified).not.toBeNull();
204 let expected = fromCmds(test.out);
205 // Do a tolerant match.
206 let diff = diffPaths(expected, simplified);
207 if (test.expectMatch === 'yes'){
Kevin Lubick97d6d982018-08-10 15:53:16 -0400208 // Check fill type
209 expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
210 // diff should be null if the paths are identical (modulo rounding)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400211 if (diff) {
212 expect(`[${testName}] ${diff}`).toBe('');
213 addSVG('[Simplify] ' + testName, expected, simplified, diff);
214 }
215 } else if (test.expectMatch === 'flaky') {
216 // Don't worry about it, at least it didn't crash.
217 } else {
218 if (!diff) {
219 expect(`[${testName}] was expected to not match output`).not.toBe('');
220 }
221 }
222 expected.delete();
223 }
224 simplified.delete();
225 path.delete();
226 }
227 done();
228 });
229 });
230 });
231 });
Kevin Lubick97d6d982018-08-10 15:53:16 -0400232});