Is it possible to detect if the client supports a particular Unicode character or if it will be rendered as a missing glyph box?
Important: Support in as many browser
You can use a canvas to check whether or not the character is rendered identically to a character you know is not supported. U+FFFF is a good choice for a character to compare to, since it's guaranteed not to be a valid unicode character.
So you create one canvas where you render a U+FFFF character, and another canvas where you render the character you want to test. You then compare the two canvases by comparing their data URLs by using the toDataURL method. If the canvases are identical, the test character was rendered identically to the unsupported U+FFFF character, meaning it's not supported, and if the canvases are not identical, the test character was not rendered in the same way as unsupported characters so it is supported.
The following code does that:
//The first argument is the character you want to test, and the second argument is the font you want to test it in.
//If the second argument is left out, it defaults to the font of the element.
//The third argument isn't used under normal circumstances, it's just used internally to avoid infinite recursion.
function characterIsSupported(character, font = getComputedStyle(document.body).fontFamily, recursion = false){
//Create the canvases
let testCanvas = document.createElement("canvas");
let referenceCanvas = document.createElement("canvas");
testCanvas.width = referenceCanvas.width = testCanvas.height = referenceCanvas.height = 150;
//Render the characters
let testContext = testCanvas.getContext("2d");
let referenceContext = referenceCanvas.getContext("2d");
testContext.font = referenceContext.font = "100px " + font;
testContext.fillStyle = referenceContext.fillStyle = "black";
testContext.fillText(character, 0, 100);
referenceContext.fillText('\uffff', 0, 100);
//Firefox renders unsupported characters by placing their character code inside the rectangle making each unsupported character look different.
//As a workaround, in Firefox, we hide the inside of the character by placing a black rectangle on top of it.
//The rectangle we use to hide the inside has an offset of 10px so it can still see part of the character, reducing the risk of false positives.
//We check for Firefox and browers that behave similarly by checking if U+FFFE is supported, since U+FFFE is, just like U+FFFF, guaranteed not to be supported.
if(!recursion && characterIsSupported('\ufffe', font, true)){
testContext.fillStyle = referenceContext.fillStyle = "black";
testContext.fillRect(10, 10, 80, 80);
referenceContext.fillRect(10, 10, 80, 80);
}
//Check if the canvases are identical
return testCanvas.toDataURL() != referenceCanvas.toDataURL();
}
//Examples
console.log("a is supported: " + characterIsSupported('a')); //Returns true, 'a' should be supported in all browsers
console.log("\ufffe is supported: " + characterIsSupported('\ufffe')); //Returns false, U+FFFE is guaranteed to be unsupported just like U+FFFF
console.log("\u2b61 is supported: " + characterIsSupported('\u2b61')); //Results vary depending on the browser. At the time of writing this, this returns true in Chrome on Windows and false in Safari on iOS.
console.log("\uf8ff is supported: " + characterIsSupported('\uf8ff')); //The unicode Apple logo is only supported on Apple devices, so this should return true on Apple devices and false on non-Apple devices.