GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — ft2font: Add internal API for accessing font variations by QuLogic · Pull Request #31434 · matplotlib/matplotlib

Conversation

@QuLogic

@QuLogic QuLogic commented Apr 1, 2026

Copy link
Copy Markdown
Member

PR summary

This is internal API for querying font variations and setting them. FreeType sometimes calls them MM (multiple master) vars or variation axes, but I tried to stick to variations/variation axis. For the named styles, FreeType sometimes calls that a named style or a named instance, and I tried to use named style everywhere.

Font variations essentially allow fonts to have multiple styles in one by varying some parameter along an "Axis". For example, one font can contain all weights with a Weight axis ranging from Thin to Extra Bold, or an upright and italic font with a Slant axis for the angle. Some fonts may contain named styles which group some specific settings together under a a name (e.g., Bold may be Weight=700 or whatever the designer decides.)

Current internal API additions are:

  • FT2Font.get_variation_descriptor which returns a list of the variation axes and a list of the named styles.
  • FT2Font.get_default_variation_style which returns the index of the default name style (in the above list.)
  • FT2Font.get_variations which returns the float values for each axis (in the order of the above list.)
  • FT2Font.set_variations which allows setting the float values for each axis as a list, or selecting a named style by index.

This does not contain any public API; for that there are some questions that we might need to think about:

  • Should users select a font (by name or otherwise) and then apply variations with something like Text.set_fontvariations and/or FontProperties.set_variations?
  • Or, should variable fonts match anything within their range?
    • That is, if a variable font has a Weight axis between 100 and 1000, should that font automatically match ("FontName", weight="bold") / ("FontName", weight="regular"), etc.
    • If a variable font has a named style for, e.g., Bold, should that automatically add a name of "FontName Bold" as well (as in ENH: Allow fonts to be addressed by any of their SFNT family names #31183)?

I do not expect this to be ready for 3.11 until we discuss some of these options.

AI Disclosure

None

PR checklist

  • "closes #0000" is in the body of the PR description to link the related issue
  • new and changed code is tested
  • Plotting related features are demonstrated in an example
  • New Features and API Changes are noted with a directive and release note
  • Documentation complies with general and docstring guidelines

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

rokokol reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@QuLogic QuLogic added topic: text/fonts status: needs comment/discussion needs consensus on next step labels Apr 1, 2026
@QuLogic QuLogic added this to Font and text overhaul Apr 1, 2026
@github-project-automation github-project-automation Bot moved this to Waiting for other PR in Font and text overhaul Apr 1, 2026
@tacaswell tacaswell added this to the v3.12.0 milestone Apr 2, 2026
@QuLogic

QuLogic commented Apr 3, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

As an example, one can look at Roboto Flex, which has 13 axes (though more than half are hidden):

>>> from matplotlib.ft2font import FT2Font
>>> font = FT2Font('RobotoFlex-VariableFont.ttf')
>>> axes, styles = font.get_variation_descriptor()
>>> for axis in axes:
...     print(axis)
VariationAxis(name='OpticalSize', minimum=8, default=14, maximum=144, tag=1869640570, names={(3, 1, 1033): 'Optical Size'}, flags=VarAxisFlags.DEFAULT)
VariationAxis(name='Weight', minimum=100, default=400, maximum=1000, tag=2003265652, names={(3, 1, 1033): 'Weight'}, flags=VarAxisFlags.DEFAULT)
VariationAxis(name='GRAD', minimum=-200, default=0, maximum=150, tag=1196572996, names={(3, 1, 1033): 'Grade'}, flags=VarAxisFlags.DEFAULT)
VariationAxis(name='Width', minimum=25, default=100, maximum=151, tag=2003072104, names={(3, 1, 1033): 'Width'}, flags=VarAxisFlags.DEFAULT)
VariationAxis(name='Slant', minimum=-10, default=0, maximum=0, tag=1936486004, names={(3, 1, 1033): 'Slant'}, flags=VarAxisFlags.DEFAULT)
VariationAxis(name='XOPQ', minimum=27, default=96, maximum=175, tag=1481592913, names={(3, 1, 1033): 'Parametric Thick Stroke'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='YOPQ', minimum=25, default=79, maximum=135, tag=1498370129, names={(3, 1, 1033): 'Parametric Thin Stroke'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='XTRA', minimum=323, default=468, maximum=603, tag=1481921089, names={(3, 1, 1033): 'Parametric Counter Width'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='YTUC', minimum=528, default=712, maximum=760, tag=1498699075, names={(3, 1, 1033): 'Parametric Uppercase Height'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='YTLC', minimum=416, default=514, maximum=570, tag=1498696771, names={(3, 1, 1033): 'Parametric Lowercase Height'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='YTAS', minimum=649, default=750, maximum=854, tag=1498693971, names={(3, 1, 1033): 'Parametric Ascender Height'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='YTDE', minimum=-305, default=-203, maximum=-98, tag=1498694725, names={(3, 1, 1033): 'Parametric Descender Depth'}, flags=VarAxisFlags.HIDDEN)
VariationAxis(name='YTFI', minimum=560, default=738, maximum=788, tag=1498695241, names={(3, 1, 1033): 'Parametric Figure Height'}, flags=VarAxisFlags.HIDDEN)
>>> for style in styles:
...     print(style)
VariationNamedStyle(names={(3, 1, 1033): 'Thin'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'ExtraLight'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Light'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Regular'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Medium'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'SemiBold'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Bold'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'ExtraBold'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Black'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'ExtraBlack'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Thin Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'ExtraLight Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Light Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Medium Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'SemiBold Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Bold Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'ExtraBold Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'Black Italic'}, psnames=None)
VariationNamedStyle(names={(3, 1, 1033): 'ExtraBlack Italic'}, psnames=None)
>>> font.get_default_variation_style()
4
>>> font.get_variations()
[14.0, 400.0, 0.0, 100.0, 0.0, 96.0, 79.0, 468.0, 712.0, 514.0, 750.0, -203.0, 738.0]
>>> font.set_variations([i + 20 for i in font.get_variations()])  # Change all variation axes.
>>> font.get_variations()
[34.0, 420.0, 20.0, 120.0, 20.0, 116.0, 99.0, 488.0, 732.0, 534.0, 770.0, -183.0, 758.0]
>>> font.set_variations(11)  # This is Thin Italic (it appears to be 1-based index; we may want to change that)
>>> font.get_variations()
[14.0, 100.0, 0.0, 100.0, -10.0, 96.0, 79.0, 468.0, 712.0, 514.0, 750.0, -203.0, 738.0]  # Now [1] (weight) is 100 and [4] (slant) is -10

You can see an example of using these font variations in mplcairo. Note that mplcairo uses the suffix-the-filename approach that was also used for font features. We likely won't do that here since we didn't do that for font features either.

mplcairo calls Cairo's cairo_font_options_set_variations which takes a string of comma-separated tag=value settings. The tags in the above output are currently 32-bit integers, but if we treat them as 4 bytes in ASCII, they should correspond with what Cairo uses.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@QuLogic
QuLogic force-pushed the font-variations branch from 781e309 to b5bda9b Compare April 3, 2026 07:12
@QuLogic
QuLogic changed the base branch from text-overhaul to main April 11, 2026 01:25
@QuLogic
QuLogic force-pushed the font-variations branch from b5bda9b to d48619c Compare April 11, 2026 01:26
@github-actions github-actions Bot added the status: needs rebase label Jul 24, 2026
@QuLogic
ft2font: Add internal API for accessing font variations
a432f53
@QuLogic
QuLogic force-pushed the font-variations branch from d48619c to a432f53 Compare August 10, 2026 22:46
@github-actions github-actions Bot removed the status: needs rebase label Aug 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

status: needs comment/discussion needs consensus on next step topic: text/fonts

Projects

Font and text overhaul
Status: Waiting for other PR

Milestone

v3.12.0

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@QuLogic @tacaswell
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.