Skip to content

Various issues with FontProperties #10249

Open

Description

@anntzer

FontProperties (henceforth "FP") is used by Matplotlib for font selection. Instances can be passed e.g. to text() (as the fontproperties kwarg) or legend() (as the prop kwarg); they behave essentially as a mapping with fields such as family (DejaVu Sans, etc), weight (a CSS/OpenType numeric value, per https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight, with a mapping for names such as "bold", etc.), etc., that are matched against the list of system fonts (in the infamous fontList.json), with font_manager.py, using a custom-designed distance function.

FPs can be constructed either by passing kwargs to the constructor (FP(family=..., weight=..., ...)) or a fontconfig-style pattern string (FP("DejaVu Sans:bold:..."), https://www.freedesktop.org/software/fontconfig/fontconfig-user.html).

A few issues with FPs are listed below. Note that this discussion is not directly related to the font cache (misguessing of font weights, etc.) issues (although the font cache is used to match FPs).

  • The user API is not uniform: text(..., fontproperties=...) accepts a FP instance or a fontconfig pattern string (but not a dict) vs legend(..., prop=...) which uses a different kwarg and accepts a FP instance or a dict (but not a fontconfig pattern string). While this issue should be relatively easy to fix, I am reluctant to encourage usage of fontconfig pattern strings until the next issue is fixed.

  • The fontconfig-style strings use CSS/OpenType weights, which are very different from fontconfig weights (https://lists.freedesktop.org/archives/fontconfig/2011-September/003646.html). For example, text(.5, .5, "foo", fontproperties=":weight=200") will use a thin font (CSS 200 = "Extra/Ultra Light") but fc-list :weight=200 at the terminal will return bold fonts (FC 200 = "Bold"). It would seem more reasonable e.g. to use CSS-style strings (https://developer.mozilla.org/en-US/docs/Web/CSS/font), although these require a font size to be included (but it doesn't seem too bad to allow ignoring it). Note that if we really want to do so, it seems possible to support both fc-style and CSS-style strings (with optional weight) because they can be differentiated (namely, a fc-string (other than a bare family name, which looks the same in both cases up to quoting) contains colons whereas css-strings do not). Not sure it's really worth it though...

  • The FP matching algorithm is custom-made, while we could actually follow a standard algorithm (e.g. https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm, or whatever fontconfig uses (which seems a bit underspecified though, and probably technically depends on the user's fonts.conf...)). Arguably this is less an issue as long as the current algorithm does something "reasonable".

  • The FP constructor is a bit a mess because it overloads the first argument to be either the family or a fontconfig pattern (the latter needs to be escaped, but not the former), so the syntax for requesting a sans-serif font depends on whether one requests additional properties or not:

[ins] In [1]: matplotlib.font_manager.FontProperties("sans-serif").get_family()
<deep pyparsing traceback, then ValueError: Could not parse font string: 'sans-serif'>

[ins] In [2]: matplotlib.font_manager.FontProperties("sans-serif", weight="bold").get_family()  
Out[2]: ['sans-serif']

[ins] In [3]: matplotlib.font_manager.FontProperties("sans\-serif").get_family()                
Out[3]: ['sans-serif']

[ins] In [4]: matplotlib.font_manager.FontProperties("sans\-serif", weight="bold").get_family() 
Out[4]: ['sans\\-serif']

(i.e. one needs to write sans-serif if the weight is given, but sans\-serif if not).


I think the end-result should be something as follows:

  • Deprecate the fontproperties kwarg for Text, prop for legend, etc., in favor of a new font kwarg for every one; deprecate FP as a public class.
  • The font kwarg takes one of
    • a Path object (explicit font path; raises if the files embeds multiple font faces)
    • a (Path, index) pair (to handle files with multiple font faces embedded)
    • a CSS font string, with the following allowances:
      • "bare" font names do not need to be quoted
      • the font size can be ignored (defaulting to the rcparam)
      • the font family can be ignored (defaulting to the rcparam)
    • a CSS font mapping ({"family": ..., "size": ...} corresponding to the CSS font-family: ...; font-size: ...)
    • possibly (though unclear if needed), a fontconfig font string (using fontconfig weights) or fontconfig mapping (as above), although the latter definitely needs to be marked in some way to distinguish it from CSS font mappings.

Some other potentially relevant CSS options (it's not so much that I deeply love CSS, just that it has the benefit of being a spec that already exists...), just treating this as a list of ideas:

  • line-height (i.e. linespacing for us)

Some more notes:
Unfortunately even tokenizing CSS seems not so trivial; the only Python libs that I found for that task are https://github.com/Kozea/tinycss (no commit for more than a year), https://github.com/Kozea/tinycss2 (essentially abandoned, per Kozea/tinycss2#4 (comment)), and https://bitbucket.org/cthedot/cssutils (LGPL, and no commit for more than a year). (And parsing still needs to go on top of that.)
Moreover, having read the CSS font spec a few times, I just don't understand how the "font" shorthand resolves whether "normal" refers to "font-weight", "font-stetch", or "font-style".
If there's any specialist of CSS syntax I'm all ears.
However the plan of just taking a mapping kwarg remains.

Activity

  1. added this to the milestone on Jan 17, 2018
  2. modified the milestones: v3.0, v3.1 on Aug 11, 2018
  3. modified the milestones: v3.1.0, v3.2.0 on Mar 18, 2019
  4. liZe commented on Jul 17, 2019

    @liZe

    Unfortunately even tokenizing CSS seems not so trivial; the only Python libs that I found for that task are https://github.com/Kozea/tinycss (no commit for more than a year), https://github.com/Kozea/tinycss2 (essentially abandoned, per Kozea/tinycss2#4 (comment)), and https://bitbucket.org/cthedot/cssutils (LGPL, and no commit for more than a year).

    For the record: TinyCSS2 and CSSSelect2 are maintained, even if there's not much to do (the specs don't change that often 😄).

    Moreover, having read the CSS font spec a few times, I just don't understand how the "font" shorthand resolves whether "normal" refers to "font-weight", "font-stetch", or "font-style".
    If there's any specialist of CSS syntax I'm all ears.

    You can find an implementation of the font expander in WeasyPrint. The function yields tuples of (font_property_name, list_of_tokens).

  5. anntzer commented on Jul 17, 2019

    @anntzer
    ContributorAuthor

    That's really great to hear :) I may revisit this issue, then...

  6. liZe commented on Jul 18, 2019

    @liZe

    Moreover, having read the CSS font spec a few times, I just don't understand how the "font" shorthand resolves whether "normal" refers to "font-weight", "font-stetch", or "font-style".
    If there's any specialist of CSS syntax I'm all ears.

    I should have answered this directly: as the initial value for these properties is "normal", and as the possible values for each property are mutually exclusive, you can actually skip the "normal" values and assign the other values to the matching properties.

  7. anntzer commented on Jul 18, 2019

    @anntzer
    ContributorAuthor

    thanks.

  8. github-actions commented on Apr 29, 2023

    @github-actions

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  9. liuzhenqi77 commented on Apr 29, 2023

    @liuzhenqi77

    Similar open issue #4822.
    Is there an easier way to specify the exact font to use with matplotlib.rcParams['font.family'] or alike?

  10. github-actions commented on Jun 24, 2024

    @github-actions

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  11. added
    keepItems to be ignored by the “Stale” Github Action
    and removed
    status: inactiveMarked by the “Stale” Github Action
    on Jun 24, 2024
  12. bmcfee commented on Jul 21, 2025

    @bmcfee
    Contributor

    (i.e. one needs to write sans-serif if the weight is given, but sans\-serif if not).

    Just nudging this issue - I ran into this exact issue just now and was very confused about sans working but sans-serif not working.

  13. QuLogic commented on Jul 22, 2025

    @QuLogic
    Member

    The mixed constructor forms are supposed to have been deprecated: #28843 Now it should be either:

    FontProperties(family='sans-serif')  # Specific property
    FontProperties('sans\-serif')  # FontConfig pattern
    

    However, while there is a deprecation for other forms (mixing positional and keyword arguments), it seems that the former still goes through the FontConfig path. This seems like an oversight, but I'm not sure if @timhoffm specifically left that code path to be deprecated later after the more egregious ones.

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

    keepItems to be ignored by the “Stale” Github Actiontopic: text/fonts

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions