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

Discuss: Plans for Android AFont font provider #608

Open
rcombs opened this issue Apr 5, 2022 · 5 comments
Open

Discuss: Plans for Android AFont font provider #608

rcombs opened this issue Apr 5, 2022 · 5 comments
Labels

Comments

@rcombs
Copy link
Member

rcombs commented Apr 5, 2022

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:

  • There's no way to get a list of fonts that matched a name; AFontMatcher_match always returns a single font.
  • There's no way to get a font matching a name without passing in text; AFontMatcher_match requires at least 1 character.
  • There's no way to get a name for the returned font; AFont only provides a path and index.

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

  • First, we should attempt to find a match using memory fonts only, then search system fonts. These should really be separate lists.
  • 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 insert elements into our internal search list.
    • (What text should we use if code == 0? Maybe " "?)

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
Copy link
Member

astiob commented Apr 5, 2022

How does this relate to #312?

See also my own plans/hopes for reworked font providers in #511 (comment).

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.

@astiob astiob added the fonts label Apr 5, 2022
@rcombs
Copy link
Member Author

rcombs commented Apr 5, 2022

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
Copy link
Contributor

  • 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
Copy link
Member Author

rcombs commented May 14, 2024

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
Copy link
Contributor

moi15moi commented Jun 11, 2024

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

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

No branches or pull requests

3 participants