sw/qa/extras/layout/data/merge_hidden_redline.docx |binary
 sw/qa/extras/layout/layout2.cxx                    |  246 ++++++++++++++++++++-
 sw/source/core/docnode/node.cxx                    |    6 
 sw/source/core/inc/rootfrm.hxx                     |    1 
 sw/source/core/inc/scriptinfo.hxx                  |    2 
 sw/source/core/inc/txtfrm.hxx                      |   13 -
 sw/source/core/layout/newfrm.cxx                   |    2 
 sw/source/core/text/itrform2.cxx                   |   10 
 sw/source/core/text/porlay.cxx                     |   15 +
 sw/source/core/text/redlnitr.cxx                   |   62 ++++-
 sw/source/core/text/txtfrm.cxx                     |   73 ++++--
 sw/source/core/view/viewsh.cxx                     |   26 +-
 12 files changed, 389 insertions(+), 67 deletions(-)

New commits:
commit bf4b4966f5a61fe0daef0c09a9c7a8f4503148b9
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Wed Feb 19 16:25:35 2025 +0100
Commit:     Thorsten Behrens <thorsten.behr...@allotropia.de>
CommitDate: Sat Feb 22 14:15:39 2025 +0100

    sw: use same paragraph properties as Word for hidden text
    
    This is a follow-up to commit 2bcfb7231b5ca74f02274cfb74ca8463f78905d6
    "tdf#152872 sw: conditionally hide paragraph breaks".
    
    Word determines the paragraph properties differently depending on
    whether paragraphs are merged by hidden text (where the first paragraph
    containing non-hidden text wins) or delete redlines (where the last
    paragraph wins).
    
    This fixes the hidden text situation while leaving the delete redline
    situation as it is.
    
    The problem is that CheckParaRedlineMerge() only considers hidden text
    on the paragraph end marker (RES_PARATR_LIST_AUTOFMT) but not elsewhere
    in the paragraph, so there are extents for the hidden text.
    
    The actual hiding of that is done via ScriptInfo::m_HiddenChg, which
    cannot be removed and replaced by merging because it is required for
    Writer's notion of hidden paragraphs in SwTextFrame::IsHiddenNowImpl().
    
    * FindParaPropsNodeIgnoreHidden() skips over nodes that contain only
      hidden text in case hidden text isn't shown
    
    * factor out ScriptInfo::InitScriptInfoHidden() because it is now needed
      to find the pParaPropsNode, but the rest of InitScriptInfo() requires
      the pParaPropsNode...
    
    * testTdf152872 requires changes as it was relying on ControlCharacters
      alone toggling the merging
    
    list of commits that tweaked setting pParaPropsNode:
    58353884dc86bdb3c1464f8bbf8c3e131584b78a (related: tdf#130685) sw: adapt 
definition of sw_redlinehide index 0
    b86ff2c6a88aa41379e74f11e8ec8497ff85ffd0 tdf#118699 sw_redlinehide: need 
some more changes to use the last node
    fa5eb82b398e29ae033f7b7c8c8195dfc10cf5b0 tdf#118699 change tracking: don't 
number empty lines
    beec1594587d0bf1ea2268f9a435c948b5580278 tdf#125319 sw_redlinehide: handle 
empty paragraphs more like Word
    c20308f1b919ca5ce61233068946e5fddb7eadb3 sw_redlinehide_4b: surprising 
discoveries
    
    Change-Id: If0e49a4d105dbf7d71e753967f36f2ec56f21f1d
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181918
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    Tested-by: Jenkins
    (cherry picked from commit 8712673a445edeb28a5f3029bbcaa096f38d72e6)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181986
    Tested-by: allotropia jenkins <jenk...@allotropia.de>
    Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de>

diff --git a/sw/qa/extras/layout/data/merge_hidden_redline.docx 
b/sw/qa/extras/layout/data/merge_hidden_redline.docx
new file mode 100644
index 000000000000..7a7013321402
Binary files /dev/null and b/sw/qa/extras/layout/data/merge_hidden_redline.docx 
differ
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index f5402cbc2c30..f968b10bf698 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -25,7 +25,10 @@
 
 #include <unotxdoc.hxx>
 #include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <bodyfrm.hxx>
 #include <txtfrm.hxx>
+#include <ndtxt.hxx>
 #include <wrtsh.hxx>
 #include <IDocumentLayoutAccess.hxx>
 #include <IDocumentRedlineAccess.hxx>
@@ -875,7 +878,11 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
     // 5 is empty and hidden
     assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds"_ostr, 
"height"_ostr, "0");
 
