html-to-image icon indicating copy to clipboard operation
html-to-image copied to clipboard

Firefox download broken

Open gmurph91 opened this issue 8 months ago • 8 comments
trafficstars

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

  1. upgrade html-to-image to 1.11.12
  2. 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 avatar Feb 28 '25 04:02 gmurph91

👋 @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.

biiibooo[bot] avatar Feb 28 '25 04:02 biiibooo[bot]

I could fix it temporarily by adding:

  const dataUrl = await toPng(viewport as HTMLElement, {
    skipFonts: true
  });

undefinedhuman avatar Feb 28 '25 23:02 undefinedhuman

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

DSteph avatar Mar 12 '25 08:03 DSteph

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. 🎊

wencyen avatar Mar 20 '25 22:03 wencyen

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.

gjvoosten avatar May 06 '25 10:05 gjvoosten

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

agido-freudenreich avatar May 07 '25 12:05 agido-freudenreich

Have the same problem.

getPropertyValue("font-family") on style object gives expected result (at least empty string) in both Firefox and Chrome.

shchasny avatar May 12 '25 11:05 shchasny

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

vincerubinetti avatar Jun 12 '25 01:06 vincerubinetti

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.

movabo avatar Jun 23 '25 09:06 movabo

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

ayan4m1 avatar Jul 04 '25 23:07 ayan4m1