Fix Flatpak missing locale support (#12751)

# Description

Add the localization/ directory to the Flatpak source list so
run_gettext.sh can compile .po files into .mo translations.

Replace LC_ALL=C.UTF-8 with LC_NUMERIC=C in the entrypoint script to
preserve the user's language settings while still preventing
decimal-separator parsing issues.
fixes #12714

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
This commit is contained in:
SoftFever
2026-03-16 14:38:54 +08:00
committed by GitHub
11 changed files with 367 additions and 92 deletions

3
.gitignore vendored
View File

@@ -44,4 +44,5 @@ test.js
/.cache/
.clangd
internal_docs/
*.flatpak
*.flatpak
/flatpak-repo/

View File

@@ -0,0 +1,218 @@
#!/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.
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
PRIVILEGED=false
CONTAINER_IMAGE="ghcr.io/flathub-infra/flatpak-github-actions:gnome-49"
normalize_arch() {
case "$1" in
arm64|aarch64)
echo "aarch64"
;;
x86_64|amd64)
echo "x86_64"
;;
*)
echo "$1"
;;
esac
}
# ---------- 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 ;;
--privileged)
PRIVILEGED=true; shift ;;
--image)
CONTAINER_IMAGE="$2"; shift 2 ;;
-h|--help)
echo "Usage: $0 [--arch <x86_64|aarch64>] [--no-debug-info] [--no-pull] [--keep-build] [--privileged] [--image <image>]"
exit 0 ;;
*)
echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
ARCH="$(normalize_arch "$ARCH")"
case "$ARCH" in
x86_64|aarch64)
;;
*)
echo "Unsupported architecture: $ARCH. Supported: x86_64, aarch64" >&2
exit 1
;;
esac
# ---------- 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 " Privileged: $([ "$PRIVILEGED" = true ] && echo "enabled" || echo "disabled")"
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"
# Ensure cleanup on exit (success or failure)
trap 'rm -f "$PROJECT_ROOT/$MANIFEST_DOCKER"' EXIT
# Build Docker-specific manifest with customizations (piped to avoid sed -i portability)
{
if [ "$NO_DEBUG_INFO" = true ]; then
sed '/^build-options:/a\
no-debuginfo: true\
strip: true
'
else
cat
fi
} < "$MANIFEST_SRC" | \
sed "/name: OrcaSlicer/{
n
s|^\([[:space:]]*\)buildsystem: simple|\1buildsystem: simple\\
\1build-options:\\
\1 env:\\
\1 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
DOCKER_RUN_ARGS=(run --rm)
if [ "$PRIVILEGED" = true ]; then
DOCKER_RUN_ARGS+=(--privileged)
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" "${DOCKER_RUN_ARGS[@]}" \
-v "$PROJECT_ROOT":/src:Z \
-w /src \
-e "BUILD_ARCH=$ARCH" \
-e "BUNDLE_NAME=$BUNDLE_NAME" \
-e "FORCE_CLEAN_FLAG=$FORCE_CLEAN_FLAG" \
"$CONTAINER_IMAGE" \
bash -s <<'EOF'
set -euo pipefail
format_duration() {
local total_seconds="$1"
local hours=$((total_seconds / 3600))
local minutes=$(((total_seconds % 3600) / 60))
local seconds=$((total_seconds % 60))
printf "%02d:%02d:%02d" "$hours" "$minutes" "$seconds"
}
overall_start=$(date +%s)
install_start=$overall_start
# Install required SDK extensions (not pre-installed in the container image)
flatpak install -y --noninteractive --arch="$BUILD_ARCH" flathub \
org.gnome.Platform//49 \
org.gnome.Sdk//49 \
org.freedesktop.Sdk.Extension.llvm21//25.08 || true
install_end=$(date +%s)
install_duration=$((install_end - install_start))
builder_start=$(date +%s)
flatpak-builder $FORCE_CLEAN_FLAG \
--verbose \
--ccache \
--disable-rofiles-fuse \
--state-dir=.flatpak-builder \
--arch="$BUILD_ARCH" \
--repo=flatpak-repo \
flatpak-build \
scripts/flatpak/io.github.orcaslicer.OrcaSlicer.docker.yml
builder_end=$(date +%s)
builder_duration=$((builder_end - builder_start))
bundle_start=$(date +%s)
flatpak build-bundle \
--arch="$BUILD_ARCH" \
flatpak-repo \
"$BUNDLE_NAME" \
io.github.orcaslicer.OrcaSlicer
bundle_end=$(date +%s)
bundle_duration=$((bundle_end - bundle_start))
# Fix ownership so output files are not root-owned on the host
owner="$(stat -c %u:%g /src)"
chown -R "$owner" .flatpak-builder flatpak-build flatpak-repo "$BUNDLE_NAME" 2>/dev/null || true
overall_end=$(date +%s)
overall_duration=$((overall_end - overall_start))
echo ""
echo "=== Build complete ==="
echo "=== Build Stats ==="
echo " Runtime install: $(format_duration "$install_duration")"
echo " flatpak-builder: $(format_duration "$builder_duration")"
echo " Bundle export: $(format_duration "$bundle_duration")"
echo " Overall: $(format_duration "$overall_duration")"
EOF
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

@@ -4,6 +4,8 @@
grep -q org.freedesktop.Platform.GL.nvidia /.flatpak-info && export WEBKIT_DISABLE_DMABUF_RENDERER=1
# Work-around https://github.com/bambulab/BambuStudio/issues/3440
export LC_ALL=C.UTF-8
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
# while preserving the user's language/locale for translations.
export LC_NUMERIC=C
exec /app/bin/orca-slicer "$@"

View File

@@ -117,10 +117,9 @@ modules:
- -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld
- -DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld
sources:
- type: git
url: https://github.com/SoftFever/Orca-deps-wxWidgets
tag: orca-3.1.5-1
commit: 139e4f2a62a9d1c40bdcf36523d94a517b14ca79
- type: archive
url: https://github.com/SoftFever/Orca-deps-wxWidgets/archive/refs/tags/orca-3.1.5-1.tar.gz
sha256: 1dc9d3865d899cb71c27a7e549aa5491e832ef6e81a7b6653ccb11f9c37fa99d
# OrcaSlicer C++ dependencies (built offline with pre-downloaded archives)
- name: orca_deps
@@ -333,6 +332,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
@@ -347,6 +351,9 @@ modules:
- type: dir
path: ../../src
dest: src
- type: dir
path: ../../localization
dest: localization
- type: file
path: ../../CMakeLists.txt

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);