fix crash when opening preference dialog

This commit is contained in:
SoftFever
2026-03-16 00:18:39 +08:00
parent 0cc4b442e3
commit 8168d0a4e0
10 changed files with 284 additions and 87 deletions

1
.gitignore vendored
View File

@@ -45,3 +45,4 @@ test.js
.clangd
internal_docs/
*.flatpak
/flatpak-repo/

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env bash
#
# Build OrcaSlicer Flatpak locally using Docker with the same container image
# as the CI (build_all.yml).
#
# Usage:
# ./scripts/build_flatpak_with_docker.sh [--arch <x86_64|aarch64>] [--no-debug-info]
#
# Requirements:
# - Docker (or Podman with docker compatibility)
#
# The resulting .flatpak bundle is placed in the project root.
# A persistent Docker volume "flatpak-builder-cache" is used to cache
# downloaded sources across builds. Remove it with:
# docker volume rm flatpak-builder-cache
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# ---------- defaults ----------
ARCH="$(uname -m)"
NO_DEBUG_INFO=false
NO_PULL=false
FORCE_CLEAN=true
CONTAINER_IMAGE="ghcr.io/flathub-infra/flatpak-github-actions:gnome-49"
# ---------- parse args ----------
while [[ $# -gt 0 ]]; do
case "$1" in
--arch)
ARCH="$2"; shift 2 ;;
--no-debug-info)
NO_DEBUG_INFO=true; shift ;;
--no-pull)
NO_PULL=true; shift ;;
--keep-build)
FORCE_CLEAN=false; shift ;;
--image)
CONTAINER_IMAGE="$2"; shift 2 ;;
-h|--help)
echo "Usage: $0 [--arch <x86_64|aarch64>] [--no-debug-info] [--no-pull] [--keep-build] [--image <image>]"
exit 0 ;;
*)
echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
# ---------- version & commit ----------
cd "$PROJECT_ROOT"
VER_PURE=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
if [ -z "$VER_PURE" ]; then
echo "Error: could not extract version from version.inc" >&2
exit 1
fi
VER="V${VER_PURE}"
GIT_COMMIT_HASH=$(git rev-parse HEAD)
BUNDLE_NAME="OrcaSlicer-Linux-flatpak_${VER}_${ARCH}.flatpak"
echo "=== OrcaSlicer Flatpak Build ==="
echo " Version: ${VER} (${VER_PURE})"
echo " Commit: ${GIT_COMMIT_HASH}"
echo " Arch: ${ARCH}"
echo " Image: ${CONTAINER_IMAGE}"
echo " Bundle: ${BUNDLE_NAME}"
echo " Debug info: $([ "$NO_DEBUG_INFO" = true ] && echo "disabled" || echo "enabled")"
echo " ccache: enabled"
echo ""
# ---------- prepare manifest ----------
MANIFEST_SRC="scripts/flatpak/io.github.orcaslicer.OrcaSlicer.yml"
MANIFEST_DOCKER="scripts/flatpak/io.github.orcaslicer.OrcaSlicer.docker.yml"
cp "$MANIFEST_SRC" "$MANIFEST_DOCKER"
# Ensure cleanup on exit (success or failure)
trap 'rm -f "$PROJECT_ROOT/$MANIFEST_DOCKER"' EXIT
# Optionally strip debug info (matches CI behaviour for faster builds)
if [ "$NO_DEBUG_INFO" = true ]; then
sed -i '/^build-options:/a\ no-debuginfo: true\n strip: true' "$MANIFEST_DOCKER"
fi
# Inject git commit hash (same sed as CI)
sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$GIT_COMMIT_HASH\"|}" "$MANIFEST_DOCKER"
# ---------- run build in Docker ----------
DOCKER="${DOCKER:-docker}"
if [ "$NO_PULL" = false ]; then
echo "=== Pulling container image ==="
"$DOCKER" pull "$CONTAINER_IMAGE"
fi
FORCE_CLEAN_FLAG=""
if [ "$FORCE_CLEAN" = true ]; then
FORCE_CLEAN_FLAG="--force-clean"
fi
# Pass build parameters as env vars so the inner script doesn't need
# variable expansion from the outer shell (avoids quoting issues).
echo "=== Starting Flatpak build inside container ==="
"$DOCKER" run --rm --privileged \
-v "$PROJECT_ROOT":/src:Z \
-v flatpak-builder-cache:/src/.flatpak-builder \
-w /src \
-e "BUILD_ARCH=$ARCH" \
-e "BUNDLE_NAME=$BUNDLE_NAME" \
-e "FORCE_CLEAN_FLAG=$FORCE_CLEAN_FLAG" \
"$CONTAINER_IMAGE" \
bash -c '
set -euo pipefail
# Install required SDK extensions (not pre-installed in the container image)
flatpak install -y --noninteractive flathub \
org.freedesktop.Sdk.Extension.llvm21//25.08 || true
flatpak-builder $FORCE_CLEAN_FLAG \
--ccache \
--disable-rofiles-fuse \
--arch="$BUILD_ARCH" \
--repo=flatpak-repo \
flatpak-build \
scripts/flatpak/io.github.orcaslicer.OrcaSlicer.docker.yml
flatpak build-bundle \
--arch="$BUILD_ARCH" \
flatpak-repo \
"$BUNDLE_NAME" \
io.github.orcaslicer.OrcaSlicer
# Fix ownership so output files are not root-owned on the host
chown "$(stat -c %u:%g /src)" "$BUNDLE_NAME" flatpak-build flatpak-repo
echo "=== Build complete ==="
'
echo ""
echo "=== Flatpak bundle ready ==="
echo " ${PROJECT_ROOT}/${BUNDLE_NAME}"
echo ""
echo "Install with:"
echo " flatpak install --user ${BUNDLE_NAME}"

