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

directwrite: fix match_fonts #503

Merged
merged 1 commit into from May 12, 2021
Merged

Conversation

Apache553
Copy link
Contributor

match_fonts is supposed to add all the fonts with the same family name for fontselect. The previous PR adds only one font, which leads to font with multiple styles cannot be correctly selected.


add_font(font, fontFamily, provider);
UINT32 fontCount = IDWriteFontFamily_GetFontCount(fontFamily);
for (UINT32 fontIdx = 0; fontIdx < fontCount; ++fontIdx) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of style nits: libass uses snake_case instead of camelCase, so it's better to follow that. Also I think that such long names for variables used in a couple of neighboring lines is overkill: n is enough for fontCount and i for fontIdx. And AFAIK we've decided to prefer postfix increment.

Copy link
Member

@astiob astiob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks good, although I haven’t tested. (I can easily test Windows binaries, but building my own is not very convenient.) There’s a naming bikeshed, but I’m not sure how long we should dwell on it.

Note: #502 implies this whole approach isn’t good enough for font managers that dynamically activate fonts, and native GDI calls are needed to cater for them in any case. This isn’t a regression—our DirectWrite provider has never worked with that—but if we do pursue that, then it seems to me that this code is effectively a stopgap.

My remaining worry is: is it possible that DirectWrite’s notion of “family” is too narrow in some situation, and the system has another font with the given name that it fails to count as part of the family and therefore fails to give us?

