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