fix(qt): stabilize Japanese font fallback
cx
This commit is contained in:
8 files changed
+107
-45
No files matched your search
@@ -317,8 +317,8 @@ fn build_rhi_items() {
|
||||
let moc = qtbuild.moc(&header, qt_build_utils::MocArguments::default());
|
||||
cc.file(source).file(&moc.cpp);
|
||||
}
|
||||
// Plain helper, no QObject/moc: registers one script-specific bundled
|
||||
// font through QFontDatabase before Main.qml constructs any text.
|
||||
// Plain helper, no QObject/moc: registers script-specific bundled font
|
||||
// fallbacks through QFontDatabase before Main.qml constructs any text.
|
||||
println!("cargo:rerun-if-changed=cxx/font_fallback.cpp");
|
||||
cc.file("cxx/font_fallback.cpp");
|
||||
// Plain helper, no QObject/moc: enumerates the exact QVulkanInstance that
|
||||
|
||||
@@ -73,3 +73,27 @@ extern "C" bool qbz_register_devanagari_fallback()
|
||||
installFontMessageFilter();
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" bool qbz_register_japanese_fallback()
|
||||
{
|
||||
// LINE Seed JP is already bundled as a selectable UI/lyrics face. Make
|
||||
// it Qt's deterministic fallback for the three scripts used by Japanese
|
||||
// text as well. Without this, a Latin-only choice such as Source Sans 3
|
||||
// delegates to the host's first fontconfig match; a user-installed
|
||||
// MS Gothic can therefore receive synthetic Medium/Bold and render much
|
||||
// heavier than the surrounding UI.
|
||||
const auto id = QFontDatabase::addApplicationFont(QStringLiteral(
|
||||
":/qt/qml/com/blitzfc/qbz/qml/assets/fonts/"
|
||||
"LINESeedJP-Regular.ttf"));
|
||||
if (id < 0)
|
||||
return false;
|
||||
|
||||
const auto families = QFontDatabase::applicationFontFamilies(id);
|
||||
if (families.isEmpty())
|
||||
return false;
|
||||
const auto &family = families.constFirst();
|
||||
QFontDatabase::addApplicationFallbackFontFamily(QChar::Script_Han, family);
|
||||
QFontDatabase::addApplicationFallbackFontFamily(QChar::Script_Hiragana, family);
|
||||
QFontDatabase::addApplicationFallbackFontFamily(QChar::Script_Katakana, family);
|
||||
return true;
|
||||
}
|
||||
@@ -20,8 +20,8 @@
|
||||
// Hence this document: `QQmlApplicationEngine::load` returns with its
|
||||
// FontLoader already in the font database (verified: the family is present
|
||||
// the instant load() returns), so main.rs can set the application font in the
|
||||
// gap between this document and Main.qml. "System" maps to an empty source:
|
||||
// no UI family is loaded and Qt keeps the operating-system default.
|
||||
// gap between this document and Main.qml. "System" leaves the Loader inactive:
|
||||
// no empty URL is assigned and Qt keeps the operating-system default.
|
||||
//
|
||||
// It is deliberately NON-VISUAL — a QtObject, not a Window — so loading it
|
||||
// adds a root object and nothing else.
|
||||
@@ -46,7 +46,11 @@ QtObject {
|
||||
return "assets/fonts/SourceSans3-VariableFont_wght.ttf"
|
||||
return ""
|
||||
}
|
||||
readonly property FontLoader selected: FontLoader {
|
||||
source: root.selectedSource
|
||||
readonly property Component selectedComponent: Component {
|
||||
FontLoader { source: root.selectedSource }
|
||||
}
|
||||
readonly property Loader selected: Loader {
|
||||
active: root.selectedSource !== ""
|
||||
sourceComponent: root.selectedComponent
|
||||
}
|
||||
}
|
||||
@@ -58,17 +58,26 @@ Item {
|
||||
Math.min(root.width * 0.026, 48)))
|
||||
|
||||
readonly property string fontDir: "qrc:/qt/qml/com/blitzfc/qbz/qml/assets/fonts/"
|
||||
FontLoader { id: fLineSeed; source: QbzLyrics.fontIndex === 1 ? root.fontDir + "LINESeedJP-Regular.ttf" : "" }
|
||||
FontLoader { id: fMontserrat; source: QbzLyrics.fontIndex === 2 ? root.fontDir + "Montserrat-VariableFont_wght.ttf" : "" }
|
||||
FontLoader { id: fNoto; source: QbzLyrics.fontIndex === 3 ? root.fontDir + "NotoSans-VariableFont_wdth,wght.ttf" : "" }
|
||||
FontLoader { id: fSource; source: QbzLyrics.fontIndex === 4 ? root.fontDir + "SourceSans3-VariableFont_wght.ttf" : "" }
|
||||
readonly property string fontFamily: {
|
||||
if (QbzLyrics.fontIndex === 1) return fLineSeed.status === FontLoader.Ready ? fLineSeed.name : ""
|
||||
if (QbzLyrics.fontIndex === 2) return fMontserrat.status === FontLoader.Ready ? fMontserrat.name : ""
|
||||
if (QbzLyrics.fontIndex === 3) return fNoto.status === FontLoader.Ready ? fNoto.name : ""
|
||||
if (QbzLyrics.fontIndex === 4) return fSource.status === FontLoader.Ready ? fSource.name : ""
|
||||
readonly property string fontSource: {
|
||||
if (QbzLyrics.fontIndex === 1) return root.fontDir + "LINESeedJP-Regular.ttf"
|
||||
if (QbzLyrics.fontIndex === 2) return root.fontDir + "Montserrat-VariableFont_wght.ttf"
|
||||
if (QbzLyrics.fontIndex === 3) return root.fontDir + "NotoSans-VariableFont_wdth,wght.ttf"
|
||||
if (QbzLyrics.fontIndex === 4) return root.fontDir + "SourceSans3-VariableFont_wght.ttf"
|
||||
return ""
|
||||
}
|
||||
Component {
|
||||
id: fontLoaderComponent
|
||||
FontLoader { source: root.fontSource }
|
||||
}
|
||||
Loader {
|
||||
id: selectedFontLoader
|
||||
active: root.fontSource !== ""
|
||||
sourceComponent: fontLoaderComponent
|
||||
}
|
||||
readonly property string fontFamily:
|
||||
selectedFontLoader.item
|
||||
&& selectedFontLoader.item.status === FontLoader.Ready
|
||||
? selectedFontLoader.item.name : ""
|
||||
|
||||
// --- Entry-animation retrigger (:57-74) --------------------------------
|
||||
readonly property int watchedIndex: root.activeIndex
|
||||
|
||||
@@ -86,17 +86,26 @@ Item {
|
||||
1.0 - root.ambientPrimary.b, 1.0), 1.18)
|
||||
|
||||
readonly property string fontDir: "qrc:/qt/qml/com/blitzfc/qbz/qml/assets/fonts/"
|
||||
FontLoader { id: fLineSeed; source: QbzLyrics.fontIndex === 1 ? root.fontDir + "LINESeedJP-Regular.ttf" : "" }
|
||||
FontLoader { id: fMontserrat; source: QbzLyrics.fontIndex === 2 ? root.fontDir + "Montserrat-VariableFont_wght.ttf" : "" }
|
||||
FontLoader { id: fNoto; source: QbzLyrics.fontIndex === 3 ? root.fontDir + "NotoSans-VariableFont_wdth,wght.ttf" : "" }
|
||||
FontLoader { id: fSource; source: QbzLyrics.fontIndex === 4 ? root.fontDir + "SourceSans3-VariableFont_wght.ttf" : "" }
|
||||
readonly property string lyricFontFamily: {
|
||||
if (QbzLyrics.fontIndex === 1) return fLineSeed.status === FontLoader.Ready ? fLineSeed.name : ""
|
||||
if (QbzLyrics.fontIndex === 2) return fMontserrat.status === FontLoader.Ready ? fMontserrat.name : ""
|
||||
if (QbzLyrics.fontIndex === 3) return fNoto.status === FontLoader.Ready ? fNoto.name : ""
|
||||
if (QbzLyrics.fontIndex === 4) return fSource.status === FontLoader.Ready ? fSource.name : ""
|
||||
readonly property string fontSource: {
|
||||
if (QbzLyrics.fontIndex === 1) return root.fontDir + "LINESeedJP-Regular.ttf"
|
||||
if (QbzLyrics.fontIndex === 2) return root.fontDir + "Montserrat-VariableFont_wght.ttf"
|
||||
if (QbzLyrics.fontIndex === 3) return root.fontDir + "NotoSans-VariableFont_wdth,wght.ttf"
|
||||
if (QbzLyrics.fontIndex === 4) return root.fontDir + "SourceSans3-VariableFont_wght.ttf"
|
||||
return ""
|
||||
}
|
||||
Component {
|
||||
id: fontLoaderComponent
|
||||
FontLoader { source: root.fontSource }
|
||||
}
|
||||
Loader {
|
||||
id: selectedFontLoader
|
||||
active: root.fontSource !== ""
|
||||
sourceComponent: fontLoaderComponent
|
||||
}
|
||||
readonly property string lyricFontFamily:
|
||||
selectedFontLoader.item
|
||||
&& selectedFontLoader.item.status === FontLoader.Ready
|
||||
? selectedFontLoader.item.name : ""
|
||||
|
||||
function fmt(sec) {
|
||||
sec = Math.max(0, Math.floor(sec))
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
// moves of <= 2 lines, INSTANT for bigger jumps and the initial sync,
|
||||
// 1:1 with Slint. The animation drives `contentY` only on the auto-follow
|
||||
// path, so user wheel input is never animated.
|
||||
// - the four embedded lyric font families (lazy: a FontLoader only gets a
|
||||
// source once its option is selected).
|
||||
// - the embedded lyric font families (lazy: one FontLoader exists only while
|
||||
// a non-System option is selected).
|
||||
//
|
||||
// Unsynced/plain documents render as a static top-aligned list: no spacers,
|
||||
// no active line, no scroll-follow.
|
||||
@@ -60,23 +60,32 @@ Item {
|
||||
|
||||
readonly property real contentWidth: Math.max(0, width - 2 * sidePadding)
|
||||
|
||||
// ---- embedded lyric fonts (lazy) -------------------------------------
|
||||
// ---- embedded lyric font (lazy) --------------------------------------
|
||||
readonly property string fontDir: "qrc:/qt/qml/com/blitzfc/qbz/qml/assets/fonts/"
|
||||
FontLoader { id: fLineSeed; source: view.fontIndex === 1 ? view.fontDir + "LINESeedJP-Regular.ttf" : "" }
|
||||
FontLoader { id: fMontserrat; source: view.fontIndex === 2 ? view.fontDir + "Montserrat-VariableFont_wght.ttf" : "" }
|
||||
FontLoader { id: fNoto; source: view.fontIndex === 3 ? view.fontDir + "NotoSans-VariableFont_wdth,wght.ttf" : "" }
|
||||
FontLoader { id: fSource; source: view.fontIndex === 4 ? view.fontDir + "SourceSans3-VariableFont_wght.ttf" : "" }
|
||||
readonly property string fontSource: {
|
||||
if (view.fontIndex === 1) return view.fontDir + "LINESeedJP-Regular.ttf"
|
||||
if (view.fontIndex === 2) return view.fontDir + "Montserrat-VariableFont_wght.ttf"
|
||||
if (view.fontIndex === 3) return view.fontDir + "NotoSans-VariableFont_wdth,wght.ttf"
|
||||
if (view.fontIndex === 4) return view.fontDir + "SourceSans3-VariableFont_wght.ttf"
|
||||
return ""
|
||||
}
|
||||
Component {
|
||||
id: fontLoaderComponent
|
||||
FontLoader { source: view.fontSource }
|
||||
}
|
||||
Loader {
|
||||
id: selectedFontLoader
|
||||
active: view.fontSource !== ""
|
||||
sourceComponent: fontLoaderComponent
|
||||
}
|
||||
|
||||
// "" = the real Qt/system default = Slint's "System" option. A family
|
||||
// that has not finished loading falls back to the default rather than
|
||||
// rendering in a wrong face.
|
||||
readonly property string fontFamily: {
|
||||
if (view.fontIndex === 1) return fLineSeed.status === FontLoader.Ready ? fLineSeed.name : ""
|
||||
if (view.fontIndex === 2) return fMontserrat.status === FontLoader.Ready ? fMontserrat.name : ""
|
||||
if (view.fontIndex === 3) return fNoto.status === FontLoader.Ready ? fNoto.name : ""
|
||||
if (view.fontIndex === 4) return fSource.status === FontLoader.Ready ? fSource.name : ""
|
||||
return ""
|
||||
}
|
||||
readonly property string fontFamily:
|
||||
selectedFontLoader.item
|
||||
&& selectedFontLoader.item.status === FontLoader.Ready
|
||||
? selectedFontLoader.item.name : ""
|
||||
|
||||
// ---- auto-center scroll ----------------------------------------------
|
||||
// Last index centered on; -1 = nothing yet -> the next center is INSTANT.
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
//! Script-specific application font fallbacks.
|
||||
//!
|
||||
//! The UI's "System" choice remains Qt's real system font. This module only
|
||||
//! gives Qt a deterministic face when a selected UI font lacks Devanagari,
|
||||
//! avoiding its noisy scan through unrelated installed families.
|
||||
//! The UI's "System" choice remains Qt's real system font for covered glyphs.
|
||||
//! This module only gives Qt deterministic bundled faces when that font (or a
|
||||
//! selected UI/lyrics font) lacks Japanese or Devanagari, avoiding arbitrary
|
||||
//! and sometimes visually incompatible host fallbacks.
|
||||
|
||||
unsafe extern "C" {
|
||||
fn qbz_register_devanagari_fallback() -> bool;
|
||||
fn qbz_register_japanese_fallback() -> bool;
|
||||
}
|
||||
|
||||
pub(crate) fn register_devanagari_fallback() {
|
||||
// SAFETY: the C++ function has no arguments, is called once on the GUI
|
||||
// thread after QGuiApplication exists, and only mutates QFontDatabase's
|
||||
pub(crate) fn register_script_fallbacks() {
|
||||
// SAFETY: these C++ functions have no arguments, are called once on the
|
||||
// GUI thread after QGuiApplication exists, and only mutate QFontDatabase's
|
||||
// documented process-global application-font registry.
|
||||
if unsafe { qbz_register_japanese_fallback() } {
|
||||
log::info!("[qbz-qt] font fallback -> LINE Seed JP (Han/Hiragana/Katakana)");
|
||||
} else {
|
||||
log::warn!("[qbz-qt] bundled LINE Seed JP fallback failed to load");
|
||||
}
|
||||
if unsafe { qbz_register_devanagari_fallback() } {
|
||||
log::info!("[qbz-qt] font fallback -> Noto Sans Devanagari");
|
||||
} else {
|
||||
|
||||
@@ -3422,7 +3422,7 @@ fn main() {
|
||||
"qrc:/qt/qml/com/blitzfc/qbz/qml/FontPreload.qml",
|
||||
));
|
||||
}
|
||||
font_qt::register_devanagari_fallback();
|
||||
font_qt::register_script_fallbacks();
|
||||
let app_font_family = settings_qt::app_font_family();
|
||||
if !app_font_family.is_empty() {
|
||||
if let Some(app) = app.as_mut() {
|
||||
|
||||
Reference in new issue
Block a user