diff --git a/sources/textlines.h b/sources/textlines.h index e9a15e130..8d3382382 100644 --- a/sources/textlines.h +++ b/sources/textlines.h @@ -29,6 +29,11 @@ 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). + A paragraph that is not wrapped gives exactly its line of + toPlainText().split('\n'), as these exports wrote before: trailing + spaces are kept, and non-breaking spaces become plain spaces. In a + wrapped paragraph, the lines lose their trailing spaces (the one a + line was broken at, and a line made only of spaces becomes empty). */ inline QStringList layoutLines(const QTextDocument *document) { @@ -39,18 +44,32 @@ namespace TextLines { const QString text = block.text(); const QTextLayout *layout = block.layout(); - if (!layout || layout->lineCount() == 0) { - lines << text; + const int count = layout ? layout->lineCount() : 0; + if (count == 0) { + lines << QString(text).replace(QChar::Nbsp, QLatin1Char(' ')); continue; } - for (int i = 0 ; i < layout->lineCount() ; ++i) + + //Wrapped: a line ends without a line break typed by the user + bool wrapped = false; + for (int i = 0 ; i < count - 1 ; ++i) { + const QTextLine line = layout->lineAt(i); + const int end = line.textStart() + line.textLength(); + if (end == 0 || text.at(end - 1) != QChar::LineSeparator) + wrapped = true; + } + + for (int i = 0 ; i < count ; ++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; + if (part.endsWith(QChar::LineSeparator)) + part.chop(1); //A line break typed by the user (Shift+Enter) + if (wrapped) { + while (!part.isEmpty() && part.back().isSpace()) + part.chop(1); + } + lines << part.replace(QChar::Nbsp, QLatin1Char(' ')); } } return lines; diff --git a/tests/qttest/tst_textlines.cpp b/tests/qttest/tst_textlines.cpp index fb987c694..e9bd25ec3 100644 --- a/tests/qttest/tst_textlines.cpp +++ b/tests/qttest/tst_textlines.cpp @@ -34,6 +34,32 @@ private slots: QStringList({QStringLiteral("Motor protection"), QStringLiteral("switch Q12")})); } + // A text without a width gives exactly the lines the DXF export wrote + // before, toPlainText().split('\n'): trailing spaces and tabs kept, + // non-breaking spaces as plain spaces,
as a line break. + void unwrappedTextIsUnchanged_data() + { + QTest::addColumn("html"); + QTest::newRow("trailing spaces") << QStringLiteral("K1 \nswitch\t"); + QTest::newRow("non-breaking spaces") << QStringLiteral("

K1  24V DC

"); + QTest::newRow("line break") << QStringLiteral("

Wiper to 6
contacts

"); + QTest::newRow("paragraphs") << QStringLiteral("

relais voyant

preventa

"); + QTest::newRow("empty") << QString(); + } + + void unwrappedTextIsUnchanged() + { + QFETCH(QString, html); + QTextDocument document; + if (Qt::mightBeRichText(html)) + document.setHtml(html); + else + document.setPlainText(html); + + QCOMPARE(TextLines::layoutLines(&document), + document.toPlainText().split(QLatin1Char('\n'))); + } + // A text wrapped to its width gives one line per drawn line, without // the space the line was broken at. void wrappedTextGivesTheDrawnLines() @@ -48,6 +74,41 @@ private slots: QStringList({QStringLiteral("Motor"), QStringLiteral("protection"), QStringLiteral("switch"), QStringLiteral("Q12")})); } + + // A wrapped paragraph has no trailing spaces left, even on its last + // line: tabs and spaces that wrap to a line of their own (as in the + // affuteuse_250h example) give an empty line, which is not written but + // still keeps the line spacing. + void wrappedTextHasNoTrailingSpaces() + { + QTextDocument document(QStringLiteral("\tMotor\t\t ")); + QTextOption option = document.defaultTextOption(); + option.setWrapMode(QTextOption::WordWrap); + document.setDefaultTextOption(option); + document.setTextWidth(80); + + const QStringList lines = TextLines::layoutLines(&document); + QVERIFY(lines.size() > 1); + QVERIFY(lines.contains(QStringLiteral("Motor"))); + for (const QString &line : lines) + QVERIFY2(line.isEmpty() || !line.back().isSpace(), qPrintable(line)); + } + + // Non-breaking spaces also become plain spaces in a wrapped text. + void wrappedTextHasPlainSpaces() + { + QTextDocument document; + document.setHtml(QStringLiteral("

K1 24V and more words

")); + QTextOption option = document.defaultTextOption(); + option.setWrapMode(QTextOption::WordWrap); + document.setDefaultTextOption(option); + document.setTextWidth(1); + + const QStringList lines = TextLines::layoutLines(&document); + QCOMPARE(lines.first(), QStringLiteral("K1 24V")); + for (const QString &line : lines) + QVERIFY(!line.contains(QChar::Nbsp)); + } }; QTEST_MAIN(tst_textlines)