mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-10 17:25:09 +03:00
fix(arm64-linux): fix CJK font rendering on flutter-elinux (#15324)
* fix(arm64-linux): fix CJK font rendering on flutter-elinux The flutter-elinux engine used for ARM64 Linux builds is compiled without --enable-fontconfig, so Flutter's text shaper cannot discover system fonts. This causes CJK characters to render as tofu boxes even when fonts such as Noto Sans CJK are installed. See flutter/flutter#139293. Fix by loading a CJK font at startup via FontLoader (bypassing fontconfig) and propagating it through two paths so all text widgets are covered: 1. MyTheme.applyFontFallback() — updates textTheme on both light and dark ThemeData so Material components receive the fallback through the theme. 2. _mergeCjkFallback() in GetMaterialApp builders — wraps child widgets in DefaultTextStyle.merge so bare Text() widgets and those with inherit:true also render CJK characters correctly. Font discovery queries fc-list for zh, ja, and ko separately, preferring fonts present in all three sets (true pan-CJK fonts such as NotoSansCJK or SourceHanSans) over Chinese-only fonts that may lack Japanese kana or Korean hangul glyphs. Falls back to a hardcoded search-path list covering Debian/Ubuntu, Fedora/RHEL, Arch Linux, and WenQuanYi font layouts. This is an app-level workaround. The engine-level fix is tracked at flutter/flutter#180235 (open as of 2026-06). Fixes #10666 Signed-off-by: Bia503 <yinwenche189@gmail.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Signed-off-by: Bia503 <yinwenche189@gmail.com> Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -598,6 +598,22 @@ class MyTheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies [fallbacks] as fontFamilyFallback to every text style in both
|
||||||
|
/// themes. Called once at startup on ARM64 Linux after a CJK font has been
|
||||||
|
/// loaded via FontLoader (see flutter/flutter#139293).
|
||||||
|
static void applyFontFallback(List<String> fallbacks) {
|
||||||
|
lightTheme = lightTheme.copyWith(
|
||||||
|
textTheme: lightTheme.textTheme.apply(fontFamilyFallback: fallbacks),
|
||||||
|
primaryTextTheme:
|
||||||
|
lightTheme.primaryTextTheme.apply(fontFamilyFallback: fallbacks),
|
||||||
|
);
|
||||||
|
darkTheme = darkTheme.copyWith(
|
||||||
|
textTheme: darkTheme.textTheme.apply(fontFamilyFallback: fallbacks),
|
||||||
|
primaryTextTheme:
|
||||||
|
darkTheme.primaryTextTheme.apply(fontFamilyFallback: fallbacks),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static ThemeMode currentThemeMode() {
|
static ThemeMode currentThemeMode() {
|
||||||
final preference = getThemeModePreference();
|
final preference = getThemeModePreference();
|
||||||
if (preference == ThemeMode.system) {
|
if (preference == ThemeMode.system) {
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import 'mobile/pages/home_page.dart';
|
|||||||
import 'mobile/pages/server_page.dart';
|
import 'mobile/pages/server_page.dart';
|
||||||
import 'mobile/widgets/deploy_dialog.dart';
|
import 'mobile/widgets/deploy_dialog.dart';
|
||||||
import 'models/platform_model.dart';
|
import 'models/platform_model.dart';
|
||||||
|
import 'native/font_manager.dart'
|
||||||
|
if (dart.library.html) 'web/font_manager.dart';
|
||||||
|
|
||||||
import 'package:flutter_hbb/plugin/handlers.dart'
|
import 'package:flutter_hbb/plugin/handlers.dart'
|
||||||
if (dart.library.html) 'package:flutter_hbb/web/plugin/handlers.dart';
|
if (dart.library.html) 'package:flutter_hbb/web/plugin/handlers.dart';
|
||||||
@@ -37,10 +39,15 @@ import 'package:flutter_hbb/plugin/handlers.dart'
|
|||||||
int? kWindowId;
|
int? kWindowId;
|
||||||
WindowType? kWindowType;
|
WindowType? kWindowType;
|
||||||
late List<String> kBootArgs;
|
late List<String> kBootArgs;
|
||||||
|
bool _cjkFontLoaded = false;
|
||||||
|
|
||||||
Future<void> main(List<String> args) async {
|
Future<void> main(List<String> args) async {
|
||||||
earlyAssert();
|
earlyAssert();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
_cjkFontLoaded = await loadSystemCJKFonts();
|
||||||
|
if (_cjkFontLoaded) {
|
||||||
|
MyTheme.applyFontFallback([kLinuxCjkFontFamily]);
|
||||||
|
}
|
||||||
|
|
||||||
debugPrint("launch args: $args");
|
debugPrint("launch args: $args");
|
||||||
kBootArgs = List.from(args);
|
kBootArgs = List.from(args);
|
||||||
@@ -383,6 +390,7 @@ void _runApp(
|
|||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
child = _keepScaleBuilder(context, child);
|
child = _keepScaleBuilder(context, child);
|
||||||
child = botToastBuilder(context, child);
|
child = botToastBuilder(context, child);
|
||||||
|
if (_cjkFontLoaded) child = _mergeCjkFallback(context, child);
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -533,6 +541,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
: (context, child) {
|
: (context, child) {
|
||||||
child = _keepScaleBuilder(context, child);
|
child = _keepScaleBuilder(context, child);
|
||||||
child = botToastBuilder(context, child);
|
child = botToastBuilder(context, child);
|
||||||
|
if (_cjkFontLoaded) child = _mergeCjkFallback(context, child);
|
||||||
if ((isDesktop && desktopType == DesktopType.main) ||
|
if ((isDesktop && desktopType == DesktopType.main) ||
|
||||||
isWebDesktop) {
|
isWebDesktop) {
|
||||||
child = keyListenerBuilder(context, child);
|
child = keyListenerBuilder(context, child);
|
||||||
@@ -586,6 +595,19 @@ _registerEventHandler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merges the theme's fontFamilyFallback into [DefaultTextStyle] so that
|
||||||
|
/// bare [Text] widgets (and those with inherit:true styles) also pick up the
|
||||||
|
/// CJK fallback font loaded on ARM64 Linux.
|
||||||
|
Widget _mergeCjkFallback(BuildContext context, Widget? child) {
|
||||||
|
final result = child ?? Container();
|
||||||
|
final fallback = Theme.of(context).textTheme.bodyMedium?.fontFamilyFallback;
|
||||||
|
if (fallback == null || fallback.isEmpty) return result;
|
||||||
|
return DefaultTextStyle.merge(
|
||||||
|
style: TextStyle(fontFamilyFallback: fallback),
|
||||||
|
child: result,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget keyListenerBuilder(BuildContext context, Widget? child) {
|
Widget keyListenerBuilder(BuildContext context, Widget? child) {
|
||||||
return RawKeyboardListener(
|
return RawKeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
|
|||||||
109
flutter/lib/native/font_manager.dart
Normal file
109
flutter/lib/native/font_manager.dart
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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<bool> 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<String?> _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 = <String, Set<String>>{};
|
||||||
|
for (final lang in const ['zh', 'ja', 'ko']) {
|
||||||
|
final paths = <String>{};
|
||||||
|
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(<String>{}, (acc, s) => acc..addAll(s));
|
||||||
|
|
||||||
|
// Among candidates, prefer well-known pan-CJK font families.
|
||||||
|
String? pick(Iterable<String> 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;
|
||||||
|
}
|
||||||
8
flutter/lib/web/font_manager.dart
Normal file
8
flutter/lib/web/font_manager.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/// Web stub for `native/font_manager.dart`.
|
||||||
|
///
|
||||||
|
/// The native implementation depends on `dart:io` (Process/File/Platform) to
|
||||||
|
/// load a system CJK font on ARM64 Linux, which cannot compile for the web
|
||||||
|
/// target. The web build has no such fontconfig limitation, so this is a no-op.
|
||||||
|
const kLinuxCjkFontFamily = 'SystemCJK';
|
||||||
|
|
||||||
|
Future<bool> loadSystemCJKFonts() async => false;
|
||||||
Reference in New Issue
Block a user