sw/qa/extras/layout/data/hidden-para-before-table.rtf | 5 + sw/qa/extras/layout/layout5.cxx | 14 +++ sw/source/core/text/redlnitr.cxx | 72 ++++++++++++------ 3 files changed, 70 insertions(+), 21 deletions(-)
New commits: commit 800c79f8efcc8ba150a108104adceee41c7a8802 Author: Mike Kaganski <[email protected]> AuthorDate: Thu Oct 9 18:11:17 2025 +0500 Commit: Miklos Vajna <[email protected]> CommitDate: Fri Oct 10 09:06:56 2025 +0200 tdf#168116: an ugly hack to handle linebreak-before-hidden-paragraph-mark Word eliminates a completely empty line ending with a hidden paragraph mark, which may appear when a paragraph has a line break followed by such a break, even when the following is not another paragraph, but a table. This change detects this event, and removes the linebreak instead. Known problems: 1. If there is a completely hidden paragraph between the paragraph with trailing line break and hidden mark, this doesn't work. E.g., this RTF markup: AAAA\line {\par} {\par} \intbl BBBB Still, this change allows to fix the bugdoc layout. 2. When formatting marks are shown, the paragraph ends with pilcrow, not a linebreak symbol. This is minor. Change-Id: I66b06cdac9ab41bcccffab669f7caffe0a23b686 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192104 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Miklos Vajna <[email protected]> diff --git a/sw/qa/extras/layout/data/hidden-para-before-table.rtf b/sw/qa/extras/layout/data/hidden-para-before-table.rtf new file mode 100644 index 000000000000..1456504bec84 --- /dev/null +++ b/sw/qa/extras/layout/data/hidden-para-before-table.rtf @@ -0,0 +1,5 @@ +{ tf1 +AAAA\line +{\par} +\intbl BBBB+} \ No newline at end of file diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx index 3869a52b448a..5feeb4d3c80e 100644 --- a/sw/qa/extras/layout/layout5.cxx +++ b/sw/qa/extras/layout/layout5.cxx @@ -1980,6 +1980,20 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf166181) CPPUNIT_ASSERT_LESS(sal_Int32(500), height.toInt32()); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf168116) +{ + // Given a paragraph with a line break immediately followed by a hidden paragraph mark: + createSwDoc("hidden-para-before-table.rtf"); + auto pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "//body"); + assertXPathChildren(pXmlDoc, "//body", 4); + // It must have a "merged" data; without the fix, this failed: + assertXPath(pXmlDoc, "//body/txt[1]/merged"); + // It must only have one line; without the fix, there were two (the second empty): + assertXPath(pXmlDoc, "//body/txt[1]/SwParaPortion/SwLineLayout", 1); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx index 60545116aa21..d31a45e5df8c 100644 --- a/sw/source/core/text/redlnitr.cxx +++ b/sw/source/core/text/redlnitr.cxx @@ -106,6 +106,17 @@ public: // Note: caller is responsible for checking for immediately adjacent hides bool Next() { + // A special case (tdf#168116 hack): the node should hide its paragraph break, but it was + // impossible; yet, the last paragraph character ( ) was hidden instead. + if (m_oParagraphBreak + && m_oParagraphBreak->first.GetNodeIndex() == m_oParagraphBreak->second.GetNodeIndex()) + { + // This is a call to Next after we returned the fake position for the last hidden + // paragraph mark. We are done. + m_pStartPos = nullptr; + m_pEndPos = nullptr; + return false; + } SwPosition const* pNextRedlineHide(nullptr); assert(m_pEndPos); if (m_isHideRedlines) @@ -244,29 +255,48 @@ public: } return false; }; - if (m_isHideParagraphBreaks - && m_pEndPos->GetNode().IsTextNode() // ooo27109-1.sxw - // only merge if next node is also text node - && m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->IsTextNode() - && hasHiddenItem(*m_pEndPos->GetNode().GetTextNode()) - // no merge if there's a page break on any node - && !hasBreakBefore(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode()) - // first node, see SwTextFrame::GetBreak() - && !hasBreakAfter(*m_Start.GetNode().GetTextNode())) - { - m_oParagraphBreak.emplace( - SwPosition(*m_pEndPos->GetNode().GetTextNode(), m_pEndPos->GetNode().GetTextNode()->Len()), - SwPosition(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode(), 0)); - m_pStartPos = &m_oParagraphBreak->first; - m_pEndPos = &m_oParagraphBreak->second; - return true; - } - else // nothing + + if (auto pTextNode = m_pEndPos->GetNode().GetTextNode(); + pTextNode // ooo27109-1.sxw + && m_isHideParagraphBreaks + && hasHiddenItem(*pTextNode)) { - m_pStartPos = nullptr; - m_pEndPos = nullptr; - return false; + auto pNextNode = m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex() + 1]; + if (pNextNode->IsTextNode() // only merge if next node is also text node + // no merge if there's a page break on any node + && !hasBreakBefore(*pNextNode->GetTextNode()) + // first node, see SwTextFrame::GetBreak() + && !hasBreakAfter(*m_Start.GetNode().GetTextNode())) + { + m_oParagraphBreak.emplace(SwPosition(*pTextNode, pTextNode->Len()), + SwPosition(*pNextNode->GetTextNode(), 0)); + m_pStartPos = &m_oParagraphBreak->first; + m_pEndPos = &m_oParagraphBreak->second; + return true; + } + // A special case (tdf#168116 hack): a line break immediately before hidden marker, + // which itself cannot be hidden. Hide the preceding line break instead. + if (pTextNode->Len() > 0 + && pTextNode->GetText()[pTextNode->Len() - 1] == ' ') + { + SwPosition nodeEnd(*pTextNode, pTextNode->Len()); + // Only if we didn't include the line break in the previous result + if (*m_pEndPos < nodeEnd) + { + // This fake m_oParagraphBreak can easily be distinguished from the proper + // one created a few lined above: both positions are inside the same node. + m_oParagraphBreak.emplace(SwPosition(*pTextNode, pTextNode->Len() - 1), + nodeEnd); + m_pStartPos = &m_oParagraphBreak->first; + m_pEndPos = &m_oParagraphBreak->second; + return true; + } + } } + + m_pStartPos = nullptr; + m_pEndPos = nullptr; + return false; } } };
