From d4644286429481b5ee3c0b1ddeac27e432b860be Mon Sep 17 00:00:00 2001 From: Beat Hangartner Date: Mon, 5 Oct 2026 18:00:26 +0200 Subject: [PATCH] Export wrapped texts to DXF line by line A symbol text with a width (text_width) is drawn wrapped on the folio, but the DXF export split texts on '\n' only, so it was written as one long line, running past its box and over the drawing. The export now writes the lines of folio texts as they are laid out (TextLines::layoutLines(), new header-only textlines.h). Texts without a width give the same lines as before. Tests: tst_textlines, tst_dxfwrappedtext (fails without this change). Co-Authored-By: Claude Opus 5.5 --- sources/dxfexport.cpp | 4 +- sources/textlines.h | 60 +++++++++++++++++++++ tests/qttest/CMakeLists.txt | 18 +++++++ tests/qttest/fixtures/dxf_wrapped_text.qet | 61 ++++++++++++++++++++++ tests/qttest/tst_dxfwrappedtext.cpp | 60 +++++++++++++++++++++ tests/qttest/tst_textlines.cpp | 54 +++++++++++++++++++ 6 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 sources/textlines.h create mode 100644 tests/qttest/fixtures/dxf_wrapped_text.qet create mode 100644 tests/qttest/tst_dxfwrappedtext.cpp create mode 100644 tests/qttest/tst_textlines.cpp diff --git a/sources/dxfexport.cpp b/sources/dxfexport.cpp index 5f3c266ff..6db86c5b4 100644 --- a/sources/dxfexport.cpp +++ b/sources/dxfexport.cpp @@ -35,6 +35,7 @@ #include "qetgraphicsitem/independenttextitem.h" #include "qetgraphicsitem/qetshapeitem.h" #include "qetgraphicsitem/terminal.h" +#include "textlines.h" #include #include @@ -358,7 +359,8 @@ void DxfExport::write(Diagram *diagram, int width, int height, qreal y = dti->scenePos().y() - ydir * fontSize * 1.8 - xdir * fontSize * 0.9; - QStringList lines = dti -> toPlainText().split('\n'); + //As drawn: a text with a width is wrapped + QStringList lines = TextLines::layoutLines(dti -> document()); qreal offset = fontSize * 1.6; foreach (QString line, lines) { if (line.size() > 0 && line != "_" ) diff --git a/sources/textlines.h b/sources/textlines.h new file mode 100644 index 000000000..e9a15e130 --- /dev/null +++ b/sources/textlines.h @@ -0,0 +1,60 @@ +/* + 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 . +*/ +#ifndef TEXTLINES_H +#define TEXTLINES_H + +#include +#include +#include +#include + +namespace TextLines +{ + /** + @return the lines of document as they are laid out: a paragraph + wrapped to the width of the text gives several lines. For the + exports that write a text line by line (DXF). + */ + inline QStringList layoutLines(const QTextDocument *document) + { + document->size(); //Lay the document out + + QStringList lines; + for (QTextBlock block = document->begin() ; block.isValid() ; block = block.next()) + { + const QString text = block.text(); + const QTextLayout *layout = block.layout(); + if (!layout || layout->lineCount() == 0) { + lines << text; + continue; + } + for (int i = 0 ; i < layout->lineCount() ; ++i) + { + const QTextLine line = layout->lineAt(i); + QString part = text.mid(line.textStart(), line.textLength()); + //The space the line was broken at, or a line separator + while (!part.isEmpty() && part.back().isSpace()) + part.chop(1); + lines << part; + } + } + return lines; + } +} + +#endif // TEXTLINES_H diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 3b7e8c2b5..f5182993d 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -104,6 +104,14 @@ add_test(NAME tst_textgrid COMMAND tst_textgrid) target_include_directories(tst_textgrid PRIVATE ${QET_DIR}/sources) target_link_libraries(tst_textgrid PRIVATE Qt::Test) +# textlines.h is header-only: the lines of a text as they are laid out, +# for the exports that write a text line by line. +add_executable(tst_textlines tst_textlines.cpp) +add_test(NAME tst_textlines COMMAND tst_textlines) +set_tests_properties(tst_textlines PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen") +target_include_directories(tst_textlines PRIVATE ${QET_DIR}/sources) +target_link_libraries(tst_textlines PRIVATE Qt::Test Qt::Gui) + # elementviewgrid.h is header-only: the symbol editor's snap step at each # zoom (bugtracker #112), tested without an editor window. add_executable(tst_elementviewgrid tst_elementviewgrid.cpp) @@ -403,6 +411,16 @@ target_compile_definitions(tst_wiringlistexport PRIVATE "QET_TEST_BINARY_PATH=\"$\"" "QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"") +# --export-dxf writes a wrapped symbol text line by line, as it is drawn. +add_executable( + tst_dxfwrappedtext + tst_dxfwrappedtext.cpp) +add_test(NAME tst_dxfwrappedtext COMMAND tst_dxfwrappedtext) +add_dependencies(tst_dxfwrappedtext qelectrotech) +target_link_libraries(tst_dxfwrappedtext PRIVATE Qt::Test) +target_compile_definitions(tst_dxfwrappedtext PRIVATE + "QET_TEST_BINARY_PATH=\"$\"") + # --export-pdf puts an A3 folio on an A3 page whichever way it is turned: # QPageSize matches standard sheets upright only, so a wide folio is matched # upright and turned. fixtures/pdf_page_a3.qet has one folio each way. diff --git a/tests/qttest/fixtures/dxf_wrapped_text.qet b/tests/qttest/fixtures/dxf_wrapped_text.qet new file mode 100644 index 000000000..920aebc74 --- /dev/null +++ b/tests/qttest/fixtures/dxf_wrapped_text.qet @@ -0,0 +1,61 @@ + + + 10/2/26 + 02-10-2026 + 2026-10-02 + skeleton + /tmp/qet-mcp-new-k41dmnyo/skeleton.qet + 18:39 + + + + + + + + + + + + + + + + + + + + + + + + + + WrapAlpha WrapBeta WrapGamma WrapDelta WrapEpsilon + LineAlpha LineBeta LineGamma LineDelta LineEpsilon + + + + + + + + + Imported elements + + + + + + Wrapped symbol text + + + + WrapAlpha WrapBeta WrapGamma WrapDelta WrapEpsilon + LineAlpha LineBeta LineGamma LineDelta LineEpsilon + + + + + + diff --git a/tests/qttest/tst_dxfwrappedtext.cpp b/tests/qttest/tst_dxfwrappedtext.cpp new file mode 100644 index 000000000..857c32084 --- /dev/null +++ b/tests/qttest/tst_dxfwrappedtext.cpp @@ -0,0 +1,60 @@ +/* + 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 . +*/ +#include +#include +#include + +/** + --export-dxf writes a symbol text wrapped to its width line by line, + as it is drawn, rather than as one long line. A text without a width + is still written on one line. +*/ +class tst_dxfwrappedtext : public QObject +{ + Q_OBJECT + +private slots: + void wrappedLines() + { + const QString project = QFINDTESTDATA("fixtures/dxf_wrapped_text.qet"); + QVERIFY2(!project.isEmpty(), "fixture project not found"); + QTemporaryDir dir; + QVERIFY(dir.isValid()); + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen")); + QProcess proc; + proc.setProcessEnvironment(env); + proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--export-dxf"), project, dir.path()}); + QVERIFY2(proc.waitForFinished(60000), "--export-dxf timed out"); + QCOMPARE(proc.exitCode(), 0); + + QFile file(dir.filePath(QStringLiteral("01_diagram.dxf"))); + QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); + const QStringList values = QString::fromUtf8(file.readAll()).split(QLatin1Char('\n')); + + //One text entity per word of the text 70 px wide... + for (const QString &word : {QStringLiteral("WrapAlpha"), QStringLiteral("WrapGamma"), QStringLiteral("WrapEpsilon")}) + QVERIFY2(values.contains(word), qPrintable(word)); + //...one for the whole text without a width + QVERIFY(values.contains(QStringLiteral("LineAlpha LineBeta LineGamma LineDelta LineEpsilon"))); + } +}; + +QTEST_APPLESS_MAIN(tst_dxfwrappedtext) +#include "tst_dxfwrappedtext.moc" diff --git a/tests/qttest/tst_textlines.cpp b/tests/qttest/tst_textlines.cpp new file mode 100644 index 000000000..fb987c694 --- /dev/null +++ b/tests/qttest/tst_textlines.cpp @@ -0,0 +1,54 @@ +/* + 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 . +*/ +#include +#include +#include + +#include "textlines.h" + +class tst_textlines : public QObject +{ + Q_OBJECT + +private slots: + // A text without a width gives its own lines. + void unwrappedTextGivesItsLines() + { + QTextDocument document(QStringLiteral("Motor protection\nswitch Q12")); + QCOMPARE(TextLines::layoutLines(&document), + QStringList({QStringLiteral("Motor protection"), QStringLiteral("switch Q12")})); + } + + // A text wrapped to its width gives one line per drawn line, without + // the space the line was broken at. + void wrappedTextGivesTheDrawnLines() + { + QTextDocument document(QStringLiteral("Motor protection switch Q12")); + QTextOption option = document.defaultTextOption(); + option.setWrapMode(QTextOption::WordWrap); + document.setDefaultTextOption(option); + document.setTextWidth(1); + + QCOMPARE(TextLines::layoutLines(&document), + QStringList({QStringLiteral("Motor"), QStringLiteral("protection"), + QStringLiteral("switch"), QStringLiteral("Q12")})); + } +}; + +QTEST_MAIN(tst_textlines) +#include "tst_textlines.moc"