plotly.js icon indicating copy to clipboard operation
plotly.js copied to clipboard

Missing fonts in PNG exports

Open cjacksudo opened this issue 4 years ago • 30 comments

Here's an example of a plotly chart with a custom font included:

https://codepen.io/etpinard/pen/jAzVVL

When clicking the "download plot as PNG" option, the downloaded file appears to have a different font than the chart.

Is there something I need to do to make sure that the font is included with the exported image?

cjacksudo avatar May 29 '20 23:05 cjacksudo

Hmm... not sure if this is a bug. @antoinerg FYI - codepen somehow displays the custom font but it is not used in the PNG export. However if I past the JS code in the dashboard, the custom font does not show up in the browser.

archmoj avatar Jun 01 '20 13:06 archmoj

Certainly looks like a bug to me - thanks for the report @cjacksudo!

Did a little poking around - if you ask for an svg download (using config option toImageButtonOptions: {format: 'svg'}) the font is included correctly, so the problem is at the svg -> raster step.

My guess is the problem is with this canvas:

https://github.com/plotly/plotly.js/blob/5b8b1db1386b82db747cbad239fed754e624a18f/src/snapshot/toimage.js#L44

not being appended to the DOM, so it doesn't have access to fonts loaded in the document. Possibly just adding it to the DOM (but putting it offscreen somewhere) would fix the issue?

alexcjohnson avatar Jun 01 '20 14:06 alexcjohnson

Has there been any progress on this? Or is there a temporary work-around?

In my case the font isn't even included in svg downloads.

LTribelhorn avatar Nov 13 '20 08:11 LTribelhorn

I just tried the following Codepen and to my surprise, the PNG export does contain the correct font on Chromium Version 85.0.4183.83 (Developer Build) (64-bit) on Linux. I could however reproduce the issue on Firefox.

antoinerg avatar Nov 13 '20 15:11 antoinerg

I forked the original Codepen provided in this issue (https://github.com/plotly/plotly.js/issues/4885#issue-627594588) and replaced 'Oswald' by Oswald and fonts are now properly rendered even in Firefox: https://codepen.io/antoinerg/pen/vYKbGKm

~~In summary, it seems like Firefox and some other browsers choke on single quotes.~~ The library performs some manipulation of quotes in https://github.com/plotly/plotly.js/blob/master/src/snapshot/tosvg.js#L114. I suspect the fix will be in this file.

antoinerg avatar Nov 13 '20 16:11 antoinerg

Has there been any progress on this? Or is there a temporary work-around?

In my case the font isn't even included in svg downloads.

@LTribelhorn are you using single quotes? Can you try removing them? See https://github.com/plotly/plotly.js/issues/4885#issuecomment-726852154 for an explanation.

antoinerg avatar Nov 13 '20 16:11 antoinerg

@antoinerg for what it's worth, I tried your codepen without any luck - the behavior is the same, and the font is not included in the downloaded png.

I'm on chrome 86.0.4240.198

cjacksudo avatar Nov 13 '20 17:11 cjacksudo

@antoinerg for what it's worth, I tried your codepen without any luck - the behavior is the same, and the font is not included in the downloaded png.

I'm on chrome 86.0.4240.198

Thanks @cjacksudo for the quick reply! What is your operating system?

Does this one also fail for you: https://codepen.io/antoinerg/pen/pobGgQZ ?

antoinerg avatar Nov 13 '20 17:11 antoinerg

I'm on macOS (10.15.6).

Yeah, I'm seeing the same thing with https://codepen.io/antoinerg/pen/pobGgQZ

cjacksudo avatar Nov 13 '20 17:11 cjacksudo

I updated my Codepen to use a dev build of plotly.js which contains a fix. Can someone please confirm it is indeed working on their OS/browser?

cc @archmoj @cjacksudo

antoinerg avatar Nov 13 '20 19:11 antoinerg

I updated my Codepen to use a dev build of plotly.js which contains a fix. Can someone please confirm it is indeed working on their OS/browser?

cc @archmoj @cjacksudo

Not working on my machine. @antoinerg Wondering if one should open the downloaded image in the browser?

archmoj avatar Nov 13 '20 19:11 archmoj

Not working on my machine.

What's your browser and OS @archmoj ? It works for me both in FF and Chromium on Linux... :confused:

Wondering if one should open the downloaded image in the browser?

Since they are PNG, it shouldn't matter what you use to open them!

antoinerg avatar Nov 13 '20 19:11 antoinerg

That new codepen still fails for me (Chrome 86 or any other browser, Mac OS 11.0.1)

alexcjohnson avatar Nov 13 '20 19:11 alexcjohnson

@antoinerg Wondering if one should open the downloaded image in the browser?

Nevermind. It is a png so it should not matter.

archmoj avatar Nov 13 '20 19:11 archmoj

Still fails for me as well

cjacksudo avatar Nov 13 '20 20:11 cjacksudo

It turns out my Codepen was using fonts already installed on my workstation so please disregard my comments above :man_facepalming:

The bad news is that we rely on <img> to turn our SVG into an image and according to https://stackoverflow.com/a/42405731

For security reasons, <img> inner documents can not make any external requests. This means that you'll have to embed all your external resources as dataURIs inside your svg markup itself, before loading it to the <img> element.

Therefore, to support custom fonts in SVG/PNG exports via CSS (ultimately the @font-face rule), we would need to inject the style in the SVG itself inside <defs>. Example:

<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
    <defs>
        <style>
            @import url("https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i");
        </style>
    </defs>
    <style><![CDATA[svg text{stroke:none}]]></style>
    <text x="20" y="50" font-family="Roboto">Roboto</text>
</svg>

@alexcjohnson is this something we want to support (ie. allow users to specify fonts to embed via some attribute)?

antoinerg avatar Nov 13 '20 22:11 antoinerg

It would be a bit unfortunate if we needed to require users to explicitly specify these, just to make the downloaded image match what's already in their graph; I was hoping we'd be able to just read out the@font-face rules that were active in the document, and add them automatically. And it seems like we can do that for locally-defined rules (by hunting through document.styleSheets) but it's forbidden for imported stylesheets... we can see the URLs and could just @import all of them?

That sounds like a pain though, and might cause problems in some contexts, so yeah perhaps in the short term we could just accept an option to specify these fonts explicitly.

alexcjohnson avatar Nov 13 '20 23:11 alexcjohnson

Has there been any progress on this? Or is there a temporary work-around? In my case the font isn't even included in svg downloads.

@LTribelhorn are you using single quotes? Can you try removing them? See #4885 (comment) for an explanation.

@antoinerg Sorry for the late response and thank you very much for your effort. I am using the R-Implementation of plotly so the usecase is different but I thought the problem might be connected. If you don't think so I could open a new issue in the R-Repository.

For context: I am using plotly in a Shiny-app and I define the fonts as specified in the documentation with font_p <- list(family = "Roboto", size = 14) and import the font in the UI with tags$head(tags$style(HTML("@import url('//fonts.googleapis.com/css2?family=Roboto:wght@300;700&display=swap');")))

LTribelhorn avatar Nov 14 '20 10:11 LTribelhorn

~~@antoinerg if the issue is downloading the font, shouldn't this pen work: https://codepen.io/cjacksudo/pen/yLazRRE?~~

~~I looked through the SO answer you linked (https://stackoverflow.com/a/42405731) and it seems like the setting the font-face rule to reference a data string should solve it, but it doesn't appear to...~~

EDIT: I see it - confirmed that adding the rule inside of the svg fixes it.

cjacksudo avatar Dec 23 '20 20:12 cjacksudo

I still have this issue.

MacOS "plotly.js": "^2.2.1", "react-plotly.js": "^2.5.1",

Matteobikk90 avatar Aug 05 '21 10:08 Matteobikk90

While this issue is being resolved, perhaps it should be noted (here and maybe in the documentation as well) that for the png exports to work properly, one should use fonts installed on the system.

On Mac OS, what worked for me was looking at what fonts were available in the Font Book.

MarekSvob avatar Oct 29 '21 14:10 MarekSvob

Do we have any fix for missing fonts in PNG exports? Any way by which we could override a custom font to image or append anything in the data url generated? @antoinerg

shivansingh9 avatar Jul 07 '22 10:07 shivansingh9

~@antoinerg if the issue is downloading the font, shouldn't this pen work: https://codepen.io/cjacksudo/pen/yLazRRE?~

~I looked through the SO answer you linked (https://stackoverflow.com/a/42405731) and it seems like the setting the font-face rule to reference a data string should solve it, but it doesn't appear to...~

EDIT: I see it - confirmed that adding the rule inside of the svg fixes it.

Hello.

I'm running into this exact issue. @antoinerg: Could you elaborate how you did this?

I tried adding custom styles with the fonts as data-urls (as mentioned above, maybe I did not get it right) to the svg, but this seems to be ignored by the toImage-function.

For different reasons I cannot include fonts hosted on a third party.

cs-sebastian avatar Oct 24 '22 14:10 cs-sebastian

Is there a working workaround for this issue?

wimbschimmelpfennig avatar Mar 03 '23 15:03 wimbschimmelpfennig

I just ran into this issue. For us the downloaded images had annotations with text that overflowed their containers. I think because the size of the container was calculated with the fonts loaded, but when you download the font aren't there, the text changes size, but the calculation isn't updated.

For example: https://ibb.co/fdtWWCL

I "fixed" it by making the plot always render with font-family: 'Open Sans', verdana, arial, sans-serif;, not ideal as this isn't our brand font, but at least it means the downloaded images work :)

I would be perfectly happy to provide some kind of config that told plotly which fonts to include in the SVG.

WilliamMayor avatar May 16 '23 13:05 WilliamMayor

I just ran into this issue. For us the downloaded images had annotations with text that overflowed their containers. I think because the size of the container was calculated with the fonts loaded, but when you download the font aren't there, the text changes size, but the calculation isn't updated.

For example: https://ibb.co/fdtWWCL

I "fixed" it by making the plot always render with font-family: 'Open Sans', verdana, arial, sans-serif;, not ideal as this isn't our brand font, but at least it means the downloaded images work :)

I would be perfectly happy to provide some kind of config that told plotly which fonts to include in the SVG.

The link is dead.

ziyuang avatar Aug 11 '23 23:08 ziyuang

Ah yes, sorry about that, I assumed the image board would keep the image around for longer.

I could try to re-create the image if that's useful? It was a picture of an annotation where the text was too wide to fit inside its container.

WilliamMayor avatar Aug 14 '23 09:08 WilliamMayor

Ah yes, sorry about that, I assumed the image board would keep the image around for longer.

I could try to re-create the image if that's useful? It was a picture of an annotation where the text was too wide to fit inside its container.

No thanks :) I can imagine what it could have been.

ziyuang avatar Aug 15 '23 19:08 ziyuang

It turns out my Codepen was using fonts already installed on my workstation so please disregard my comments above 🤦‍♂️

The bad news is that we rely on <img> to turn our SVG into an image and according to https://stackoverflow.com/a/42405731

For security reasons, <img> inner documents can not make any external requests. This means that you'll have to embed all your external resources as dataURIs inside your svg markup itself, before loading it to the <img> element.

Therefore, to support custom fonts in SVG/PNG exports via CSS (ultimately the @font-face rule), we would need to inject the style in the SVG itself inside <defs>. Example:

<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
    <defs>
        <style>
            @import url("https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i");
        </style>
    </defs>
    <style><![CDATA[svg text{stroke:none}]]></style>
    <text x="20" y="50" font-family="Roboto">Roboto</text>
</svg>

@alexcjohnson is this something we want to support (ie. allow users to specify fonts to embed via some attribute)?

How did you put the style inside defs? I have tried some DOM manipulation, but it seems Plotly.js recreates the plot from its data and layout, so the inserted style will not go to the downloaded image.

ziyuang avatar Aug 15 '23 19:08 ziyuang