FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Matplotlib would not try to apply all the font in font list to draw all characters in the given string. · Issue #18883 · matplotlib/matplotlib · GitHub

Repository navigation

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

Closed

Description

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

Activity

  1. anntzer commented on Nov 3, 2020

    Contributor

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

  2. Hsins commented on Nov 3, 2020

    Author

    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.

  3. anntzer commented on Nov 3, 2020

    Contributor

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

  4. Hsins commented on Nov 3, 2020

    Author

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

  5. bmcfee commented on Dec 22, 2021

    Contributor

    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.

  6. added this to the v3.6.0 milestone on Dec 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Difficulty: Hardhttps://matplotlib.org/devdocs/devel/contribute.html#good-first-issuestopic: text/fonts

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL