From 0fa2591c4f805bbb849eb39da0ad4110349e68e5 Mon Sep 17 00:00:00 2001 From: Dieter Mayer Date: Sat, 1 Aug 2026 08:48:07 +0200 Subject: [PATCH 1/2] Report salvaged and unreadable font descriptions after opening a project Until now a font description that could not be parsed only produced console warnings most users never see, so nobody learned that their texts silently lost their formatting (see the reports in issue #553). Count in QETUtils::fontFromString() how many descriptions were salvaged from a foreign or corrupt format and how many stayed unreadable, and show a message box after opening a project when either happened: salvaged descriptions are rewritten in the stable format on the next save, unreadable ones fall back to the default font. Projects without font issues open exactly as before, and non-interactive opens only log the counters. Verified with a Qt 5.15 build on a project carrying 52 19-field and one 21-field description: the dialog reports 53 restored descriptions; the same file on a Qt 6.11 build (which parses those formats natively) shows no dialog. See issue #553. --- sources/qetdiagrameditor.cpp | 42 ++++++++++++++++++++++++++++++++ sources/utils/qetutils.cpp | 46 ++++++++++++++++++++++++++++++++++++ sources/utils/qetutils.h | 3 +++ 3 files changed, 91 insertions(+) diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 88c239d6e..e8f1e75a6 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -43,6 +43,7 @@ #include "ui/backupdialog.h" #include "ui/dialogwaiting.h" #include "undocommand/addelementtextcommand.h" +#include "utils/qetutils.h" #include "undocommand/rotateselectioncommand.h" #include "undocommand/rotatetextscommand.h" #include "diagram.h" @@ -1168,6 +1169,11 @@ bool QETDiagramEditor::openAndAddProject( //Create the project DialogWaiting::instance(this); + //Per-project window for the font counters reported below; the folios + //(and with them the stored font descriptions) are built between here + //and the end of addProject(). + QETUtils::resetFontRestorationCounters(); + QETProject *project = new QETProject(filepath); if (project -> state() != QETProject::Ok) { @@ -1194,6 +1200,42 @@ bool QETDiagramEditor::openAndAddProject( addProject(project); DialogWaiting::dropInstance(); + //Report font descriptions which could not be read as-is (written by + //an incompatible Qt version or corrupted), so the user learns about + //it from somewhere else than the console. See issue #553. + const int salvaged_fonts = QETUtils::salvagedFontCount(); + const int unreadable_fonts = QETUtils::unreadableFontCount(); + if (salvaged_fonts || unreadable_fonts) + { + qInfo().nospace() << "Project font descriptions: " + << salvaged_fonts << " salvaged from a foreign format, " + << unreadable_fonts << " unreadable (default font applies)"; + } + if (interactive && (salvaged_fonts || unreadable_fonts)) + { + QStringList details; + if (salvaged_fonts) { + details << tr("%n description(s) de police écrite(s) dans un " + "format étranger ou corrompu ont été restaurée(s). " + "Elles seront réécrites dans un format stable au " + "prochain enregistrement du projet.", + "message box content", + salvaged_fonts); + } + if (unreadable_fonts) { + details << tr("%n description(s) de police n'ont pas pu être " + "lue(s) ; la police par défaut sera utilisée pour " + "ces textes.", + "message box content", + unreadable_fonts); + } + QET::QetMessageBox::information( + this, + tr("Polices du projet", "message box title"), + details.join("\n\n") + ); + } + BackupDialog backup_dialog(this); if (backup_dialog.exec() == QDialog::Accepted) { diff --git a/sources/utils/qetutils.cpp b/sources/utils/qetutils.cpp index 8cf457a67..a2dcd1878 100644 --- a/sources/utils/qetutils.cpp +++ b/sources/utils/qetutils.cpp @@ -153,6 +153,12 @@ void QETUtils::pixelSizedFont(QFont &font) namespace { + /* Counters for fontFromString(), reset per project load so the editor + * can report how many stored font descriptions needed salvaging or were + * unreadable. Font parsing only happens on the main thread. */ + int salvaged_font_count = 0; + int unreadable_font_count = 0; + /** * Legacy (Qt 5) weight <- OpenType weight, closest match, * same table Qt uses when parsing a 10/11 field string. @@ -235,6 +241,10 @@ QString QETUtils::fontToString(const QFont &font) */ bool QETUtils::fontFromString(QFont &font, const QString &description) { + if (description.trimmed().isEmpty()) { + return false; + } + QFont parsed(font); if (parsed.fromString(description)) { font = parsed; @@ -271,8 +281,10 @@ bool QETUtils::fontFromString(QFont &font, const QString &description) if (parsed.fromString(legacy)) { font = parsed; + ++salvaged_font_count; return true; } + ++unreadable_font_count; return false; } @@ -284,7 +296,41 @@ bool QETUtils::fontFromString(QFont &font, const QString &description) if (count > 11 && parsed.fromString(QStringList(l.mid(0, 11)).join(comma))) { font = parsed; + ++salvaged_font_count; return true; } + ++unreadable_font_count; return false; } + +/** + * @brief QETUtils::resetFontRestorationCounters + * Reset the counters incremented by fontFromString(), to be called before + * loading a project so the numbers reported afterwards are per-project. + */ +void QETUtils::resetFontRestorationCounters() +{ + salvaged_font_count = 0; + unreadable_font_count = 0; +} + +/** + * @brief QETUtils::salvagedFontCount + * @return How many font descriptions fontFromString() restored from a + * foreign or corrupt format since the counters were last reset. Such + * descriptions are rewritten in the stable format on the next save. + */ +int QETUtils::salvagedFontCount() +{ + return salvaged_font_count; +} + +/** + * @brief QETUtils::unreadableFontCount + * @return How many font descriptions fontFromString() could not restore at + * all since the counters were last reset (the caller's default font applies). + */ +int QETUtils::unreadableFontCount() +{ + return unreadable_font_count; +} diff --git a/sources/utils/qetutils.h b/sources/utils/qetutils.h index 81872ce41..f679b0320 100644 --- a/sources/utils/qetutils.h +++ b/sources/utils/qetutils.h @@ -34,6 +34,9 @@ namespace QETUtils void pixelSizedFont (QFont &font); QString fontToString (const QFont &font); bool fontFromString (QFont &font, const QString &description); + void resetFontRestorationCounters (); + int salvagedFontCount (); + int unreadableFontCount (); bool sortBeginIntString(const QString &str_a, const QString &str_b); From 3bc2e8e712331062095144f17990ab6973dab5cf Mon Sep 17 00:00:00 2001 From: Dieter Mayer Date: Sat, 1 Aug 2026 11:13:57 +0200 Subject: [PATCH 2/2] Make the font restoration counters safe against nested project loads DialogWaiting pumps the event loop while the folios of a project are built, so a second openAndAddProject() can run to completion nested inside the first one (drop on another editor window, queued open) and the plain reset/read counters would then report the wrong numbers. Replace them with a RAII counting window (FontRestorationScope): the constructor keeps the enclosing counts aside, the destructor restores them. The nesting is strictly LIFO - the nested load completes inside the pump of the outer one - so each load reports exactly its own numbers, and the early-return paths of openAndAddProject() restore the outer window automatically. Suggested by ispyisail in the review of the reporting change. --- sources/qetdiagrameditor.cpp | 10 ++++++---- sources/utils/qetutils.cpp | 31 ++++++++++++++++++++----------- sources/utils/qetutils.h | 26 +++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index e8f1e75a6..bd6336ebc 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -1171,8 +1171,10 @@ bool QETDiagramEditor::openAndAddProject( //Per-project window for the font counters reported below; the folios //(and with them the stored font descriptions) are built between here - //and the end of addProject(). - QETUtils::resetFontRestorationCounters(); + //and the end of addProject(). RAII, because DialogWaiting pumps the + //event loop during the load: a nested openAndAddProject() gets its + //own window and this one resumes unharmed. + QETUtils::FontRestorationScope font_scope; QETProject *project = new QETProject(filepath); if (project -> state() != QETProject::Ok) @@ -1203,8 +1205,8 @@ bool QETDiagramEditor::openAndAddProject( //Report font descriptions which could not be read as-is (written by //an incompatible Qt version or corrupted), so the user learns about //it from somewhere else than the console. See issue #553. - const int salvaged_fonts = QETUtils::salvagedFontCount(); - const int unreadable_fonts = QETUtils::unreadableFontCount(); + const int salvaged_fonts = font_scope.salvaged(); + const int unreadable_fonts = font_scope.unreadable(); if (salvaged_fonts || unreadable_fonts) { qInfo().nospace() << "Project font descriptions: " diff --git a/sources/utils/qetutils.cpp b/sources/utils/qetutils.cpp index a2dcd1878..6abbbc4f2 100644 --- a/sources/utils/qetutils.cpp +++ b/sources/utils/qetutils.cpp @@ -304,33 +304,42 @@ bool QETUtils::fontFromString(QFont &font, const QString &description) } /** - * @brief QETUtils::resetFontRestorationCounters - * Reset the counters incremented by fontFromString(), to be called before - * loading a project so the numbers reported afterwards are per-project. + * @brief QETUtils::FontRestorationScope::FontRestorationScope + * Open a fresh counting window: the enclosing window's counts are kept + * aside and restored by the destructor, so a project load nested inside + * another one (through the event loop) reports its own numbers only. */ -void QETUtils::resetFontRestorationCounters() +QETUtils::FontRestorationScope::FontRestorationScope() : + m_outer_salvaged(salvaged_font_count), + m_outer_unreadable(unreadable_font_count) { salvaged_font_count = 0; unreadable_font_count = 0; } +QETUtils::FontRestorationScope::~FontRestorationScope() +{ + salvaged_font_count = m_outer_salvaged; + unreadable_font_count = m_outer_unreadable; +} + /** - * @brief QETUtils::salvagedFontCount + * @brief QETUtils::FontRestorationScope::salvaged * @return How many font descriptions fontFromString() restored from a - * foreign or corrupt format since the counters were last reset. Such - * descriptions are rewritten in the stable format on the next save. + * foreign or corrupt format since this window was opened. Such descriptions + * are rewritten in the stable format on the next save. */ -int QETUtils::salvagedFontCount() +int QETUtils::FontRestorationScope::salvaged() const { return salvaged_font_count; } /** - * @brief QETUtils::unreadableFontCount + * @brief QETUtils::FontRestorationScope::unreadable * @return How many font descriptions fontFromString() could not restore at - * all since the counters were last reset (the caller's default font applies). + * all since this window was opened (the caller's default font applies). */ -int QETUtils::unreadableFontCount() +int QETUtils::FontRestorationScope::unreadable() const { return unreadable_font_count; } diff --git a/sources/utils/qetutils.h b/sources/utils/qetutils.h index f679b0320..86fef778b 100644 --- a/sources/utils/qetutils.h +++ b/sources/utils/qetutils.h @@ -34,9 +34,29 @@ namespace QETUtils void pixelSizedFont (QFont &font); QString fontToString (const QFont &font); bool fontFromString (QFont &font, const QString &description); - void resetFontRestorationCounters (); - int salvagedFontCount (); - int unreadableFontCount (); + + /** + RAII counting window for the font descriptions fontFromString() + salvages or fails to read: construction opens a fresh window, the + destructor restores the enclosing one. Windows nest strictly LIFO, + which covers project loads re-entered through the event loop + (DialogWaiting pumps it while the folios are built). + */ + class FontRestorationScope + { + public: + FontRestorationScope(); + ~FontRestorationScope(); + FontRestorationScope(const FontRestorationScope &) = delete; + FontRestorationScope &operator=(const FontRestorationScope &) = delete; + + int salvaged() const; + int unreadable() const; + + private: + int m_outer_salvaged; + int m_outer_unreadable; + }; bool sortBeginIntString(const QString &str_a, const QString &str_b);