Detect when Path2D with SVG path string is supported - javascript

It appears Microsoft Edge partially supports the Path2D API - it doesn't support the usage SVG path strings, i.e.,
const path = new Path2D('M20 L30');
Is there a way to feature detect whether or not passing in SVG paths is supported?

There is no catchable error that gets thrown in this case, and passing an invalid string does not throw either in supporting browsers. (This sounds like a specs flow to me btw...)
So to be able to detect this exact case of the constructor being supported, but the argument getting ignored, we have to actually check if something got drawn or not...
You can do so by stroking a simple M0,0H1 Path2D and then check if the pixel at coords 0,0 of you context has been painted.
function supports() {
// no simple support
if (typeof Path2D !== 'function') return false;
// create a new context
var x = document.createElement('canvas')
.getContext('2d');
// stroke a simple path
x.stroke(new Path2D('M0,0H1'));
// check it did paint something
return !!x.getImageData(0, 0, 1, 1).data[3];
};
console.log(supports());
But note that Edge does not seem to support either the Path2D.addPath() method, so if you include a polyfill like Google's canvas-5-polyfill which does include a Path2D polyfill, then the whole Path2D will use the polyfilled version (because they also check for addPath support).

Related

How can I convert a Path2D object into an SVG String in JavaScript? [duplicate]

I want to examine the individual path segments of a Path2D object. For example, suppose that
path = new Path2D();
path.ellipse(70,90,2,2,0,0,2*Math.PI);
I want to pass path to some other function and pull out the elements of the Path2D so that they can be translated into another format. The big picture goal is to allow the user to create a path and translate it to TikZ for inclusion in a LaTeX document.
Is there something like Java's Shape.getPathIterator(), which allows one to examine each segment along a path. Can this be done Javascript? As a template, here is an outline of the Java code that does what I'm hoping to do in Javascript.
PathIterator p = path.getPathIterator(new AffineTransform());
while (p.isDone() == false)
{
double c = new double[6];
int t = p.currentSegment(c);
if (t == PathIterator.SEG_MOVETO)
// (c[0],c[1]) is the "move-to" point.
else if (t == PathIterator.SEG_LINETO)
// (c[0],c[1]) is the "line-to" point.
else if (t == == PathIterator.SEG_CUBICTO)
// c[0] through c[5] specify the cubic curve
else
// etc., etc.
}
Editing this after seeing #Kaiido's answer.
What I ended up doing was extending Path2D to a new class that is functionally identical, except that it stores each call to arc(), lineTo(), etc. to an array as the call is made. This allows me to examine the record of past calls. It may not be a sufficiently general solution for everyone, but it works for my needs.
No there is currently nothing in the API that allows us to do that.
There are some discussions to extend the Path2D API so it's less "opaque", but nothing tangible yet, I don't think anyone is actively working on it, and we can't be sure what it will include.
The current proposal reads
Path2D Inspection. Allow inspection of Path2D objects, that are currently opaque.
For what it's worth, I myself started working on this idea a few months ago, in the hope I could help this discussion get started somehow with a possible feature design.
My very ambitious idea is to bring an API like SVG's SVGPathData and add methods to Path2D like getBBox, getPathData(), setPathData(), toSVGString(), getTotalLength(), or getPointAtLength().
You can check this repository where lives my project, and below is a demo using your input.
const path = new Path2D();
path.ellipse(70,90,50,20,0,0,2*Math.PI);
const svgString = path.toSVGString();
document.querySelector("pre").textContent += svgString;
document.querySelector("path").setAttribute("d", svgString);
document.querySelector("canvas").getContext("2d").fill(path);
<script src="https://cdn.jsdelivr.net/gh/Kaiido/path2D-inspection#master/build/path2D-inspection.min.js"></script>
<pre>
const path = new Path2D();
path.ellipse(70,90,50,20,0,0,2*Math.PI);
path.toSVGString();
</pre>
SVG:<br>
<svg><path fill="green"></path></svg><br>
Canvas:<br>
<canvas></canvas>

How to handle msToBlob producing security error on IE? [duplicate]

