diff --git a/sources/editor/graphicspart/partdynamictextfield.cpp b/sources/editor/graphicspart/partdynamictextfield.cpp index ee9b0c647..f8dece784 100644 --- a/sources/editor/graphicspart/partdynamictextfield.cpp +++ b/sources/editor/graphicspart/partdynamictextfield.cpp @@ -20,6 +20,7 @@ #include "../../QPropertyUndoCommand/qpropertyundocommand.h" #include "../../qetapp.h" #include "../elementscene.h" +#include "../../utils/qetutils.h" #include #include @@ -142,7 +143,7 @@ const QDomElement PartDynamicTextField::toXml(QDomDocument &dom_doc) const root_element.setAttribute("y", QString::number(y)); root_element.setAttribute("z", QString::number(zValue())); root_element.setAttribute("rotation", QString::number(QET::correctAngle(rot))); - root_element.setAttribute("font", font().toString()); + root_element.setAttribute("font", QETUtils::fontToString(font())); root_element.setAttribute("uuid", m_uuid.toString()); root_element.setAttribute("frame", m_frame? "true" : "false"); root_element.setAttribute("text_width", QString::number(m_text_width)); @@ -214,7 +215,7 @@ void PartDynamicTextField::fromXml(const QDomElement &dom_elmt) { if (dom_elmt.hasAttribute("font")) { QFont font_; - font_.fromString(dom_elmt.attribute("font")); + QETUtils::fontFromString(font_, dom_elmt.attribute("font")); setFont(font_); } else if (dom_elmt.hasAttribute("font_size")) { diff --git a/sources/editor/graphicspart/parttext.cpp b/sources/editor/graphicspart/parttext.cpp index 25e02f1c4..7f4112d86 100644 --- a/sources/editor/graphicspart/parttext.cpp +++ b/sources/editor/graphicspart/parttext.cpp @@ -23,6 +23,7 @@ #include "../elementprimitivedecorator.h" #include "../elementscene.h" #include "../ui/texteditor.h" +#include "../../utils/qetutils.h" /** Constructeur @@ -125,7 +126,7 @@ void PartText::fromXml(const QDomElement &xml_element) { } else if (xml_element.hasAttribute("font")) { QFont font_; - font_.fromString(xml_element.attribute("font")); + QETUtils::fontFromString(font_, xml_element.attribute("font")); setFont(font_); } @@ -165,7 +166,7 @@ const QDomElement PartText::toXml(QDomDocument &xml_document) const xml_element.setAttribute("x", QString::number(x)); xml_element.setAttribute("y", QString::number(y)); xml_element.setAttribute("text", toPlainText()); - xml_element.setAttribute("font", font().toString()); + xml_element.setAttribute("font", QETUtils::fontToString(font())); xml_element.setAttribute("rotation", QString::number(rot)); xml_element.setAttribute("color", defaultTextColor().name()); diff --git a/sources/factory/elementpicturefactory.cpp b/sources/factory/elementpicturefactory.cpp index cd22ac9c7..99d21ca10 100644 --- a/sources/factory/elementpicturefactory.cpp +++ b/sources/factory/elementpicturefactory.cpp @@ -22,6 +22,7 @@ #include "../properties/elementdata.h" #include "../qetapp.h" #include "../qetversion.h" +#include "../utils/qetutils.h" #include #include @@ -531,7 +532,7 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter, font_ = QETApp::diagramTextsFont(dom.attribute("size").toDouble()); } else if (dom.hasAttribute("font")) { - font_.fromString(dom.attribute("font")); + QETUtils::fontFromString(font_, dom.attribute("font")); } QColor text_color(dom.attribute("color", "#000000")); diff --git a/sources/factory/ui/addtabledialog.cpp b/sources/factory/ui/addtabledialog.cpp index dce18168d..7c5624383 100644 --- a/sources/factory/ui/addtabledialog.cpp +++ b/sources/factory/ui/addtabledialog.cpp @@ -221,12 +221,12 @@ void AddTableDialog::saveConfig() header_object.insert("margins", QETUtils::marginsToString(this->headerMargins())); auto me = QMetaEnum::fromType(); header_object.insert("alignment", me.valueToKey(int(this->headerAlignment()))); - header_object.insert("font", this->headerFont().toString()); + header_object.insert("font", QETUtils::fontToString(this->headerFont())); QJsonObject table_object; table_object.insert("margins", QETUtils::marginsToString(this->tableMargins())); table_object.insert("alignment", me.valueToKey(int(this->tableAlignment()))); - table_object.insert("font", this->tableFont().toString()); + table_object.insert("font", QETUtils::fontToString(this->tableFont())); QJsonObject config_object; config_object.insert("header", header_object); @@ -275,7 +275,7 @@ void AddTableDialog::loadConfig() default: ui->m_header_alignment_cb->setCurrentIndex(2); } - m_header_font.fromString(header_object.value("font").toString()); + QETUtils::fontFromString(m_header_font, header_object.value("font").toString()); ui->m_header_font_pb->setText(m_header_font.family()); //Table @@ -292,7 +292,7 @@ void AddTableDialog::loadConfig() default: ui->m_table_alignment_cb->setCurrentIndex(2); } - m_table_font.fromString(table_object.value("font").toString()); + QETUtils::fontFromString(m_table_font, table_object.value("font").toString()); ui->m_table_font_pb->setText(m_table_font.family()); } diff --git a/sources/properties/terminaldata.cpp b/sources/properties/terminaldata.cpp index 57b135421..2b9cd36c2 100644 --- a/sources/properties/terminaldata.cpp +++ b/sources/properties/terminaldata.cpp @@ -17,6 +17,8 @@ */ #include "terminaldata.h" +#include "../utils/qetutils.h" + #include #include @@ -115,7 +117,7 @@ QDomElement TerminalData::toXml(QDomDocument &xml_document) const xml_element.setAttribute("show_name", "true"); xml_element.setAttribute("label_x", QString::number(m_label_pos.x())); xml_element.setAttribute("label_y", QString::number(m_label_pos.y())); - xml_element.setAttribute("label_font", m_label_font.toString()); + xml_element.setAttribute("label_font", QETUtils::fontToString(m_label_font)); xml_element.setAttribute("label_rotation", QString::number(m_label_rotation)); xml_element.setAttribute("label_halign", static_cast(m_label_halignment)); xml_element.setAttribute("label_valign", static_cast(m_label_valignment)); @@ -186,7 +188,7 @@ bool TerminalData::fromXml (const QDomElement &xml_element) QString font_str = xml_element.attribute("label_font"); if (!font_str.isEmpty()) - m_label_font.fromString(font_str); + QETUtils::fontFromString(m_label_font, font_str); m_label_rotation = xml_element.attribute("label_rotation", "0").toDouble(); diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index 83d26f9d4..deb20b161 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -26,6 +26,7 @@ #include "projectview.h" #include "qetdiagrameditor.h" #include "qeticons.h" +#include "utils/qetutils.h" #include "qetmessagebox.h" #include "qetproject.h" #include "qtextorientationspinboxwidget.h" @@ -1425,7 +1426,7 @@ QFont QETApp::diagramTextsItemFont(qreal size) //Font to use QFont font_ = diagramTextsItemFont(); if (settings.contains("diagrameditor/dynamic_text_font")) { - font_.fromString(settings.value( + QETUtils::fontFromString(font_, settings.value( "diagrameditor/dynamic_text_font" ).toString()); } @@ -1449,7 +1450,7 @@ QFont QETApp::indiTextsItemFont(qreal size) //Font to use QFont font_ = diagramTextsItemFont(); if (settings.contains("diagrameditor/independent_text_font")) { - font_.fromString(settings.value( + QETUtils::fontFromString(font_, settings.value( "diagrameditor/independent_text_font" ).toString()); } diff --git a/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp b/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp index 91f137379..7ef031504 100644 --- a/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp +++ b/sources/qetgraphicsitem/ViewItem/projectdbmodel.cpp @@ -22,6 +22,7 @@ #include "../../qetinformation.h" #include "../../qetproject.h" #include "../../qetxml.h" +#include "../../utils/qetutils.h" #include #include @@ -252,7 +253,7 @@ QDomElement ProjectDBModel::toXml(QDomDocument &document) const //Add index 0,0 data auto index_00 = document.createElement("index00"); - index_00.setAttribute("font", m_index_0_0_data.value(Qt::FontRole).toString()); + index_00.setAttribute("font", QETUtils::fontToString(m_index_0_0_data.value(Qt::FontRole).value())); auto me = QMetaEnum::fromType(); index_00.setAttribute("alignment", me.valueToKey(m_index_0_0_data.value(Qt::TextAlignmentRole).toInt())); dom_element.appendChild(index_00); @@ -290,7 +291,7 @@ void ProjectDBModel::fromXml(const QDomElement &element) //Index 0,0 auto index_00 = element.firstChildElement("index00"); QFont font_; - font_.fromString(index_00.attribute("font")); + QETUtils::fontFromString(font_, index_00.attribute("font")); m_index_0_0_data.insert(Qt::FontRole, font_); auto me = QMetaEnum::fromType(); m_index_0_0_data.insert(Qt::TextAlignmentRole, me.keyToValue(index_00.attribute("alignment").toStdString().data())); diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index 194296d60..8f6359e97 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -23,6 +23,7 @@ #include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/terminal.h" #include "../qetinformation.h" +#include "../utils/qetutils.h" #include "crossrefitem.h" #include "element.h" #include "elementtextitemgroup.h" @@ -96,7 +97,7 @@ QDomElement DynamicElementTextItem::toXml(QDomDocument &dom_doc) const root_element.setAttribute("uuid", m_uuid.toString()); root_element.setAttribute("frame", m_frame? "true" : "false"); root_element.setAttribute("text_width", QString::number(m_text_width)); - root_element.setAttribute("font", font().toString()); + root_element.setAttribute("font", QETUtils::fontToString(font())); root_element.setAttribute("keep_visual_rotation", m_keep_visual_rotation ? "true" : "false"); QMetaEnum me = textFromMetaEnum(); @@ -167,7 +168,7 @@ void DynamicElementTextItem::fromXml(const QDomElement &dom_elmt) if (dom_elmt.hasAttribute("font")) { QFont font; - font.fromString(dom_elmt.attribute("font")); + QETUtils::fontFromString(font, dom_elmt.attribute("font")); setFont(font); } else //Retrocompatibility during the 0.7 dev because the font property was added lately. TODO remove this part in futur diff --git a/sources/qetgraphicsitem/independenttextitem.cpp b/sources/qetgraphicsitem/independenttextitem.cpp index 1fc4b8559..ba92533b8 100644 --- a/sources/qetgraphicsitem/independenttextitem.cpp +++ b/sources/qetgraphicsitem/independenttextitem.cpp @@ -21,6 +21,7 @@ #include "../diagramcommands.h" #include "../qet.h" #include "../qetapp.h" +#include "../utils/qetutils.h" #include #include @@ -64,7 +65,7 @@ void IndependentTextItem::fromXml(const QDomElement &e) { if (e.hasAttribute("font")) { QFont font; - font.fromString(e.attribute("font")); + QETUtils::fontFromString(font, e.attribute("font")); setFont(font); } } @@ -80,7 +81,7 @@ QDomElement IndependentTextItem::toXml(QDomDocument &document) const result.setAttribute("y", QString("%1").arg(pos().y())); result.setAttribute("text", toHtml()); result.setAttribute("rotation", QString::number(QET::correctAngle(rotation()))); - result.setAttribute("font", font().toString()); + result.setAttribute("font", QETUtils::fontToString(font())); return(result); } diff --git a/sources/qetxml.cpp b/sources/qetxml.cpp index 92db3f787..042d13ae7 100644 --- a/sources/qetxml.cpp +++ b/sources/qetxml.cpp @@ -18,6 +18,7 @@ #include "qetxml.h" #include "NameList/nameslist.h" +#include "utils/qetutils.h" #include #include @@ -477,7 +478,7 @@ QDomElement QETXML::modelHeaderDataToXml( else if (role == Qt::FontRole) { auto font = variant.value(); - text_node.setData(font.toString()); + text_node.setData(QETUtils::fontToString(font)); } else if (role == Qt::TextAlignmentRole) { @@ -543,7 +544,7 @@ void QETXML::modelHeaderDataFromXml( else if (role_ == Qt::FontRole) { QFont font; - font.fromString(text_); + QETUtils::fontFromString(font, text_); data_ = font; } else if (role_ == Qt::TextAlignmentRole) diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index d639e3a6a..000b1c547 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -21,6 +21,7 @@ #include "../../qeticons.h" #include "ui_generalconfigurationpage.h" #include "../../utils/qetsettings.h" +#include "../../utils/qetutils.h" #include "../../qetmessagebox.h" #include #include @@ -100,7 +101,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : if (settings.contains("diagrameditor/dynamic_text_font")) { QFont font; - font.fromString(settings.value("diagrameditor/dynamic_text_font").toString()); + QETUtils::fontFromString(font, settings.value("diagrameditor/dynamic_text_font").toString()); QString fontInfos = font.family() + " " + QString::number(font.pointSize()) + " (" + @@ -113,7 +114,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : if (settings.contains("diagrameditor/independent_text_font")) { QFont font; - font.fromString(settings.value("diagrameditor/independent_text_font").toString()); + QETUtils::fontFromString(font, settings.value("diagrameditor/independent_text_font").toString()); QString fontInfos = font.family() + " " + QString::number(font.pointSize()) + " (" + @@ -455,11 +456,11 @@ void GeneralConfigurationPage::on_m_dyn_text_font_pb_clicked() bool ok; QSettings settings; QFont curFont; - curFont.fromString(settings.value("diagrameditor/dynamic_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString()); + QETUtils::fontFromString(curFont, settings.value("diagrameditor/dynamic_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString()); QFont font = QFontDialog::getFont(&ok, curFont, this); if (ok) { - settings.setValue("diagrameditor/dynamic_text_font", font.toString()); + settings.setValue("diagrameditor/dynamic_text_font", QETUtils::fontToString(font)); QString fontInfos = font.family() + " " + QString::number(font.pointSize()) + " (" + font.styleName() + ")"; @@ -559,11 +560,11 @@ void GeneralConfigurationPage::on_m_indi_text_font_pb_clicked() bool ok; QSettings settings; QFont curFont; - curFont.fromString(settings.value("diagrameditor/independent_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString()); + QETUtils::fontFromString(curFont, settings.value("diagrameditor/independent_text_font", "Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular").toString()); QFont font = QFontDialog::getFont(&ok, curFont, this); if (ok) { - settings.setValue("diagrameditor/independent_text_font", font.toString()); + settings.setValue("diagrameditor/independent_text_font", QETUtils::fontToString(font)); QString fontInfos = font.family() + " " + QString::number(font.pointSize()) + " (" + font.styleName() + ")"; diff --git a/sources/utils/qetutils.cpp b/sources/utils/qetutils.cpp index 4f6a393f7..8cf457a67 100644 --- a/sources/utils/qetutils.cpp +++ b/sources/utils/qetutils.cpp @@ -150,3 +150,141 @@ void QETUtils::pixelSizedFont(QFont &font) auto px = font.pointSizeF()/72 * QFontMetrics{font}.fontDpi(); font.setPixelSize(qRound(px)); } + +namespace +{ + /** + * Legacy (Qt 5) weight <- OpenType weight, closest match, + * same table Qt uses when parsing a 10/11 field string. + */ + int legacyFontWeight(const int opentype_weight) + { + static const int weight_map[9][2] = { + {0, 100}, {12, 200}, {25, 300}, {50, 400}, {57, 500}, + {63, 600}, {75, 700}, {81, 800}, {87, 900} + }; + int legacy_weight = weight_map[0][0]; + int closest_diff = qAbs(opentype_weight - weight_map[0][1]); + for (int i = 1 ; i < 9 ; ++i) + { + const int diff = qAbs(opentype_weight - weight_map[i][1]); + if (diff < closest_diff) { + closest_diff = diff; + legacy_weight = weight_map[i][0]; + } + } + return legacy_weight; + } +} + +/** + * @brief QETUtils::fontToString + * Serialize a font to the 10/11 field description format written by Qt 5, + * to be used instead of QFont::toString() everywhere a font description is + * stored in a project, element or settings file. + * The format of QFont::toString() is not stable across Qt versions : Qt 6.11 + * switched to a 19 field format carrying OpenType weights, which + * QFont::fromString() of Qt 5.x and Qt <= 6.10 rejects, leaving a broken font. + * The 10/11 field form is parsed correctly by every Qt version (they convert + * the legacy weight scale as needed), so composing it ourselves keeps files + * readable by every QET build in circulation. + * See @link https://github.com/qelectrotech/qelectrotech-source-mirror/issues/553 @endlink + * @param font + * @return the font description string + */ +QString QETUtils::fontToString(const QFont &font) +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + return font.toString(); +#else + const int legacy_weight = legacyFontWeight(font.weight()); + + const QChar comma(QLatin1Char(',')); + QString description = font.family() + comma + + QString::number(font.pointSizeF()) + comma + + QString::number(font.pixelSize()) + comma + + QString::number(int(font.styleHint())) + comma + + QString::number(legacy_weight) + comma + + QString::number(int(font.style())) + comma + + QString::number(int(font.underline())) + comma + + QString::number(int(font.strikeOut())) + comma + + QString::number(int(font.fixedPitch())) + comma + + QString::number(0); + if (!font.styleName().isEmpty()) { + description += QChar(',') + font.styleName(); + } + return description; +#endif +} + +/** + * @brief QETUtils::fontFromString + * Restore a font from a description string, tolerating descriptions written + * by any Qt version. QFont::fromString() of Qt 5.x and Qt <= 6.10 rejects the + * >= 19 field format QFont::toString() emits since Qt 6.11 (16 base fields + * plus style name, font-feature count and variable-axis count), leaving a + * broken font behind. When the native parser fails, salvage such a + * description by re-composing the 10/11 field form (OpenType weight mapped + * back to the legacy scale) and parsing that instead, so no font information + * stored in existing files is lost. + * See @link https://github.com/qelectrotech/qelectrotech-source-mirror/issues/553 @endlink + * @param font : font to restore into; left untouched on failure, so the + * caller's default keeps applying. + * @param description : font description string + * @return true if the description could be parsed + */ +bool QETUtils::fontFromString(QFont &font, const QString &description) +{ + QFont parsed(font); + if (parsed.fromString(description)) { + font = parsed; + return true; + } + + const QStringList l = description.split(QLatin1Char(',')); + const int count = l.size(); + const QChar comma(QLatin1Char(',')); + + //16 base fields + style name + feature count + axis count; a family + //name containing commas adds leading fields. Descriptions actually + //using font features or variable axes append value groups whose + //layout we cannot anchor reliably, so only salvage the plain + //"...,0,0" case (the only one QET-edited files can contain). + if (count >= 19 + && l.at(count - 1).trimmed() == QLatin1String("0") + && l.at(count - 2).trimmed() == QLatin1String("0")) + { + QString legacy = QStringList(l.mid(0, count - 18)).join(comma) + comma + // family + l.at(count - 18) + comma + // pointSizeF + l.at(count - 17) + comma + // pixelSize + l.at(count - 16) + comma + // styleHint + QString::number(legacyFontWeight(l.at(count - 15).toInt())) + comma + + l.at(count - 14) + comma + // style + l.at(count - 13) + comma + // underline + l.at(count - 12) + comma + // strikeOut + l.at(count - 11) + comma + // fixedPitch + QString::number(0); + const QString style_name = l.at(count - 3); + if (!style_name.isEmpty()) { + legacy += comma + style_name; + } + + if (parsed.fromString(legacy)) { + font = parsed; + return true; + } + return false; + } + + //Some historical builds double-serialized fonts, embedding a complete + //legacy description as the family name of a second one (21 fields: + //"Caladea,9,-1,5,75,1,0,0,0,0,Bold Italic,9,-1,0,50,0,0,0,0,0,Regular"). + //The embedded leading description carries the real font, and taking it + //matches what the lenient parser of Qt 6.11+ resolves such strings to. + if (count > 11 + && parsed.fromString(QStringList(l.mid(0, 11)).join(comma))) { + font = parsed; + return true; + } + return false; +} diff --git a/sources/utils/qetutils.h b/sources/utils/qetutils.h index ffc003db2..81872ce41 100644 --- a/sources/utils/qetutils.h +++ b/sources/utils/qetutils.h @@ -32,6 +32,8 @@ namespace QETUtils QMargins marginsFromString(const QString &string); qreal graphicsHandlerSize(QGraphicsItem *item); void pixelSizedFont (QFont &font); + QString fontToString (const QFont &font); + bool fontFromString (QFont &font, const QString &description); bool sortBeginIntString(const QString &str_a, const QString &str_b);