From 8168d0a4e088748dde3d843fd1aa77eed245f528 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 16 Mar 2026 00:18:39 +0800 Subject: [PATCH] fix crash when opening preference dialog --- .gitignore | 3 +- scripts/build_flatpak_with_docker.sh | 144 ++++++++++++++++++ scripts/flatpak/.gitignore | 1 + .../io.github.orcaslicer.OrcaSlicer.yml | 5 + src/slic3r/GUI/GUI_App.cpp | 132 ++++++++++------ src/slic3r/GUI/GUI_App.hpp | 3 +- src/slic3r/GUI/MainFrame.cpp | 11 +- src/slic3r/GUI/Preferences.cpp | 11 +- src/slic3r/GUI/Preferences.hpp | 3 + src/slic3r/GUI/Widgets/Label.cpp | 58 ++++--- 10 files changed, 284 insertions(+), 87 deletions(-) create mode 100755 scripts/build_flatpak_with_docker.sh diff --git a/.gitignore b/.gitignore index 4b186b4e14..8007f0a05c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ test.js /.cache/ .clangd internal_docs/ -*.flatpak \ No newline at end of file +*.flatpak +/flatpak-repo/ \ No newline at end of file diff --git a/scripts/build_flatpak_with_docker.sh b/scripts/build_flatpak_with_docker.sh new file mode 100755 index 0000000000..2ebf30a416 --- /dev/null +++ b/scripts/build_flatpak_with_docker.sh @@ -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 ] [--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 ] [--no-debug-info] [--no-pull] [--keep-build] [--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}" \ No newline at end of file diff --git a/scripts/flatpak/.gitignore b/scripts/flatpak/.gitignore index 49269ac6b0..3b01f15d01 100644 --- a/scripts/flatpak/.gitignore +++ b/scripts/flatpak/.gitignore @@ -1,2 +1,3 @@ builddir .flatpak-builder +*.docker.yml diff --git a/scripts/flatpak/io.github.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/io.github.orcaslicer.OrcaSlicer.yml index db9e46fc94..6a06e136c3 100644 --- a/scripts/flatpak/io.github.orcaslicer.OrcaSlicer.yml +++ b/scripts/flatpak/io.github.orcaslicer.OrcaSlicer.yml @@ -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 diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 157b168372..04334c73c5 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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(); - 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 diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index ae51add264..f89f583873 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -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 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"; } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 80b801a4c0..047aa2cc62 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -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); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index e5afafad99..5c47e629c7 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -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; } } diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 886f5f7cec..a097dccee2 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -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: diff --git a/src/slic3r/GUI/Widgets/Label.cpp b/src/slic3r/GUI/Widgets/Label.cpp index 35a35c80d6..60a2a90a9e 100644 --- a/src/slic3r/GUI/Widgets/Label.cpp +++ b/src/slic3r/GUI/Widgets/Label.cpp @@ -5,6 +5,9 @@ #include #include #include +#ifdef __linux__ +#include +#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);