Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matplotlib would not try to apply all the font in font list to draw all characters in the given string. #18883

Closed
Hsins opened this issue Nov 3, 2020 · 5 comments · Fixed by #20740
Labels
Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues topic: text/fonts
Milestone

Comments

@Hsins
Copy link

Hsins commented Nov 3, 2020

Bug report

Bug summary

The document page Configuring the font family said that:

You can explicitly set which font family is picked up for a given font style (e.g., 'serif', 'sans-serif', or 'monospace').
In the example below, we only allow one font family (Tahoma) for the sans-serif font style. You the default family with the font.family rc param, e.g.,:

rcParams['font.family'] = 'sans-serif'

and for the font.family you set a list of font styles to try to find in order:

rcParams['font.sans-serif'] = ['Tahoma', 'DejaVu Sans', 'Lucida Grande', 'Verdana']

But the mechanism behind the strings rendering with the font-family is not as same as what most users think.

  • The matplotlib just finds the font in the font list and apply the first valid one (can be found in the given path) to all characters in the given string.
  • We can use a list of fonts to the font-family settings in Visual Studio Code, Sublime Text, and websites. They have their own algorithm to deal with the problem of fonts fallback so that we can apply each font in the list in order to draw the correct character. Refer to How do web browsers implement font fallback?

Would matplotlib have any plan to implement this feature? There were so many users got into the trouble of missing fonts when we want to show the string mixed with CJK characters and Latin characters.

Code for reproduction

Just take a look at this Colab Notebook.

use ['DejaVu Sans', 'SimHei', 'sans-serif']

# Setup the fonts
matplotlib.rcParams['font.sans-serif'] = ['DejaVu Sans', 'SimHei', 'sans-serif']

def model(x, p):
    return x ** (2 * p + 1) / (1 + x ** (2 * p))

x = np.linspace(0.75, 1.25, 201)

fig, ax = plt.subplots()
for p in [10, 15, 20, 30, 50, 100]:
    ax.plot(x, model(x, p), label=p)
ax.legend(title='Order')
ax.set(xlabel='電壓 Voltage (mV)')
ax.set(ylabel='電流 Current (A)')
ax.autoscale(tight=True)
  • 電壓 and 電流 can't be correctly rendered with the current font DejaVu Sans.

use ['SimHei', 'DejaVu Sans', 'sans-serif']

# Setup the fonts
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'sans-serif']

def model(x, p):
    return x ** (2 * p + 1) / (1 + x ** (2 * p))

x = np.linspace(0.75, 1.25, 201)

fig, ax = plt.subplots()
for p in [10, 15, 20, 30, 50, 100]:
    ax.plot(x, model(x, p), label=p)
ax.legend(title='Order')
ax.set(xlabel='電壓 Voltage (mV)')
ax.set(ylabel='電流 Current (A)')
ax.autoscale(tight=True)
  • 電壓 and 電流 can be correctly rendered with the current font SimHei.
  • Voltage and Current are also be rendered with the current font SimHei.

Expected outcome

We would like to apply the Chinese font SimHei to the Chinese characters and the English font DejaVu Sans to the English characters. Just like what we use font-family style in CSS:

Similar Issues

@anntzer anntzer added topic: text/fonts Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues labels Nov 3, 2020
@anntzer
Copy link
Contributor

anntzer commented Nov 3, 2020

Your understanding of the problem is correct, but as noted in #15260 supporting multiple fonts per text element would require pretty big surgery on the text handling machinery. I'm labeling as "hard" not because the implementation is hard, but because it's a lot of work. So I guess PRs are welcome :-)

A partial solution (which may be much simpler to implement) may be to just scan the list of fonts to see whether there's one that supports all requested glyphs (so in the case given above we'd always use SimHei for everything).

@Hsins
Copy link
Author

Hsins commented Nov 3, 2020

I don't think that the implementation is not hard 😢...

The font fallback problem is always complex and hard for any software, you may figure out so many questions about it on the internet even from 10 years ago. e.g. servo, Discord, Figma, LaTeX... and so on.

It's a possible (which may be sufficient for matplotlib because we don't give the texts too long as an article or GUI) solution to iterating through all the fonts for each character in the text from this answer:

It checks whether the first font has a glyph for the character, if so it uses it, if not it falls back to the next font.

Do you have any further solutions? The possible solution I mentioned above is simple to implement but its complexity would be higher than current state.

@anntzer
Copy link
Contributor

anntzer commented Nov 3, 2020

As stated above, I think autosearching for a font that covers all characters is reasonable. Still quite a bit of work, but should be doable.

Switching fonts mid-text is much harder because of the way Matplotlib is architected (a single font "object" is in charge of laying out the text, but having multiple fonts per text would require inverting this architecture all across the library, which is what I meant by "big surgery"). Perhaps that architecture choice was not optimal, but it was made a long time ago and that's what we are stuck with.

Moreover, if we get support for multi-font text, then it seems quite natural that the next question people will ask is whether they can arbitrarily switch fonts in the middle of a text object (it seems a bit unfair if you can use DejaVu for character 1 and SimHei for character 2 if character 1 is latin and character 2 is chinese, but cannot use DejaVu for character 1 and Times for character 2 which are both latin); but then comes the question of whether we really want to have an API to specify that, which doesn't look too appetizing...

@Hsins
Copy link
Author

Hsins commented Nov 3, 2020

Moreover, if we get support for multi-font text, then it seems quite natural that the next question people will ask is whether they can arbitrarily switch fonts in the middle of a text object (it seems a bit unfair if you can use DejaVu for character 1 and SimHei for character 2 if character 1 is latin and character 2 is chinese, but cannot use DejaVu for character 1 and Times for character 2 which are both latin); but then comes the question of whether we really want to have an API to specify that, which doesn't look too appetizing...

It's no need to please anyone for further questions. That's what the order of font-family list actually means. As the figure I mentioned in the first thread in this issue:

  • When the font-family list is ['標楷體', Consolas] then all the characters in that string would apply with '標楷體'.
  • When the font-family list is [Consolas, '標楷體'] then the Latin characters in that string would apply with Consolas and the CJK characters in that string would apply with '標楷體' since the missing glyphs.

But it's still really a quite bit of work to deal with the single font "object" using in architecture...

@bmcfee
Copy link
Contributor

bmcfee commented Dec 22, 2021

Just chiming in here to express enthusiastic support for per-character font fallback. The lack of fallback support is really hamstringing some display features in librosa that would require music notation. The symbols in question are, AFAIK, only supported by FreeSerif, and forcing a full-string level fallback to FreeSerif would result in some pretty weird looking figures if the preference is otherwise for sans fonts.

@tacaswell tacaswell added this to the v3.6.0 milestone Dec 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Difficulty: Hard https://matplotlib.org/devdocs/devel/contribute.html#good-first-issues topic: text/fonts
Projects
None yet
4 participants