blob: 089f9bc027068b86cd5ee7a42fe6d33f135a935f [file] [log] [blame]
Kevin Lubick644d8e72018-08-09 13:58:04 -04001var dumpErrors = false;
2var container;
3
4function getViewBox(path) {
5 let bounds = path.getBounds();
6 return `${(bounds.fLeft-2)*.95} ${(bounds.fTop-2)*.95} ${(bounds.fRight+2)*1.05} ${(bounds.fBottom+2)*1.05}`;
7}
8
9function addSVG(testName, expectedPath, actualPath, message) {
10 if (!dumpErrors) {
11 return;
12 }
13 if (!container) {
14 let styleEl = document.createElement('style');
15 document.head.appendChild(styleEl);
16 let sheet = styleEl.sheet;
17 sheet.insertRule(`svg {
18 border: 1px solid #DDD;
19 max-width: 45%;
20 vertical-align: top;
21 }`, 0);
22
23 container = document.createElement('div');
24 document.body.appendChild(container);
25
26 }
27
28 let thisTest = document.createElement('div');
29 thisTest.innerHTML = `
30 <h2>Failed test ${testName}</h2>
31
32 <div>${message}</div>
33
34 <svg class='expected' viewBox='${getViewBox(expectedPath)}'>
Kevin Lubickf14a3c02018-08-22 09:35:32 -040035 <path stroke=black fill=white stroke-width=0.01 d="${expectedPath.toSVGString()}"></path>
Kevin Lubick644d8e72018-08-09 13:58:04 -040036 </svg>
37
38 <svg class='actual' viewBox='${getViewBox(actualPath)}'>
Kevin Lubickf14a3c02018-08-22 09:35:32 -040039 <path stroke=black fill=white stroke-width=0.01 d="${actualPath.toSVGString()}"></path>
Kevin Lubick644d8e72018-08-09 13:58:04 -040040 </svg>
41`;
42 container.appendChild(thisTest);
43
44}
45
46const TOLERANCE = 0.0001;
47
48function diffPaths(expected, actual) {
49 // Look through commands and see if they are within tolerance.
50 let eCmds = expected.toCmds(), aCmds = actual.toCmds();
51 if (eCmds.length !== aCmds.length) {
52 //console.log(`Expected: ${JSON.stringify(eCmds)} and Actual: ${JSON.stringify(aCmds)}`);
53 return `Different amount of verbs. Expected had ${eCmds.length}, Actual had ${aCmds.length}`;
54 }
55 for(let idx = 0; idx < eCmds.length; idx++){
56 let eCmd = eCmds[idx], aCmd = aCmds[idx];
57 if (eCmd.length !== aCmd.length) {
58 // Should never happen, means WASM code is returning bad ops.
59 return `Command index ${idx} differs in num arguments. Expected had ${eCmd.length}, Actual had ${aCmd.length}`;
60 }
61 let eVerb = eCmd[0], aVerb = aCmd[0];
62 if (eVerb !== aVerb) {
63 return `Command index ${idx} differs. Expected had ${eVerb}, Actual had ${aVerb}`;
64 }
65 for (let arg = 1; arg < eCmd.length; arg++) {
66 if (Math.abs(eCmd[arg] - aCmd[arg]) > TOLERANCE) {
67 return `Command index ${idx} has different argument for verb ${eVerb} at position ${arg}. Expected had ${eCmd[arg]}, Actual had ${aCmd[arg]}`
68 }
69 }
70 }
71 return null;
72}
73
74describe('PathKit\'s PathOps Behavior', function() {
Kevin Lubick644d8e72018-08-09 13:58:04 -040075 var PATHOP_MAP = {};
76 var FILLTYPE_MAP = {};
Kevin Lubickd2544352019-03-12 09:20:32 -040077
78 function init() {
79 if (PathKit && !PATHOP_MAP['kIntersect_SkPathOp']) {
80 PATHOP_MAP = {
81 'kIntersect_SkPathOp': PathKit.PathOp.INTERSECT,
82 'kDifference_SkPathOp': PathKit.PathOp.DIFFERENCE,
83 'kUnion_SkPathOp': PathKit.PathOp.UNION,
84 'kXOR_SkPathOp': PathKit.PathOp.XOR,
85 'kXOR_PathOp': PathKit.PathOp.XOR,
86 'kReverseDifference_SkPathOp': PathKit.PathOp.REVERSE_DIFFERENCE,
87 };
88 FILLTYPE_MAP = {
89 'kWinding_FillType': PathKit.FillType.WINDING,
90 'kEvenOdd_FillType': PathKit.FillType.EVENODD,
91 'kInverseWinding_FillType': PathKit.FillType.INVERSE_WINDING,
92 'kInverseEvenOdd_FillType': PathKit.FillType.INVERSE_EVENODD,
93 };
Kevin Lubick644d8e72018-08-09 13:58:04 -040094 }
Kevin Lubickd2544352019-03-12 09:20:32 -040095 }
Kevin Lubick644d8e72018-08-09 13:58:04 -040096
97 function getFillType(str) {
98 let e = FILLTYPE_MAP[str];
99 expect(e).toBeTruthy(`Could not find FillType Enum for ${str}`);
100 return e;
101 }
102
103 function getPathOp(str) {
104 let e = PATHOP_MAP[str];
105 expect(e).toBeTruthy(`Could not find PathOp Enum for ${str}`);
106 return e;
107 }
108
Kevin Lubick644d8e72018-08-09 13:58:04 -0400109 it('combines two paths with .op() and matches what we see from C++', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500110 LoadPathKit.then(catchException(done, () => {
Kevin Lubickd2544352019-03-12 09:20:32 -0400111 init();
Kevin Lubick644d8e72018-08-09 13:58:04 -0400112 // Test JSON created with:
Kevin Lubickc7d05712018-08-31 10:03:23 -0400113 // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$
Kevin Lubick644d8e72018-08-09 13:58:04 -0400114 fetch('/base/tests/PathOpsOp.json').then((r) => {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500115 r.json().then((json) => {
Kevin Lubick644d8e72018-08-09 13:58:04 -0400116 expect(json).toBeTruthy();
117 let testNames = Object.keys(json);
Kevin Lubickd6719ca2018-08-29 08:26:21 -0400118 // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid.
119 expect(testNames.length > 0).toBeTruthy();
Kevin Lubick644d8e72018-08-09 13:58:04 -0400120 testNames.sort();
121 for (testName of testNames) {
122 let test = json[testName];
123
Kevin Lubickd9936482018-08-24 10:44:16 -0400124 let path1 = PathKit.FromCmds(test.p1);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400125 expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`);
126 path1.setFillType(getFillType(test.fillType1));
127
Kevin Lubickd9936482018-08-24 10:44:16 -0400128 let path2 = PathKit.FromCmds(test.p2);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400129 expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`);
130 path2.setFillType(getFillType(test.fillType2));
131
132 let combined = path1.op(path2, getPathOp(test.op));
133
134 if (test.expectSuccess === 'no') {
135 expect(combined).toBeNull(`Test ${testName} should have not created output, but did`);
136 } else {
137 expect(combined).not.toBeNull();
Kevin Lubickd9936482018-08-24 10:44:16 -0400138 let expected = PathKit.FromCmds(test.out);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400139 // Do a tolerant match.
140 let diff = diffPaths(expected, combined);
141 if (test.expectMatch === 'yes'){
Kevin Lubick97d6d982018-08-10 15:53:16 -0400142 // Check fill type
143 expect(combined.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
144 // diff should be null if the paths are identical (modulo rounding)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400145 if (diff) {
146 expect(`[${testName}] ${diff}`).toBe('');
147 addSVG('[PathOps] ' + testName, expected, combined, diff);
148 }
149 } else if (test.expectMatch === 'flaky') {
150 // Don't worry about it, at least it didn't crash.
151 } else {
152 if (!diff) {
153 expect(`[${testName}] was expected to have paths that differed`).not.toBe('');
154 }
155 }
156 expected.delete();
157 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400158 // combined === path1, so we only have to delete one.
Kevin Lubick644d8e72018-08-09 13:58:04 -0400159 path1.delete();
160 path2.delete();
161 }
162 done();
163 });
164 });
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500165 }));
Kevin Lubick644d8e72018-08-09 13:58:04 -0400166 });
167
168 it('simplifies a path with .simplify() and matches what we see from C++', function(done) {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500169 LoadPathKit.then(catchException(done, () => {
Kevin Lubickd2544352019-03-12 09:20:32 -0400170 init();
Kevin Lubick644d8e72018-08-09 13:58:04 -0400171 // Test JSON created with:
Kevin Lubickc7d05712018-08-31 10:03:23 -0400172 // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$
Kevin Lubick644d8e72018-08-09 13:58:04 -0400173 fetch('/base/tests/PathOpsSimplify.json').then((r) => {
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500174 r.json().then((json) => {
Kevin Lubick644d8e72018-08-09 13:58:04 -0400175 expect(json).toBeTruthy();
176 let testNames = Object.keys(json);
Kevin Lubickd6719ca2018-08-29 08:26:21 -0400177 // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid.
178 expect(testNames.length > 0).toBeTruthy();
Kevin Lubick644d8e72018-08-09 13:58:04 -0400179 testNames.sort();
180 for (testName of testNames) {
181 let test = json[testName];
182
Kevin Lubickd9936482018-08-24 10:44:16 -0400183 let path = PathKit.FromCmds(test.path);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400184 expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`);
185 path.setFillType(getFillType(test.fillType));
186
187 let simplified = path.simplify();
188
189 if (test.expectSuccess === 'no') {
190 expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`);
191 } else {
192 expect(simplified).not.toBeNull();
Kevin Lubickd9936482018-08-24 10:44:16 -0400193 let expected = PathKit.FromCmds(test.out);
Kevin Lubick644d8e72018-08-09 13:58:04 -0400194 // Do a tolerant match.
195 let diff = diffPaths(expected, simplified);
196 if (test.expectMatch === 'yes'){
Kevin Lubick97d6d982018-08-10 15:53:16 -0400197 // Check fill type
198 expect(simplified.getFillType().value).toEqual(getFillType(test.fillTypeOut).value);
199 // diff should be null if the paths are identical (modulo rounding)
Kevin Lubick644d8e72018-08-09 13:58:04 -0400200 if (diff) {
201 expect(`[${testName}] ${diff}`).toBe('');
202 addSVG('[Simplify] ' + testName, expected, simplified, diff);
203 }
204 } else if (test.expectMatch === 'flaky') {
205 // Don't worry about it, at least it didn't crash.
206 } else {
207 if (!diff) {
208 expect(`[${testName}] was expected to not match output`).not.toBe('');
209 }
210 }
211 expected.delete();
212 }
Kevin Lubick11194ab2018-08-17 13:52:56 -0400213 // simplified === path, so we only have to delete one.
Kevin Lubick644d8e72018-08-09 13:58:04 -0400214 path.delete();
215 }
216 done();
217 });
218 });
Kevin Lubicke71e9ef2018-11-05 07:51:40 -0500219 }));
Kevin Lubick644d8e72018-08-09 13:58:04 -0400220 });
Kevin Lubick97d6d982018-08-10 15:53:16 -0400221});