i have a chart that created with highchart.
i need to save svg to png in internet Explorer.
i use from follow code and exist security Error in ie11.
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var imgChart = document.createElement('img');
imgChart.setAttribute('src', 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))));
imgChart.onload = function () {
ctx.drawImage(imgChart, 0, 0);
var blobObject = canvas.msToBlob();
window.navigator.msSaveBlob(blobObject, 'save.png');
})
I didn't found an satisfying dupe target, so I'll rewrite it as an answer :
Drawing an SVG image through drawImage method will taint the canvas in IE < Edge for security reasons.
This operation is somehow sensitive for browsers, since svg images imply to parse some XML, and that it can contain some tricky elements (though IE doesn't support <foreignObject>...)
So quite often, browsers will add security restrictions when SVG images are drawn to it, and will block all exporting methods.
This is the case in safari > 9 when an <foreignObject> is drawn on it, this was also the case in chrome, but only when the image comes from an Blob (an implementation bug, but they finally leveraged the security restriction altogether anyway).
And then in IE < Edge, with any SVG.
The only way to workaround this issue is to parse yourself the SVG, and then use the canvas' methods to reproduce it.
This is all doable, but can take some time to implement, so even though I don't really like it, you'd probably be better using an library like canvg, which does exactly this (parsing + rendering with canvas methods).

.addHitRegion() doesn't work in Chrome

I have the latest Chrome version and I see in specs that it should support .addHitRegion() method, as mentioned on MDN. For some reason I get Uncaught TypeError: context.addHitRegion is not a function error.
My code is as simple as this:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(10,10,100,100);
context.fill();
context.addHitRegion({'id': 'The First Button', 'cursor': 'pointer'});
How do I fix it?
Go here with your browser: chrome://flags
and then
Set the flag Experimental Web Platform features to true to enable it.
As the other answers states, you can enable this through flags, however: you won't be able to ask your users to do the same. And the support is limited to a few browsers. I would therefor recommend looking to other solutions - I list some here:
A notch better approach is to use Path2D objects. They provide the same flexibility in terms of defining hit shapes. Use these with isPointInPath() which also takes a path object. Store each path in an array which you loop through using the position to test with. Unfortunately though, also this is limited to a few browsers, but you can at least use a poly-fill such as this to fix that to some extend (see notes in the link for limitations).
A better option perhaps in regards to support and availability, and the one requiring a bit more work, is to rebuild each single path you want to test on the context itself, then use as above the isPointInPath() to see if the mouse position is inside that path.
If the shapes are simple such as rectangles or circles, you can do simple mathematical tests which is a performant alternative.
So you need to set the experimental flag here
From The compatibility table at the bottom of the page you linked:
This feature is behind a feature flag. Set the flag
ExperimentalCanvasFeatures to true to enable it.
To turn on experimental canvas features browse to “chrome://flags“, turn on “Enable experimental canvas features” and relaunch.
Unfortunately the hit region feature is now obsolete and doesn't appear to be enable-able. You can use isPointInPath() as an alternative. You'll need to create a path object to be able to pass into that function. Something like:
const rectangle = new Path2D();
ctx.beginPath();
rectangle.rect(10, 10, 100, 100);
ctx.fill(rectangle);
...then to check, you could put it into an event listener:
canvas.addEventListener("mousemove", (e) => {
if (ctx.isPointInPath(rectangle, e.offsetX, e.offsetY)) {
console.log("rectangle is hit");
});

Raphael.js function getBBox give back NAN/NAN/NAN in IE8

using Raphaël 2.1.4 - JavaScript Vector Library
do something like that:
var textDummy = paper.text(50,500, 'hello world').attr({fill: 'transparent', 'font-size': 14});
var textBox = textDummy.getBBox();
with chrome and firefox everything is fine,
but in IE8 it give back NaN/NaN/NaN,
par exemple textBox.height is NaN.
how i can fix this?
i found a workaround solution from this answer to the question
"Raphael JS and Text positioning"
If i use _getBBox() instead of getBBox() everything is working in ie 8 also.
_getBBox() is undocumented but used internally by Raphael itself, and it works!
I had the same problem in Rapahel 2.2.0 and 2.2.1, and using ._getBBox() didn't fix it for me.
What did fix it for me is falling back to .auxGetBBox() if it's defined and regular .getBBox() doesn't work, like this:
var bbox = path.getBBox( );
// Workaround for apparent bug in Raphael's VML module's getBBox() override
if( isNaN( bbox.x ) && path.auxGetBBox ){
bbox = path.auxGetBBox();
}
I don't have a fix for the underlying bug, but I have found the source of it.
In VML mode, Raphael takes the initial getBBox() function, saves it as auxGetBBox() on the element prototype, then replaces it with a function that appears to be broken.
It has calculations based on a variable defined as var z = 1/this.paper._viewBoxShift.scale;, which clearly expects _viewBoxShift.scale to be some factor of the scale of the current viewbox compared to the initial viewbox , but actually _viewBoxShift.scale is an object like this which appears to come from paperproto.getSize():
{ height: someNumber, width: someNumber }
This is where all the NaNs are coming from. Cannae divide by an object.
So this workaround works fine if no zoom is applied using a viewbox, but may give incorrect results if a zoom has been applied (something I can't get to work at all in recent versions of raphael in VML mode, but that's a seperate question). Fixing that will involve digging deep into Raphael's VML module to pipe a proper zoom factor into this z variable.

Best way to detect that HTML5 <canvas> is not supported

The standard way to deal with situations where the browser does not support the HTML5 <canvas> tag is to embed some fallback content like:
<canvas>Your browser doesn't support "canvas".</canvas>
But the rest of the page remains the same, which may be inappropriate or misleading. I'd like some way of detecting canvas non-support so that I can present the rest of my page accordingly. What would you recommend?
This is the technique used in Modernizr and basically every other library that does canvas work:
function isCanvasSupported(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
Since your question was for detection when it's not supported, I recommend using it like so:
if (!isCanvasSupported()){ ...
There are two popular methods of detecting canvas support in browsers:
Matt's suggestion of checking for the existence of getContext, also used in a similar fashion by the Modernizr library:
var canvasSupported = !!document.createElement("canvas").getContext;
Checking the existence of the HTMLCanvasElement interface, as defined by the WebIDL and HTML specifications. This approach was also recommended in a blog post from the IE 9 team.
var canvasSupported = !!window.HTMLCanvasElement;
My recommendation is a variation of the latter (see Additional Notes), for several reasons:
Every known browser supporting canvas ― including IE 9 ― implements this interface;
It's more concise and instantly obvious what the code is doing;
The getContext approach is significantly slower across all browsers, because it involves creating an HTML element. This is not ideal when you need to squeeze as much performance as possible (in a library like Modernizr, for example).
There are no noticeable benefits to using the first method. Both approaches can be spoofed, but this not likely to happen by accident.
Additional Notes
It may still be necessary to check that a 2D context can be retrieved. Reportedly, some mobile browsers can return true for both above checks, but return null for .getContext('2d'). This is why Modernizr also checks the result of .getContext('2d').  However, WebIDL & HTML ― again ― gives us another better, faster option:
var canvas2DSupported = !!window.CanvasRenderingContext2D;
Notice that we can skip checking for the canvas element entirely and go straight to checking for 2D rendering support. The CanvasRenderingContext2D interface is also part of the HTML specification.
You must use the getContext approach for detecting WebGL support because, even though the browser may support the WebGLRenderingContext, getContext() may return null if the browser is unable to interface with the GPU due to driver issues and there is no software implementation. In this case, checking for the interface first allows you to skip checking for getContext:
var cvsEl, ctx;
if (!window.WebGLRenderingContext)
window.location = "http://get.webgl.org";
else {
cvsEl = document.createElement("canvas");
ctx = cvsEl.getContext("webgl") || cvsEl.getContext("experimental-webgl");
if (!ctx) {
// Browser supports WebGL, but cannot create the context
}
}
##Performance Comparison
Performance of the getContext approach is 85-90% slower in Firefox 11 and Opera 11 and about 55% slower in Chromium 18.
    
I usually run a check for getContext when I create my canvas object.
(function () {
var canvas = document.createElement('canvas'), context;
if (!canvas.getContext) {
// not supported
return;
}
canvas.width = 800;
canvas.height = 600;
context = canvas.getContext('2d');
document.body.appendChild(canvas);
}());
If it is supported, then you can continue the canvas setup and add it to the DOM. This is a simple example of Progressive Enhancement, which I (personally) prefer over Graceful Degradation.
Why not try modernizr ? It's a JS library that provides detection capability.
Quote:
Have you ever wanted to do
if-statements in your CSS for the
availability of cool features like
border-radius? Well, with Modernizr
you can accomplish just that!
try {
document.createElement("canvas").getContext("2d");
alert("HTML5 Canvas is supported in your browser.");
} catch (e) {
alert("HTML5 Canvas is not supported in your browser.");
}
There may be a gotcha here- some clients do not support all canvas methods.
var hascanvas= (function(){
var dc= document.createElement('canvas');
if(!dc.getContext) return 0;
var c= dc.getContext('2d');
return typeof c.fillText== 'function'? 2: 1;
})();
alert(hascanvas)
You can use canisuse.js script to detect if your browsers supports canvas or not
caniuse.canvas()
If you're going to get the context of your canvas, you might as well use it as the test:
var canvas = document.getElementById('canvas');
var context = (canvas.getContext?canvas.getContext('2d'):undefined);
if(!!context){
/*some code goes here, and you can use 'context', it is already defined*/
}else{
/*oof, no canvas support :(*/
}

Categories

Resources