View File

@@ -1,2 +1,3 @@
builddir
.flatpak-builder
*.docker.yml

View File

@@ -333,6 +333,11 @@ modules:
- install -Dm644 LICENSE.txt /app/share/licenses/${FLATPAK_ID}/LICENSE.txt
- | # Install fonts into fontconfig-scanned directory so Pango finds them
# before initialization (avoids ensure_faces crash from AddPrivateFont)
install -Dm644 -t /app/share/fonts/OrcaSlicer/ resources/fonts/*.ttf
fc-cache -f /app/share/fonts/OrcaSlicer/
sources:
# OrcaSlicer source tree (specific dirs to avoid copying .git from worktree)
- type: dir

View File

@@ -6199,7 +6199,7 @@ static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguage
if (! it->empty()) {
const std::string &locale = *it;
const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale));
if (wxLocale::IsAvailable(lang->Language))
if (lang != nullptr && wxLocale::IsAvailable(lang->Language))
return lang;
}
return language;
@@ -6241,7 +6241,10 @@ bool GUI_App::select_language()
names.Alloc(language_infos.size());
// Some valid language should be selected since the application start up.
const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage());
const wxString active_language_code = current_language_code();
const wxLanguageInfo* active_language_info = wxLocale::FindLanguageInfo(active_language_code);
const wxLanguage current_language = active_language_info != nullptr ? wxLanguage(active_language_info->Language) : wxLanguage(m_wxLocale->GetLanguage());
const wxString active_lang_prefix = active_language_code.BeforeFirst('_');
int init_selection = -1;
int init_selection_alt = -1;
int init_selection_default = -1;
@@ -6249,9 +6252,9 @@ bool GUI_App::select_language()
if (wxLanguage(language_infos[i]->Language) == current_language)
// The dictionary matches the active language and country.
init_selection = i;
else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) ||
else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == active_lang_prefix) ||
// if the active language is Slovak, mark the Czech language as active.
(language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk"))
(language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && active_lang_prefix == "sk"))
// The dictionary matches the active language, it does not necessarily match the country.
init_selection_alt = i;
if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en")
@@ -6369,7 +6372,10 @@ bool GUI_App::load_language(wxString language, bool initial)
language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US);
}
BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data();
const wxLanguageInfo *translation_language_info = language_info;
const wxString requested_language_code = translation_language_info->CanonicalName;
const wxLanguageInfo *locale_language_info = translation_language_info;
BOOST_LOG_TRIVIAL(trace) << boost::format("Requested translation language %1%") % requested_language_code.ToUTF8().data();
// Select language for locales. This language may be different from the language of the dictionary.
//if (language_info == m_language_info_best || language_info == m_language_info_system) {
@@ -6382,8 +6388,8 @@ bool GUI_App::load_language(wxString language, bool initial)
// language_info = m_language_info_system;
// Alternate language code.
wxLanguage language_dict = wxLanguage(language_info->Language);
if (language_info->CanonicalName.BeforeFirst('_') == "sk") {
wxLanguage language_dict = wxLanguage(translation_language_info->Language);
if (translation_language_info->CanonicalName.BeforeFirst('_') == "sk") {
// Slovaks understand Czech well. Give them the Czech translation.
language_dict = wxLANGUAGE_CZECH;
BOOST_LOG_TRIVIAL(info) << "Using Czech dictionaries for Slovak language";
@@ -6392,19 +6398,34 @@ bool GUI_App::load_language(wxString language, bool initial)
#ifdef __linux__
// If we can't find this locale , try to use different one for the language
// instead of just reporting that it is impossible to switch.
if (! wxLocale::IsAvailable(language_info->Language) && m_language_info_system) {
std::string original_lang = into_u8(language_info->CanonicalName);
language_info = linux_get_existing_locale_language(language_info, m_language_info_system);
BOOST_LOG_TRIVIAL(info) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.")
% original_lang % language_info->CanonicalName.ToUTF8().data();
if (!wxLocale::IsAvailable(locale_language_info->Language) && m_language_info_system) {
std::string original_lang = into_u8(locale_language_info->CanonicalName);
locale_language_info = linux_get_existing_locale_language(locale_language_info, m_language_info_system);
if (locale_language_info != nullptr && locale_language_info != translation_language_info) {
BOOST_LOG_TRIVIAL(info) << boost::format("Can't use locale %1% directly (missing locales). Using locale %2% instead.")
% original_lang % locale_language_info->CanonicalName.ToUTF8().data();
}
}
if (locale_language_info == nullptr || !wxLocale::IsAvailable(locale_language_info->Language)) {
auto try_locale = [](const wxLanguageInfo* candidate) -> const wxLanguageInfo* {
return (candidate && wxLocale::IsAvailable(candidate->Language)) ? candidate : nullptr;
};
const wxLanguageInfo* fallback_locale_info =
try_locale(m_wxLocale ? wxLocale::GetLanguageInfo(wxLanguage(m_wxLocale->GetLanguage())) : nullptr);
if (!fallback_locale_info) fallback_locale_info = try_locale(m_language_info_system);
if (!fallback_locale_info) fallback_locale_info = try_locale(m_language_info_best);
if (!fallback_locale_info) fallback_locale_info = try_locale(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US));
if (!fallback_locale_info) fallback_locale_info = try_locale(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_UK));
if (fallback_locale_info != nullptr) {
BOOST_LOG_TRIVIAL(info) << boost::format("Using fallback locale %1% while keeping translation dictionary %2%.")
% fallback_locale_info->CanonicalName.ToUTF8().data() % requested_language_code.ToUTF8().data();
locale_language_info = fallback_locale_info;
}
}
#endif
if (! wxLocale::IsAvailable(language_info->Language)&&initial) {
language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_UK);
app_config->set("language", language_info->CanonicalName.ToUTF8().data());
}
else if (initial) {
if (initial) {
// bbs supported languages
//TODO: use a global one with Preference
//wxLanguage supported_languages[]{
@@ -6438,9 +6459,11 @@ bool GUI_App::load_language(wxString language, bool initial)
//}
}
if (! wxLocale::IsAvailable(language_info->Language)) {
BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % locale_language_info->CanonicalName.ToUTF8().data();
if (!wxLocale::IsAvailable(locale_language_info->Language)) {
// Loading the language dictionary failed.
wxString message = "Switching Orca Slicer to language " + language_info->CanonicalName + " failed.";
wxString message = "Switching Orca Slicer to language " + requested_language_code + " failed.";
#if !defined(_WIN32) && !defined(__APPLE__)
// likely some linux system
message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n";
@@ -6458,12 +6481,13 @@ bool GUI_App::load_language(wxString language, bool initial)
//FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now.
m_wxLocale.release();
m_wxLocale = Slic3r::make_unique<wxLocale>();
m_wxLocale->Init(language_info->Language);
m_wxLocale->Init(locale_language_info->Language);
// Override language at the active wxTranslations class (which is stored in the active m_wxLocale)
// to load possibly different dictionary, for example, load Czech dictionary for Slovak language.
wxTranslations::Get()->SetLanguage(language_dict);
m_wxLocale->AddCatalog(SLIC3R_APP_KEY);
m_imgui->set_language(into_u8(language_info->CanonicalName));
m_active_language_code = requested_language_code;
m_imgui->set_language(into_u8(requested_language_code));
//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
//wxSetlocale(LC_NUMERIC, "C");
@@ -6751,49 +6775,57 @@ void GUI_App::show_ip_address_enter_dialog_handler(wxCommandEvent& evt)
void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_option)
{
bool app_layout_changed = false;
bool need_recreate_gui = false;
std::string pending_language;
{
// the dialog needs to be destroyed before the call to recreate_GUI()
// or sometimes the application crashes into wxDialogBase() destructor
// so we put it into an inner scope
PreferencesDialog dlg(mainframe, open_on_tab, highlight_option);
dlg.ShowModal();
this->plater_->get_current_canvas3D()->force_set_focus();
// BBS
//app_layout_changed = dlg.settings_layout_changed();
need_recreate_gui = dlg.recreate_GUI();
pending_language = dlg.pending_language();
if (!need_recreate_gui) {
this->plater_->get_current_canvas3D()->force_set_focus();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
#else
if (dlg.seq_top_layer_only_changed())
if (dlg.seq_top_layer_only_changed())
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
this->plater_->reload_print();
this->plater_->reload_print();
#ifdef _WIN32
if (is_editor()) {
if (app_config->get("associate_3mf") == "true")
associate_files(L"3mf");
if (app_config->get("associate_stl") == "true")
associate_files(L"stl");
if (app_config->get("associate_step") == "true") {
associate_files(L"step");
associate_files(L"stp");
if (is_editor()) {
if (app_config->get("associate_3mf") == "true")
associate_files(L"3mf");
if (app_config->get("associate_stl") == "true")
associate_files(L"stl");
if (app_config->get("associate_step") == "true") {
associate_files(L"step");
associate_files(L"stp");
}
associate_url(L"orcaslicer");
}
else {
if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
}
associate_url(L"orcaslicer");
}
else {
if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
}
#endif // _WIN32
}
}
// BBS
/*
if (app_layout_changed) {
// hide full main_sizer for mainFrame
mainframe->GetSizer()->Show(false);
mainframe->update_layout();
mainframe->select_tab(size_t(0));
}*/
if (!pending_language.empty()) {
const std::string previous_language = app_config->get("language");
app_config->set("language", pending_language);
if (!load_language(wxString::FromUTF8(pending_language), false)) {
app_config->set("language", previous_language);
if (this->plater_)
this->plater_->get_current_canvas3D()->force_set_focus();
return;
}
}
if (need_recreate_gui)
recreate_GUI(_L("Changing application language"));
}
bool GUI_App::has_unsaved_preset_changes() const

