Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 1 | var dumpErrors = false; |
| 2 | var container; |
| 3 | |
| 4 | function 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 | |
| 9 | function 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 Lubick | f14a3c0 | 2018-08-22 09:35:32 -0400 | [diff] [blame] | 35 | <path stroke=black fill=white stroke-width=0.01 d="${expectedPath.toSVGString()}"></path> |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 36 | </svg> |
| 37 | |
| 38 | <svg class='actual' viewBox='${getViewBox(actualPath)}'> |
Kevin Lubick | f14a3c0 | 2018-08-22 09:35:32 -0400 | [diff] [blame] | 39 | <path stroke=black fill=white stroke-width=0.01 d="${actualPath.toSVGString()}"></path> |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 40 | </svg> |
| 41 | `; |
| 42 | container.appendChild(thisTest); |
| 43 | |
| 44 | } |
| 45 | |
| 46 | const TOLERANCE = 0.0001; |
| 47 | |
| 48 | function 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 | |
| 74 | describe('PathKit\'s PathOps Behavior', function() { |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 75 | var PATHOP_MAP = {}; |
| 76 | var FILLTYPE_MAP = {}; |
Kevin Lubick | d254435 | 2019-03-12 09:20:32 -0400 | [diff] [blame] | 77 | |
| 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 Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 94 | } |
Kevin Lubick | d254435 | 2019-03-12 09:20:32 -0400 | [diff] [blame] | 95 | } |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 96 | |
| 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 Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 109 | it('combines two paths with .op() and matches what we see from C++', function(done) { |
Kevin Lubick | e71e9ef | 2018-11-05 07:51:40 -0500 | [diff] [blame] | 110 | LoadPathKit.then(catchException(done, () => { |
Kevin Lubick | d254435 | 2019-03-12 09:20:32 -0400 | [diff] [blame] | 111 | init(); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 112 | // Test JSON created with: |
Kevin Lubick | c7d0571 | 2018-08-31 10:03:23 -0400 | [diff] [blame] | 113 | // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$ |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 114 | fetch('/base/tests/PathOpsOp.json').then((r) => { |
Kevin Lubick | e71e9ef | 2018-11-05 07:51:40 -0500 | [diff] [blame] | 115 | r.json().then((json) => { |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 116 | expect(json).toBeTruthy(); |
| 117 | let testNames = Object.keys(json); |
Kevin Lubick | d6719ca | 2018-08-29 08:26:21 -0400 | [diff] [blame] | 118 | // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid. |
| 119 | expect(testNames.length > 0).toBeTruthy(); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 120 | testNames.sort(); |
| 121 | for (testName of testNames) { |
| 122 | let test = json[testName]; |
| 123 | |
Kevin Lubick | d993648 | 2018-08-24 10:44:16 -0400 | [diff] [blame] | 124 | let path1 = PathKit.FromCmds(test.p1); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 125 | expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`); |
| 126 | path1.setFillType(getFillType(test.fillType1)); |
| 127 | |
Kevin Lubick | d993648 | 2018-08-24 10:44:16 -0400 | [diff] [blame] | 128 | let path2 = PathKit.FromCmds(test.p2); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 129 | 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 Lubick | d993648 | 2018-08-24 10:44:16 -0400 | [diff] [blame] | 138 | let expected = PathKit.FromCmds(test.out); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 139 | // Do a tolerant match. |
| 140 | let diff = diffPaths(expected, combined); |
| 141 | if (test.expectMatch === 'yes'){ |
Kevin Lubick | 97d6d98 | 2018-08-10 15:53:16 -0400 | [diff] [blame] | 142 | // 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 Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 145 | 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 Lubick | 11194ab | 2018-08-17 13:52:56 -0400 | [diff] [blame] | 158 | // combined === path1, so we only have to delete one. |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 159 | path1.delete(); |
| 160 | path2.delete(); |
| 161 | } |
| 162 | done(); |
| 163 | }); |
| 164 | }); |
Kevin Lubick | e71e9ef | 2018-11-05 07:51:40 -0500 | [diff] [blame] | 165 | })); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 166 | }); |
| 167 | |
| 168 | it('simplifies a path with .simplify() and matches what we see from C++', function(done) { |
Kevin Lubick | e71e9ef | 2018-11-05 07:51:40 -0500 | [diff] [blame] | 169 | LoadPathKit.then(catchException(done, () => { |
Kevin Lubick | d254435 | 2019-03-12 09:20:32 -0400 | [diff] [blame] | 170 | init(); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 171 | // Test JSON created with: |
Kevin Lubick | c7d0571 | 2018-08-31 10:03:23 -0400 | [diff] [blame] | 172 | // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$ |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 173 | fetch('/base/tests/PathOpsSimplify.json').then((r) => { |
Kevin Lubick | e71e9ef | 2018-11-05 07:51:40 -0500 | [diff] [blame] | 174 | r.json().then((json) => { |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 175 | expect(json).toBeTruthy(); |
| 176 | let testNames = Object.keys(json); |
Kevin Lubick | d6719ca | 2018-08-29 08:26:21 -0400 | [diff] [blame] | 177 | // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid. |
| 178 | expect(testNames.length > 0).toBeTruthy(); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 179 | testNames.sort(); |
| 180 | for (testName of testNames) { |
| 181 | let test = json[testName]; |
| 182 | |
Kevin Lubick | d993648 | 2018-08-24 10:44:16 -0400 | [diff] [blame] | 183 | let path = PathKit.FromCmds(test.path); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 184 | 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 Lubick | d993648 | 2018-08-24 10:44:16 -0400 | [diff] [blame] | 193 | let expected = PathKit.FromCmds(test.out); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 194 | // Do a tolerant match. |
| 195 | let diff = diffPaths(expected, simplified); |
| 196 | if (test.expectMatch === 'yes'){ |
Kevin Lubick | 97d6d98 | 2018-08-10 15:53:16 -0400 | [diff] [blame] | 197 | // 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 Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 200 | 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 Lubick | 11194ab | 2018-08-17 13:52:56 -0400 | [diff] [blame] | 213 | // simplified === path, so we only have to delete one. |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 214 | path.delete(); |
| 215 | } |
| 216 | done(); |
| 217 | }); |
| 218 | }); |
Kevin Lubick | e71e9ef | 2018-11-05 07:51:40 -0500 | [diff] [blame] | 219 | })); |
Kevin Lubick | 644d8e7 | 2018-08-09 13:58:04 -0400 | [diff] [blame] | 220 | }); |
Kevin Lubick | 97d6d98 | 2018-08-10 15:53:16 -0400 | [diff] [blame] | 221 | }); |