fontique: on Android, `sans-serif` and `system-ui` resolve to Roboto Flex instead of Roboto
Generated from repros/fontique-android-roboto-flex-generic/README.md when the site was built at 7aa23e2.
What niso does
Section titled “What niso does”crates/niso-core/src/lib.rs:11-26 (android_font_context, used at lib.rs:164) builds its own fontique
Collection on Android and resets GenericFamily::SansSerif and GenericFamily::SystemUi to just “Roboto”.
Expected vs actual
Section titled “Expected vs actual”fontique hardcodes the Android generic families in fontique/src/backend/android.rs:22-39
(DEFAULT_GENERIC_FAMILIES), unchanged since linebender/parley#28 (2024-04):
// TODO: Use actual generic families here, where available, when fonts.xml is properly parsed.(GenericFamily::SansSerif, &["Roboto Flex", "Roboto", "Noto Sans"]),(GenericFamily::SystemUi, &["Roboto Flex", "Roboto", "Noto Sans"]),Same lines at niso’s fork rev 00cbfc6ae4dc26e530f1e05c11097e3e149b8b9a and on main 746fdcf0d53fab04c5da1e2d7bde89018cbbb2b4.
Android’s own definition is /system/etc/fonts.xml: the first family (“first font is default”) is
<family name="sans-serif"> backed by Roboto-Regular.ttf (the variable Roboto, family name “Roboto”); Roboto
Flex is a separate, opt-in family <family name="roboto-flex"> backed by RobotoFlex-Regular.ttf (AOSP
frameworks/base/data/fonts/fonts.xml, main, lines 38 and 292). Typeface.DEFAULT/SANS_SERIF, and so Compose’s
FontFamily.Default/FontFamily.SansSerif and BasicText, resolve to that sans-serif family, i.e. Roboto.
On any device that ships RobotoFlex-Regular.ttf, fontique instead picks Roboto Flex: a different design with its
own metrics (advances, ascent/descent) and an opsz axis, so text no longer matches native text in the same box.
fontique does parse fonts.xml, but only for aliases and language/script fallback; it never maps the
sans-serif family to its files.
- Expected: first family of
SansSerifandSystemUiis “Roboto” (whatever fonts.xml’ssans-serifis). - Actual (from the code above; not run here):
["Roboto Flex", "Roboto", "Noto Sans"]on devices with Roboto Flex.
This cannot run here: fontique’s Android backend is compiled only for target_os = "android", and this machine has
no Android device, emulator or NDK. On the host the program exits 2 with a message. It was type-checked with
cargo check --target aarch64-linux-android against the fork rev and against parley main.
On a machine with the NDK and a device or emulator (API 34+ images ship Roboto Flex):
rustup target add aarch64-linux-android # x86_64-linux-android for an x86_64 emulatorcargo install cargo-ndkexport ANDROID_NDK_HOME=/path/to/ndkcargo ndk -t arm64-v8a build --releaseadb push target/aarch64-linux-android/release/fontique-android-roboto-flex-generic /data/local/tmp/adb shell /data/local/tmp/fontique-android-roboto-flex-generic; echo "exit=$?"adb shell grep -n -A1 'family name=\"sans-serif\"' /system/etc/fonts.xmlExpected output while the bug reproduces (on a device with Roboto Flex):
Roboto Flex installed: trueSansSerif: expected first family "Roboto" (fonts.xml sans-serif), actual ["Roboto Flex", "Roboto", "Noto Sans"]SystemUi: expected first family "Roboto" (fonts.xml sans-serif), actual ["Roboto Flex", "Roboto", "Noto Sans"]exit=1Upstream main
Section titled “Upstream main”Not runnable here; the hardcoded list is identical on linebender/parley main 746fdcf (read in source), so it
should reproduce there too.
Existing issue or PR
Section titled “Existing issue or PR”None found. Searched linebender/parley issues and PRs (all 944) for Roboto, Android, generic, fonts.xml. Related: #355 (open, “fontique: generic families (Fedora/KDE)”, Linux), #884 (open, Android CJK fallback without postScriptName), #337 (open, Android kana fallback), #30 (open, generic-family-specific fallback).
Issue text
Section titled “Issue text”Title: fontique (Android): sans-serif/system-ui resolve to Roboto Flex, but Android’s default is Roboto
Body:
fontique/src/backend/android.rs hardcodes SansSerif and SystemUi as ["Roboto Flex", "Roboto", "Noto Sans"].
On Android, sans-serif is the first family in /system/etc/fonts.xml (Roboto-Regular.ttf, family “Roboto”),
which is what Typeface.DEFAULT, TextView and Compose use; Roboto Flex is the separate roboto-flex family.
On devices that ship RobotoFlex-Regular.ttf, parley text therefore uses a different font, with different metrics,
from native text.
use fontique::{Collection, CollectionOptions, GenericFamily};
fn main() { let mut c = Collection::new(CollectionOptions { shared: false, system_fonts: true }); let ids: Vec<_> = c.generic_families(GenericFamily::SansSerif).collect(); let names: Vec<_> = ids.iter().filter_map(|&id| c.family_name(id).map(str::to_owned)).collect(); println!("{names:?}"); // ["Roboto Flex", "Roboto", "Noto Sans"]; expected "Roboto" first}Suggested fix: put “Roboto” ahead of “Roboto Flex” (or drop Roboto Flex), or better, resolve the generics from
fonts.xml: map the first <family name="sans-serif"> (and serif, monospace, …) to the families of the files it
lists, as the existing TODO says.