I'm trying to display a custom web font on a <canvas>
element, however it doesn't always display correctly first time as the font has not been pre-loaded:
How it currently looks:
Here's how it should look:
I'm aware that several methods of pre-loading web fonts exist but unfortunately they're not applicable to this situation because the page can use up to 90 fonts.
The reason for the large number is because I'm using a Chinese font which has been segmented into many separate .woff2
files so that my CSS file can make use of the unicode-range
descriptor like so:
/* unicode range [1] */
@font-face {
font-family: 'ma-shan-zheng';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Ma Shan Zheng Regular'), local('MaShanZheng-Regular'), url(ma-shan-zheng.5.woff2) format('woff2');
unicode-range: U+fee3, U+fef3, U+ff03-ff04, U+ff07, U+ff0a, U+ff17-ff19, U+ff1c-ff1d, U+ff20-ff3a, U+ff3c, U+ff3e-ff5b, U+ff5d, U+ff61-ff65, U+ff67-ff6a, U+ff6c, U+ff6f-ff78, U+ff7a-ff7d, U+ff80-ff84, U+ff86, U+ff89-ff8e, U+ff92, U+ff97-ff9b, U+ff9d-ff9f, U+ffe0-ffe4, U+ffe6, U+ffe9, U+ffeb, U+ffed, U+fffc, U+1f004, U+1f170-1f171, U+1f192-1f195, U+1f198-1f19a, U+1f1e6-1f1e8;
}
/* unicode range [2] */
@font-face {
font-family: 'ma-shan-zheng';
font-style: normal;
font-weight: 400;
font-display: swap;
src: local('Ma Shan Zheng Regular'), local('MaShanZheng-Regular'), url(ma-shan-zheng.6.woff2) format('woff2');
unicode-range: U+f0a7, U+f0b2, U+f0b7, U+f0c9, U+f0d8, U+f0da, U+f0dc-f0dd, U+f0e0, U+f0e6, U+f0eb, U+f0fc, U+f101, U+f104-f105, U+f107, U+f10b, U+f11b, U+f14b, U+f18a, U+f193, U+f1d6-f1d7, U+f244, U+f27a, U+f296, U+f2ae, U+f471, U+f4b3, U+f610-f611, U+f880-f881, U+f8ec, U+f8f5, U+f8ff, U+f901, U+f90a, U+f92c-f92d, U+f934, U+f937, U+f941, U+f965, U+f967, U+f969, U+f96b, U+f96f, U+f974, U+f978-f979, U+f97e, U+f981, U+f98a, U+f98e, U+f997, U+f99c, U+f9b2, U+f9b5, U+f9ba, U+f9be, U+f9ca, U+f9d0-f9d1, U+f9dd, U+f9e0-f9e1, U+f9e4, U+f9f7, U+fa00-fa01, U+fa08, U+fa0a, U+fa11, U+fb01-fb02, U+fdfc, U+fe0e, U+fe30-fe31, U+fe33-fe44, U+fe49-fe52, U+fe54-fe57, U+fe59-fe66, U+fe68-fe6b, U+fe8e, U+fe92-fe93, U+feae, U+feb8, U+fecb-fecc, U+fee0;
}
/* etc. all the way up to [90] */
This has the obvious benefit of only downloading the relevant .woff2
file when it's needed, but it also means that when the user downloads the web font for the first time they will see the unstyled text as shown above.
In an ideal world I'd be able to attach a callback function to the automatic download of the fonts, but it seems there is no access to this part of the browser's behaviour.
My current workaround
I've modified a solution from an old SO question about web font pre-loading - it's very hacky, but it does work.
In short, it creates a <span>
element with some text in a default font, measures the width/height, sets the element font to the web font, measures the size again and compares the results. If the size has changed, it's assumed the web font has loaded:
function waitFontLoaded(font, phrase, callback) {
var node = document.createElement("span");
// Set node content to the desired phrase/text
node.innerHTML = phrase;
// Visible - so we can measure it - but not on the screen
node.style.position = "absolute";
node.style.left = "-10000px";
node.style.top = "-10000px";
// Large font size makes even subtle changes obvious
node.style.fontSize = "300px";
// Reset any font properties
node.style.fontFamily = "sans-serif";
node.style.fontVariant = "normal";
node.style.fontStyle = "normal";
node.style.fontWeight = "normal";
node.style.letterSpacing = "0";
document.body.appendChild(node);
// Remember size with no applied web font
var width = node.offsetWidth;
var height = node.offsetHeight;
node.style.fontFamily = font + ", sans-serif";
var interval;
// Compare current size with original size
function checkFont() {
if (node && (node.offsetWidth !== width || node.offsetHeight !== height)) {
node.parentNode.removeChild(node);
node = null;
clearInterval(interval);
callback();
return true;
}
return false;
}
if (!checkFont()) {
interval = setInterval(checkFont, 50);
}
}
As I said, this does work, but is clearly not a robust solution as it's not impossible for two characters to be the same size in both the default system font and the web font.
Another very hacky solution would be to simply refresh the <canvas>
element every second, e.g. by using a setInterval
.
I feel like there must be a cleaner and more elegant way of doing this. Can anyone offer any suggestions?