-    dispatchCommand(mxComponent, u".uno:ControlCodes"_ustr, {});
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
+    aViewOptions.SetShowHiddenChar(true);
+    aViewOptions.SetViewMetaChars(true);
+    pWrtShell->ApplyViewOptions(aViewOptions);
 
     discardDumpedLayout();
     pXmlDoc = parseLayoutDump();
@@ -885,18 +892,15 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
                 u"C "_ustr);
     assertXPath(pXmlDoc, 
"/root/page/body/txt[2]/SwParaPortion/SwLineLayout"_ostr, "portion"_ostr,
                 u"D"_ustr);
-    // 3 is an empty paragraph with RES_CHRATR_HIDDEN which results in 0-height
-    // frame; ideally it should only be hidden when control codes are hidden
-    // and be a full-height frame now, but that needs more work...
-    assertXPath(pXmlDoc, "/root/page/body/txt[3]/infos/bounds"_ostr, 
"height"_ostr, u"0"_ustr);
+    // 3 is an empty paragraph with RES_CHRATR_HIDDEN
+    assertXPath(pXmlDoc, "/root/page/body/txt[3]/infos/bounds"_ostr, 
"height"_ostr, u"398"_ustr);
     assertXPath(pXmlDoc, 
"/root/page/body/txt[4]/SwParaPortion/SwLineLayout"_ostr, "portion"_ostr,
                 u"E"_ustr);
-    // 5 is an empty paragraph with RES_CHRATR_HIDDEN which results in 0-height
-    // frame; ideally it should only be hidden when control codes are hidden
-    // and be a full-height frame now, but that needs more work...
-    assertXPath(pXmlDoc, "/root/page/body/txt[5]/infos/bounds"_ostr, 
"height"_ostr, "0");
+    // 5 is an empty paragraph with RES_CHRATR_HIDDEN
+    assertXPath(pXmlDoc, "/root/page/body/txt[5]/infos/bounds"_ostr, 
"height"_ostr, "398");
 
-    dispatchCommand(mxComponent, u".uno:ControlCodes"_ustr, {});
+    aViewOptions.SetViewMetaChars(false);
+    pWrtShell->ApplyViewOptions(aViewOptions);
 
     discardDumpedLayout();
     pXmlDoc = parseLayoutDump();
@@ -908,6 +912,228 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
     assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds"_ostr, 
"height"_ostr, "0");
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testHiddenParaProps)
+{
+    createSwDoc("merge_hidden_redline.docx");
+
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
+    aViewOptions.SetShowHiddenChar(true);
+    aViewOptions.SetViewMetaChars(true);
+    pWrtShell->ApplyViewOptions(aViewOptions);
+
+    // note: do not use layout dump here, because it doesn't work:
+    // SwTextFrame::Format doesn't actually create the SwMarginPortion for
+    // non-left-aligned frames; instead, it sets SetFormatAdj() flag and later
+    // *SwTextPainter* checks via GetAdjusted() if the flag is set and calls
+    // CalcAdjLine() which inserts the SwMarginPortion.
+
+    SwRootFrame* pRoot = pWrtShell->GetLayout();
+    CPPUNIT_ASSERT(pRoot->GetLower()->IsPageFrame());
+    SwPageFrame* pPage = static_cast<SwPageFrame*>(pRoot->GetLower());
+    CPPUNIT_ASSERT(pPage->GetLower()->IsBodyFrame());
+    SwBodyFrame* pBody = static_cast<SwBodyFrame*>(pPage->GetLower());
+    CPPUNIT_ASSERT(pBody->GetLower()->IsTextFrame());
+    SwTextFrame* pTextFrame = dynamic_cast<SwTextFrame*>(pBody->GetLower());
+
+    CPPUNIT_ASSERT_EQUAL(u"1 hidden, delete-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    // TODO: redlines don't merge like in Word yet
+    CPPUNIT_ASSERT_EQUAL(u"Abcdef"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"2 visible, delete-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcghi"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"3 delete-merge"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"4 delete-merge, delete-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"5 visible, hidden-merge, visible"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"6 hidden-merge, visible"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"7 visible, hidden-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcdef"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"8 visible, delete-merge, visible, hidden-merge, 
visible"_ustr,
+                         pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Right,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"9 hidden-merge"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"10 visible, hidden-merge, visible, delete-merge, 
visible"_ustr,
+                         pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Right,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+
+    aViewOptions.SetShowHiddenChar(false);
+    pWrtShell->ApplyViewOptions(aViewOptions);
+
+    // the problem was that the wrong SwTextNode was used for properties
+    pTextFrame = dynamic_cast<SwTextFrame*>(pBody->GetLower());
+    CPPUNIT_ASSERT_EQUAL(u"1 hidden, delete-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    // TODO: redlines don't merge like in Word yet
+    CPPUNIT_ASSERT_EQUAL(u"Abcdef"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"2 visible, delete-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcghi"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"3 delete-merge"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"4 delete-merge, delete-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"def"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"5 visible, hidden-merge, visible"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcdef"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"6 hidden-merge, visible"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcdef"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"7 visible, hidden-merge"_ustr, 
pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcdefghi"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"8 visible, delete-merge, visible, hidden-merge, 
visible"_ustr,
+                         pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abc"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Right,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"defghi"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Center,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"9 hidden-merge"_ustr, pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcdef"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"10 visible, hidden-merge, visible, delete-merge, 
visible"_ustr,
+                         pTextFrame->GetText());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"abcdef"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Right,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+    pTextFrame = dynamic_cast<SwTextFrame*>(pTextFrame->GetNext());
+    CPPUNIT_ASSERT_EQUAL(u"ghi"_ustr, pTextFrame->GetText());
+    CPPUNIT_ASSERT_EQUAL(
+        SvxAdjust::Left,
+        
pTextFrame->GetTextNodeForParaProps()->GetSwAttrSet().Get(RES_PARATR_ADJUST).GetAdjust());
+}
+
 CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf151954)
 {
     createSwDoc("tdf151954.docx");
diff --git a/sw/source/core/docnode/node.cxx b/sw/source/core/docnode/node.cxx
index ffa6679a5e05..48db4d099d9c 100644
--- a/sw/source/core/docnode/node.cxx
+++ b/sw/source/core/docnode/node.cxx
@@ -1449,8 +1449,10 @@ void SwContentNode::DelFrames(SwRootFrame const*const 
pLayout)
                     // node as SwFrame::InvalidatePage() will access them.
                     // Note: cannot send via SwClientNotify from dtor
                     // because that would access deleted wrong-lists
-                    sw::UpdateMergedParaForDelete(*pMerged, true,
-                            *static_cast<SwTextNode*>(this), 0, Len());
+                    sw::UpdateMergedParaForDelete(*pMerged,
+                        pFrame->getRootFrame()->GetParagraphBreakMode(),
+                        static_cast<SwTextFrame *>(pFrame)->GetScriptInfo(),
+                        true, *static_cast<SwTextNode*>(this), 0, Len());
                     if (this == pMerged->pParaPropsNode)
                     {
                         // otherwise pointer should have been updated to a 
different node
diff --git a/sw/source/core/inc/rootfrm.hxx b/sw/source/core/inc/rootfrm.hxx
index f2b53a82f302..a5379002a800 100644
--- a/sw/source/core/inc/rootfrm.hxx
+++ b/sw/source/core/inc/rootfrm.hxx
@@ -48,6 +48,7 @@ namespace sw {
     };
 
     enum class FieldmarkMode { ShowCommand = 1, ShowResult = 2, ShowBoth = 3 };
+    // this has evolved into something that could be called HiddenTextMode?
     enum class ParagraphBreakMode { Shown, Hidden };
 };
 
diff --git a/sw/source/core/inc/scriptinfo.hxx 
b/sw/source/core/inc/scriptinfo.hxx
index 377455f6e15f..27a9d8597784 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -105,6 +105,8 @@ public:
     SwScriptInfo();
     ~SwScriptInfo();
 
+    // partial init: only m_HiddenChg/m_Bookmarks
+    void InitScriptInfoHidden(const SwTextNode& rNode, sw::MergedPara const* 
pMerged);
     // determines script changes
     void InitScriptInfo(const SwTextNode& rNode, sw::MergedPara const* 
pMerged, bool bRTL);
     void InitScriptInfo(const SwTextNode& rNode, sw::MergedPara const* 
pMerged);
diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx
index a70a824374e7..aec9d88bd9c1 100644
--- a/sw/source/core/inc/txtfrm.hxx
+++ b/sw/source/core/inc/txtfrm.hxx
@@ -32,6 +32,7 @@
 namespace com::sun::star::linguistic2 { class XHyphenatedWord; }
 
 namespace sw::mark { class IMark; }
+namespace sw { enum class ParagraphBreakMode; }
 class SwCharRange;
 class SwTextNode;
 class SwTextAttrEnd;
@@ -50,6 +51,7 @@ class SwPortionHandler;
 class SwScriptInfo;
 enum class ExpandMode;
 class SwTextAttr;
+class SwViewShell;
 class SwWrtShell;
 class SwNode;
 class SwFlyAtContentFrame;
@@ -106,6 +108,10 @@ class InsertText;
 std::pair<SwTextNode*, sal_Int32> MapViewToModel(MergedPara const&, 
TextFrameIndex nIndex);
 TextFrameIndex MapModelToView(MergedPara const&, SwTextNode const* pNode, 
sal_Int32 nIndex);
 
+bool IsShowHiddenChars(SwViewShell const*const pViewShell);
+void FindParaPropsNodeIgnoreHidden(MergedPara & rMerged,
+        sw::ParagraphBreakMode const eMode, SwScriptInfo * pScriptInfo);
+
 // warning: Existing must be used only once; a second use would delete the 
frame created by the first one...
 enum class FrameMode { New, Existing };
 std::unique_ptr<sw::MergedPara> CheckParaRedlineMerge(SwTextFrame & rFrame, 
SwTextNode & rTextNode, FrameMode eMode);
@@ -125,6 +131,7 @@ void GotoPrevLayoutTextFrame(SwNodeIndex & rIndex, 
SwRootFrame const* pLayout);
 void GotoNextLayoutTextFrame(SwNodeIndex & rIndex, SwRootFrame const* pLayout);
 
 TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged,
+        sw::ParagraphBreakMode eMode, SwScriptInfo * pScriptInfo,
         bool isRealDelete,
         SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 nLen);
 
@@ -696,6 +703,7 @@ public:
 
     /// Returns the script info stored at the paraportion
     const SwScriptInfo* GetScriptInfo() const;
+    SwScriptInfo* GetScriptInfo();
 
     /// Swaps width and height of the text frame
     void SwapWidthAndHeight();
@@ -1005,12 +1013,11 @@ struct MergedPara
     SwTextNode const* pLastNode;
     MergedPara(SwTextFrame & rFrame, std::vector<Extent>&& rExtents,
             OUString aText,
-            SwTextNode *const pProps, SwTextNode *const pFirst,
+            SwTextNode *const pFirst,
             SwTextNode const*const pLast)
         : listener(rFrame), extents(std::move(rExtents)), 
mergedText(std::move(aText))
-        , pParaPropsNode(pProps), pFirstNode(pFirst), pLastNode(pLast)
+        , pParaPropsNode(nullptr), pFirstNode(pFirst), pLastNode(pLast)
     {
-        assert(pParaPropsNode);
         assert(pFirstNode);
         assert(pLastNode);
     }
diff --git a/sw/source/core/layout/newfrm.cxx b/sw/source/core/layout/newfrm.cxx
index 786fbb5f2387..d590bda8dabb 100644
--- a/sw/source/core/layout/newfrm.cxx
+++ b/sw/source/core/layout/newfrm.cxx
@@ -419,7 +419,7 @@ SwRootFrame::SwRootFrame( SwFrameFormat *pFormat, 
SwViewShell * pSh ) :
     m_FieldmarkMode(pSh->GetViewOptions()->IsFieldName()
             ? sw::FieldmarkMode::ShowCommand
             : sw::FieldmarkMode::ShowResult),
-    m_ParagraphBreakMode(pSh->GetViewOptions()->IsParagraph()
+    m_ParagraphBreakMode(sw::IsShowHiddenChars(pSh)
             ? sw::ParagraphBreakMode::Shown
             : sw::ParagraphBreakMode::Hidden),
     mnBrowseWidth(MIN_BROWSE_WIDTH),
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index 47e2a4db8047..ffc40527c1ec 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -3300,6 +3300,16 @@ void SwTextFormatter::MergeCharacterBorder( 
SwLinePortion& rPortion, SwLinePorti
     Seek(rInf.GetIdx());
 }
 
+namespace sw {
+    bool IsShowHiddenChars(SwViewShell const*const pViewShell)
+    {
+        SwViewOption const*const pOpt{pViewShell ? 
pViewShell->GetViewOptions() : nullptr};
+        const bool bShowInDocView{pViewShell && pViewShell->GetWin() && 
pOpt->IsShowHiddenChar()};
+        const bool bShowForPrinting{pViewShell && pOpt->IsShowHiddenChar(true) 
&& pOpt->IsPrinting()};
+        return (bShowInDocView || bShowForPrinting);
+    }
+}
+
 namespace {
     // calculates and sets optimal repaint offset for the current line
     tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 778f7e19b5e3..dfcc776c34d5 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -1213,8 +1213,9 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
     InitScriptInfo( rNode, pMerged, m_nDefaultDir == UBIDI_RTL );
 }
 
-void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
-        sw::MergedPara const*const pMerged, bool bRTL)
+// note: must not use pMerged->pParaPropsNode to avoid circular dependency
+void SwScriptInfo::InitScriptInfoHidden(const SwTextNode& rNode,
+        sw::MergedPara const*const pMerged)
 {
     assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
 
@@ -1359,6 +1360,14 @@ void SwScriptInfo::InitScriptInfo(const SwTextNode& 
rNode,
             m_HiddenChg.push_back( TextFrameIndex(nEnd) );
         }
     }
+}
+
+void SwScriptInfo::InitScriptInfo(const SwTextNode& rNode,
+        sw::MergedPara const*const pMerged, bool bRTL)
+{
+    InitScriptInfoHidden(rNode, pMerged);
+
+    const OUString& rText(pMerged ? pMerged->mergedText : rNode.GetText());
 
     // SCRIPT AND SCRIPT RELATED INFORMATION
 
@@ -2705,7 +2714,7 @@ SwScriptInfo* SwScriptInfo::GetScriptInfo( const 
SwTextNode& rTNd,
 
     for( SwTextFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() )
     {
-        pScriptInfo = const_cast<SwScriptInfo*>(pLast->GetScriptInfo());
+        pScriptInfo = pLast->GetScriptInfo();
         if ( pScriptInfo )
         {
             if (bAllowInvalid ||
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
index afa87aba0963..a49f6d03dc50 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -264,6 +264,47 @@ public:
 
 namespace sw {
 
+void FindParaPropsNodeIgnoreHidden(sw::MergedPara & rMerged,
+        sw::ParagraphBreakMode const eMode, SwScriptInfo * pScriptInfo)
+{
+    if (eMode == sw::ParagraphBreakMode::Hidden)
+    {
+        ::std::optional<SwScriptInfo> oScriptInfo;
+        if (pScriptInfo == nullptr)
+        {
+            oScriptInfo.emplace();
+            pScriptInfo = &*oScriptInfo;
+        }
+        // always init: when called from SwTextFrame::SwClientNotify() it is 
stale!
+        pScriptInfo->InitScriptInfoHidden(*rMerged.pFirstNode, &rMerged);
+        TextFrameIndex nHiddenStart{COMPLETE_STRING};
+        TextFrameIndex nHiddenEnd{0};
+        pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex{0}, nHiddenStart, 
nHiddenEnd);
+        if (TextFrameIndex{0} == nHiddenStart)
+        {
+            if (nHiddenEnd == TextFrameIndex{rMerged.mergedText.getLength()})
+            {
+                rMerged.pParaPropsNode = 
const_cast<SwTextNode*>(rMerged.pLastNode);
+            }
+            else
+            {   // this requires MapViewToModel to never return a position at
+                // the end of a node (when all its text is hidden)
+                rMerged.pParaPropsNode = sw::MapViewToModel(rMerged, 
nHiddenEnd).first;
+            }
+            return;
+        }
+    }
+    if (!rMerged.extents.empty())
+    {   // para props from first node that isn't empty (OOo/LO compat)
+        rMerged.pParaPropsNode = rMerged.extents.begin()->pNode;
+    }
+    else
+    {   // if every node is empty, the last one wins (Word compat)
+        // (OOo/LO historically used first one)
+        rMerged.pParaPropsNode = const_cast<SwTextNode*>(rMerged.pLastNode);
+    }
+}
+
 std::unique_ptr<sw::MergedPara>
 CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode,
        FrameMode const eMode)
@@ -278,7 +319,6 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & 
rTextNode,
     std::vector<SwSectionNode *> sections;
     std::vector<sw::Extent> extents;
     OUStringBuffer mergedText;
-    SwTextNode * pParaPropsNode(nullptr);
     SwTextNode * pNode(&rTextNode);
     sal_Int32 nLastEnd(0);
     for (auto iter = HideIterator(rTextNode,
@@ -420,22 +460,23 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & 
rTextNode,
     if (extents.empty()) // there was no text anywhere
     {
         assert(mergedText.isEmpty());
-        pParaPropsNode = pNode; // if every node is empty, the last one wins
     }
     else
     {
         assert(!mergedText.isEmpty());
-        pParaPropsNode = extents.begin()->pNode; // para props from first node 
that isn't empty
     }
-//    pParaPropsNode = &rTextNode; // well, actually...
+    auto pRet{std::make_unique<sw::MergedPara>(rFrame, std::move(extents),
+                mergedText.makeStringAndClear(), &rTextNode, nodes.back())};
+    FindParaPropsNodeIgnoreHidden(*pRet, 
rFrame.getRootFrame()->GetParagraphBreakMode(), nullptr);
+    assert(pRet->pParaPropsNode);
     // keep lists up to date with visible nodes
-    if (pParaPropsNode->IsInList() && 
!pParaPropsNode->GetNum(rFrame.getRootFrame()))
+    if (pRet->pParaPropsNode->IsInList() && 
!pRet->pParaPropsNode->GetNum(rFrame.getRootFrame()))
     {
-        pParaPropsNode->AddToListRLHidden(); // try to add it...
+        pRet->pParaPropsNode->AddToListRLHidden(); // try to add it...
     }
     for (auto const pTextNode : nodes)
     {
-        if (pTextNode != pParaPropsNode)
+        if (pTextNode != pRet->pParaPropsNode)
         {
             pTextNode->RemoveFromListRLHidden();
         }
@@ -449,12 +490,12 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & 
rTextNode,
         // for non-first nodes that are already merged with this frame,
         // need to remove here too, otherwise footnotes can be removed only
         // by lucky accident, e.g. TruncLines().
-        auto itExtent(extents.begin());
+        auto itExtent(pRet->extents.begin());
         for (auto const pTextNode : nodes)
         {
             sal_Int32 nLast(0);
             std::vector<std::pair<sal_Int32, sal_Int32>> hidden;
-            for ( ; itExtent != extents.end(); ++itExtent)
+            for ( ; itExtent != pRet->extents.end(); ++itExtent)
             {
                 if (itExtent->pNode != pTextNode)
                 {
@@ -490,9 +531,6 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & 
rTextNode,
             
pSectionNode->GetSection().GetFormat()->DelFrames(/*rFrame.getRootFrame()*/);
         }
     }
-    auto pRet(std::make_unique<sw::MergedPara>(rFrame, std::move(extents),
-                mergedText.makeStringAndClear(), pParaPropsNode, &rTextNode,
-                nodes.back()));
     for (SwTextNode * pTmp : nodes)
     {
         pRet->listener.StartListening(pTmp);
diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx
index 654a59b30a63..578d24a4b09a 100644
--- a/sw/source/core/text/txtfrm.cxx
+++ b/sw/source/core/text/txtfrm.cxx
@@ -1018,6 +1018,7 @@ namespace sw {
 // 1. if real insert => correct nStart/nEnd for full nLen
 // 2. if rl un-delete => do not correct nStart/nEnd but just include un-deleted
 static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged,
+        sw::ParagraphBreakMode const eMode, SwScriptInfo *const pScriptInfo,
         bool const isRealInsert,
         SwTextNode const& rNode, sal_Int32 const nIndex, sal_Int32 const nLen)
 {
@@ -1129,13 +1130,6 @@ static TextFrameIndex 
UpdateMergedParaForInsert(MergedPara & rMerged,
         rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), 
nIndex, nIndex + nLen);
         text.insert(nTFIndex, rNode.GetText().subView(nIndex, nLen));
         nInserted = nLen;
-        if (rMerged.extents.size() == 1 // also if it was empty!
-            || rMerged.pParaPropsNode->GetIndex() < rNode.GetIndex())
-        {   // text inserted after current para-props node
-            rMerged.pParaPropsNode->RemoveFromListRLHidden();
-            rMerged.pParaPropsNode = &const_cast<SwTextNode&>(rNode);
-            rMerged.pParaPropsNode->AddToListRLHidden();
-        }
         // called from SwRangeRedline::InvalidateRange()
         if (rNode.GetRedlineMergeFlag() == SwNode::Merge::Hidden)
         {
@@ -1143,12 +1137,24 @@ static TextFrameIndex 
UpdateMergedParaForInsert(MergedPara & rMerged,
         }
     }
     rMerged.mergedText = text.makeStringAndClear();
+    if ((!bInserted && rMerged.extents.size() == 1) // also if it was empty!
+        || rNode.GetIndex() <= rMerged.pParaPropsNode->GetIndex())
+    {   // text inserted before current para-props node
+        SwTextNode *const pOldParaPropsNode{rMerged.pParaPropsNode};
+        FindParaPropsNodeIgnoreHidden(rMerged, eMode, pScriptInfo);
+        if (rMerged.pParaPropsNode != pOldParaPropsNode)
+        {
+            pOldParaPropsNode->RemoveFromListRLHidden();
+            rMerged.pParaPropsNode->AddToListRLHidden();
+        }
+    }
     return TextFrameIndex(nInserted);
 }
 
 // 1. if real delete => correct nStart/nEnd for full nLen
 // 2. if rl delete => do not correct nStart/nEnd but just exclude deleted
 TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged,
+        sw::ParagraphBreakMode const eMode, SwScriptInfo *const pScriptInfo,
         bool const isRealDelete,
         SwTextNode const& rNode, sal_Int32 nIndex, sal_Int32 const nLen)
 {
@@ -1159,7 +1165,7 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & 
rMerged,
     sal_Int32 nToDelete(nLen);
     sal_Int32 nDeleted(0);
     size_t nFoundNode(0);
-    size_t nErased(0);
+//    size_t nErased(0);
     auto it = rMerged.extents.begin();
     for (; it != rMerged.extents.end(); )
     {
@@ -1195,7 +1201,7 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & 
rMerged,
                     bErase = nDeleteHere == it->nEnd - it->nStart;
                     if (bErase)
                     {
-                        ++nErased;
+//                        ++nErased;
                         assert(it->nStart == nIndex);
                         it = rMerged.extents.erase(it);
                     }
@@ -1261,21 +1267,23 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & 
rMerged,
 // can't do: might be last one in node was erased   assert(nLen == 0 || 
rMerged.empty() || (it-1)->nEnd <= nIndex);
     // note: if first node gets deleted then that must call DelFrames as
     // pFirstNode is never updated
-    if (nErased && nErased == nFoundNode)
+    rMerged.mergedText = text.makeStringAndClear();
+// could be all-hidden now so always check!    if (nErased && nErased == 
nFoundNode)
     {   // all visible text from node was erased
 #if 1
         if (rMerged.pParaPropsNode == &rNode)
         {
-            rMerged.pParaPropsNode->RemoveFromListRLHidden();
-            rMerged.pParaPropsNode = rMerged.extents.empty()
-                ? const_cast<SwTextNode*>(rMerged.pLastNode)
-                : rMerged.extents.front().pNode;
-            rMerged.pParaPropsNode->AddToListRLHidden();
+            SwTextNode *const pOldParaPropsNode{rMerged.pParaPropsNode};
+            FindParaPropsNodeIgnoreHidden(rMerged, eMode, pScriptInfo);
+            if (rMerged.pParaPropsNode != pOldParaPropsNode)
+            {
+                pOldParaPropsNode->RemoveFromListRLHidden();
+                rMerged.pParaPropsNode->AddToListRLHidden();
+            }
         }
 #endif
 // NOPE must listen on all non-hidden nodes; particularly on pLastNode        
rMerged.listener.EndListening(&const_cast<SwTextNode&>(rNode));
     }
-    rMerged.mergedText = text.makeStringAndClear();
     return TextFrameIndex(nDeleted);
 }
 
@@ -1521,7 +1529,7 @@ bool SwTextFrame::IsHiddenNowImpl() const
         else // ParaPortion is created in Format, but this is called earlier
         {
             SwScriptInfo aInfo;
-            aInfo.InitScriptInfo(*m_pMergedPara->pFirstNode, 
m_pMergedPara.get(), IsRightToLeft());
+            aInfo.InitScriptInfoHidden(*m_pMergedPara->pFirstNode, 
m_pMergedPara.get());
             aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0),
                         nHiddenStart, nHiddenEnd);
         }
@@ -2135,8 +2143,9 @@ void UpdateMergedParaForMove(sw::MergedPara & rMerged,
     for (auto const& it : deleted)
     {
         sal_Int32 const nStart(it.first - nSourceStart + nDestStart);
-        TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged, 
false,
-            rDestNode, nStart, it.second - it.first);
+        TextFrameIndex const nDeleted = UpdateMergedParaForDelete(rMerged,
+            rTextFrame.getRootFrame()->GetParagraphBreakMode(), 
rTextFrame.GetScriptInfo(),
+            false, rDestNode, nStart, it.second - it.first);
 //FIXME asserts valid for join - but if called from split, the new node isn't 
there yet and it will be added later...       assert(nDeleted);
 //            assert(nDeleted == it.second - it.first);
         if(nDeleted)
@@ -2314,7 +2323,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, 
SfxHint const& rHint)
             sal_Int32 const nNLen = pRedlineDelText->nLen;
             nPos = MapModelToView(&rNode, nNPos);
             // update merged before doing anything else
-            nLen = UpdateMergedParaForDelete(*m_pMergedPara, false, rNode, 
nNPos, nNLen);
+            nLen = UpdateMergedParaForDelete(*m_pMergedPara,
+                    getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(),
+                    false, rNode, nNPos, nNLen);
             const sal_Int32 m = -nNLen;
             if (nLen && IsIdxInside(nPos, nLen))
             {
@@ -2336,7 +2347,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, 
SfxHint const& rHint)
             sal_Int32 const nNPos = pRedlineUnDelText->nStart;
             sal_Int32 const nNLen = pRedlineUnDelText->nLen;
             nPos = MapModelToView(&rNode, nNPos);
-            nLen = UpdateMergedParaForInsert(*m_pMergedPara, false, rNode, 
nNPos, nNLen);
+            nLen = UpdateMergedParaForInsert(*m_pMergedPara,
+                    getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(),
+                    false, rNode, nNPos, nNLen);
             if (IsIdxInside(nPos, nLen))
             {
                 if (!nLen)
@@ -2398,7 +2411,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, 
SfxHint const& rHint)
             nLen = TextFrameIndex(pInsertText->nLen);
             if (m_pMergedPara)
             {
-                UpdateMergedParaForInsert(*m_pMergedPara, true, rNode, 
pInsertText->nPos, pInsertText->nLen);
+                UpdateMergedParaForInsert(*m_pMergedPara,
+                    getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(),
+                    true, rNode, pInsertText->nPos, pInsertText->nLen);
             }
             if( IsIdxInside( nPos, nLen ) )
             {
@@ -2424,7 +2439,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, 
SfxHint const& rHint)
         nPos = MapModelToView(&rNode, pDeleteText->nStart);
         if (m_pMergedPara)
         {   // update merged before doing anything else
-            nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, 
pDeleteText->nStart, pDeleteText->nLen);
+            nLen = UpdateMergedParaForDelete(*m_pMergedPara,
+                getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(),
+                true, rNode, pDeleteText->nStart, pDeleteText->nLen);
         }
         else
         {
@@ -2451,7 +2468,9 @@ void SwTextFrame::SwClientNotify(SwModify const& rModify, 
SfxHint const& rHint)
         nPos = MapModelToView(&rNode, pDeleteChar->m_nPos);
         if (m_pMergedPara)
         {
-            nLen = UpdateMergedParaForDelete(*m_pMergedPara, true, rNode, 
pDeleteChar->m_nPos, 1);
+            nLen = UpdateMergedParaForDelete(*m_pMergedPara,
+                getRootFrame()->GetParagraphBreakMode(), GetScriptInfo(),
+                true, rNode, pDeleteChar->m_nPos, 1);
         }
         else
         {
@@ -4148,6 +4167,12 @@ const SwScriptInfo* SwTextFrame::GetScriptInfo() const
     return pPara ? &pPara->GetScriptInfo() : nullptr;
 }
 
+SwScriptInfo* SwTextFrame::GetScriptInfo()
+{
+    SwParaPortion* pPara = GetPara();
+    return pPara ? &pPara->GetScriptInfo() : nullptr;
+}
+
 /**
  * Helper function for SwTextFrame::CalcBasePosForFly()
  */
diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx
index 00e84594dab5..ac31011e01f6 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -2378,18 +2378,9 @@ void SwViewShell::ImplApplyViewOptions( const 
SwViewOption &rOpt )
     // ( - SwEndPortion must _no_ longer be generated. )
     // - Of course, the screen is something completely different than the 
printer ...
     bool const isToggleFieldNames(mpOpt->IsFieldName() != rOpt.IsFieldName());
-
-    if (mpOpt->IsFieldName() != rOpt.IsFieldName()
-        || mpOpt->IsParagraph() != rOpt.IsParagraph())
-    {
-        GetLayout()->SetFieldmarkMode( rOpt.IsFieldName()
-                    ? sw::FieldmarkMode::ShowCommand
-                    : sw::FieldmarkMode::ShowResult,
-                rOpt.IsParagraph()
-                    ? sw::ParagraphBreakMode::Shown
-                    : sw::ParagraphBreakMode::Hidden);
-        bReformat = true;
-    }
+    bool const isToggleLayoutHide{isToggleFieldNames
+                || mpOpt->IsParagraph() != rOpt.IsParagraph()
+                || mpOpt->IsShowHiddenChar() != rOpt.IsShowHiddenChar()};
 
     // The map mode is changed, minima/maxima will be attended by UI
     if( mpOpt->GetZoom() != rOpt.GetZoom() && !IsPreview() )
@@ -2459,6 +2450,17 @@ void SwViewShell::ImplApplyViewOptions( const 
SwViewOption &rOpt )
 
     mxDoc->GetDocumentSettingManager().set(DocumentSettingId::HTML_MODE, 0 != 
::GetHtmlMode(mxDoc->GetDocShell()));
 
+    if (isToggleLayoutHide)
+    {
+        GetLayout()->SetFieldmarkMode( rOpt.IsFieldName()
+                    ? sw::FieldmarkMode::ShowCommand
+                    : sw::FieldmarkMode::ShowResult,
+                sw::IsShowHiddenChars(this)
+                    ? sw::ParagraphBreakMode::Shown
+                    : sw::ParagraphBreakMode::Hidden);
+        bReformat = true;
+    }
+
     if( bBrowseModeChanged || bHideWhitespaceModeChanged )
     {
         // #i44963# Good occasion to check if page sizes in

Reply via email to