libass / libass

libass is a portable subtitle renderer for the ASS/SSA (Advanced Substation Alpha/Substation Alpha) subtitle format.
ISC License
925 stars 211 forks source link

Discuss: Plans for Android AFont font provider #608

Open rcombs opened 2 years ago

rcombs commented 2 years ago

The Android NDK functions applicable to font selection are documented here: https://developer.android.com/ndk/reference/group/font

Using this API conditionally depending on the API's existence should be fairly easy using something like this:

#define DECLARE_FUNCPTR(name) __typeof__(name) *p##name;
typedef struct afont_funcs {
    DECLARE_FUNCPTR(AFontMatcher_create)
    DECLARE_FUNCPTR(AFontMatcher_destroy)
    DECLARE_FUNCPTR(AFontMatcher_match)
    DECLARE_FUNCPTR(AFontMatcher_setLocales)
    DECLARE_FUNCPTR(AFontMatcher_setStyle)

    DECLARE_FUNCPTR(AFont_close)
    DECLARE_FUNCPTR(AFont_getFontFilePath)
    DECLARE_FUNCPTR(AFont_getCollectionIndex)
} AFontFuncs;

AFontFuncs *get_funcs(void)
{
    AFontFuncs *funcs = calloc(sizeof(AFontFuncs), 1);

#define FILL_FUNCPTR(name) if (!(funcs->p##name = dlsym(RTLD_DEFAULT, #name))) goto fail;

    FILL_FUNCPTR(AFontMatcher_create)
    FILL_FUNCPTR(AFontMatcher_destroy)
    FILL_FUNCPTR(AFontMatcher_match)
    FILL_FUNCPTR(AFontMatcher_setLocales)
    FILL_FUNCPTR(AFontMatcher_setStyle)

    FILL_FUNCPTR(AFont_close)
    FILL_FUNCPTR(AFont_getFontFilePath)
    FILL_FUNCPTR(AFont_getCollectionIndex)

    return funcs;

fail:
    free(funcs);
    return NULL;
}

The hard parts come from the API's limitations:

So, I think there are a few major changes that'll be needed here:

This means that Provider fonts may never enter ASS_FontProviderMetaData, or would only enter a secondary internal list that isn't used for our internal first-pass matching.

This mechanism is entirely reasonable for fallback/unstyled cases, but isn't suitable for authoring. In authoring mode (…a thing we should have), we should fall back on fontconfig, or the slow-but-cross-platform "load every font on the system" method. This probably isn't particularly relevant to Android, but we may want to transition other platforms to this new mechanism for performance and locale-handling reasons.

Any thoughts?

astiob commented 2 years ago

How does this relate to #312?

See also my own plans/hopes for reworked font providers in https://github.com/libass/libass/pull/511#issuecomment-873572826.

First, we should attempt to find a match using memory fonts only, then search system fonts.

We already do this, and we are too eager to avoid system fonts; see #509. In the sense that this is incompatible with VSFilter: it always merges attached fonts with system fonts. Of course, one could argue that it’s undesirable for script portability etc., but that’s when the fabled authoring mode comes up, and we’ve generally stuck to striving for VSFilter compatibility in this respect as far as each font provider backend allows.

These should really be separate lists.

Agree.

What text should we use if code == 0? Maybe " "?

Yeah, I’m thinking of U+0020 SPACE, too. It’s a very ubiquitous glyph, and even HarfBuzz uses it for rendering invisible characters by default.

When searching system fonts, we should give the font selection mechanism an opportunity to perform a complete match and return a single font (path/stream + name/ID) directly, rather than […] enter ASS_FontProviderMetaData

(Correct me if I got that abridgement wrong.)

We could load the file at the given path and read & return all fonts from it, with ASS_FontProviderMetaData and all. This would trivially work for single-font files and could be extra beneficial for single-family multiple-font collection files, where we could then (in our usual manner) refine the result by attributes other than the name.

rcombs commented 2 years ago

This replaces #312. It has the downside of only working on recent-ish Android, but consumers that want to support older versions can point a fontconfig .conf file at /system/fonts and get better performance anyway. I don't like #312's approach, both because it relies on internal data that's explicitly stated not to be meant for application usage, and because it individually opens and loads every single installed font, which is problematic performance-wise.

The concept I'm suggesting here seems to be more or less the same as what you described in #511, so I think we're more or less on the same page here.

moi15moi commented 3 months ago
  • There's no way to get a list of fonts that matched a name; AFontMatcher_match always returns a single font.

A user opened this issue: [Feature Request] Be able to get all the font in a FontFamily. If google implement it, it would resolve this problem.

rcombs commented 3 months ago

Note that Android now supports if (__builtin_available(android 29, *)) and weak linking, just like on Apple platforms. This means we can define __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ and build with -Werror=unguarded-availability, and skip all the dlsym rigamarole. This should work regardless of runtime android version (or target version), and just requires NDK r23 or newer at build-time.

moi15moi commented 2 months ago

It has the downside of only working on recent-ish Android, but consumers that want to support older versions can point a fontconfig .conf file at /system/fonts and get better performance anyway.

Just as a side note. I used FindSystemFontsFilename and it actually retrieve font not only in /system/fonts. It also retrieved font in /product/fonts. FindSystemFontsFilename use ASystemFontIterator from the NDK. But, my phone is under Android 14 (a.k.a API 34), so fonts in /product/fonts may only exist on recent android version. It doesn't seems documented. Actually, it is a bit documented here, but it doesn't say in which version it was introduced