import 'dart:ffi' show Abi; import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; /// Font family name registered with [FontLoader] when a system CJK font is /// successfully loaded on ARM64 Linux. const kLinuxCjkFontFamily = 'SystemCJK'; const _kFontSearchPaths = [ // Debian / Ubuntu (noto-fonts / fonts-noto-cjk) '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', '/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc', '/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf', // Fedora / RHEL / Rocky (google-noto-sans-cjk-fonts) '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc', '/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc', // Arch Linux (noto-fonts-cjk) '/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc', '/usr/share/fonts/noto-cjk/NotoSansCJKsc-Regular.otf', // Generic fallback paths '/usr/share/fonts/noto/NotoSansCJK-Regular.ttc', '/usr/share/fonts/noto/NotoSansCJKsc-Regular.otf', // WenQuanYi — commonly pre-installed on CJK-locale systems '/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', '/usr/share/fonts/wqy-microhei/wqy-microhei.ttc', '/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc', ]; /// Loads a system CJK font on ARM64 Linux into Flutter's font registry via /// [FontLoader], working around the missing fontconfig support in the /// flutter-elinux engine (https://github.com/flutter/flutter/issues/139293). /// /// Returns true if a CJK font was successfully loaded; false otherwise. /// On all other platforms this is a no-op and returns false immediately. Future loadSystemCJKFonts() async { if (Abi.current() != Abi.linuxArm64) return false; final path = await _findCjkFontPath(); if (path == null) { debugPrint('ARM64 Linux: no CJK font found; CJK text may not render'); return false; } try { final loader = FontLoader(kLinuxCjkFontFamily); final bytes = await File(path).readAsBytes(); loader.addFont(Future.value(ByteData.view(bytes.buffer, bytes.offsetInBytes, bytes.lengthInBytes))); await loader.load(); debugPrint('ARM64 Linux: loaded CJK font from $path'); return true; } catch (e) { debugPrint('ARM64 Linux: failed to load CJK font: $e'); return false; } } Future _findCjkFontPath() async { // Query fc-list for each CJK script separately. Fonts present in all three // sets (zh ∩ ja ∩ ko) are true pan-CJK fonts; prefer them so we don't // accidentally pick a Chinese-only font that lacks Japanese kana or Korean // hangul glyphs. fc-list is a fontconfig CLI tool available on most Linux // systems independent of whether the Flutter engine was built with fontconfig. final byLang = >{}; for (final lang in const ['zh', 'ja', 'ko']) { final paths = {}; try { final r = await Process.run('fc-list', [':lang=$lang', '--format=%{file}\n']); if (r.exitCode == 0) { for (final line in r.stdout.toString().split('\n')) { final p = line.trim(); if (p.isNotEmpty && File(p).existsSync()) paths.add(p); } } } catch (e) { debugPrint('ARM64 Linux: fc-list failed for lang=$lang: $e'); } byLang[lang] = paths; } final panCjk = byLang['zh']! .intersection(byLang['ja']!) .intersection(byLang['ko']!); final anyCjk = byLang.values.fold({}, (acc, s) => acc..addAll(s)); // Among candidates, prefer well-known pan-CJK font families. String? pick(Iterable pool) { const preferred = ['notosanscjk', 'sourcehansans', 'sourcehanserif']; for (final name in preferred) { for (final p in pool) { if (p.toLowerCase().contains(name)) return p; } } return pool.isNotEmpty ? pool.first : null; } final found = pick(panCjk) ?? pick(anyCjk); if (found != null) return found; for (final p in _kFontSearchPaths) { if (File(p).existsSync()) return p; } return null; }