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

Font issue: findfonts should differentiate between thin and regular ttf fonts · Issue #8788 · matplotlib/matplotlib · GitHub

Repository navigation

Font issue: findfonts should differentiate between thin and regular ttf fonts #8788

Closed

Description

Bug report

Bug summary

The selection criteria for finding fonts using the algorithm in matplotlib.font_manager.findfonts are incomplete. The problem arises with high-quality TTF fonts that have many different styles, for instance "Helvetica Neue", or "Lato". For instance, the "Thin" versions of these fonts are indistinguishable from the "Regular" versions by their font weight (both have e.g. a value of 400) such that the font that is returned by findfonts depends on the ordering of the list matplotlib.font_manager.fontManager.ttflist.

A workaround would be to simply uninstall the unwanted versions of the conflicting fonts from the system. Unfortunately, for the figures that I am trying to create, "Helvetica Neue" is mandatory and it is also an OSX system font, thus cannot be disabled.

I am unsure whether the fonts (for instance) "HelveticaNeue-Thin.ttf" and "HelveticaNeue.ttf" could be distinguished by adding another font metric to the Font class. If not, the proposed solution would be to allow selecting fonts in matplotlib.rcParams by their file name, e.g. setting mathtext.rm : "HelveticaNeue.ttf". This solution would require changing the fontconfig parser in matplotlib.fontconfig_pattern.py to allow filenames.

Code for reproduction

The result of the code below is system-dependent, even when exactly the same font files are installed, because of the ordering of matplotlib.font_manager.fontManager.ttflist.

import matplotlib.font_manager as fm
p = fm.FontProperties(family = 'Helvetica Neue')
fm.findfont(p)

--> 'HelveticaNeue.ttf' or 'HelveticaNeue-Thin.ttf'

The properties of the corresponding Font objects are identical (see matplotlib.font_manager.fontManager.ttflist)

<Font 'Helvetica Neue' (HelveticaNeue.ttf) normal normal 400 normal>
<Font 'Helvetica Neue' (HelveticaNeue-Thin.ttf) normal normal 400 normal>

Actual outcome

See above, system-dependent.

Expected outcome

Findfonts should be deterministic.

Matplotlib version

  • Operating System: OSX, Windows
  • Matplotlib Version: 2.0.0
  • Python Version: 3.6.1

Activity

  1. tacaswell commented on Jun 22, 2017

    Member

    Related to #8551 #8607 #5250 #8550 #7931

    This seems to have become a major problem recently.

  2. added this to the milestone on Jun 22, 2017
  3. modified the milestones: , on Sep 24, 2017
  4. lukelbd commented on Dec 18, 2019

    Contributor

    This has been a recurring issue when adding new fonts to proplot -- I have to manually delete all of the "thin" files. Looks like the issue is in font_manager.ttfFontProperty. Here is where styles and weights are read:

    def ttfFontProperty(font):
    """
    Extract information from a TrueType font file.
    Parameters
    ----------
    font : `.FT2Font`
    The TrueType font file from which information will be extracted.
    Returns
    -------
    `FontEntry`
    The extracted font properties.
    """
    name = font.family_name
    # Styles are: italic, oblique, and normal (default)
    sfnt = font.get_sfnt()
    # These tables are actually mac_roman-encoded, but mac_roman support may be
    # missing in some alternative Python implementations and we are only going
    # to look for ASCII substrings, where any ASCII-compatible encoding works
    # - or big-endian UTF-16, since important Microsoft fonts use that.
    sfnt2 = (sfnt.get((1, 0, 0, 2), b'').decode('latin-1').lower() or
    sfnt.get((3, 1, 0x0409, 2), b'').decode('utf_16_be').lower())
    sfnt4 = (sfnt.get((1, 0, 0, 4), b'').decode('latin-1').lower() or
    sfnt.get((3, 1, 0x0409, 4), b'').decode('utf_16_be').lower())
    if sfnt4.find('oblique') >= 0:
    style = 'oblique'
    elif sfnt4.find('italic') >= 0:
    style = 'italic'
    elif sfnt2.find('regular') >= 0:
    style = 'normal'
    elif font.style_flags & ft2font.ITALIC:
    style = 'italic'
    else:
    style = 'normal'
    # Variants are: small-caps and normal (default)
    # !!!! Untested
    if name.lower() in ['capitals', 'small-caps']:
    variant = 'small-caps'
    else:
    variant = 'normal'
    if font.style_flags & ft2font.BOLD:
    weight = 700
    else:
    weight = next((w for w in weight_dict if w in sfnt4), 400)
    # Stretch can be absolute and relative
    # Absolute stretches are: ultra-condensed, extra-condensed, condensed,
    # semi-condensed, normal, semi-expanded, expanded, extra-expanded,
    # and ultra-expanded.
    # Relative stretches are: wider, narrower
    # Child value is: inherit
    if any(word in sfnt4 for word in ['narrow', 'condensed', 'cond']):
    stretch = 'condensed'
    elif 'demi cond' in sfnt4:
    stretch = 'semi-condensed'
    elif any(word in sfnt4 for word in ['wide', 'expanded', 'extended']):
    stretch = 'expanded'
    else:
    stretch = 'normal'
    # Sizes can be absolute and relative.
    # Absolute sizes are: xx-small, x-small, small, medium, large, x-large,
    # and xx-large.
    # Relative sizes are: larger, smaller
    # Length value is an absolute font size, e.g., 12pt
    # Percentage values are in 'em's. Most robust specification.
    if not font.scalable:
    raise NotImplementedError("Non-scalable fonts are not supported")
    size = 'scalable'
    return FontEntry(font.fname, name, style, variant, weight, stretch, size)

    What can we do to detect the 'thin' property? Also is 'thin' a style or a weight?

  5. anntzer commented on Dec 18, 2019

    Contributor

    If you're on linux (macos perhaps? if fontconfig is present) you can use https://github.com/anntzer/mplfccache to generate a more accurate font cache (asking fontconfig to list the font properties for you).

    I have translated into Python the actual algorithm used by fontconfig to determine font properties at https://github.com/anntzer/freetypybind/blob/master/lib/freetypybind/query_face.py (using my own freetype binding but that shouldn't matter much); someone "just" needs to PR that into Matplotlib...

  6. anntzer commented on May 28, 2020

    Contributor

    Mostly closed by #16203.

  7. modified the milestones: needs sorting, v3.3.0 on May 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL