d3plus / d3plus-text

A smart SVG text box with line wrapping and automatic font size scaling.
MIT License
104 stars 19 forks source link

fontExists always returning false with custom fonts #132

Open francescocretti opened 1 year ago

francescocretti commented 1 year ago

As suggested by @davelandry in this comment , I'm trying to use a custom font in a d3 Plus viz.

I'm using React v17, d3plus-react v1.2.1 and the viz is a Treemap. I want both the labels in the chart and in the legend to have the same custom font.

Here's my (partial) configuration:

const treemapConfig = {
  shapeConfig: {
    labelConfig: {
      fontFamily: 'Droid Sans Mono',
      fontWeight: 400
    }
  },
  legendConfig: {
    align: 'left',
    padding: 15,
    shapeConfig: {
      width: 20,
      height: 20,
      labelConfig: {
        fontSize: 13,
        fontFamily: 'Droid Sans Mono',
        fontWeight: 400
      },
    },
  },
};

that is just passed to the component: <Treemap config={treemapConfig} />.

I obviously included the font files (ttf, woff and woff2) in my CSS:

@font-face {
  font-family: 'Droid Sans Mono';
  src: url('../../../fonts/DroidSansMono-webfont.woff2') format('woff2'),
       url('../../../fonts/DroidSansMono-webfont.woff') format('woff'),
       url('../../../fonts/DroidSansMono-webfont.ttf') format('truetype');
  font-weight: normal;
  font-style: normal;
}

I know the import is working because I can use the Droid Sans Mono font in other parts of my application without any issue.

However, something odd is happening: the font displayed is a serif font, but the spaces seem to be calculated as a monospace font (I attached a screenshot) and the <text> elements in the chart (and in the legend) have a font-family: false attribute.

So I dug a little bit in the source code and the problem is that the fontExists method returns false.

After a couple of tests I noticed that the width of the test string alpha = "abcdefghiABCDEFGHI_!@#$%^&*()_+1234567890" calculated by textWidth method is the same for my font and for the default monospace font. This makes sense since they are both monospace fonts but this also make the condition at line 32 to return false and the method itself to return false.

I also tried with a custom sans-serif font and I have the same behaviour but with the default sans-serif (aka proportional).

Am I missing something here? :)

I am working on MacOS Monterey v12.6v with both Chrome v108.0.5359.124v and Firefox v108.0.2.

Thanks in advance for any help and thanks to the creators and maintainers of this tool, that remains awesome.

Screenshot 2023-01-05 at 16 25 44
davelandry commented 1 year ago

I've encountered this bug in the past, and from what I have seen is that it happens sporadically, and is often related to a race condition between the font files being successfully loaded and when fontExists runs (if the function runs before the files have been loaded by the browser, it will return false).

Generally, I have seen this "fix itself" by refreshing the browser (as the font files are now cached in your browser). Unfortunately, I haven't found the perfect solution for this, and I have experimented with different preloading methods (typically defined in the <head>). If you're ready to go down that rabbit hole, I would suggest starting by reading about FOUC: https://en.wikipedia.org/wiki/Flash_of_unstyled_content

p.s. as a quick improvement, pass fallback fonts to the fontFamily so that you don't get serif fonts here. It will use the first font that passes fontExists (which helps with cross-browser compatibility for build-in fonts):

fontFamily: '"Droid Sans Mono", "Helvetica", "Arial", sans-serif'