Merge master into perf/parallel-picture-decode

tests/qttest/CMakeLists.txt: this branch's tst_folioimageload and
master's tst_svgsmallpictures (#1478) were added at the same place;
both are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-10-14 18:36:36 +13:00
13 changed files with 272 additions and 9 deletions
+9
View File
@@ -275,6 +275,15 @@ qet_add_app_test(tst_imagehandleescape tst_imagehandleescape.cpp)
# pixels and position; an <image> with no picture does not stop the others.
qet_add_app_test(tst_folioimageload tst_folioimageload.cpp)
# SVG export with smaller pictures (--svg-small-pictures, the export dialog's
# option): a picture drawn at a tenth of its size is stored at four times
# that, a small one whole; without the option every picture is whole.
qet_add_app_test(tst_svgsmallpictures tst_svgsmallpictures.cpp)
add_dependencies(tst_svgsmallpictures qelectrotech)
target_link_libraries(tst_svgsmallpictures PRIVATE Qt::Svg)
target_compile_definitions(tst_svgsmallpictures PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# TextResizeHandles: the corner handles that change the width of a text box,
# on a plain QGraphicsTextItem with the "textWidth" property QET texts have.
add_executable(
@@ -0,0 +1,41 @@
<project title="A large picture shown small" uuid="{3f6a2c91-8d47-4b5e-a0c3-91e2d7b4f600}" 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">svg_small_pictures</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="{3f6a2c91-8d47-4b5e-a0c3-91e2d7b4f610}" 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/>
<images>
<image z="0" rotation="0" y="100" x="100" size="0.1" is_movable="1">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</image>
<image z="0" rotation="0" y="300" x="100" size="1" is_movable="1">iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAAP0lEQVR42u3PMQoAIAwEwRVC1F/7dFuxFoKwqS7XHNNgJSR0OMP1Pu+DQckFs2pYsWLFihUrVqxYsWLFir8Rb7mhAuQBqxaIAAAAAElFTkSuQmCC</image>
</images>
</diagram>
<collection>
<category name="import">
<names>
<name lang="en">Imported elements</name>
</names>
</category>
</collection>
</project>
+143
View File
@@ -0,0 +1,143 @@
/*
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 "qetapptest.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include <QBuffer>
#include <QDomDocument>
#include <QPicture>
#include <QProcess>
#include <QRegularExpression>
#include <QSvgGenerator>
#include <QTemporaryDir>
/**
SVG export with "smaller pictures" (ExportProperties::svg_small_pictures,
--svg-small-pictures) stores each picture at four times the size it is
drawn at when that is smaller than the picture, and at full resolution
otherwise. fixtures/svg_small_pictures.qet has an 800x600 picture drawn
at a tenth of its size and a 40x30 one at full size.
*/
class tst_svgsmallpictures : public QObject
{
Q_OBJECT
QString m_fixture;
/// Pixel size of each picture embedded in @p svg, smallest first
static QList<QSize> embeddedSizes(const QByteArray &svg)
{
static const QRegularExpression data(
QStringLiteral("data:image/png;base64,([A-Za-z0-9+/=\\s]+)"));
QList<QSize> sizes;
auto it = data.globalMatch(QString::fromLatin1(svg));
while (it.hasNext())
sizes << QImage::fromData(QByteArray::fromBase64(
it.next().captured(1).toLatin1())).size();
std::sort(sizes.begin(), sizes.end(), [](const QSize &a, const QSize &b) {
return a.width() < b.width();
});
return sizes;
}
QByteArray exportWithCli(const QStringList &extra)
{
QTemporaryDir dir;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
QProcess proc;
proc.setProcessEnvironment(env);
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
QStringList{QStringLiteral("--export-svg"), m_fixture, dir.path()} + extra);
if (!proc.waitForFinished(60000) || proc.exitCode() != 0)
return {};
const QStringList files = QDir(dir.path()).entryList({QStringLiteral("*.svg")});
QFile svg(QDir(dir.path()).filePath(files.value(0)));
return svg.open(QIODevice::ReadOnly) ? svg.readAll() : QByteArray();
}
/// As ExportDialog::generateSvg(): the folio drawn into a QPicture,
/// played into the SVG generator
static QByteArray exportLikeTheDialog(Diagram *diagram, bool small)
{
const QSize size = diagram->imageSize();
QPicture picture;
diagram->setSmallPictures(small);
diagram->toPaintDevice(picture, size.width(), size.height());
diagram->setSmallPictures(false);
QByteArray svg;
QBuffer buffer(&svg);
buffer.open(QIODevice::WriteOnly);
QSvgGenerator generator;
generator.setSize(size);
generator.setOutputDevice(&buffer);
QPainter painter(&generator);
picture.play(&painter);
painter.end();
return svg;
}
private slots:
void initTestCase()
{
m_fixture = QFINDTESTDATA("fixtures/svg_small_pictures.qet");
QVERIFY2(!m_fixture.isEmpty(), "fixture project not found");
QVERIFY(QetAppTest::editor());
}
void cliFullResolutionByDefault()
{
const QByteArray svg = exportWithCli({});
QVERIFY2(!svg.isEmpty(), "--export-svg failed");
QCOMPARE(embeddedSizes(svg), (QList<QSize>{QSize(40, 30), QSize(800, 600)}));
}
void cliSmallPictures()
{
const QByteArray svg = exportWithCli({QStringLiteral("--svg-small-pictures")});
QVERIFY2(!svg.isEmpty(), "--export-svg --svg-small-pictures failed");
// 800 x 600 drawn at 0.1, times 4; the 40 x 30 one is kept whole
QCOMPARE(embeddedSizes(svg), (QList<QSize>{QSize(40, 30), QSize(320, 240)}));
}
void dialogSmallPicturesThenFullAgain()
{
QFile file(m_fixture);
QVERIFY(file.open(QIODevice::ReadOnly));
QDomDocument document;
QVERIFY(document.setContent(&file));
const QDomElement image_xml = document.elementsByTagName(
QStringLiteral("image")).at(0).toElement();
Diagram *diagram = QetAppTest::diagram();
auto *picture = new DiagramImageItem();
QVERIFY(picture->fromXml(image_xml));
diagram->addItem(picture);
QCOMPARE(embeddedSizes(exportLikeTheDialog(diagram, true)),
QList<QSize>{QSize(320, 240)});
// The option lasts for one export only
QVERIFY(!diagram->smallPictures());
QCOMPARE(embeddedSizes(exportLikeTheDialog(diagram, false)),
QList<QSize>{QSize(800, 600)});
delete picture;
}
};
QET_APP_TEST_MAIN(tst_svgsmallpictures)
#include "tst_svgsmallpictures.moc"