View File

@@ -273,6 +273,7 @@ private:
const wxLanguageInfo *m_language_info_system = nullptr;
// Best translation language, provided by Windows or OSX, owned by wxWidgets.
const wxLanguageInfo *m_language_info_best = nullptr;
wxString m_active_language_code;
OpenGLManager m_opengl_mgr;
std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
@@ -563,7 +564,7 @@ public:
void preset_deleted_from_cloud(std::string setting_id);
wxString filter_string(wxString str);
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
wxString current_language_code() const { return m_active_language_code.empty() && m_wxLocale ? m_wxLocale->GetCanonicalName() : m_active_language_code; }
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
wxString current_language_code_safe() const;
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }

View File

@@ -3145,15 +3145,7 @@ void MainFrame::init_menubar_as_editor()
append_menu_item(
parent_menu, wxID_ANY, _L("Preferences") + "\t" + ctrl + ",", "",
[this](wxCommandEvent &) {
PreferencesDialog dlg(this);
dlg.ShowModal();
plater()->get_current_canvas3D()->force_set_focus();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
#else
if (dlg.seq_top_layer_only_changed())
#endif
plater()->reload_print();
wxGetApp().open_preferences();
},
"", nullptr, []() { return true; }, this, 1);
//parent_menu->Insert(1, preference_item);
@@ -3174,7 +3166,6 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent &) {
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_preferences();
plater()->get_current_canvas3D()->force_set_focus();
},
"", nullptr, []() { return true; }, this);
//m_topbar->AddDropDownMenuItem(preference_item);

