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

libass doesn't know how to handle OpenType Variable Fonts #386

Open
sheik124 opened this issue Apr 1, 2020 · 9 comments
Open

libass doesn't know how to handle OpenType Variable Fonts #386

sheik124 opened this issue Apr 1, 2020 · 9 comments

Comments

@sheik124
Copy link

sheik124 commented Apr 1, 2020

https://docs.microsoft.com/en-us/typography/font-list/bahnschrift

This is the font in reference, couldn't include the TTF since Microsoft doesn't seem to allow redistribution. If you have a current install of Windows 10, you have Bahnschrift.ttf already.

Noticed this behavior in MPV and Plex, did some homework and found out they use libass.

Bahnschrift (and other variable fonts) have variable weight and width. Some also include "preset" combinations of weight/width - e.g. regular Bahnschrift is 400 wt 100 wd, but Bahnschrift Condensed is 400 wt 75 wd. If I pick "Bahnschrift Condensed" as a font, MPV and Plex can't display it properly, with MPV showing the regular version, and Plex showing a misscaled bigger version of regular Bahnschrift, even with Bahnschrift.ttf embedded. XySubFilter (or perhaps LAV Splitter?) can properly find the font and select "Condensed", whether it's only attached to the MKV or installed on the system.

Current workaround was to use FontLab 7 (trial, paid) to split Bahnschrift.ttf into independent .otf fonts based on weight. If I pick the new "Bahn-Cond" font I made and attach bond-cond.otf, libass can display it properly.

I don't know if it has anything to do with Aegisub 3.2.2's font name handling.

@TheOneric
Copy link
Member

TheOneric commented Apr 28, 2020

To ease testing, since Microsofts font is hard to get:
Here's an open-source OpenType Varaible Font variable in width and weight and width predefined/named Variations: https://github.com/googlefonts/Inconsolata
Another one would be: https://github.com/jenskutilek/homecomputer-fonts
Or adobes testing prototype for variable fonts: https://github.com/adobe-fonts/adobe-variable-font-prototype

Using Inconsolata-VF.ttf, I tested the following ASS event:

Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\fs96}{\bord6\b0} Test\N  {\fnInconsolata Extra-light}Test {\fnInconsolata Light}Test {\fnInconsolata}Test {\fnInconsolata\b1}Test{\b0} {\fnInconsolata Black}Test\N{\fnInconsolata} {\b200} Test {\b300}Test {\b400}Test {\b700}Test {\b900}Test

First line is for comparsion with default font, second line attempts to use named weight variations and third selects the same weight variations via {\bxxx} (the latter will not work in VSFilter).
Here's the result I got with libass (using fontconfig 2.13.1):
grafik

As you can see, the \b[100,900]-tags work in libass, but they will not work in VSFilter, and the named variants, which work in VSFilter, do currently not work in libass.

@sheik124
Copy link
Author

Huh. Thanks. It looks like it can't actually parse the weights when you write them as names, right?

The second line should show Extra-Light, Light, b1, b0, and Black? But whenever you're not using \b tags, it just uses the regular font.

@TheOneric
Copy link
Member

TheOneric commented May 29, 2021

An update/correction: According to @astiob's research, VSFilter can't actually select all weight variants of an embedded fonts via \b[100,900]; instead only the named width subsets can be select via \fn and the bold/italic variant of that weight then via \b1, \i1. With Windows 7, Windows XP and presumably older Windows version, only the regular variant of a variable fonts can be used at all.
This means, for portability \b[100,900] should not be used with variable fonts, and unfortunately this also means there currently is no way to use anything but the regular variant of an variable font portably between VSFilter (executed on Windows 10) and libass (and there will never be a way to use named variable fonts variants except Regular in VSFilter executed on Windows 7 and older; likely there also will never be a way to use un-named variants of a variable font in VSFilter regardless of Windows version)

I've updated my previous post to reflect that \b[100,900]-tags are not portable to VSFilter.


EDIT (2021-07-11):
Another clarification, since this wasn't clear from my previous posts: In current libass + fontconfig, only named variants can be selected via \b-tags, see:
addendum

@moi15moi
Copy link
Contributor

This message explain how to generate the name for an Variation font.

Overall, there is 3 main steps.

1- Family (each platform does this)

If an Typographic Family Name (nameID 16) is available, take it.
If not, take the Family name (nameID 1).

It seems to follow these recommandations (see Font family): https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#terminology

