sw/qa/extras/layout/data/section-break-hidden-paragraphs.rtf |   68 +++++++++++
 sw/qa/extras/layout/layout2.cxx                              |   48 +++++++
 sw/source/core/text/porlay.cxx                               |   15 ++
 sw/source/core/text/txtfrm.cxx                               |   13 +-
 4 files changed, 138 insertions(+), 6 deletions(-)

New commits:
commit 1f40bc159ef5fd22c42c28752f7849aa61fd03d8
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Fri Mar 7 19:05:15 2025 +0100
Commit:     Michael Stahl <michael.st...@allotropia.de>
CommitDate: Tue Mar 11 16:10:12 2025 +0100

    sw: layout: hide text frame before table like Word
    
    There are several empty paragraphs before a table, all have the
    paragraph end marker hidden, and the first one is preceded by a section
    break, which is represented as a break item on the first text node in
    Writer.
    
    Word hides the paragraphs, so that the following table starts at the top
    of the page.
    
    Writer merges the paragraphs into one text frame, but it is not
    considered hidden because it doesn't contain any hidden characters; if
    you insert a character into a paragraph, the merged text frame will
    disappear!
    
    The first idea was to adapt SwTextFrame::IsHiddenNowImpl() to check if
    there isn't any text in the text frame and the paragraph end marker is
    hidden.
    
    But it turns out that the problem is more general: it's a problem of the
    sw_redlinehide merging, because an un-merged paragraph is hidden via a
    check of RES_CHRATR_HIDDEN in SwScriptInfo::selectHiddenTextProperty().
    
    It's a bit nonobvious why this didn't work, and how it would interact
    with delete redlines, but 2 problems are that the case of the empty text
    frame needs a special case handling to get to this check in
    selectHiddenTextProperty(), and then GetBoundsOfHiddenRange() doesn't
    assign its outparameters in the special case of the empty paragraph; it
    looks like the values in m_HiddenChg should be 0, 0 then.
    
    Change-Id: I2ba85e98c02fb69159ee7ba77a7e4694fdf6fd52
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182642
    Tested-by: Jenkins
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    (cherry picked from commit 7a9a1e8ef5ad9dd3e9b7d286a8f85d2f18088d29)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182774

diff --git a/sw/qa/extras/layout/data/section-break-hidden-paragraphs.rtf 
b/sw/qa/extras/layout/data/section-break-hidden-paragraphs.rtf
new file mode 100644
index 000000000000..f8526dafe5ea
--- /dev/null
+++ b/sw/qa/extras/layout/data/section-break-hidden-paragraphs.rtf
@@ -0,0 +1,68 @@
+{ 
tf1deflang1025nsinsicpg1252\uc1deff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang2057\deflangfe1028
       hemelang2057    hemelangfe1028  hemelangcs1025
+{onttbl{1bidi swisscharset0prq2{\*\panose 020b0604020202020204}Arial;}
+{14bidi nilcharset136prq2{\*\panose 02010601000101010101}PMingLiU{\*alt 
Arial Unicode MS};}{34bidi romancharset0prq2{\*\panose 
02040503050406030204}Cambria Math;}
+{37bidi swisscharset0prq2{\*\panose 020f0502020204030204}Calibri;}{294
bidi nilcharset136prq2{\*\panose 00000000000000000000}@PMingLiU;}
+{lomajor31500bidi romancharset0prq2{\*\panose 02020603050405020304}Times 
New Roman{\*alt Arial};}{dbmajor31501bidi nilcharset136prq2{\*\panose 
02010601000101010101}PMingLiU{\*alt Arial Unicode MS};}
+{himajor31502bidi swisscharset0prq2{\*\panose 
020f0302020204030204}Calibri Light;}{bimajor31503bidi romancharset0
prq2{\*\panose 02020603050405020304}Times New Roman{\*alt Arial};}
+{lominor31504bidi romancharset0prq2{\*\panose 02020603050405020304}Times 
New Roman{\*alt Arial};}{dbminor31505bidi nilcharset136prq2{\*\panose 
02010601000101010101}PMingLiU{\*alt Arial Unicode MS};}
+{himinor31506bidi swisscharset0prq2{\*\panose 
020f0502020204030204}Calibri;}{biminor31507bidi swisscharset0
prq2{\*\panose 020b0604020202020204}Arial;}{305bidi swisscharset238prq2 
Arial CE;}
+{306bidi swisscharset204prq2 Arial Cyr;}{308bidi swisscharset161prq2 
Arial Greek;}{309bidi swisscharset162prq2 Arial Tur;}{310bidi swiss
charset177prq2 Arial (Hebrew);}
+{311bidi swisscharset178prq2 Arial (Arabic);}{312bidi swisscharset186
prq2 Arial Baltic;}{313bidi swisscharset163prq2 Arial (Vietnamese);}{635
bidi romancharset238prq2 Cambria Math CE;}
+{636bidi romancharset204prq2 Cambria Math Cyr;}{638bidi roman
charset161prq2 Cambria Math Greek;}{639bidi romancharset162prq2 Cambria 
Math Tur;}{642bidi romancharset186prq2 Cambria Math Baltic;}
+{643bidi romancharset163prq2 Cambria Math (Vietnamese);}{665bidi swiss
charset238prq2 Calibri CE;}{666bidi swisscharset204prq2 Calibri Cyr;}{
668bidi swisscharset161prq2 Calibri Greek;}
+{669bidi swisscharset162prq2 Calibri Tur;}{670bidi swisscharset177
prq2 Calibri (Hebrew);}{671bidi swisscharset178prq2 Calibri (Arabic);}{
672bidi swisscharset186prq2 Calibri Baltic;}
+{673bidi swisscharset163prq2 Calibri (Vietnamese);}{lomajor31508bidi 
romancharset238prq2 Times New Roman CE{\*alt Arial};}{lomajor31509bidi 
romancharset204prq2 Times New Roman Cyr{\*alt Arial};}
+{lomajor31511bidi romancharset161prq2 Times New Roman Greek{\*alt 
Arial};}{lomajor31512bidi romancharset162prq2 Times New Roman Tur{\*alt 
Arial};}
+{lomajor31513bidi romancharset177prq2 Times New Roman (Hebrew){\*alt 
Arial};}{lomajor31514bidi romancharset178prq2 Times New Roman (Arabic){\*
alt Arial};}
+{lomajor31515bidi romancharset186prq2 Times New Roman Baltic{\*alt 
Arial};}{lomajor31516bidi romancharset163prq2 Times New Roman 
(Vietnamese){\*alt Arial};}{himajor31528bidi swisscharset238prq2 Calibri 
Light CE;}
+{himajor31529bidi swisscharset204prq2 Calibri Light Cyr;}{himajor31531
bidi swisscharset161prq2 Calibri Light Greek;}{himajor31532bidi swiss
charset162prq2 Calibri Light Tur;}
+{himajor31533bidi swisscharset177prq2 Calibri Light (Hebrew);}{himajor
31534bidi swisscharset178prq2 Calibri Light (Arabic);}{himajor31535bidi 
swisscharset186prq2 Calibri Light Baltic;}
+{himajor31536bidi swisscharset163prq2 Calibri Light (Vietnamese);}{
bimajor31538bidi romancharset238prq2 Times New Roman CE{\*alt Arial};}{
bimajor31539bidi romancharset204prq2 Times New Roman Cyr{\*alt Arial};}
+{bimajor31541bidi romancharset161prq2 Times New Roman Greek{\*alt 
Arial};}{bimajor31542bidi romancharset162prq2 Times New Roman Tur{\*alt 
Arial};}
+{bimajor31543bidi romancharset177prq2 Times New Roman (Hebrew){\*alt 
Arial};}{bimajor31544bidi romancharset178prq2 Times New Roman (Arabic){\*
alt Arial};}
+{bimajor31545bidi romancharset186prq2 Times New Roman Baltic{\*alt 
Arial};}{bimajor31546bidi romancharset163prq2 Times New Roman 
(Vietnamese){\*alt Arial};}
+{lominor31548bidi romancharset238prq2 Times New Roman CE{\*alt 
Arial};}{lominor31549bidi romancharset204prq2 Times New Roman Cyr{\*alt 
Arial};}
+{lominor31551bidi romancharset161prq2 Times New Roman Greek{\*alt 
Arial};}{lominor31552bidi romancharset162prq2 Times New Roman Tur{\*alt 
Arial};}
+{lominor31553bidi romancharset177prq2 Times New Roman (Hebrew){\*alt 
Arial};}{lominor31554bidi romancharset178prq2 Times New Roman (Arabic){\*
alt Arial};}
+{lominor31555bidi romancharset186prq2 Times New Roman Baltic{\*alt 
Arial};}{lominor31556bidi romancharset163prq2 Times New Roman 
(Vietnamese){\*alt Arial};}{himinor31568bidi swisscharset238prq2 Calibri 
CE;}
+{himinor31569bidi swisscharset204prq2 Calibri Cyr;}{himinor31571bidi 
swisscharset161prq2 Calibri Greek;}{himinor31572bidi swisscharset162
prq2 Calibri Tur;}
+{himinor31573bidi swisscharset177prq2 Calibri (Hebrew);}{himinor31574
bidi swisscharset178prq2 Calibri (Arabic);}{himinor31575bidi swiss
charset186prq2 Calibri Baltic;}
+{himinor31576bidi swisscharset163prq2 Calibri (Vietnamese);}{biminor
31578bidi swisscharset238prq2 Arial CE;}{biminor31579bidi swiss
charset204prq2 Arial Cyr;}
+{biminor31581bidi swisscharset161prq2 Arial Greek;}{biminor31582bidi 
swisscharset162prq2 Arial Tur;}{biminor31583bidi swisscharset177prq2 
Arial (Hebrew);}
+{biminor31584bidi swisscharset178prq2 Arial (Arabic);}{biminor31585
bidi swisscharset186prq2 Arial Baltic;}{biminor31586bidi swiss
charset163prq2 Arial (Vietnamese);}
+{295bidi romancharset238prq2 Times New Roman CE{\*alt Arial};}{296bidi 
romancharset204prq2 Times New Roman Cyr{\*alt Arial};}{298bidi roman
charset161prq2 Times New Roman Greek{\*alt Arial};}
+{299bidi romancharset162prq2 Times New Roman Tur{\*alt Arial};}{300
bidi romancharset177prq2 Times New Roman (Hebrew){\*alt Arial};}{301bidi 
romancharset178prq2 Times New Roman (Arabic){\*alt Arial};}
+{302bidi romancharset186prq2 Times New Roman Baltic{\*alt Arial};}{303
bidi romancharset163prq2 Times New Roman (Vietnamese){\*alt Arial};}}
+{\*\defchp s22\lochf31506\hichf31506\dbchf31505 }{\*\defpap \ql \li0 
i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0 }
oqfpromote
+{\stylesheet{
+\ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnum
aautodjustright in0\lin0\itap0  tlchcs1 f31507fs22lang1025 \ltrchcs0 
s22\lang2057\langfe1028\loch31506\hichf31506\dbchf31505+\snext0 \sqformat 
\spriority0 Normal;}{\*+  s11     srowd   rftsWidthB3     rpaddl108       
rpaddr108       rpaddfl3        rpaddft3        rpaddfb3        rpaddfr3        
blind0  blindtype3      svertalt        sbrdrt  sbrdrl  sbrdrb  sbrdrr  
sbrdrdgl        sbrdrdgr        sbrdrh  sbrdrv \ql \li0 i0\sa160\sl259\slmult1
+\widctlpar\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0  tlchcs1 
f31507fs22lang1025 \ltrchcs0 s22\lang2057\langfe1028\loch
31506\hichf31506\dbchf31505+Normal Table;}{\*  s15     srowd   
rbrdrtrdrsrdrw10      rbrdrlrdrsrdrw10      rbrdrbrdrsrdrw10      
rbrdrrrdrsrdrw10      rbrdrhrdrsrdrw10      rbrdrvrdrsrdrw10 
+       rftsWidthB3     rpaddl108       rpaddr108       rpaddfl3        
rpaddft3        rpaddfb3        rpaddfr3        blind0  blindtype3      
svertalt        sbrdrt  sbrdrl  sbrdrb  sbrdrr  sbrdrdgl        sbrdrdgr        
sbrdrh  sbrdrv 
+\ql \li0 i0\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0  tlchcs1 f31507fs22lang1025 \ltrchcs0 
s22\lang2057\langfe1028\loch31506\hichf31506\dbchf31505+\sbasedon11 \snext15 
\spriority39 Table Grid;}}
+
+\paperw11906\paperh16838\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect
 
+\widowctrltnbjenddoc rackmoves0      rackformatting1\donotembedsysfont1 
elyonvml0\donotembedlingdata0\grfdocevents0
alidatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1
oxlattoyen
+xpshrtn
oultrlspc\dntblnsbdb
ospaceforul
ormshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1
+\jexpandiewkind1iewscale100\pgbrdrhead\pgbrdrfoot\splytwnine
tnlytwnine\htmautsp
olnhtadjtbl\useltbalnlntblind\lytcalctblwd\lyttblrtgr\lnbrkrule
obrkwrptbl\snaptogridincellllowfieldendsel\wrppunct
+sianbrkrule
ewtblstyruls
ogrowautofit\usenormstyforlist
oindnmbrtselnbrelev
ocxsptable\indrlsweleven
oafcnsttblfelev\utinl\hwelev\spltpgpar
otcvasp
otbrkcnstfrctbl
otvatxbx\krnprsnet+{\*\wgrffmtfilter 2450}
ofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708
ootery708+\pard\plain \ltrpar\ql \li0 
i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0  tlchcs1 f31507fs22lang1025 \ltrchcs0 
+
s22\lang2057\langfe1028\lochf31506\hichf31506\dbchf31505+\linex0\headery708
ootery708+\ltrchcs0 
s22\lang2057\langfe1028\lochf31506\hichf31506\dbchf31505+\par \par }{ tlch
cs1 f31507 \ltrchcs0 \lang1031\langfe1028\langnp1031
+\par \ltrrow}  rowd \irow0\irowband0\lastrow \ltrrow   s15     rgaph108        
rleft5  rbrdrtrdrsrdrw10      rbrdrlrdrsrdrw10      rbrdrbrdrsrdrw10      
rbrdrrrdrsrdrw10      rbrdrhrdrsrdrw10      rbrdrvrdrsrdrw10 
+       rftsWidth1      rftsWidthB3     rautofit1       rpaddl108       
rpaddr108       rpaddfl3        rpaddft3        rpaddfb3        rpaddfr3        
bllkhdrrows     bllkhdrcols     bllknocolband   blind0  blindtype3 ++
s22\lang2057\langfe1028\lochf31506\hichf31506\dbchf31505+\ql \li0 
i0\sa160\sl259\slmult1\widctlpar\intbl\wrapdefaultspalphaspnum
aautodjustright in0\lin0  tlchcs1 f31507fs22lang1025 \ltrchcs0 
s22\lang2057\langfe1028\lochf31506\hichf31506\dbchf31505+ tlchcs1 f31507 
\ltrchcs0 \lang1031\langfe1028\langnp1031      rowd \irow0\irowband0\lastrow 
\ltrrow   s15     rgaph108        rleft5  rbrdrtrdrsrdrw10      
rbrdrlrdrsrdrw10      rbrdrbrdrsrdrw10      rbrdrrrdrsrdrw10      rbrdrh
+rdrsrdrw10   rbrdrvrdrsrdrw10      rftsWidth1      rftsWidthB3     
rautofit1       rpaddl108       rpaddr108       rpaddfl3        rpaddft3        
rpaddfb3        rpaddfr3        bllkhdrrows     bllkhdrcols     bllknocolband   
blind0  blindtype3 +rdrsrdrw10 + tlchcs1 f31507 \ltrchcs0 
\lang1031\langfe1028\langnp1031\hichf31506\dbchf31505\loch31506 End}{ tlch
cs1 f31507 \ltrchcs0 \lang1031\langfe1028\langnp1031
+\par }
+}
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index 06819b3caf68..5ad19155f95b 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -22,6 +22,7 @@
 #include <unotools/syslocaleoptions.hxx>
 #include <editeng/unolingu.hxx>
 #include <o3tl/string_view.hxx>
+#include <vcl/scheduler.hxx>
 
 #include <unotxdoc.hxx>
 #include <rootfrm.hxx>
@@ -931,6 +932,53 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf152872)
     assertXPath(pXmlDoc, "/root/page/body/txt[2]/infos/bounds"_ostr, 
"height"_ostr, "0");
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testHiddenParaBreaks)
+{
+    createSwDoc("section-break-hidden-paragraphs.rtf");
+
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
+    aViewOptions.SetShowHiddenChar(true);
+    aViewOptions.SetViewMetaChars(true);
+    pWrtShell->ApplyViewOptions(aViewOptions);
+    Scheduler::ProcessEventsToIdle();
+
+    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+
+    assertXPath(pXmlDoc, "/root/page[1]/body/txt", 1);
+    assertXPath(pXmlDoc, 
"/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout", "portion",
+                "First");
+    // actually Word shows an additional paragraph before the table
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt", 3);
+    assertXPath(pXmlDoc, 
"/root/page[2]/body/txt[1]/SwParaPortion/SwLineLayout", "portion", "");
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt[1]/infos/bounds", "top", 
"18846");
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt[1]/infos/bounds", "height", 
"269");
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt[2]/infos/bounds", "top", 
"19115");
+    // this was 450 on master? just important it's not 0
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt[2]/infos/bounds", "height", 
"447");
+    // this was 19565 on master
+    assertXPath(pXmlDoc, "/root/page[2]/body/tab[1]/infos/bounds", "top", 
"19562");
+    assertXPath(pXmlDoc, 
"/root/page[2]/body/txt[3]/SwParaPortion/SwLineLayout", "portion", "End");
+    discardDumpedLayout();
+
+    aViewOptions.SetViewMetaChars(false);
+    pWrtShell->ApplyViewOptions(aViewOptions);
+    Scheduler::ProcessEventsToIdle();
+
+    pXmlDoc = parseLayoutDump();
+
+    assertXPath(pXmlDoc, "/root/page[1]/body/txt", 1);
+    assertXPath(pXmlDoc, 
"/root/page[1]/body/txt[1]/SwParaPortion/SwLineLayout", "portion",
+                "First");
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt", 2);
+    // this one is merged; if it were 2 0-height frames that would work too
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt[1]/infos/bounds", "top", 
"18846");
+    assertXPath(pXmlDoc, "/root/page[2]/body/txt[1]/infos/bounds", "height", 
"0");
+    assertXPath(pXmlDoc, "/root/page[2]/body/tab[1]/infos/bounds", "top", 
"18846");
+    assertXPath(pXmlDoc, 
"/root/page[2]/body/txt[2]/SwParaPortion/SwLineLayout", "portion", "End");
+    discardDumpedLayout();
+}
+
 CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testHiddenParaProps)
 {
     createSwDoc("merge_hidden_redline.docx");
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 087e2b85d5e9..a229937269b8 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -1173,8 +1173,19 @@ void SwScriptInfo::InitScriptInfoHidden(const 
SwTextNode& rNode,
         SwTextNode const* pNode(nullptr);
         TextFrameIndex nOffset(0);
         std::optional<std::vector<sw::Extent>::const_iterator> oPrevIter;
-        for (auto iter = pMerged->extents.begin(); iter != 
pMerged->extents.end();
-             oPrevIter = iter)
+        if (pMerged->extents.empty())
+        {
+            Range aRange(0, pMerged->pLastNode->Len() > 0 ? 
pMerged->pLastNode->Len() - 1 : 0);
+            MultiSelection aHiddenMulti(aRange);
+            CalcHiddenRanges(*pMerged->pLastNode, aHiddenMulti, nullptr);
+            if (aHiddenMulti.GetRangeCount() != 0)
+            {
+                m_HiddenChg.push_back(TextFrameIndex(0));
+                m_HiddenChg.push_back(TextFrameIndex(0));
+            }
+        }
+        else for (auto iter = pMerged->extents.begin();
+                    iter != pMerged->extents.end(); oPrevIter = iter)
         {
             if (iter->pNode == pNode)
             {
diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx
index 578d24a4b09a..f777a6314158 100644
--- a/sw/source/core/text/txtfrm.cxx
+++ b/sw/source/core/text/txtfrm.cxx
@@ -1521,20 +1521,25 @@ bool SwTextFrame::IsHiddenNowImpl() const
     {
         TextFrameIndex nHiddenStart(COMPLETE_STRING);
         TextFrameIndex nHiddenEnd(0);
+        bool hasHidden{false};
         if (auto const pScriptInfo = GetScriptInfo())
         {
-            pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0),
+            hasHidden = pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0),
                     nHiddenStart, nHiddenEnd);
         }
         else // ParaPortion is created in Format, but this is called earlier
         {
             SwScriptInfo aInfo;
             aInfo.InitScriptInfoHidden(*m_pMergedPara->pFirstNode, 
m_pMergedPara.get());
-            aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0),
+            hasHidden = aInfo.GetBoundsOfHiddenRange(TextFrameIndex(0),
                         nHiddenStart, nHiddenEnd);
         }
-        if (TextFrameIndex(0) == nHiddenStart &&
-            TextFrameIndex(GetText().getLength()) <= nHiddenEnd)
+        if ((TextFrameIndex(0) == nHiddenStart
+                && TextFrameIndex(GetText().getLength()) <= nHiddenEnd)
+            // special case: GetBoundsOfHiddenRange doesn't assign!
+            // but it does return that there *is* something hidden, in case
+            // the frame is empty then the whole thing must be hidden
+            || (hasHidden && m_pMergedPara->mergedText.isEmpty()))
         {
             bHiddenCharsHidePara = true;
         }

Reply via email to