html-to-image
html-to-image copied to clipboard
Firefox download broken
Firefox downloads broken
embed only used fonts (https://github.com/bubkoo/html-to-image/issues/476) (09bee44) breaks downloads in firefox (html-to-image versions >= 1.11.12)
TypeError: font is undefined.
Expected Behavior
Firefox downloads should work
Current Behavior
Firefox is broken
Possible Solution
rule.style.fontFamily is undefined
Steps To Reproduce
- upgrade html-to-image to 1.11.12
- download image in firefox (not sure if it needs to include a local font)
Error Message & Stack Trace
TypeError: font is undefined
normalizeFontFamily embed-webfonts.ts:206
cssTexts embed-webfonts.ts:237
getWebFontCSS embed-webfonts.ts:236
embedWebFonts embed-webfonts.ts:259
Additional Context
Your Environment
- html-to-image: ^1.11.12
- OS: macOS Sonoma 14.6.1
- Browser: Firefox 135.0.1
👋 @gmurph91
Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you include steps to reproduce it. To help make it easier for us to investigate your issue, please follow the contributing guidelines.
We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.
I could fix it temporarily by adding:
const dataUrl = await toPng(viewport as HTMLElement, {
skipFonts: true
});
Hello, I'm using html-to-image and am looking forward to this getting fixed, here are my findings on the matter:
The fault lies here:
function _getWebFontCSS() {
_getWebFontCSS = (0,_code_app_client_node_modules_angular_devkit_build_angular_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (node, options) {
const rules = yield parseWebFontRules(node, options);
const usedFonts = getUsedFonts(node);
const cssTexts = yield Promise.all(rules.filter(rule => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map(rule => {
const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
return (0,_embed_resources__WEBPACK_IMPORTED_MODULE_3__.embedResources)(rule.cssText, baseUrl, options);
}));
return cssTexts.join('\n');
});
return _getWebFontCSS.apply(this, arguments);
}
usedFonts was added in 1.11.12 I assume to optimize performances, but when we call normalizeFontFamily(rule.style.fontFamily), the fact is that rule.style.fontFamily exists only in Chrome, in Firefox the 'rule' object's architecture is different, hence it returns undefined and crashes.
The answer might lie in :
function _parseWebFontRules() {
_parseWebFontRules = (0,_code_app_client_node_modules_angular_devkit_build_angular_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (node, options) {
if (node.ownerDocument == null) {
throw new Error('Provided element is not within a Document');
}
const styleSheets = (0,_util__WEBPACK_IMPORTED_MODULE_1__.toArray)(node.ownerDocument.styleSheets);
const cssRules = yield getCSSRules(styleSheets, options);
return getWebFontRules(cssRules);
});
return _parseWebFontRules.apply(this, arguments);
}
which is the function that defines rules. A special behavior for Firefox might be in order, or something more elegant that I don't see. For now I am going to lock in 1.11.11 but hope this was helpful.
Regards
Thank you @undefinedhuman for providing the skipFonts: true suggestion. This solved my issue! Would love to see this issue fixed though. @DSteph thanks for pointing out the issue. 🎊
skipFonts: true might evade the error with Firefox, but also has impact on other browsers, as it potentially makes (fonts in) the exported image look completely different, which, if you had carefully aligned the text before exporting, may result in unreadable (or clipped-off) text in the exported image. So a proper solution is needed.
I ran into the same problem. This fix seems to work:
export async function getWebFontCSS<T extends HTMLElement>(
node: T,
options: Options,
): Promise<string> {
const rules = await parseWebFontRules(node, options)
const usedFonts = getUsedFonts(node)
const cssTexts = await Promise.all(
rules
.filter((rule) => {
const fontFamily = rule.style.fontFamily || rule.cssText.match(/font-family:\s*([^;]+)/)?.[1];
if (!fontFamily) return false;
return usedFonts.has(normalizeFontFamily(fontFamily));
})
.map((rule) => {
const baseUrl = rule.parentStyleSheet
? rule.parentStyleSheet.href
: null
return embedResources(rule.cssText, baseUrl, options)
}),
)
return cssTexts.join('\n')
}
RFC
Have the same problem.
getPropertyValue("font-family") on style object gives expected result (at least empty string) in both Firefox and Chrome.
Has anyone found that skipFonts: true causes a glitchy downloaded image on FireFox, like this:
MacBook Pro M1 Sequoia 15.5
EDIT created separate issue #549
This polyfill work-around worked for me:
(function () {
if (typeof CSSStyleDeclaration !== 'undefined' && !Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, 'fontFamily')) {
Object.defineProperty(CSSStyleDeclaration.prototype, 'fontFamily', {
get: function () {
return this.getPropertyValue("font-family");
},
enumerable: true,
configurable: true
});
}
})();
It adds a getter this.getPropertyValue("font-family"); to the CSSStyleDeclaration class.
This is an issue for me as well, and @movabo 's patch does not fix the issue, instead I get an error with message "[object Event]"
EDIT: Workaround is to pin version 1.11.11