View File

@@ -318,13 +318,10 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS
m_current_language_selected = combobox->GetSelection();
if (m_current_language_selected >= 0 && m_current_language_selected < vlist.size()) {
app_config->set(param, vlist[m_current_language_selected]->CanonicalName.ToUTF8().data());
wxGetApp().load_language(vlist[m_current_language_selected]->CanonicalName, false);
Close();
// Reparent(nullptr);
GetParent()->RemoveChild(this);
wxGetApp().recreate_GUI(_L("Changing application language"));
m_pending_language = vlist[m_current_language_selected]->CanonicalName.ToUTF8().data();
m_recreate_GUI = true;
EndModal(wxID_OK);
return;
}
}

View File

@@ -6,6 +6,7 @@
#include <wx/dialog.h>
#include <wx/timer.h>
#include <string>
#include <vector>
#include <list>
#include <map>
@@ -43,10 +44,12 @@ protected:
// bool m_settings_layout_changed {false};
bool m_seq_top_layer_only_changed{false};
bool m_recreate_GUI{false};
std::string m_pending_language;
public:
bool seq_top_layer_only_changed() const { return m_seq_top_layer_only_changed; }
bool recreate_GUI() const { return m_recreate_GUI; }
const std::string& pending_language() const { return m_pending_language; }
void on_dpi_changed(const wxRect &suggested_rect) override;
public:

