agilgur5 / react-signature-canvas

A React wrapper component around signature_pad (in < 150 LoC). Unopinionated and heavily updated fork of react-signature-pad
https://agilgur5.github.io/react-signature-canvas/
Other
546 stars 119 forks source link

Cannot get SVG data url after `getTrimmedCanvas()` #101

Closed Szorcu closed 1 year ago

Szorcu commented 1 year ago

Hi,

I have a problem with the getTrimmedCanvas() function. After calling it, calling toDataUrl('image/svg+xml') on the trimmed canvas does not work as expected.

During debugging, I noticed that this is because the function returns HTMLCanvasElement instead of ReactSignatureCanvas or SignaturePad. The short way to put it is that calling the getTrimmedCanvas() function involves losing access to all the functions/variables provided by ReactSignatureCanvas.

agilgur5 commented 1 year ago

Duplicate of #49

Yes, this is expected behavior. getTrimmedCanvas() is not built-in to signature_pad so there is no way for it to return a signature_pad. The typings and docs do explicitly say that it returns a native canvas element.

Per the docs, getTrimmedCanvas uses a tiny library trim-canvas under-the-hood (which is suggested in the signature_pad docs nowadays) that operates on a native canvas element.

I did consider building in support for SVG trimming by wrapper another tiny trim-svg library (a counterpart to trim-canvas), but the API would be a bit clunky as it wouldn't be built-in to signature_pad either.

trim-canvas is meant to be used as the final operation after the usage of the canvas is completed to convert it into a PNG data URL. The same would apply to trim-svg, if it were to exist; it would be the final operation to convert it into an SVG data URL. A signature_pad instance cannot be returned by either, as neither is raw point data.

Szorcu commented 1 year ago

Hmm, this won't work? @agilgur5

getTrimmedCanvas = (): SignaturePad => {
    // copy the canvas
    const canvas = this.getCanvas()
    const copy = document.createElement('canvas')
    copy.width = canvas.width
    copy.height = canvas.height

    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    copy.getContext('2d')!.drawImage(canvas, 0, 0)

    const trimmedCanvas = trimCanvas(copy)

    return new SignaturePad(trimmedCanvas, this._excludeOurProps())
}
agilgur5 commented 1 year ago
return new SignaturePad(trimmedCanvas, this._excludeOurProps())

A canvas is pure raster data (basically just pixels, unlike a vector format like SVG), there is no point data that is passed to signature_pad in that case. You won't be able to convert that to an SVG as there is no point data associated with it. You can try it yourself to view the behavior as well.

You can see signature_pad's constructor here; no point data is extracted from the canvas. And here is the toSVG function, which converts the point data to an SVG (it does not use the canvas element).