xref: /aosp_15_r20/external/skia/modules/pathkit/tests/pathops.spec.js (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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