bubkoo / html-to-image

✂️ Generates an image from a DOM node using HTML5 canvas and SVG.
MIT License
5.4k stars 505 forks source link

Using embedFontCss with fonts served from localhost does not render font. #412

Closed sirganya closed 9 months ago

sirganya commented 1 year ago

Using embedFontCss with fonts served from localhost does not render font.

The local fonts are correctly loaded by the browser and render correctly in the element to be targeted.

Expected Behavior

The image text should be rendered correctly

Current Behavior

The fonts is rendered in the browser fallback

Steps To Reproduce

if (ref.current) {
        const fontCss =
          `@font-face {font-family: "DegularDisplayDemo-Bold.otf"; src: url("http://localhost:3000/get-font/DegularDisplayDemo-Light.otf") format("opentype"}`;

        toPng(ref.current, {
          fontEmbedCSS: fontCss,
          height: landscape ? 90 : 160,
          width: landscape ? 160 : 90,
          filter: previewerFilter,
        })
          .then((dataUrl) => {
            var img = new Image();
            img.src = dataUrl;
            document.body.appendChild(img);
            setPreviewImage(idx, dataUrl);
          })
          .catch(() => {
            // console.log(err);
          });
      }
vivcat[bot] commented 1 year ago

👋 @sirganya

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.

LatoAndroid commented 10 months ago

I have also noticed this issue when packaging the project as static resources and placing it in the Android assets directory, I found that html-to-image cannot export images correctly.

OldDream commented 9 months ago

I'm using iconfont,got the same problem.

sirganya commented 9 months ago

I solved this issue. I have a react app that downloads fonts dynamically and then takes a snapshot of the rendered component using html-to-image. The browser rendered the font but it wasn't appearing in the snapshot.

I was using something like this to load the font


fetch(url).then(r => r.arrayBuffer()).then((fontContent) => { //font content is raw data
  const newFont = new FontFace(fontName, fontContent); 
  newFont.load().then(() => {
        document.fonts.add(newFont);
   })
})

It was necessary to also add a @font-face rule using something like

function appendFont(font: string, url: string) {
  const fontCss = document.createElement("style");
  const fontCssRule = `
                @font-face {
                font-family: "${font}";
                src: url("${url}"),
              }`;

  fontCss.appendChild(document.createTextNode(fontCssRule));
  document.head.appendChild(fontCss);
}

The url I passed was the data from fetching the url in the first method. html-to-image recognised it was already a data-url and handled it with no issues.

sirganya commented 9 months ago

:)

Parth909 commented 5 months ago

Hi @sirganya can you please give an example as to how you are doing it? Can you please show using actual values for url.

I have tried what you are saying but it is still not working

sirganya commented 5 months ago

I have a function that loads the font data and when it loads it adds the data as a font to the document. The url is standard and I'm using open type fonts

https://get-font.quickpoint.me/Helvetica.otf

Then it calls an appendFont function (below). There's a check for the format of the fontName, Firefox and Safari seems to follow the specs (the specs require quotes around the fontName) but Chrome seems to break with them. That would be one area to experiment with. I need to use quoted font-names for fontEmbedCSS regardless of the browser it's in. This could cause you problems as the fonts might break in Chrome if they are to work in

const url = `${clientConfig.getFont()}/${font}`;
          fetch(url).then(r => r.arrayBuffer()).then((fontContent) => {
            const navigator = parser();
            const fontName = navigator.engine.name == "Gecko" || type === "preview" ? `"${font}"` : font;
            const newFont = new FontFace(fontName, fontContent);
            newFont.load().then(() => {
              document.fonts.add(newFont);
              resolve(font);
              if (type === "preview") appendFontToPreview(font, url);
            });
          });

In my code I have a function that adds the font face rule to the document head

function appendFont(font: string, url: string) {
  const fontCss = document.createElement("style");
  const fontCssRule = `
                @font-face {
                font-family: "${font}";
                src: url("${url}"),
              }`;

  fontCss.appendChild(document.createTextNode(fontCssRule));
  document.head.appendChild(fontCss);
}

A font file would be too big to add to this answer but the first few bytes come down the wire in postman looking like this

wOF2��{?FFTM�B�x�0`�.  �(
��8��9�t6$�l �j��FM[�ַ�2֜�����IFrjֳ��6�]�}�Jm����(c�r��[��y�J��oÊ��������@���������������/^��vG�̮�+.w.��;�!��!2�Dɩԍ���,����x�72de�j�4�ͥ.e�f�FҒ�;�׫�������뤣l���t����;|ߴ�6��