jsPDF icon indicating copy to clipboard operation
jsPDF copied to clipboard

Context2D does not implement clip() correctly

Open andrewcmyers opened this issue 3 years ago • 3 comments

clip() is supposed to take an argument to specify the clipping rule. Context2D drops this argument on the floor and implements the default clipping rule, I guess.

It can also be given two arguments where one argument is the path. This is also not implemented.

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clip

andrewcmyers avatar Apr 09 '22 15:04 andrewcmyers

Thanks. Pull requests welcome ;)

HackbrettXXX avatar Apr 14 '22 10:04 HackbrettXXX

I'm pretty confused by the code that is currently there so I'm going to leave this one alone.

andrewcmyers avatar Apr 14 '22 12:04 andrewcmyers

I believe arcs are the only problem. If I have no arcs in my clip path, everything is fine. If I add any arcs, it breaks.

A quick inspection leads me to believe that the problem is the handling of the drawArc internal helper function in context2d.js which is always calling the doClip function; doClip must only be called at the end of the complete path, as it (among other things) discards the remaining path.

https://github.com/parallax/jsPDF/blob/5d09af9135a2fe049c7d3c8b95df280d22e4a6db/src/modules/context2d.js#L2234 https://github.com/parallax/jsPDF/blob/5d09af9135a2fe049c7d3c8b95df280d22e4a6db/src/modules/context2d.js#L2251

A very quick monkey patch that got me unblocked on my particular project:

    const clip = ctx.clip.bind(ctx)
    const pdfClip = (ctx as any).pdf.clip
    const pdfDiscardPath = (ctx as any).pdf.discardPath

    const patchClip = function (this: Context2d): jsPDF {
        const self = this as any

        // clip is recursive for some cases; disable recursion into ourselves
        ctx.clip = clip

        // disable the problem calls into the pdf backend
        self.pdf.clip = () => {}
        self.pdf.discardPath = () => {}

        // perform real clipping
        clip()

        // restore problem calls into the pdf backend
        self.pdf.clip = pdfClip
        self.pdf.discardPath = pdfDiscardPath

        // finalize the real clipping operation (was disabled)
        self.pdf.clip()
        self.pdf.discardPath()

        // restore patched clip
        ctx.clip = patchClip
        return self.pdf
    }
    ctx.clip = patchClip

A proper fix would likely just be to make the drawArc not automatically do the doClip function and require any callers to perform that operation, which would be correct for the general clipping case at least.

seanmiddleditch avatar Aug 09 '23 05:08 seanmiddleditch