@@ -677,7 +677,7 @@ static void match_fonts(void *priv, ASS_Library *lib,
{
ProviderPrivate *provider_priv = (ProviderPrivate *)priv;
IDWriteFont *font = NULL;
IDWriteFontFamily *fontFamily = NULL;
IDWriteFontFamily *fontfamily = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a little naming war here 😄 add_font spells this as fontFamily, matching DirectWrite’s GetFontFamily(IDWriteFontFamily **fontFamily), so I took the liberty to change it in the first PR to match. @MrSmile wants snake_case. You seem to like fontfamily as a single word.

Currently, this file uses a mix of snake_case and camelCase in its existing names but seems to spell each individual name consistently. My impression is that it uses camelCase for formal arguments in function prototypes and for local variables used as actual arguments in function calls, and snake_case for everything else. This is likely intended to match DirectWrite’s parameter names and parameter naming convention.

Given this, to me, it (still) seems most consistent to name this fontFamily as it’s used as the actual argument passed as fontFamily in the add_font calls. Your thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that naming style should be consistent throughout libass, i.e. always snake_case when possible (perhaps excluding copy-pasted prototypes, but I would have adapted even them). So I think that argument of add_font should be font_family (or maybe even simply family). It's not a target for this PR to fix style of old code, but I think all new variables should follow libass style conventions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I always believe local consistency trumps global consistency. If this hunk is to introduce font_family, we’ll end up with two different names for the same thing in this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I always believe local consistency trumps global consistency.

In that case style violation once introduced risks to never get dedicated cosmetic PR to fix it. Also such restyle-only commits complicate code history and blaming. I think incremental fixing around new changes is superior. And in this case I believe there are no consistency at all, as even local variables in the same function have named differently.

Copy link
Member

@astiob astiob May 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses camel case for DirectWrite function arguments to match their declared formal parameter names, and it propagates these camel-case names throughout the code to ensure each name has exactly one spelling throughout the file. It uses snake case for other variables and fields to match libass’s default style.

This may have been a poor choice, but at least it makes some sense to me and is easy to explain and describe. If we start half-changing existing names, it will just be chaos instead.

For fontFamily especially, just searching for fontFamily within the file currently immediately brings up the full chain of control it goes through.

Also such restyle-only commits complicate code history and blaming.

When a logic change touches a line anyway, then fixing minor style nits at the same time reduces noise. But with larger changes, I find it to be the exact opposite: when blaming, if I can tell that the last commit to change the line is purely stylistic from the commit message, I can immediately skip over it; but if it’s a commit that mixes logic with style, I must spend time figuring out why that commit touched this line.

In this particular case, I see a change from fontFamily as increasing entropy, not decreasing it. It would be interesting to hear the opinions of @rcombs and @TheOneric, as well as a word from @Apache553 who used neither option and wrote it as a single word :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there seems to be some regularity to the current naming and to preserve the ability to find all instances of a variable being used (even in other functions it's passed to), sticking to fontFamily in this commit seems reasonable to me.
Also adjusting all other instances of fontFamily in this commit would just introduce noise. If we want to change this, it should be done for the entire file and preferably in a separate commit.

@Apache553
Copy link
Contributor Author

Apache553 commented May 8, 2021

Note: #502 implies this whole approach isn’t good enough for font managers that dynamically activate fonts, and native GDI calls are needed to cater for them in any case. This isn’t a regression—our DirectWrite provider has never worked with that—but if we do pursue that, then it seems to me that this code is effectively a stopgap.

For font managers that dynamically load fonts to avoid font installation, the possible cause that some programs using DirectWrite failed to find the font is the system font cache. The system font collection used by libass in previous verison to enumerate installed fonts may be not updated without delay (see msdn). It's like some kind of magic due to the lack of documentation of the font cache. So I don't have much idea. But at least my tools using AddFontResourceExW work well with current code...

My remaining worry is: is it possible that DirectWrite’s notion of “family” is too narrow in some situation, and the system has another font with the given name that it fails to count as part of the family and therefore fails to give us?

As the MSDN page tells:

A font family is a set of fonts that share the same family name

This family name comes from truetype/opentype name table. So I think DirectWrite's "family" is what we expected. By the way, for some historical reasons, a font family in GDI can only hold up to 4 fonts. So the GDI family name may differ from its real family name. I am not sure whether it will be a problem. But I guess it's ok since we have done font selection by ourselves.

@astiob
Copy link
Member

astiob commented May 8, 2021

This family name comes from truetype/opentype name table. So I think DirectWrite's "family" is what we expected.

Yes, but a single font can have many names in its name table :-) I don’t know how exactly DirectWrite groups them. But neither do you, I imagine.

By the way, for some historical reasons, a font family in GDI can only hold up to 4 fonts. So the GDI family name may differ from its real family name. I am not sure whether it will be a problem. But I guess it's ok since we have done font selection by ourselves.

Hmmmm. Are you saying GDI’s font selection can be weird when there are more than four same-family-name fonts available?

Our goal in font selection is to emulate GDI’s CreateFontIndirect, which is used by VSFilter. So far, I don’t think we’ve received complaints that font families with many variants don’t work. But if they don’t, we want to make sure we handle them like GDI does.

@Apache553
Copy link
Contributor Author

I don’t know how exactly DirectWrite groups them.

IIRC the family name have its own id. id 1 for gdi-compatible family name, and id 16 for typographic family name (my real family name). DirectWrite provides almost each name according to the id as IDWriteLocalizedStrings, which is a string list can be indexed by locale that enables us to get every variant of those names.

Hmmmm. Are you saying GDI’s font selection can be weird when there are more than four same-family-name fonts available?

Yes. I have once installed a third-party font which shared the family name with "Heiti", which is a basic system font. Then every place using GDI and "Heiti" is showing the newly installed font so that I have to remove it. I think it's an abnormal condition something like the undefined behavior of c++. The behavior was shown long time ago and I cannot try it again since I lost those troublesome fonts. I think there is no need to emulate that until someone complains.

@astiob
Copy link
Member

astiob commented May 8, 2021

DirectWrite provides almost each name according to the id as IDWriteLocalizedStrings, which is a string list can be indexed by locale that enables us to get every variant of those names.

Yep, in this context I’m primarily worried about localized names (because I can’t really imagine what could go wrong other than localized names). Something stupid like, two fonts that share a Chinese family name but have different English family names. Then again, localized names probably pose a problem in any case, as Windows presumably still matches the English name and the current-system-locale name but not other names. Ultimately, we can’t fully 100% truly always match VSFilter on localized names anyway, as it’s using a GDI call that does exactly that kind of locale-sensitive font selection and we can never know what locale the script author used. At that point, the stupid worrying case rather becomes two fonts that share an English family name and have different Chinese family names.

I have once installed a third-party font which shared the family name with "Heiti", which is a basic system font. Then every place using GDI and "Heiti" is showing the newly installed font so that I have to remove it.

Doesn’t that seem like merely one font overriding the other in a situation where they can’t be distinguished by any parameter? But in any case, we’ll try to investigate many-font families.

@Apache553
Copy link
Contributor Author

Windows presumably still matches the English name and the current-system-locale name but not other names.

It seems that Windows matches fonts with all available names. For example, I can call CreateFontIndirect with a LOGFONT whose lfFaceName set to "源ノ明朝 JP" to get "Source Han Serif JP" on my Simplified Chinese version Windows.

And for those two fonts share a Chinese family name but have different English family names or vice versa, I think it is the script writer's responsibility to choose proper font name used in the script, since those fonts are non-standard and hard to handle programmatically. The behavior of GDI under such condition is not well-known, too. So it will be not easy to emulate that. And the result of CreateFontFromLOGFONT is presumably made by GDI's font selector. If we have to face such circumstances, at least we can know that the returned font and its family are the GDI preferred ones.

match_fonts is supposed to add all the fonts with the same family name
for fontselect. Commit d325c63 adds
only one font, which leads to inability to select (using family name)
correct fonts from families with multiple styles.
@astiob
Copy link
Member

astiob commented May 12, 2021

At any rate, this is an improvement, and there are no substantial complaints, so merging this. Gonna give it a few days in master for mpv builds to propagate and reconfirm that this fixes the reported regression instances and doesn’t introduce new ones, and then release 0.15.2 with the fix.

Task to self, or any other interested party with Windows access: create (or find) a bunch of fonts that share family names in various combinations and test how this behaves (and how VSFilter behaves just to be sure).

@astiob astiob merged commit b9f3468 into libass:master May 12, 2021
@astiob astiob added this to the 0.15.2 milestone May 12, 2021
@astiob
Copy link
Member

astiob commented May 23, 2021

Windows presumably still matches the English name and the current-system-locale name but not other names.

It seems that Windows matches fonts with all available names. For example, I can call CreateFontIndirect with a LOGFONT whose lfFaceName set to "源ノ明朝 JP" to get "Source Han Serif JP" on my Simplified Chinese version Windows.

Family names, yes. I can find Chinese (with English and two Chinese names) and Japanese fonts (with English and Japanese names) by any family name in GDI/VSFilter and in DirectWrite/libass.

Full names, no. In GDI/VSFilter on Russian Windows 7, I can only find bold Arial and Verdana by their Russian full names. Not even by their English full names!


More importantly, our latest DirectWrite font provider seems to fail to find fonts by any full name or PostScript name. This is a problem. @Apache553 Any clue as to why this is happening?

@Apache553
Copy link
Contributor Author

For full names, I have no idea about that.

For Postscript names, it's a blind spot since I never use PS names to match fonts (some font contains bad ps names). After some testing, it turned out that DirectWrite would not match any PostScript names. So I think it would be best to use CreateFontIndirect and CreateFontFaceFromHdc things to make things work just like VSFilter did.

@Apache553
Copy link
Contributor Author

I made a patch to this.

@astiob
Copy link
Member

astiob commented May 23, 2021

For reference, DirectWrite’s font families are supposedly based on WWS family names (which have an explicit name ID 21, as opposed to GDI [name ID 1] and typographic [name ID 16] family names).

But so far I haven’t been able to verify this with a font actually equipped with explicit WWS names.

Weirdly though, testing a random copy of Minion Pro Caption (which is used as an example on MSDN to explain WWS names) that does not have explicit WWS names and whose OS/2 table version predates the introduction of WWS names, DirectWrite on Windows 7 is somehow able to tell that it’s not part of “Minion Pro” even though its typographic family name says it is. Font browser shows it as “Minion Pro Caption ”. Opening the font shows “Minion Pro Capt” (its GDI family name). It’s worth noting that none of this font’s Microsoft-platform names are “Minion Pro Caption”, but it can be obtained either as the Macintosh-platform full name (doubtful) or as typographic family + typographic subfamily (more likely… but it gets an extra Regular!). So it seems there’s some kind of WWS name synthesis logic built in.

@astiob
Copy link
Member

astiob commented May 23, 2021

As for the patch, FYI: the reason DWriteCreateFactory is dynamically requested via GetProcAddress is to support running DirectWrite-enabled builds on Windows XP, which lacks DirectWrite. GDI exists everywhere, so it can be used directly.

@astiob
Copy link
Member

astiob commented May 23, 2021

our latest DirectWrite font provider seems to fail to find fonts by any full name or PostScript name.

To be fair, it probably makes sense! CreateFontFromLOGFONT is declared to convert a font from GDI to DirectWrite, not search for a font. Like, it’s something you’re expected to use after getting a LOGFONT from GDI (e. g. from EnumFontFamiliesEx), not after constructing one of your own.

In fact, EnumFontFamiliesEx (used by Aegisub) does not match full/PostScript names either!

In fact, Aegisub’s code seems to fail to find “Cambria”, which is its sole family, full and PostScript name (as well as “Cambria Math”). Excuse me, but what the fuck. And it does find {\fnCambria\i1} or {\fnCambria\b1}, but not the Regular Cambria.


Going back to localized names for a moment, Microsoft seems to contradict itself in its CreateFontIndirect docs:

[…] CreateFont and CreateFontIndirect take the localized typeface name only on a system locale that matches the language, while they take the English typeface name on all other system locales. The best method is to try one name and, on failure, try the other. […]

The font mapper for CreateFont, CreateFontIndirect, and CreateFontIndirectEx recognizes both the English and the localized typeface name, regardless of locale.

@astiob
Copy link
Member

astiob commented May 23, 2021

In fact, Aegisub’s code seems to fail to find “Cambria”, which is its sole family, full and PostScript name (as well as “Cambria Math”). […] And it does find {\fnCambria\i1} or {\fnCambria\b1}, but not the Regular Cambria.

Turns out it doesn’t find those fonts that are in .ttc font collection files. Great.

@astiob
Copy link
Member

astiob commented May 24, 2021

Turns out it doesn’t find those fonts that are in .ttc font collection files. Great.

I suspect that it’s Aegisub’s fault and its GdiFont::GetData either fails entirely or gets incomplete/wrong data. After all, if Enum... were to skip TTC fonts, {\fnCambria} should still show a bold or italics Cambria. Even if it returns the full file, Aegisub doesn’t implement get_font_index.

Moving on.

@Apache553
Copy link
Contributor Author

In fact, EnumFontFamiliesEx (used by Aegisub) does not match full/PostScript names either!

We need to clarify what name is supposed to appear in the script files(ass/ssa). After checking all script files I have (ass), the Fontname field in [V4+ Styles] section seems always an id 1 name of a font.

I also have done some testing with both CreateFontIndirect and CreateFontFromLOGFONT and the result shows neither supports a PostScript name. And I can't make xy-VSFilter to load a font by PostScript name.

As the doc of LOGFONT says: the lfFaceName is a typeface name. To my understanding, a typeface name is a family name, but not a full name. So I think we should always assume a given font name is an id 1 family name. The latest code should just work fine. For most compatibility with VSFilter, the patch may help.

@astiob
Copy link
Member

astiob commented May 24, 2021

I also have done some testing with […] CreateFontIndirect […] and the result shows [it does not support] a PostScript name. And I can't make xy-VSFilter to load a font by PostScript name.

You must be testing TrueType fonts. CreateFontIndirect/VSFilter finds them by family or full name, and not by PostScript name.

But OpenType fonts with CFF (PostScript) outlines are found by family or PostScript name, and not by full name. Windows does not seem to come with any such fonts, at least Windows 7 (I can’t check 10 right now), so you may need to find a font elsewhere to test. For example, Source Sans Pro is a free OpenType/CFF font family.

(See also: matches_full_or_postscript_name in our fontselect, introduced in f16cf28/#203).

We need to clarify what name is supposed to appear in the script files(ass/ssa).

It’s useless to declare what is “supposed” to appear. People can write and have written anything, and we just need to support exactly what VSFilter supports.

Besides, PostScript and full names—when supported—are more exact than family names and hence provide authors better control over the font choice, so it makes sense to advise authors to prefer them.

@astiob
Copy link
Member

astiob commented May 24, 2021

I’ve written up all that I know so far (I hope) about the behaviour of the various APIs on https://github.com/libass/libass/wiki/Fonts-across-platforms.

Back to the proposed patch: for what it’s worth, it’s basically the same as #502. Which is cool (if a little ironic), but #502 says there are some issues, so we should investigate them. Let’s continue that discussion in #502, I think. But in any case, for maximum foolproof compatibility, it seems we should do

  • CreateFontIndirect to cater for full/PostScript names
  • and also EnumFontFamiliesEx on the original requested name to cater for name-id-1 family names, in case
    • DirectWrite’s family grouping differs,
    • some fonts provided by font manager are handled better by Enum... than DirectWrite (needs reconfirming),
    • CreateFontIndirect selects a font with a different family name.

@astiob astiob mentioned this pull request May 24, 2021
3 tasks
@Apache553
Copy link
Contributor Author

You must be testing TrueType fonts. CreateFontIndirect/VSFilter finds them by family or full name, and not by PostScript name.

You are right, I didn't realize the differences between how GDI handle TrueType and OpenType CFF fonts.

But in any case, for maximum foolproof compatibility, it seems we should do [...]

So what we should implement now is:

  1. Use CreateFontIndirect to match supplied font names.
  2. Replace DirectWrite's family enumeration code with EnumFontFamiliesEx.
  3. Convert found GDI font objects to DirectWrite font objects, then feed to add_font.

Do I understand correctly?

@astiob
Copy link
Member

astiob commented May 24, 2021

Yes… And now that I think about it, based on the Chrome patch linked in the other PR, we should most likely not use GetSystemFontCollection and GetFontFromFontFace as it would undo our attempts to cater for font managers, but rather work with the IDWriteFontFaces directly, or possibly even with the GDI fonts because I don’t quite see how to get the raw font data from an IDWriteFontFace. But if we were to forget about font managers, then going through GetFontFromFontFace should probably be fine.

Somewhat unrelatedly, I want to keep DirectWrite family enumeration for #513, although it’s a QOL improvement rather than a compatibility fix.

@Apache553
Copy link
Contributor Author

I don’t quite see how to get the raw font data from an IDWriteFontFace

There is a way to get raw font data from a single IDWriteFontFace. It has already been done in init_font_private_stream and get_data.

However, if we don't get a IDWriteFont, we need to read all kinds of names by ourselves. Since there are some old fonts have non-standard name encoding (e.g. 微软繁标宋, which claims its name's encoding_id equals to TT_MS_ID_PRC, but it stores GBK bytes in uint16_t bigendian) and Windows can handle that correctly, I think doing such may introduce new problems.

@astiob
Copy link
Member

astiob commented May 24, 2021

There is a way to get raw font data from a single IDWriteFontFace. It has already been done in init_font_private_stream and get_data.

Right; of course. I continued to read after posting that comment and concluded that the same code should work, but now I’m wondering about Type 1 fonts, which have multiple files. get_data has a comment saying “DirectWrite only supports one file per face”, but that is, of course, wrong; that’s the very reason the API is designed to give a list. I think we probably mishandle Type 1 fonts on all platforms with all font providers, and it’s certainly not a common use case, and in any case reusing our existing code in get_data would be fine and any improvements should be made in get_data itself.

we need to read all kinds of names by ourselves

We do that already in get_font_info in ass_fontselect.c for embedded fonts and system fonts on macOS. If there are names we fail to read, we should fix that function.

Since there are some old fonts have non-standard name encoding (e.g. 微软繁标宋, which claims its name's encoding_id equals to TT_MS_ID_PRC, but it stores GBK bytes in uint16_t bigendian)

So much for Microsoft’s spec saying, at the end of this section, that all Microsoft-platform names must be encoded in UTF-16. Welp.

微软繁标宋, which claims its name's encoding_id equals to TT_MS_ID_PRC, but it stores GBK bytes in uint16_t bigendian

WTF! I’ve obtained a copy and can confirm that’s what it has and it does work in WIndows 7.

@Apache553
Copy link
Contributor Author

Apache553 commented May 25, 2021

OK now I have read some codes from leaked Windows XP SP1 source. It seems that Windows does following to get names (using freetype names & contants, assuming FT_SfntName::platform_id == TT_PLATFORM_MICROSOFT):

  1. Check if FT_SfntName::encoding_id equals to TT_MS_ID_BIG_5 or TT_MS_ID_PRC or TT_MS_ID_WANSUNG. If true, go step 2, else go step 4.
  2. Copy FT_SfntName::string into an ANSI buffer. Go step 3.
    Details:
    • Check two bytes in FT_SfntName::string each time.
    • If the first byte equals to 0, copy only the second byte into the buffer. ANSI data length += 1
    • Else copy both the first byte and the second byte into the buffer. ANSI data length += 2
  3. Try to decode those ANSI bytes with corresponding encodings (codepage 950 for BIG5, codepage 932 for PRC, codepage 949 for WANSUNG). If succeed (given by MultiByteToWideChar's return value), use that string. Else go step 4.
  4. Decode FT_SfntName::string as UTF16BE, and use that result string.

@astiob
Copy link
Member

astiob commented May 25, 2021

Oh, I had written a comment but didn’t post it! Here it is:

微软繁标宋, which claims its name's encoding_id equals to TT_MS_ID_PRC, but it stores GBK bytes in uint16_t bigendian

So it turns out: Microsoft reads Chinese and Wansung (not sure about Johab) Microsoft-platform names as sequences of uint16 (just like all other Microsoft-platform names), but before decoding them using the corresponding code page, it drops any leading zero byte from each uint16. And this (probably) doesn’t even apply to all names. The rest are just UTF-16BE as usual, and so are Japanese names.

Funnily enough, while the correct name shows up in the font explorer and the font installation progress popup, when I open the font file, the font viewer shows incorrectly-decoded-as-UTF-16 Macintosh-platform names (mojibake), not Microsoft-platform names at all. Both GDI/VSFilter and DirectWrite/libass do find the font by its correct Microsoft-platform name, though.

However, at least for this particular font, finding this font doesn’t help libass render it! libass is unable to map Chinese characters to glyphs in this font, so only replacement-character boxes show up.

Try to decode those ANSI bytes […] If succeed […] use that string. Else go step 4.

I’m not so sure. Win2003 apparently fails the entire font decoding if this fails.

Testing 微软繁标宋 with manually tweaked bytes on Windows 7, I haven’t been able to fail the GBK conversion at all. In fact, I’ve been able to make Windows do a buffer overread :-)

@Apache553
Copy link
Contributor Author

Well, I didn't do many tests on that. But this routine really helped me on reading names for my own project.
The source code I had read is there.

Win2003 apparently fails the entire font decoding if this fails.

According to the code, it seems that Windows restarts the whole decoding as UTF16BE if any decode procedure fails.

@astiob
Copy link
Member

astiob commented May 25, 2021

According to the code

XP code. Win2003 code is different.

@Apache553
Copy link
Contributor Author

XP code. Win2003 code is different.

Oh I realized that's different. I downloaded Win2003 code then found that font loading will fail if a required name decoding fails instead of restarting decoding as UTF16BE. That's the point!

@astiob
Copy link
Member

astiob commented May 28, 2021

So… testing on one of these days showed that EnumFontFamiliesEx matches only the main localized family name, but not any of the other family names. For example, for Chinese fonts on Chinese Windows, only the Chinese name will be found.

So it seems that, for a truly compatible lookup, our only option is to expand match_fonts parameters to include weight/slant and to use CreateFontIndirect with the specific parameters, pretty much exactly as VSFilter does it. I’ll work on this.

@astiob
Copy link
Member

astiob commented May 28, 2021

By the way, going back to Chinese/Korean font name encodings: to actually handle those fonts, we’d also need to learn to use Chinese/Korean non-Unicode character maps. That is, to convert Unicode code points to those character sets (and the other Windows Asian character sets for good measure). This sounds somewhat painful.

@astiob
Copy link
Member

astiob commented May 30, 2021

I have some good news, some bad news and some weird news.

EnumFontFamiliesEx matches only the main localized family name, but not any of the other family names. For example, for Chinese fonts on Chinese Windows, only the Chinese name will be found.

Turns out that I got fooled by another Aegisub bug. I’ve now stopped using Aegisub for testing font lookup.

EnumFontFamiliesEx does, in fact, match all the same names that CreateFontIndirect matches, so we can just use EnumFontFamiliesEx after all.

Unfortunately, it also turns out WinRT/UWP/Microsoft Store apparently forbids/lacks GDI APIs. IDWriteGdiInterop is allowed (which we use at the moment), but its worth is questionable. I know VLC builds a Microsoft Store app with libass, so this is actually relevant. In that environment, we have to make do with just DirectWrite somehow.

Luckily, DirectWrite in Windows 10 (all versions) actually includes a new IDWriteFontSet API that directly exposes and allows filtering by all font names that we’re interested in (and more). So on Windows 10, we can use this for efficient and sufficient lookup resembling the one we have with Core Text on macOS. But if I understand correctly, WinRT and Microsoft Store exist since Windows 8, so we still have a gap to fill where neither GDI nor IDWriteFontSet is available.

Also, Microsoft’s DirectWrite change log claims it supports fonts loaded on demand by font managers in Windows 10. I’ll poke some people to try and understand better what the actual situation is.

Finally, and oddly, DirectWrite in current Windows 10 also finds fonts by full name (any localization) in its CreateFontFromLOGFONT! It doesn’t on Windows 7. I don’t have a Windows 8 or older 10 at hand to test. But it still doesn’t find OpenType/CFF fonts by PostScript name like GDI does.

Putting it all together, we could:

  • on Windows 10+, use IDWriteFontSet (assuming it truly supports font managers),
  • on older desktops, use EnumFontFamiliesEx,
  • on older WinRT/UWP,
    • either return to eagerly loading all fonts
    • or stick with CreateFontFromLOGFONT, which seems to be the only available compromise.

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

Successfully merging this pull request may close these issues.

None yet

4 participants