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