Implementation with Freetype:
Try to get the TT_NAME_ID_TYPOGRAPHIC_FAMILY by callingFT_Get_Sfnt_Name and FT_Get_Sfnt_Name_Count.
If nothing found, use the TT_NAME_ID_FONT_FAMILY.

2- Style

For DWrite:

1- Use the ElidedFallbackNameID to create an "default instance"
The ElidedFallbackNameID is available in the Stat table.

2- For each Axis Value, InstanceRecord, map them to an InstanceRecord.

To map them, it is different depending on the format:

Implementation:
Freetype does not handle the Stat table.
I asked the question on freetype@nongnu.org. Here is their answer

I there a way to get the AxisRecord in the Stat table?
https://learn.microsoft.com/en-us/typography/opentype/spec/stat#axis-records

No, there isn't: FreeType has no extra support for this table. You
would have to load the table with FT_Load_Sfnt_Table and parse it by
yourself.

Note, however, that I strongly recommend to not doing this. You
should rather use a higher-level library such as 'HarfBuzz', which
handles all the OpenType intricacies automatically for you, including
the 'STAT' table.

do you know if freetype will ever exposed the Stat table?

It won't.

So, I have looked into harfbuzz, but to me, it doesn't seems to exposed the all the data from the Axis Value table, so I don't know how we can use it.
I have asked the question into harfbuzz "forum": harfbuzz/harfbuzz#3882

Also, it doesn't seems to expose the elidedFallbackNameID.

In brief, I hardly see how libass can replicate DWrite because it is pretty complicated to get the Stat table.

For CoreText and FontConfig

Each of them seems to only use the subfamilyNameID from the InstanceRecord.
I am not 100% sure about both of them, but if there is something else, it is minimal.

Implementation:
Do that for each face of the font:

I strongly suggest you to read the face_index input detail from FT_Open_Face

3- Merge the family and the style to create the name

Notes

What means "default instance"?

It is the defaultValue for each VariationAxisRecord.

What happen if we only write the font without a style (Ex: Bahnschrift)?

It will display the "default instance".

@sheik124
Copy link
Author

Thank you @moi15moi that is insightful.

@TheOneric was any progress made on trying to implement proper support for these fonts? I see a lot of issues were opened with similar issues, and one abandoned PR.

@TheOneric
Copy link
Member

TheOneric commented Jan 28, 2023 via email

@moi15moi
Copy link
Contributor

moi15moi commented Feb 8, 2023

I have tried to look how GDI create face font variable font.

Here is a gist where I have tried to implement, with fonttools, how GDI create a "face". See: https://gist.github.com/moi15moi/dd0fd510c03c7d80a274d69bf2edfb29

If you wanna test how GDI parse the font, you can use this script: https://gist.github.com/moi15moi/3b1f48448f0ea1798beab080cb83821b

Finally, if you need to create some test, you can see the one on this PR: moi15moi/FontCollector#19
It cover pretty much all the case I could think of.

Important to note:

  • Even if GDI take the bold/italic value from the Axis table, GDI use the coordinates from the NamedInstance to display the face.
    Example:
    Let's suppose we have this NamedInstance
<NamedInstance flags="0x0" subfamilyNameID="268">
<coord axis="wdth" value="400"/>
</NamedInstance>

And this AxisValue

<AxisValue index="1" Format="1">
	<AxisIndex value="0"/>
	<Flags value="0"/>
	<ValueNameID value="277"/>
	<Value value="301"/>
</AxisValue>

GDI will set the weight to 301, but it will display the 400 one.

@astiob
Copy link
Member

astiob commented Jan 22, 2024

Testing with Segoe UI Variable installed on my system (from #730, but also bundled with modern Windows), I find that asking GDI for simply “Segoe UI Variable” (which is both the family name and the full name in the name table) doesn’t find any font at all (and gives me Arial instead). Asking GDI for a named variant does finds the font.

This is very concerning.

FWIW querying DirectWrite with DWRITE_FONT_PROPERTY_ID_FAMILY_NAME does list all the variants—but it similarly does not list any face named simply “Segoe UI Variable”.

@moi15moi
Copy link
Contributor

I find that asking GDI for simply “Segoe UI Variable” doesn’t find any font at all

This is normal. It happen because none of the AxisValue use the flags ELIDABLE_AXIS_VALUE_NAME.
In general, variable font set this flags for the regular weight axis, but it can happen that it doesn't like in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants