sw/qa/extras/layout/data/merge_hidden_redline.docx |binary
 sw/qa/extras/layout/layout2.cxx                    |  247 ++++++++++++++++++++-
 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, 390 insertions(+), 67 deletions(-)

New commits:
commit 43adea12b199622bb703fe731afb3be182f2c17d
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Wed Feb 19 16:25:35 2025 +0100
Commit:     Adolfo Jayme Barrientos <fit...@ubuntu.com>
CommitDate: Fri Feb 21 13:14:16 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/+/181957
    Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com>

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 5dc1e6fff241..9364add9ecaf 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -20,6 +20,10 @@
 #include <o3tl/string_view.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>
@@ -707,24 +711,25 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
     // 5 is empty and hidden
     assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds", "height", 
u"0");
 
-    dispatchCommand(mxComponent, u".uno:ControlCodes"_ustr, {});
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
+    aViewOptions.SetShowHiddenChar(true);
+    aViewOptions.SetViewMetaChars(true);
+    pWrtShell->ApplyViewOptions(aViewOptions);
 
     pXmlDoc = parseLayoutDump();
 
     assertXPath(pXmlDoc, "/root/page[1]/body/txt", 5);
     assertXPath(pXmlDoc, "/root/page/body/txt[1]/SwParaPortion/SwLineLayout", 
"portion", u"C ");
     assertXPath(pXmlDoc, "/root/page/body/txt[2]/SwParaPortion/SwLineLayout", 
"portion", u"D");
-    // 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", "height", 
u"0");
+    // 3 is an empty paragraph with RES_CHRATR_HIDDEN
+    assertXPath(pXmlDoc, "/root/page/body/txt[3]/infos/bounds", "height", 
u"398");
     assertXPath(pXmlDoc, "/root/page/body/txt[4]/SwParaPortion/SwLineLayout", 
"portion", u"E");
-    // 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", "height", 
u"0");
+    // 5 is an empty paragraph with RES_CHRATR_HIDDEN
+    assertXPath(pXmlDoc, "/root/page/body/txt[5]/infos/bounds", "height", 
u"398");
 
-    dispatchCommand(mxComponent, u".uno:ControlCodes"_ustr, {});
+    aViewOptions.SetViewMetaChars(false);
+    pWrtShell->ApplyViewOptions(aViewOptions);
 
     pXmlDoc = parseLayoutDump();
 
@@ -734,6 +739,228 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
     assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds", "height", 
u"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 d5b86fb90120..aebade8eb36f 100644
--- a/sw/source/core/docnode/node.cxx
+++ b/sw/source/core/docnode/node.cxx
@@ -1457,8 +1457,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 d41484ea7576..cecb8e50b1b9 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 c60e8d0959a8..5d4814bf2f63 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -104,6 +104,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 9d48e488316c..c1db19420eeb 100644
--- a/sw/source/core/inc/txtfrm.hxx
+++ b/sw/source/core/inc/txtfrm.hxx
@@ -33,6 +33,7 @@
 namespace com::sun::star::linguistic2 { class XHyphenatedWord; }
 
 namespace sw::mark { class MarkBase; }
+namespace sw { enum class ParagraphBreakMode; }
 class SwCharRange;
 class SwTextNode;
 class SwTextAttrEnd;
@@ -51,6 +52,7 @@ class SwPortionHandler;
 class SwScriptInfo;
 enum class ExpandMode;
 class SwTextAttr;
+class SwViewShell;
 class SwWrtShell;
 class SwNode;
 class SwFlyAtContentFrame;
@@ -105,6 +107,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);
@@ -124,6 +130,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);
 
@@ -690,6 +697,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();
@@ -999,12 +1007,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 d2863a91ab93..737c453fb411 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 62927c06e13c..290a7ebd558d 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -3340,6 +3340,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 40b45ae691f5..a17f2dd5242b 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -1134,8 +1134,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());
 
@@ -1281,6 +1282,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
 
@@ -2372,7 +2381,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 9285bf457803..ab9dfb59cea0 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -265,6 +265,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)
@@ -279,7 +320,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,
@@ -421,22 +461,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();
         }
@@ -450,12 +491,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)
                 {
@@ -491,9 +532,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 6f964506c949..30469ddd0746 100644
--- a/sw/source/core/text/txtfrm.cxx
+++ b/sw/source/core/text/txtfrm.cxx
@@ -1017,6 +1017,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)
 {
@@ -1128,13 +1129,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)
         {
@@ -1142,12 +1136,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)
 {
@@ -1158,7 +1164,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(); )
     {
@@ -1194,7 +1200,7 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & 
rMerged,
                     bErase = nDeleteHere == it->nEnd - it->nStart;
                     if (bErase)
                     {
-                        ++nErased;
+//                        ++nErased;
                         assert(it->nStart == nIndex);
                         it = rMerged.extents.erase(it);
                     }
@@ -1260,21 +1266,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);
 }
 
@@ -1520,7 +1528,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);
         }
@@ -2134,8 +2142,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)
@@ -2318,7 +2327,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))
             {
@@ -2340,7 +2351,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)
@@ -2402,7 +2415,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 ) )
             {
@@ -2428,7 +2443,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
         {
@@ -2455,7 +2472,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
         {
@@ -4179,6 +4198,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 4af997226abc..d3b805af7dc1 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -2428,18 +2428,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() )
@@ -2509,6 +2500,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