mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-10 06:04:12 +02:00
Load a free text's non-finite position or rotation as 0
"nan", "inf" and "-inf" parse as numbers, and IndependentTextItem:: fromXml() used them as they came for x, y and rotation. A project with such a value kept it: --resave wrote x="nan" back, and the SVG export drew the text at matrix(1,0,0,1,nan,95), which is not valid SVG. Conductors and element texts already load a non-finite position as 0. Load a non-finite x, y or rotation of a free text as 0, the same way. No file-format change: finite values load and save as before. Tests: tst_freetextnonfinite runs the binary's --resave and --export-svg on a fixture with x="nan", y="inf", x="-inf", rotation="nan" and a finite text (61.7, 61.3, 37 degrees, kept as they are). The four non-finite cases and the export check fail on master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Beat Hangartner <beat@hangartners.ch>
This commit is contained in:
@@ -85,9 +85,14 @@ IndependentTextItem::~IndependentTextItem()
|
||||
void IndependentTextItem::fromXml(const QDomElement &e) {
|
||||
const QUuid uuid(e.attribute(QStringLiteral("uuid")));
|
||||
if (!uuid.isNull() && uuid != m_uuid) setUuid(uuid);
|
||||
setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble());
|
||||
//"nan" and "inf" parse as numbers; a non-finite position or
|
||||
//rotation is kept at 0, as for conductors and element texts
|
||||
const qreal x = e.attribute("x").toDouble();
|
||||
const qreal y = e.attribute("y").toDouble();
|
||||
setPos(qIsFinite(x) ? x : 0, qIsFinite(y) ? y : 0);
|
||||
setHtml(e.attribute("text"));
|
||||
setRotation(e.attribute("rotation").toDouble());
|
||||
const qreal rotation = e.attribute("rotation").toDouble();
|
||||
setRotation(qIsFinite(rotation) ? rotation : 0);
|
||||
if (e.hasAttribute("font"))
|
||||
{
|
||||
QFont font;
|
||||
|
||||
@@ -584,6 +584,19 @@ target_link_libraries(tst_freetextwidth PRIVATE Qt::Test Qt::Gui Qt::Xml)
|
||||
target_compile_definitions(tst_freetextwidth PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# A free text whose x, y or rotation is "nan" or "inf" is loaded at 0 for
|
||||
# that number, so a resave and the SVG export carry no non-finite number:
|
||||
# the real binary on fixtures/free_text_nonfinite.qet.
|
||||
add_executable(
|
||||
tst_freetextnonfinite
|
||||
tst_freetextnonfinite.cpp)
|
||||
add_test(NAME tst_freetextnonfinite COMMAND tst_freetextnonfinite)
|
||||
set_tests_properties(tst_freetextnonfinite PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
add_dependencies(tst_freetextnonfinite qelectrotech)
|
||||
target_link_libraries(tst_freetextnonfinite PRIVATE Qt::Test Qt::Xml)
|
||||
target_compile_definitions(tst_freetextnonfinite PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# The wiring list export (--export-cables) keeps every wire drawn to a folio
|
||||
# report when a report has several, joins only one-to-one report pairs, and
|
||||
# prints the folio number rather than its template. fixtures/wiring_list_arrows.qet
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<project title="Free texts with non-finite numbers" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a00}" version="0.200.1">
|
||||
<properties>
|
||||
<property name="saveddate" show="1">10/2/26</property>
|
||||
<property name="saveddate-eu" show="1">02-10-2026</property>
|
||||
<property name="saveddate-us" show="1">2026-10-02</property>
|
||||
<property name="savedfilename" show="1">free_text_nonfinite</property>
|
||||
<property name="savedtime" show="1">18:39</property>
|
||||
</properties>
|
||||
<usage enabled="true" time_spent="0"/>
|
||||
<newdiagrams>
|
||||
<border cols="17" colsize="60" displaycols="true" displayrows="true" rows="8" rowsize="80"/>
|
||||
<inset author="" auto_page_num="" date="null" displayAt="bottom" filename="" folio="" indexrev="" locmach="" plant="" title="" version=""/>
|
||||
<conductors bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
|
||||
<report label="%f-%l%c"/>
|
||||
<xrefs>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="coil" xrefpos="AlignBottom"/>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="commutator" xrefpos="AlignBottom"/>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="plc" xrefpos="AlignBottom"/>
|
||||
<xref delayprefix="" displayhas="cross" master_label="%f-%l%c" offset="0" powerprefix="" showallconfiguredslaves="false" showpowerctc="true" showterminalname="true" slave_label="(%f-%l%c)" slave_offset="0" snapto="label" switchprefix="" type="protection" xrefpos="AlignBottom"/>
|
||||
</xrefs>
|
||||
<conductors_autonums auto_break_conductors="false" current_autonum="" freeze_new_conductors="false"/>
|
||||
<folio_autonums/>
|
||||
<element_autonums current_autonum="" freeze_new_elements="false"/>
|
||||
<guides/>
|
||||
</newdiagrams>
|
||||
<diagram author="" auto_page_num="" cols="17" colsize="60" date="null" displayAt="bottom" displaycols="true" displayrows="true" filename="" folio="" freezeNewConductor="false" freezeNewElement="false" height="660" indexrev="" locmach="" order="1" plant="" rows="8" rowsize="80" title="" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a10}" version="0.200.1-dev">
|
||||
<defaultconductor bicolor="false" bus="" cable="" color2="#000000" condsize="1" conductor_color="" conductor_section="" dash-size="1" displaytext="1" formula="" function="" horizontal-alignment="AlignBottom" horizrotatetext="0" num="_" numsize="7" onetextperfolio="0" tension_protocol="" text_color="#000000" type="multi" vertical-alignment="AlignRight" vertirotatetext="270"/>
|
||||
<inputs>
|
||||
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="NanX" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a01}" x="nan" y="100"/>
|
||||
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="InfY" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a02}" x="200" y="inf"/>
|
||||
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="0" text="MinusInfX" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a03}" x="-inf" y="300"/>
|
||||
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="nan" text="NanRotation" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a04}" x="400" y="400"/>
|
||||
<input font="Liberation Sans,9,-1,5,50,0,0,0,0,0,Regular" rotation="37" text="Plain" uuid="{6a1f0c3e-2b44-4d1e-8f0a-3c5d7e9b1a05}" x="61.7" y="61.3"/>
|
||||
</inputs>
|
||||
</diagram>
|
||||
<collection>
|
||||
<category name="import">
|
||||
<names>
|
||||
<name lang="en">Imported elements</name>
|
||||
</names>
|
||||
</category>
|
||||
</collection>
|
||||
</project>
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <QtTest>
|
||||
#include <QDir>
|
||||
#include <QDomDocument>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
/**
|
||||
A free text (<input>) whose x, y or rotation is "nan", "inf" or "-inf"
|
||||
is loaded at 0 for that number, as conductors and element texts are.
|
||||
Those strings parse as numbers, so the text used to keep them: a resave
|
||||
wrote them back, and the SVG export drew the text at translate(nan,...).
|
||||
Runs the real binary's --resave and --export-svg on
|
||||
fixtures/free_text_nonfinite.qet.
|
||||
*/
|
||||
class tst_freetextnonfinite : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
QString m_fixture;
|
||||
|
||||
static bool runQet(const QStringList &arguments)
|
||||
{
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), arguments);
|
||||
return proc.waitForFinished(60000)
|
||||
&& proc.exitStatus() == QProcess::NormalExit
|
||||
&& proc.exitCode() == 0;
|
||||
}
|
||||
|
||||
/// The saved <input> whose text contains @p word.
|
||||
static QDomElement input(const QDomDocument &doc, const QString &word)
|
||||
{
|
||||
const QDomNodeList list = doc.elementsByTagName(QStringLiteral("input"));
|
||||
for (int i = 0; i < list.size(); ++i) {
|
||||
const QDomElement e = list.at(i).toElement();
|
||||
if (e.attribute(QStringLiteral("text")).contains(word)) return e;
|
||||
}
|
||||
return QDomElement();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY2(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)), "qelectrotech binary not found");
|
||||
QVERIFY(m_dir.isValid());
|
||||
m_fixture = QFINDTESTDATA("fixtures/free_text_nonfinite.qet");
|
||||
QVERIFY2(!m_fixture.isEmpty(), "fixture project not found");
|
||||
}
|
||||
|
||||
void savedNumbersAreFinite_data()
|
||||
{
|
||||
QTest::addColumn<QString>("word");
|
||||
QTest::addColumn<QString>("x");
|
||||
QTest::addColumn<QString>("y");
|
||||
QTest::addColumn<QString>("rotation");
|
||||
QTest::newRow("x nan") << "NanX" << "0" << "100" << "0";
|
||||
QTest::newRow("y inf") << "InfY" << "200" << "0" << "0";
|
||||
QTest::newRow("x -inf") << "MinusInfX" << "0" << "300" << "0";
|
||||
QTest::newRow("rotation nan") << "NanRotation" << "400" << "400" << "0";
|
||||
// A finite text, with values that round differently, is kept as it is.
|
||||
QTest::newRow("finite") << "Plain" << "61.7" << "61.3" << "37";
|
||||
}
|
||||
|
||||
void savedNumbersAreFinite()
|
||||
{
|
||||
QFETCH(QString, word);
|
||||
QFETCH(QString, x);
|
||||
QFETCH(QString, y);
|
||||
QFETCH(QString, rotation);
|
||||
|
||||
const QString out = m_dir.filePath(QStringLiteral("resaved-%1.qet").arg(word));
|
||||
QVERIFY2(runQet({QStringLiteral("--resave"), m_fixture, out}), "--resave failed");
|
||||
QFile file(out);
|
||||
QVERIFY(file.open(QIODevice::ReadOnly));
|
||||
QDomDocument doc;
|
||||
QVERIFY(doc.setContent(&file));
|
||||
|
||||
const QDomElement e = input(doc, word);
|
||||
QVERIFY2(!e.isNull(), "text not found in the resaved project");
|
||||
QCOMPARE(e.attribute(QStringLiteral("x")), x);
|
||||
QCOMPARE(e.attribute(QStringLiteral("y")), y);
|
||||
QCOMPARE(e.attribute(QStringLiteral("rotation")), rotation);
|
||||
}
|
||||
|
||||
// No "nan" or "inf" reaches the exported drawing.
|
||||
void exportHasNoNonFiniteNumbers()
|
||||
{
|
||||
const QString dir = m_dir.filePath(QStringLiteral("svg"));
|
||||
QVERIFY(QDir().mkpath(dir));
|
||||
QVERIFY2(runQet({QStringLiteral("--export-svg"), m_fixture, dir}), "--export-svg failed");
|
||||
const QStringList files = QDir(dir).entryList({QStringLiteral("*.svg")}, QDir::Files);
|
||||
QCOMPARE(files.size(), 1);
|
||||
QFile svg(QDir(dir).filePath(files.first()));
|
||||
QVERIFY(svg.open(QIODevice::ReadOnly));
|
||||
const QString content = QString::fromUtf8(svg.readAll());
|
||||
const QRegularExpression non_finite(QStringLiteral("[(,\\s=\"]-?(nan|inf)\\b"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
const QRegularExpressionMatch match = non_finite.match(content);
|
||||
QVERIFY2(!match.hasMatch(),
|
||||
qPrintable(QStringLiteral("found \"%1\" in the SVG").arg(
|
||||
content.mid(qMax(0, int(match.capturedStart()) - 30), 60))));
|
||||
QVERIFY(content.contains(QStringLiteral("NanX")));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_freetextnonfinite)
|
||||
#include "tst_freetextnonfinite.moc"
|
||||
Reference in New Issue
Block a user