summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Kaganski <mike.kaganski@collabora.com>2023-12-29 14:22:23 +0600
committerXisco Fauli <xiscofauli@libreoffice.org>2024-01-09 09:57:07 +0100
commita097d6e85308f1f9dc91746e98faf46767a1531b (patch)
tree55bb3e2c0590f329487c727bcec996a911c3b564
parenttdf#73678 Prevent conditional formatting from being lost in Calc (diff)
downloadcore-a097d6e85308f1f9dc91746e98faf46767a1531b.tar.gz
core-a097d6e85308f1f9dc91746e98faf46767a1531b.zip
tdf#57187: make sure to put trailing blanks to hole portion in narrow lines
This needs to avoid special processing of trailing spaces after some tabs (tdf#106234). Also it needs to allow cursor movement over the newly added Hole portions. It turns out, that it also simplifies the fixes for tdf#104349 and tdf#104668. The special handling of paint of the trailing blanks is not needed, when the trailing blanks are SwHolePortion, which doesn't draw any background. Also, there's no need in special code in SwTextGuess::Guess for that case. This required to enable creation of the hole portions also for left-aligned lines, which were not adjusting break position correctly, because of a wrong condition that could never be true. Change-Id: I0e391be7b1caee4277a505368c3b13a00b5d9506 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161400 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com> Signed-off-by: Xisco Fauli <xiscofauli@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/161538
-rw-r--r--sw/qa/extras/layout/data/space+break.fodt2
-rw-r--r--sw/qa/extras/layout/layout3.cxx50
-rw-r--r--sw/qa/extras/uiwriter/uiwriter4.cxx159
-rw-r--r--sw/source/core/text/guess.cxx180
-rw-r--r--sw/source/core/text/inftxt.cxx99
-rw-r--r--sw/source/core/text/inftxt.hxx19
-rw-r--r--sw/source/core/text/itrcrsr.cxx18
-rw-r--r--sw/source/core/text/itrform2.cxx2
-rw-r--r--sw/source/core/text/porlin.cxx14
-rw-r--r--sw/source/core/text/portxt.cxx4
-rw-r--r--sw/source/core/text/txttab.cxx2
11 files changed, 365 insertions, 184 deletions
diff --git a/sw/qa/extras/layout/data/space+break.fodt b/sw/qa/extras/layout/data/space+break.fodt
index fbd81f37f501..7b53a8b012fa 100644
--- a/sw/qa/extras/layout/data/space+break.fodt
+++ b/sw/qa/extras/layout/data/space+break.fodt
@@ -44,7 +44,7 @@
</office:master-styles>
<office:body>
<office:text>
- <text:p text:style-name="P1">Lorem ipsum <text:s text:c="10"/><text:line-break/>Lorem ipsum <text:s text:c="1000"/><text:line-break/>Lorem ipsum <text:s text:c="1000"/>Lorem ipsum</text:p>
+ <text:p text:style-name="P1">Lorem ipsum <text:s text:c="10"/><text:line-break/>Lorem ipsum <text:s text:c="100"/><text:line-break/>Lorem ipsum <text:s text:c="1000"/><text:line-break/>Lorem ipsum <text:s text:c="1000"/>Lorem ipsum</text:p>
</office:text>
</office:body>
</office:document> \ No newline at end of file
diff --git a/sw/qa/extras/layout/layout3.cxx b/sw/qa/extras/layout/layout3.cxx
index ebac0b257e53..f660334012d0 100644
--- a/sw/qa/extras/layout/layout3.cxx
+++ b/sw/qa/extras/layout/layout3.cxx
@@ -2123,26 +2123,62 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf152307)
CPPUNIT_ASSERT_MESSAGE(aMsg.getStr(), nTabBottom < nFooterTop);
}
-CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf158900)
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, testTdf57187_Tdf158900)
{
// Given a document with a single paragraph, having some long space runs and line breaks
createSwDoc("space+break.fodt");
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
- // Make sure there is only one page, one paragraph, and four lines
+ // Make sure there is only one page, one paragraph, and five lines
assertXPath(pXmlDoc, "/root/page"_ostr, 1);
assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion"_ostr, 1);
- // Without the fix in place, this would fail: there used to be 5 lines
- assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout"_ostr, 4);
- // Check that the second line has correct portions
- // Without the fix in place, this would fail: the line had only 2 portions (text + hole),
- // and the break was on a separate third line
+ // Without the fix in place, this would fail: there used to be 6 lines
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout"_ostr, 5);
+
+ // tdf#57187: Check that relatively short lines have spaces not participating in layout.
+ // First line has 11 spaces in the end, and then a manual line break. It is rather short:
+ // without block justification, it is narrower than the available space.
+ // It uses the "first check if everything fits to line" return path in SwTextGuess::Guess.
+ // Check that the spaces are put into a Hole portion, thus not participating in layout.
+ // Without the fix, this would fail: there were only 2 portions, no Hole nor Margin portions.
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*"_ostr, 4);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*[1]"_ostr, "type"_ostr,
+ u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*[1]"_ostr,
+ "length"_ostr, u"11"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*[2]"_ostr, "type"_ostr,
+ u"PortionType::Hole"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*[2]"_ostr,
+ "length"_ostr, u"11"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*[3]"_ostr, "type"_ostr,
+ u"PortionType::Break"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]/*[4]"_ostr, "type"_ostr,
+ u"PortionType::Margin"_ustr);
+ // Second line has 101 spaces in the end, and then a manual line break.
+ // It uses the "second check if everything fits to line" return path in SwTextGuess::Guess.
+ // Check that the spaces are put into a Hole portion, thus not participating in layout.
+ // Without the fix, this would fail: there were only 2 portions, no Hole portion.
assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]/*"_ostr, 3);
assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]/*[1]"_ostr, "type"_ostr,
u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]/*[1]"_ostr,
+ "length"_ostr, u"11"_ustr);
assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]/*[2]"_ostr, "type"_ostr,
u"PortionType::Hole"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]/*[2]"_ostr,
+ "length"_ostr, u"101"_ustr);
assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]/*[3]"_ostr, "type"_ostr,
u"PortionType::Break"_ustr);
+
+ // tdf#158900: Check that the break after a long line with trailing spaces is kept on same line.
+ // Without the fix in place, this would fail: the line had only 2 portions (text + hole),
+ // and the break was on a separate third line
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*"_ostr, 3);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[1]"_ostr, "type"_ostr,
+ u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[2]"_ostr, "type"_ostr,
+ u"PortionType::Hole"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[3]"_ostr, "type"_ostr,
+ u"PortionType::Break"_ustr);
}
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/qa/extras/uiwriter/uiwriter4.cxx b/sw/qa/extras/uiwriter/uiwriter4.cxx
index 58b4e020ae21..c1f0be1757d6 100644
--- a/sw/qa/extras/uiwriter/uiwriter4.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter4.cxx
@@ -2109,24 +2109,64 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testMsWordCompTrailingBlanks)
CPPUNIT_ASSERT_EQUAL(true, pDoc->getIDocumentSettingAccess().get(
DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS));
calcLayout();
- // Check that trailing spaces spans have no width if option is enabled
-
- CPPUNIT_ASSERT_EQUAL(
- OUString("0"),
- parseDump("/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
- "width"_ostr));
- CPPUNIT_ASSERT_EQUAL(
- OUString("0"),
- parseDump("/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
- "width"_ostr));
- CPPUNIT_ASSERT_EQUAL(
- OUString("0"),
- parseDump("/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
- "width"_ostr));
- CPPUNIT_ASSERT_EQUAL(
- OUString("0"),
- parseDump("/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
- "width"_ostr));
+ xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+ // Check that trailing spaces spans are put into Hole portion if option is enabled
+
+ assertXPath(pXmlDoc, "/root/page/body/txt"_ostr, 3);
+
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*"_ostr, 4);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "portion"_ostr, u"TEST "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "portion"_ostr, u" "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "portion"_ostr, u" T"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "type"_ostr, u"PortionType::Hole"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "portion"_ostr, u" "_ustr); // All the trailing blanks
+
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*"_ostr, 4);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "portion"_ostr, u"TEST "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "portion"_ostr, u" "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "portion"_ostr, u" T"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "type"_ostr, u"PortionType::Hole"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "portion"_ostr, u" "_ustr); // All the trailing blanks
+
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*"_ostr, 4);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "portion"_ostr, u"TEST "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "portion"_ostr, u" "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "portion"_ostr, u" T"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "type"_ostr, u"PortionType::Hole"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "portion"_ostr, u" "_ustr); // All the trailing blanks
// The option is false in settings.xml
createSwDoc("MsWordCompTrailingBlanksFalse.odt");
@@ -2134,19 +2174,76 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest4, testMsWordCompTrailingBlanks)
CPPUNIT_ASSERT_EQUAL(false, pDoc->getIDocumentSettingAccess().get(
DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS));
calcLayout();
- // Check that trailing spaces spans have width if option is disabled
- CPPUNIT_ASSERT(parseDump("/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
- "width"_ostr)
- != "0");
- CPPUNIT_ASSERT(parseDump("/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
- "width"_ostr)
- != "0");
- CPPUNIT_ASSERT(parseDump("/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
- "width"_ostr)
- != "0");
- CPPUNIT_ASSERT(parseDump("/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
- "width"_ostr)
- != "0");
+ pXmlDoc = parseLayoutDump();
+ // Check that trailing spaces spans are put into Text portions if option is disabled
+
+ assertXPath(pXmlDoc, "/root/page/body/txt"_ostr, 3);
+
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*"_ostr, 5);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "portion"_ostr, u"TEST "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "portion"_ostr, u" "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "portion"_ostr, u" T "_ustr); // first colored trailing blank span here
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "portion"_ostr, u" "_ustr); // second colored trailing blank span here
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
+ "portion"_ostr, u" "_ustr); // third colored trailing blank span here
+
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*"_ostr, 5);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "portion"_ostr, u"TEST "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "portion"_ostr, u" "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "portion"_ostr, u" T "_ustr); // first colored trailing blank span here
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "portion"_ostr, u" "_ustr); // second colored trailing blank span here
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
+ "portion"_ostr, u" "_ustr); // third colored trailing blank span here
+
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*"_ostr, 5);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[1]"_ostr,
+ "portion"_ostr, u"TEST "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[2]"_ostr,
+ "portion"_ostr, u" "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[3]"_ostr,
+ "portion"_ostr, u" T "_ustr); // first colored trailing blank span here
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[4]"_ostr,
+ "portion"_ostr, u" "_ustr); // second colored trailing blank span here
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
+ "type"_ostr, u"PortionType::Text"_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt[3]/SwParaPortion/SwLineLayout/child::*[5]"_ostr,
+ "portion"_ostr, u" "_ustr); // third colored trailing blank span here
// MsWordCompTrailingBlanks option should be false by default in new documents
createSwDoc();
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
index 6123ad1b6767..3c85a42f4f15 100644
--- a/sw/source/core/text/guess.cxx
+++ b/sw/source/core/text/guess.cxx
@@ -44,6 +44,108 @@ namespace{
bool IsBlank(sal_Unicode ch) { return ch == CH_BLANK || ch == CH_FULL_BLANK || ch == CH_NB_SPACE || ch == CH_SIX_PER_EM; }
+// Used when spaces should not be counted in layout
+// Returns adjusted cut position
+TextFrameIndex AdjustCutPos(TextFrameIndex cutPos, TextFrameIndex& rBreakPos,
+ const SwTextFormatInfo& rInf)
+{
+ assert(cutPos >= rInf.GetIdx());
+ TextFrameIndex x = rBreakPos = cutPos;
+
+ // we step back until a non blank character has been found
+ // or there is only one more character left
+ while (x && x > rInf.GetIdx() + TextFrameIndex(1) && IsBlank(rInf.GetChar(--x)))
+ --rBreakPos;
+
+ while (IsBlank(rInf.GetChar(cutPos)))
+ ++cutPos;
+
+ return cutPos;
+}
+
+bool hasBlanksInLine(const SwTextFormatInfo& rInf, TextFrameIndex end)
+{
+ for (auto x = rInf.GetLineStart(); x < end; ++x)
+ if (IsBlank(rInf.GetChar(x)))
+ return true;
+ return false;
+}
+
+// Called for the last text run in a line; if it is block-adjusted, or center / right-adjusted
+// with Word compatibility option set, and it has trailing spaces, then the function sets the
+// values, and returns 'false' value that SwTextGuess::Guess should return, to create a
+// trailing SwHolePortion.
+bool maybeAdjustPositionsForBlockAdjust(TextFrameIndex& rCutPos, TextFrameIndex& rBreakPos,
+ TextFrameIndex& rBreakStart, sal_uInt16& rBreakWidth,
+ sal_uInt16& rExtraBlankWidth, sal_uInt16& rMaxSizeDiff,
+ const SwTextFormatInfo& rInf, const SwScriptInfo& rSI,
+ sal_uInt16 maxComp)
+{
+ const auto& adjObj = rInf.GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet().GetAdjust();
+ const SvxAdjust& adjust = adjObj.GetAdjust();
+ if (adjust == SvxAdjust::Block)
+ {
+ if (rInf.DontBlockJustify())
+ return true; // See tdf#106234
+ }
+ else
+ {
+ // tdf#104668 space chars at the end should be cut if the compatibility option is enabled
+ if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS))
+ return true;
+ // for LTR mode only
+ if (rInf.GetTextFrame()->IsRightToLeft())
+ return true;
+ }
+ if (auto ch = rInf.GetChar(rCutPos); !ch) // end of paragraph - last line
+ {
+ if (adjust == SvxAdjust::Block)
+ {
+ // Check adjustment for last line
+ switch (adjObj.GetLastBlock())
+ {
+ default:
+ return true;
+ case SvxAdjust::Center: // tdf#104668
+ if (!rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
+ DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS))
+ return true;
+ break;
+ case SvxAdjust::Block:
+ break; // OK - last line uses block-adjustment
+ }
+ }
+ }
+ else if (ch != CH_BREAK && !IsBlank(ch))
+ return true;
+
+ // tdf#57187: block-adjusted line shorter than full width, terminated by manual
+ // line break, must not use trailing spaces for adjustment
+ TextFrameIndex breakPos;
+ TextFrameIndex newCutPos = AdjustCutPos(rCutPos, breakPos, rInf);
+
+ if (auto ch = rInf.GetChar(newCutPos); ch && ch != CH_BREAK)
+ return true; // next is neither line break nor paragraph end
+ if (breakPos == newCutPos)
+ return true; // no trailing whitespace
+ if (adjust == SvxAdjust::Block && adjObj.GetOneWord() != SvxAdjust::Block
+ && !hasBlanksInLine(rInf, breakPos))
+ return true; // line can't block-adjust
+
+ // Some trailing spaces actually found, and in case of block adjustment, the text portion
+ // itself has spaces to be able to block-adjust, or single word is allowed to adjust
+ rBreakStart = rCutPos = newCutPos;
+ rBreakPos = breakPos;
+
+ rInf.GetTextSize(&rSI, rInf.GetIdx(), breakPos - rInf.GetIdx(), maxComp, rBreakWidth,
+ rMaxSizeDiff, rInf.GetCachedVclData().get());
+ rInf.GetTextSize(&rSI, breakPos, rBreakStart - breakPos, maxComp, rExtraBlankWidth,
+ rMaxSizeDiff, rInf.GetCachedVclData().get());
+
+ return false; // require SwHolePortion creation
+}
+
}
// provides information for line break calculation
@@ -88,38 +190,6 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
nLineWidth += nSpacesInLine * (nSpaceWidth/0.8 - nSpaceWidth);
}
- // tdf#104668 space chars at the end should be cut if the compatibility option is enabled
- // for LTR mode only
- if ( !rInf.GetTextFrame()->IsRightToLeft() )
- {
- if (rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
- DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS))
- {
- if ( rAdjust == SvxAdjust::Right || rAdjust == SvxAdjust::Center )
- {
- TextFrameIndex nSpaceCnt(0);
- for (sal_Int32 i = rInf.GetText().getLength() - 1;
- sal_Int32(rInf.GetIdx()) <= i; --i)
- {
- sal_Unicode cChar = rInf.GetText()[i];
- if ( cChar != CH_BLANK && cChar != CH_FULL_BLANK && cChar != CH_SIX_PER_EM )
- break;
- ++nSpaceCnt;
- }
- TextFrameIndex nCharsCnt = nMaxLen - nSpaceCnt;
- if ( nSpaceCnt && nCharsCnt < rPor.GetLen() )
- {
- if (nSpaceCnt)
- rInf.GetTextSize( &rSI, rInf.GetIdx() + nCharsCnt, nSpaceCnt,
- nMaxComp, m_nExtraBlankWidth, nMaxSizeDiff );
- nMaxLen = nCharsCnt;
- if ( !nMaxLen )
- return true;
- }
- }
- }
- }
-
if ( rInf.GetLen() < nMaxLen )
nMaxLen = rInf.GetLen();
@@ -183,12 +253,15 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
{
// portion fits to line
m_nCutPos = rInf.GetIdx() + nMaxLen;
+ bool bRet = maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
+ m_nBreakWidth, m_nExtraBlankWidth,
+ nMaxSizeDiff, rInf, rSI, nMaxComp);
if( nItalic &&
(m_nCutPos >= TextFrameIndex(rInf.GetText().getLength()) ||
// #i48035# Needed for CalcFitToContent
// if first line ends with a manual line break
rInf.GetText()[sal_Int32(m_nCutPos)] == CH_BREAK))
- m_nBreakWidth = m_nBreakWidth + nItalic;
+ m_nBreakWidth += nItalic;
// save maximum width for later use
if ( nMaxSizeDiff )
@@ -196,7 +269,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
m_nBreakWidth += nLeftRightBorderSpace;
- return true;
+ return bRet;
}
}
@@ -335,8 +408,12 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
// there likely has been a pixel rounding error in GetTextBreak
if ( m_nBreakWidth <= nLineWidth )
{
+ bool bRet = maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
+ m_nBreakWidth, m_nExtraBlankWidth,
+ nMaxSizeDiff, rInf, rSI, nMaxComp);
+
if (nItalic && (m_nBreakPos + TextFrameIndex(1)) >= TextFrameIndex(rInf.GetText().getLength()))
- m_nBreakWidth = m_nBreakWidth + nItalic;
+ m_nBreakWidth += nItalic;
// save maximum width for later use
if ( nMaxSizeDiff )
@@ -344,7 +421,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
m_nBreakWidth += nLeftRightBorderSpace;
- return true;
+ return bRet;
}
}
@@ -359,36 +436,11 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
TextFrameIndex nPorLen(0);
// do not call the break iterator nCutPos is a blank
- sal_Unicode cCutChar = m_nCutPos < TextFrameIndex(rInf.GetText().getLength())
- ? rInf.GetText()[sal_Int32(m_nCutPos)]
- : 0;
+ sal_Unicode cCutChar = rInf.GetChar(m_nCutPos);
if (IsBlank(cCutChar))
{
- m_nBreakPos = m_nCutPos;
- TextFrameIndex nX = m_nBreakPos;
-
- if ( rAdjust == SvxAdjust::Left )
- {
- // we step back until a non blank character has been found
- // or there is only one more character left
- while (nX && TextFrameIndex(rInf.GetText().getLength()) < m_nBreakPos &&
- IsBlank(rInf.GetChar(--nX)))
- --m_nBreakPos;
- }
- else // #i20878#
- {
- while (nX && m_nBreakPos > rInf.GetLineStart() + TextFrameIndex(1) &&
- IsBlank(rInf.GetChar(--nX)))
- --m_nBreakPos;
- }
-
- if( m_nBreakPos > rInf.GetIdx() )
- nPorLen = m_nBreakPos - rInf.GetIdx();
- while (++m_nCutPos < TextFrameIndex(rInf.GetText().getLength()) &&
- IsBlank(rInf.GetChar(m_nCutPos)))
- ; // nothing
-
- m_nBreakStart = m_nCutPos;
+ m_nCutPos = m_nBreakStart = AdjustCutPos(m_nCutPos, m_nBreakPos, rInf);
+ nPorLen = m_nBreakPos - rInf.GetIdx();
}
else
{
diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx
index 129234c8a4cf..5e5d5718102f 100644
--- a/sw/source/core/text/inftxt.cxx
+++ b/sw/source/core/text/inftxt.cxx
@@ -1210,74 +1210,6 @@ void SwTextPaintInfo::DrawBackBrush( const SwLinePortion &rPor ) const
aFillColor = *m_pFnt->GetBackColor();
}
- // tdf#104349 do not highlight portions of space chars before end of line if the compatibility option is enabled
- // for LTR mode only
- if ( !GetTextFrame()->IsRightToLeft() )
- {
- if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS))
- {
- bool draw = false;
- bool full = false;
- const sal_Int32 nMaxLen = GetText().getLength();
- const sal_Int32 nCurrPorEnd(GetIdx() + rPor.GetLen());
- const SwLinePortion* pPos = &rPor;
- TextFrameIndex nIdx = GetIdx();
-
- do
- {
- const sal_Int32 nEndPos = std::min(sal_Int32(nIdx + pPos->GetLen()), nMaxLen);
- for (sal_Int32 i = sal_Int32(nIdx); i < nEndPos; ++i)
- {
- if (i < nMaxLen && i >= nCurrPorEnd && GetText()[i] == CH_TXTATR_NEWLINE)
- goto drawcontinue;
-
- if (i == nMaxLen || GetText()[i] != CH_BLANK)
- {
- draw = true;
- if (i >= nCurrPorEnd)
- {
- full = true;
- goto drawcontinue;
- }
- }
- }
- nIdx += pPos->GetLen();
- pPos = pPos->GetNextPortion();
- } while ( pPos );
-
- drawcontinue:
-
- if ( !draw )
- return;
-
- if ( !full )
- {
- const sal_Int32 nLastPos = std::min(nCurrPorEnd, nMaxLen) - 1;
- for (sal_Int32 i = nLastPos; TextFrameIndex(i) >= GetIdx(); --i)
- {
- if (GetText()[i] == CH_TXTATR_NEWLINE)
- continue;
-
- if (GetText()[i] != CH_BLANK)
- {
- const sal_uInt16 nOldWidth = rPor.Width();
- const sal_uInt16 nExcessWidth
- = GetTextSize(m_pOut, nullptr, GetText(), TextFrameIndex(i + 1),
- TextFrameIndex(nLastPos - i)).Width();
- const_cast<SwLinePortion&>(rPor).Width(nOldWidth - nExcessWidth);
- CalcRect( rPor, nullptr, &aIntersect, true );
- const_cast<SwLinePortion&>(rPor).Width( nOldWidth );
-
- if ( !aIntersect.HasArea() )
- return;
-
- break;
- }
- }
- }
- }
- }
-
pTmpOut->Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
pTmpOut->SetFillColor(aFillColor);
@@ -1795,6 +1727,37 @@ SwTextFormatInfo::SwTextFormatInfo( const SwTextFormatInfo& rInf,
SetFirstMulti( rInf.IsFirstMulti() );
}
+void SwTextFormatInfo::UpdateTabSeen(PortionType type)
+{
+ switch (type)
+ {
+ case PortionType::TabLeft:
+ m_eLastTabsSeen = TabSeen::Left;
+ break;
+ case PortionType::TabRight:
+ m_eLastTabsSeen = TabSeen::Right;
+ break;
+ case PortionType::TabCenter:
+ m_eLastTabsSeen = TabSeen::Center;
+ break;
+ case PortionType::TabDecimal:
+ m_eLastTabsSeen = TabSeen::Decimal;
+ break;
+ case PortionType::Break:
+ m_eLastTabsSeen = TabSeen::None;
+ break;
+ default:
+ break;
+ }
+}
+
+void SwTextFormatInfo::SetLast(SwLinePortion* pNewLast)
+{
+ m_pLast = pNewLast;
+ assert(pNewLast); // We never pass nullptr here. If we start, then a check is needed below.
+ UpdateTabSeen(pNewLast->GetWhichPor());
+}
+
bool SwTextFormatInfo::CheckFootnotePortion_( SwLineLayout const * pCurr )
{
const sal_uInt16 nHeight = pCurr->GetRealHeight();
diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx
index f2f6d146136e..2ddaee7b2e8f 100644
--- a/sw/source/core/text/inftxt.hxx
+++ b/sw/source/core/text/inftxt.hxx
@@ -517,6 +517,16 @@ class SwTextFormatInfo : public SwTextPaintInfo
sal_Unicode m_cHookChar; // For tabs in fields etc.
sal_uInt8 m_nMaxHyph; // Max. line count of followup hyphenations
+ // Used to stop justification after center/right/decimal tab stops - see tdf#tdf#106234
+ enum class TabSeen
+ {
+ None,
+ Left,
+ Center,
+ Right,
+ Decimal,
+ } m_eLastTabsSeen = TabSeen::None;
+
// Hyphenating ...
bool InitHyph( const bool bAuto = false );
bool CheckFootnotePortion_( SwLineLayout const * pCurr );
@@ -566,7 +576,7 @@ public:
void SetRoot( SwLineLayout *pNew ) { m_pRoot = pNew; }
SwLinePortion *GetLast() { return m_pLast; }
- void SetLast( SwLinePortion *pNewLast ) { m_pLast = pNewLast; }
+ void SetLast(SwLinePortion* pNewLast);
bool IsFull() const { return m_bFull; }
void SetFull( const bool bNew ) { m_bFull = bNew; }
bool IsHyphForbud() const
@@ -593,6 +603,13 @@ public:
void SetDropInit( const bool bNew ) { m_bDropInit = bNew; }
bool IsQuick() const { return m_bQuick; }
bool IsTest() const { return m_bTestFormat; }
+ // see tdf#106234
+ void UpdateTabSeen(PortionType);
+ bool DontBlockJustify() const
+ {
+ return m_eLastTabsSeen == TabSeen::Center || m_eLastTabsSeen == TabSeen::Right
+ || m_eLastTabsSeen == TabSeen::Decimal;
+ }
TextFrameIndex GetLineStart() const { return m_nLineStart; }
void SetLineStart(TextFrameIndex const nNew) { m_nLineStart = nNew; }
diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx
index 4ae582d7df59..6da37f66a5de 100644
--- a/sw/source/core/text/itrcrsr.cxx
+++ b/sw/source/core/text/itrcrsr.cxx
@@ -399,18 +399,30 @@ void SwTextCursor::CtorInitTextCursor( SwTextFrame *pNewFrame, SwTextSizeInfo *p
// GetInfo().SetOut( GetInfo().GetWin() );
}
+static bool isTrailingDecoration(SwLinePortion* p)
+{
+ // Optional no-width portion, followed only by no-width portions and/or terminating portions?
+ for (; p; p = p->GetNextPortion())
+ {
+ if (p->IsMarginPortion() || p->IsBreakPortion())
+ return true;
+ if (p->Width())
+ return false;
+ }
+ return true; // no more portions
+}
+
// tdf#120715 tdf#43100: Make width for some HolePortions, so cursor will be able to move into it.
// It should not change the layout, so this should be called after the layout is calculated.
void SwTextCursor::AddExtraBlankWidth()
{
SwLinePortion* pPos = m_pCurr->GetNextPortion();
- SwLinePortion* pNextPos;
while (pPos)
{
- pNextPos = pPos->GetNextPortion();
+ SwLinePortion* pNextPos = pPos->GetNextPortion();
// Do it only if it is the last portion that able to handle the cursor,
// else the next portion would miscalculate the cursor position
- if (pPos->ExtraBlankWidth() && (!pNextPos || pNextPos->IsMarginPortion()))
+ if (pPos->ExtraBlankWidth() && isTrailingDecoration(pNextPos))
{
pPos->Width(pPos->Width() + pPos->ExtraBlankWidth());
pPos->ExtraBlankWidth(0);
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index b5077e6c775f..c0b4894f8a38 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -1262,7 +1262,7 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
// If pCurr does not have a width, it can however already have content.
// E.g. for non-displayable characters
- auto const ch(rInf.GetText()[sal_Int32(rInf.GetIdx())]);
+ auto const ch(rInf.GetChar(rInf.GetIdx()));
SwTextFrame const*const pFrame(rInf.GetTextFrame());
SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition);
diff --git a/sw/source/core/text/porlin.cxx b/sw/source/core/text/porlin.cxx
index 31010a1e776e..4f93c9d727ce 100644
--- a/sw/source/core/text/porlin.cxx
+++ b/sw/source/core/text/porlin.cxx
@@ -21,6 +21,7 @@
#include <SwPortionHandler.hxx>
#include "porlin.hxx"
+#include "portxt.hxx"
#include "inftxt.hxx"
#include "pormulti.hxx"
#if OSL_DEBUG_LEVEL > 0
@@ -86,10 +87,10 @@ void SwLinePortion::PrePaint( const SwTextPaintInfo& rInf,
return;
const sal_uInt16 nHalfView = nViewWidth / 2;
- sal_uInt16 nLastWidth = pLast->Width();
+ sal_uInt16 nLastWidth = pLast->Width() + pLast->ExtraBlankWidth();
if ( pLast->InSpaceGrp() && rInf.GetSpaceAdd() )
- nLastWidth = nLastWidth + pLast->CalcSpacing( rInf.GetSpaceAdd(), rInf );
+ nLastWidth += pLast->CalcSpacing( rInf.GetSpaceAdd(), rInf );
sal_uInt16 nPos;
SwTextPaintInfo aInf( rInf );
@@ -276,9 +277,10 @@ void SwLinePortion::Move(SwTextPaintInfo & rInf) const
bool bCounterDir = ( ! bFrameDir && DIR_RIGHT2LEFT == rInf.GetDirection() ) ||
( bFrameDir && DIR_LEFT2RIGHT == rInf.GetDirection() );
+ SwTwips nTmp = PrtWidth() + ExtraBlankWidth();
if ( InSpaceGrp() && rInf.GetSpaceAdd(/*bShrink=*/true) )
{
- SwTwips nTmp = PrtWidth() + CalcSpacing( rInf.GetSpaceAdd(/*bShrink=*/true), rInf );
+ nTmp += CalcSpacing( rInf.GetSpaceAdd(/*bShrink=*/true), rInf );
if( rInf.IsRotated() )
rInf.Y( rInf.Y() + ( bB2T ? -nTmp : nTmp ) );
else if ( bCounterDir )
@@ -294,11 +296,11 @@ void SwLinePortion::Move(SwTextPaintInfo & rInf) const
rInf.IncKanaIdx();
}
if( rInf.IsRotated() )
- rInf.Y( rInf.Y() + ( bB2T ? -PrtWidth() : PrtWidth() ) );
+ rInf.Y(rInf.Y() + (bB2T ? -nTmp : nTmp));
else if ( bCounterDir )
- rInf.X( rInf.X() - PrtWidth() );
+ rInf.X(rInf.X() - nTmp);
else
- rInf.X( rInf.X() + PrtWidth() );
+ rInf.X(rInf.X() + nTmp);
}
if (IsMultiPortion() && static_cast<SwMultiPortion const*>(this)->HasTabulator())
rInf.IncSpaceIdx();
diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index 3dcdf37ecc90..c3914aba0285 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -435,8 +435,8 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf )
// UAX #14 Unicode Line Breaking Algorithm Non-tailorable Line breaking rule LB6:
// https://www.unicode.org/reports/tr14/#LB6 Do not break before hard line breaks
- if (rInf.GetChar(aGuess.BreakStart()) == CH_BREAK)
- bFull = false; // Keep following SwBreakPortion in the same line
+ if (auto ch = rInf.GetChar(aGuess.BreakStart()); !ch || ch == CH_BREAK)
+ bFull = false; // Keep following SwBreakPortion / para break in the same line
}
}
else // case C2, last exit
diff --git a/sw/source/core/text/txttab.cxx b/sw/source/core/text/txttab.cxx
index 389e4efb2e46..e7141aaec538 100644
--- a/sw/source/core/text/txttab.cxx
+++ b/sw/source/core/text/txttab.cxx
@@ -320,6 +320,8 @@ SwTabPortion *SwTextFormatter::NewTabPortion( SwTextFormatInfo &rInf, bool bAuto
}
}
}
+ if (pTabPor)
+ rInf.UpdateTabSeen(pTabPor->GetWhichPor());
return pTabPor;
}