View File

@@ -5,6 +5,9 @@
#include <wx/dcclient.h>
#include <wx/settings.h>
#include <boost/log/trivial.hpp>
#ifdef __linux__
#include <fontconfig/fontconfig.h>
#endif
wxFont Label::sysFont(int size, bool bold)
@@ -58,27 +61,46 @@ wxFont Label::Body_10;
wxFont Label::Body_9;
wxFont Label::Body_8;
// Check if a font family is already available via fontconfig.
#ifdef __linux__
static bool fc_font_available(const char *family_name)
{
FcPattern *pat = FcPatternCreate();
if (!pat)
return false;
FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *) family_name);
FcResult res;
FcPattern *match = FcFontMatch(nullptr, pat, &res);
bool available = false;
if (match) {
FcChar8 *matched_family = nullptr;
if (FcPatternGetString(match, FC_FAMILY, 0, &matched_family) == FcResultMatch && matched_family)
available = (strcasecmp((const char *) matched_family, family_name) == 0);
FcPatternDestroy(match);
}
FcPatternDestroy(pat);
return available;
}
#endif
void Label::initSysFont()
{
#if defined(__linux__) || defined(_WIN32)
const std::string &resource_path = Slic3r::resources_dir();
wxString font_path = wxString::FromUTF8(resource_path + "/fonts/HarmonyOS_Sans_SC_Bold.ttf");
bool result = wxFont::AddPrivateFont(font_path);
// BOOST_LOG_TRIVIAL(info) << boost::format("add font of HarmonyOS_Sans_SC_Bold returns %1%")%result;
// printf("add font of HarmonyOS_Sans_SC_Bold returns %d\n", result);
font_path = wxString::FromUTF8(resource_path + "/fonts/HarmonyOS_Sans_SC_Regular.ttf");
result = wxFont::AddPrivateFont(font_path);
// BOOST_LOG_TRIVIAL(info) << boost::format("add font of HarmonyOS_Sans_SC_Regular returns %1%")%result;
// printf("add font of HarmonyOS_Sans_SC_Regular returns %d\n", result);
// Adding NanumGothic Regular and Bold
font_path = wxString::FromUTF8(resource_path + "/fonts/NanumGothic-Regular.ttf");
result = wxFont::AddPrivateFont(font_path);
// BOOST_LOG_TRIVIAL(info) << boost::format("add font of NanumGothic-Regular returns %1%")%result;
// printf("add font of NanumGothic-Regular returns %d\n", result);
font_path = wxString::FromUTF8(resource_path + "/fonts/NanumGothic-Bold.ttf");
result = wxFont::AddPrivateFont(font_path);
// BOOST_LOG_TRIVIAL(info) << boost::format("add font of NanumGothic-Bold returns %1%")%result;
// printf("add font of NanumGothic-Bold returns %d\n", result);
// On Linux, skip AddPrivateFont for fonts already known to fontconfig
// (e.g. installed system-wide in a Flatpak). Calling AddPrivateFont
// triggers a Pango crash in ensure_faces() on Pango >= 1.48 (GNOME 49+),
// because FcConfigAppFontAddFile invalidates Pango's cached font map.
bool load_fonts = true;
#ifdef __linux__
load_fonts = !fc_font_available("HarmonyOS Sans SC") || !fc_font_available("NanumGothic");
#endif
if (load_fonts) {
const std::string &resource_path = Slic3r::resources_dir();
wxFont::AddPrivateFont(wxString::FromUTF8(resource_path + "/fonts/HarmonyOS_Sans_SC_Bold.ttf"));
wxFont::AddPrivateFont(wxString::FromUTF8(resource_path + "/fonts/HarmonyOS_Sans_SC_Regular.ttf"));
wxFont::AddPrivateFont(wxString::FromUTF8(resource_path + "/fonts/NanumGothic-Regular.ttf"));
wxFont::AddPrivateFont(wxString::FromUTF8(resource_path + "/fonts/NanumGothic-Bold.ttf"));
}
#endif
Head_48 = Label::sysFont(48, true);
Head_32 = Label::sysFont(32, true);