diff --git a/sw/qa/extras/layout/data/tdf56408-ltr.fodt b/sw/qa/extras/layout/data/tdf56408-ltr.fodt
new file mode 100644
index 000000000000..5ce76412c080
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf56408-ltr.fodt
@@ -0,0 +1,129 @@
+
+
+ Lior Kaplan2012-10-25T19:53:272024-07-08T03:20:20.348137955PT8M18S11LibreOfficeDev/25.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/9452bf222f57094e3bb100c6b0b9655d9f327737
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ English English English ((((עברית)))) English
+
+
+
\ No newline at end of file
diff --git a/sw/qa/extras/layout/data/tdf56408-no-underflow.fodt b/sw/qa/extras/layout/data/tdf56408-no-underflow.fodt
new file mode 100644
index 000000000000..7ef7ebf605d5
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf56408-no-underflow.fodt
@@ -0,0 +1,123 @@
+
+
+ Lior Kaplan2012-10-25T19:53:272024-07-08T03:21:06.304537073PT8M42S11LibreOfficeDev/25.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/9452bf222f57094e3bb100c6b0b9655d9f327737
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ English English English עברית English
+
+
+
\ No newline at end of file
diff --git a/sw/qa/extras/layout/data/tdf56408-rtl.fodt b/sw/qa/extras/layout/data/tdf56408-rtl.fodt
new file mode 100644
index 000000000000..e68774befc5f
--- /dev/null
+++ b/sw/qa/extras/layout/data/tdf56408-rtl.fodt
@@ -0,0 +1,125 @@
+
+
+ Lior Kaplan2012-10-25T19:53:272024-07-08T03:38:49.126947456PT11M10S13LibreOffice/24.2.4.2$Linux_X86_64 LibreOffice_project/d29029bfb700ea4a272da1366c5f5e7c14e351b5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ עברית עברית עברית ((((English)))) עברית
+
+
+
\ No newline at end of file
diff --git a/sw/qa/extras/layout/layout3.cxx b/sw/qa/extras/layout/layout3.cxx
index 3ee4953e52cc..d10e0c78a2f7 100644
--- a/sw/qa/extras/layout/layout3.cxx
+++ b/sw/qa/extras/layout/layout3.cxx
@@ -3037,6 +3037,50 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, TestTdf104209VertRTL)
"portion"_ostr, u"B"_ustr);
}
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, TestTdf56408LTR)
+{
+ // Verify that line breaking a first bidi portion correctly underflows in LTR text
+ createSwDoc("tdf56408-ltr.fodt");
+ auto pXmlDoc = parseLayoutDump();
+
+ assertXPath(pXmlDoc, "//page"_ostr, 1);
+
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]"_ostr, "portion"_ostr,
+ u"English English English "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]"_ostr, "portion"_ostr,
+ u"((((עברית)))) English"_ustr);
+}
+
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, TestTdf56408RTL)
+{
+ // Verify that line breaking a first bidi portion correctly underflows in RTL text
+ createSwDoc("tdf56408-rtl.fodt");
+ auto pXmlDoc = parseLayoutDump();
+
+ assertXPath(pXmlDoc, "//page"_ostr, 1);
+
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]"_ostr, "portion"_ostr,
+ u"עברית עברית עברית "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]"_ostr, "portion"_ostr,
+ u"((((English)))) עברית"_ustr);
+}
+
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, TestTdf56408NoUnderflow)
+{
+ // The fix for tdf#56408 introduced a change to line breaking between text with
+ // direction changes. This test verifies behavior in the trivial case, when a
+ // break opportunity exists at the direction change boundary.
+ createSwDoc("tdf56408-no-underflow.fodt");
+ auto pXmlDoc = parseLayoutDump();
+
+ assertXPath(pXmlDoc, "//page"_ostr, 1);
+
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[1]"_ostr, "portion"_ostr,
+ u"English English English "_ustr);
+ assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[2]"_ostr, "portion"_ostr,
+ u"עברית English"_ustr);
+}
+
} // end of anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx
index df352e6aa785..e22ad78d44a1 100644
--- a/sw/source/core/text/pormulti.cxx
+++ b/sw/source/core/text/pormulti.cxx
@@ -32,6 +32,9 @@
#include
#include
#include
+#include
+#include
+#include
#include "pormulti.hxx"
#include "inftxt.hxx"
#include "itrpaint.hxx"
@@ -1919,8 +1922,8 @@ static bool lcl_ExtractFieldFollow( SwLineLayout* pLine, SwLinePortion* &rpField
// If a multi portion completely has to go to the
// next line, this function is called to truncate
// the rest of the remaining multi portion
-static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
- TextFrameIndex const nStartIdx)
+static void lcl_TruncateMultiPortion(SwMultiPortion& rMulti, SwTextFormatInfo& rInf,
+ TextFrameIndex const nStartIdx, bool bIsBidiPortion)
{
rMulti.GetRoot().Truncate();
rMulti.GetRoot().SetLen(TextFrameIndex(0));
@@ -1935,6 +1938,31 @@ static void lcl_TruncateMultiPortion( SwMultiPortion& rMulti, SwTextFormatInfo&
rMulti.Width( 0 );
rMulti.SetLen(TextFrameIndex(0));
rInf.SetIdx( nStartIdx );
+
+ if (bIsBidiPortion)
+ {
+ // The truncated portion is a bidi portion. Bidi portions contain ordinary text, and may
+ // potentially underflow in the case that none of the text fits on the current line.
+ if (auto* pPrevTextPor = dynamic_cast(rInf.GetLast());
+ pPrevTextPor != nullptr)
+ {
+ // Check if the start of the bidi portion is a valid break. In that case, truncating
+ // the multi portion is sufficient.
+ css::i18n::LineBreakHyphenationOptions aHyphOptions;
+ css::i18n::LineBreakUserOptions aUserOptions;
+ css::lang::Locale aLocale;
+ auto aResult = g_pBreakIt->GetBreakIter()->getLineBreak(
+ rInf.GetText(), sal_Int32(nStartIdx), aLocale, sal_Int32(rInf.GetLineStart()),
+ aHyphOptions, aUserOptions);
+
+ if (aResult.breakIndex != sal_Int32{ nStartIdx })
+ {
+ // The start of the bidi portion is not a valid break. Instead, a break should be
+ // inserted into a previous text portion on this line.
+ rInf.SetUnderflow(pPrevTextPor);
+ }
+ }
+ }
}
// Manages the formatting of a SwMultiPortion. External, for the calling
@@ -2345,7 +2373,7 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
else
{
// we try to keep our ruby portion together
- lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx, /*bIsBidiPortion=*/false);
pTmp = nullptr;
// A follow field portion may still be waiting. If nobody wants
// it, we delete it.
@@ -2355,7 +2383,7 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
else if( rMulti.HasRotation() )
{
// we try to keep our rotated portion together
- lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx, /*bIsBidiPortion=*/false);
pTmp = new SwRotatedPortion( nMultiLen + rInf.GetIdx(),
rMulti.GetDirection() );
}
@@ -2363,8 +2391,10 @@ bool SwTextFormatter::BuildMultiPortion( SwTextFormatInfo &rInf,
// a new SwBidiPortion, this would cause a memory leak
else if( rMulti.IsBidi() && ! m_pMulti )
{
- if ( ! rMulti.GetLen() )
- lcl_TruncateMultiPortion( rMulti, rInf, nStartIdx );
+ if (!rMulti.GetLen())
+ {
+ lcl_TruncateMultiPortion(rMulti, rInf, nStartIdx, /*bIsBidiPortion=*/true);
+ }
// If there is a HolePortion at the end of the bidi portion,
// it has to be moved behind the bidi portion. Otherwise