Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvas calls that include NaN parameters aren't treated properly #33

Open
mgirolet-gl opened this issue Mar 18, 2024 · 0 comments
Open

Comments

@mgirolet-gl
Copy link

mgirolet-gl commented Mar 18, 2024

Situation

Sometimes (most often mistakenly), CanvasRenderingContext2D API functions may be called with NaN passed as one or more of the parameters.

Although it is not mentionned in the MDN documentation as far as I could find, it turns out that calls to such functions with NaN as one of the parameters are simply ignored with no errors returned.

As such, the following code here will have three of its lines completely ignored:

let canvas = document.getElementById('test');
let ctx = canvas.getContext('2d');

ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(NaN, 70); // Gets ignored
ctx.lineTo(70, NaN); // Gets ignored
ctx.lineTo(NaN, NaN); // Gets ignored
ctx.lineTo(100, 100);
ctx.stroke();

See for yourself: https://jsfiddle.net/d0bmx5nt/1/

The problem

It however seems that svgcanvas does not acknowledge this, and therefore encodes the NaN values into the exported svg.

Therefore, the following code:

import svgcanvas from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'

let ctx = new svgcanvas.Context(250, 250);

ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(NaN, 70); // Gets ignored
ctx.lineTo(70, NaN); // Gets ignored
ctx.lineTo(NaN, NaN); // Gets ignored
ctx.lineTo(100, 100);
ctx.stroke();

saveAs(new Blob([ctx.getSerializedSvg()], { type: 'image/svg+xml' }), 'test.svg');

Exports the following SVG file:

<svg version="1.1"
	xmlns="http://www.w3.org/2000/svg"
	xmlns:xlink="http://www.w3.org/1999/xlink" width="500" height="500">
	<defs/>
	<g>
		<path fill="none" stroke="#000000" paint-order="fill stroke markers" d=" M 50 50 L NaN NaN L NaN NaN L NaN NaN L 100 100" stroke-miterlimit="10" stroke-dasharray=""/>
	</g>
</svg>

See for yourself: https://jsfiddle.net/pcrn6471/1/

While some browsers might correctly ignore the commands that involve those NaN values, it is ultimately up to the software you are opening it with to decide what to do with it.

Inkscape, for instance, considers those NaNs as 0s, while Firefox seems to sometimes ignore them, or sometimes invalidate the whole path.

Practical use case

You might think that if there are NaNs in paths, then it's up to the programmer who has put them to fix it.

However, in practice, this very likely won't ever happen.

The reason I met this problem in the first place is when trying to export charts rendered by Chart.JS as SVG files. And even though Chart.JS is a 63.2k stars JS module, there are some draw calls that end up passing NaN to CanvasRenderingContext2D functions. And while yes I suppose an issue could be opened there, why would they bother fixing something that does not trigger any error in the first place nor cause any problem to any Chart.JS user and only affects one module that intercepts function calls that weren't meant to be intercepted in the first place?

Therefore, I think it would be more reasonable to implement a fix for this within svgcanvas, as it would make it more accurate to browsers' behaviour and would avoid opening issues for every module that uses canvas but has such calls happening in the background.

Workaround

In the meantime of this issue being resolved, you can work around the problem with one of these solutions:

  • Override Context.prototype functions to make them return if NaN is passed
  • Proxy Context.prototype functions to make them return if NaN is passed
  • Edit svgcanvas' code to make those functions natively return in case of NaN being passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant