• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1var 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)}'>
35        <path stroke=black fill=white stroke-width=0.01 d="${expectedPath.toSVGString()}"></path>
36    </svg>
37
38    <svg class='actual' viewBox='${getViewBox(actualPath)}'>
39        <path stroke=black fill=white stroke-width=0.01 d="${actualPath.toSVGString()}"></path>
40    </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() {
75    var PATHOP_MAP = {};
76    var FILLTYPE_MAP = {};
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            };
94        }
95    }
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
109    it('combines two paths with .op() and matches what we see from C++', function(done) {
110        LoadPathKit.then(catchException(done, () => {
111            init();
112            // Test JSON created with:
113            // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsOp.json -m PathOpsOp$
114            fetch('/base/tests/PathOpsOp.json').then((r) => {
115                r.json().then((json) => {
116                    expect(json).toBeTruthy();
117                    let testNames = Object.keys(json);
118                    // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid.
119                    expect(testNames.length > 0).toBeTruthy();
120                    testNames.sort();
121                    for (testName of testNames) {
122                        let test = json[testName];
123
124                        let path1 = PathKit.FromCmds(test.p1);
125                        expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`);
126                        path1.setFillType(getFillType(test.fillType1));
127
128                        let path2 = PathKit.FromCmds(test.p2);
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();
138                            let expected = PathKit.FromCmds(test.out);
139                            // Do a tolerant match.
140                            let diff = diffPaths(expected, combined);
141                            if (test.expectMatch === 'yes'){
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)
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                        }
158                        // combined === path1, so we only have to delete one.
159                        path1.delete();
160                        path2.delete();
161                    }
162                    done();
163                });
164            });
165        }));
166    });
167
168    it('simplifies a path with .simplify() and matches what we see from C++', function(done) {
169        LoadPathKit.then(catchException(done, () => {
170            init();
171            // Test JSON created with:
172            // ./out/Clang/pathops_unittest -J ./modules/pathkit/tests/PathOpsSimplify.json -m PathOpsSimplify$
173            fetch('/base/tests/PathOpsSimplify.json').then((r) => {
174                r.json().then((json) => {
175                    expect(json).toBeTruthy();
176                    let testNames = Object.keys(json);
177                    // Assert we loaded a non-zero amount of tests, i.e. the JSON is valid.
178                    expect(testNames.length > 0).toBeTruthy();
179                    testNames.sort();
180                    for (testName of testNames) {
181                        let test = json[testName];
182
183                        let path = PathKit.FromCmds(test.path);
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();
193                            let expected = PathKit.FromCmds(test.out);
194                            // Do a tolerant match.
195                            let diff = diffPaths(expected, simplified);
196                            if (test.expectMatch === 'yes'){
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)
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                        }
213                        // simplified === path, so we only have to delete one.
214                        path.delete();
215                    }
216                    done();
217                });
218            });
219        }));
220    });
221});
222