@@ -451,6 +451,98 @@ test('clip-no-divergence-after-promotion', async (t) => {
451451 await snapshotImage ( t )
452452} )
453453
454+ // Regression test for https://github.com/Brooooooklyn/canvas/issues/1198
455+ // Nested clips at different transforms should intersect in device space, not raw path coordinates
456+ test ( 'clip-nested-different-transforms' , async ( t ) => {
457+ const canvas = createCanvas ( 200 , 200 )
458+ const ctx = canvas . getContext ( '2d' ) !
459+ ctx . fillStyle = 'white'
460+ ctx . fillRect ( 0 , 0 , 200 , 200 )
461+
462+ // Clip 1 at 2x scale: rect(0,0,80,80) → device space 0,0 to 160,160
463+ ctx . setTransform ( 2 , 0 , 0 , 2 , 0 , 0 )
464+ const clip1 = new Path2D ( )
465+ clip1 . rect ( 0 , 0 , 80 , 80 )
466+ ctx . clip ( clip1 )
467+
468+ // Clip 2 at identity: rect(0,0,160,160) → device space 0,0 to 160,160
469+ ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 )
470+ const clip2 = new Path2D ( )
471+ clip2 . rect ( 0 , 0 , 160 , 160 )
472+ ctx . clip ( clip2 )
473+
474+ // Fill the intersection — should be 160x160 (both clips map to same device-space region)
475+ ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 )
476+ ctx . fillStyle = 'green'
477+ ctx . fillRect ( 0 , 0 , 200 , 200 )
478+
479+ // Expected: 160x160 green area (both clips cover 0,0 to 160,160 in device space)
480+ // Bug: only 80x80 green area (intersection computed in raw coords without transforms)
481+ await snapshotImage ( t , { canvas, ctx } )
482+ } )
483+
484+ // Regression test for https://github.com/Brooooooklyn/canvas/issues/1198
485+ // Y-flip transform with nested clip (PDF.js use case)
486+ test ( 'clip-nested-y-flip-transform' , async ( t ) => {
487+ const canvas = createCanvas ( 200 , 200 )
488+ const ctx = canvas . getContext ( '2d' ) !
489+ ctx . fillStyle = 'white'
490+ ctx . fillRect ( 0 , 0 , 200 , 200 )
491+
492+ // Y-flip transform: maps (0,0)-(200,200) in path coords to full canvas
493+ ctx . setTransform ( 1 , 0 , 0 , - 1 , 0 , 200 )
494+
495+ // First clip covering full canvas in flipped coords
496+ const clip1 = new Path2D ( )
497+ clip1 . rect ( 0 , 0 , 200 , 200 )
498+ ctx . clip ( clip1 )
499+
500+ // Second clip at different scale
501+ ctx . setTransform ( 2 , 0 , 0 , 2 , 0 , 0 )
502+ const clip2 = new Path2D ( )
503+ clip2 . rect ( 0 , 0 , 50 , 50 )
504+ ctx . clip ( clip2 )
505+
506+ // Reset transform and fill
507+ ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 )
508+ ctx . fillStyle = 'blue'
509+ ctx . fillRect ( 0 , 0 , 200 , 200 )
510+
511+ // Expected: 100x100 blue area (clip2 at 2x scale maps to 0,0-100,100 in device space,
512+ // intersected with clip1 which covers full canvas)
513+ await snapshotImage ( t , { canvas, ctx } )
514+ } )
515+
516+ // Regression test for https://github.com/Brooooooklyn/canvas/issues/1198
517+ // Current path clip (beginPath/rect/clip) also affected, not just Path2D
518+ test ( 'clip-nested-different-transforms-current-path' , async ( t ) => {
519+ const canvas = createCanvas ( 200 , 200 )
520+ const ctx = canvas . getContext ( '2d' ) !
521+ ctx . fillStyle = 'white'
522+ ctx . fillRect ( 0 , 0 , 200 , 200 )
523+
524+ // Clip 1 at 2x scale: rect(0,0,80,80) → device space 0,0 to 160,160
525+ ctx . setTransform ( 2 , 0 , 0 , 2 , 0 , 0 )
526+ ctx . beginPath ( )
527+ ctx . rect ( 0 , 0 , 80 , 80 )
528+ ctx . clip ( )
529+
530+ // Clip 2 at identity: rect(0,0,160,160) → device space 0,0 to 160,160
531+ ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 )
532+ ctx . beginPath ( )
533+ ctx . rect ( 0 , 0 , 160 , 160 )
534+ ctx . clip ( )
535+
536+ // Fill the intersection — should be 160x160
537+ ctx . setTransform ( 1 , 0 , 0 , 1 , 0 , 0 )
538+ ctx . fillStyle = 'red'
539+ ctx . fillRect ( 0 , 0 , 200 , 200 )
540+
541+ // Expected: 160x160 red area
542+ // Bug: only 80x80 red area
543+ await snapshotImage ( t , { canvas, ctx } )
544+ } )
545+
454546test ( 'closePath' , async ( t ) => {
455547 const { ctx } = t . context
456548 ctx . beginPath ( )
0 commit comments