sw/qa/extras/layout/data/merge_hidden_redline_lineheight.rtf |   56 ++++++++
 sw/qa/extras/layout/layout2.cxx                              |   68 +++++++++
 sw/source/core/text/itratr.cxx                               |   77 +++++++----
 sw/source/core/text/itratr.hxx                               |    1 
 4 files changed, 178 insertions(+), 24 deletions(-)

New commits:
commit 2a1e7e9ce92720c7ae853ef04d8a860a1fecacdd
Author:     Michael Stahl <michael.st...@allotropia.de>
AuthorDate: Wed Apr 2 13:58:07 2025 +0200
Commit:     Adolfo Jayme Barrientos <fit...@ubuntu.com>
CommitDate: Thu Apr 3 05:18:44 2025 +0200

    sw: use same line height as Word for paragraphs empty due to hidden text
    
    If paragraphs are merged by hidden paragraph end markers, and all of the
    text in the paragraphs is hidden by character formatting, then Word will
    use the formatting of the end marker of the last paragraph to calculate
    the line height; Writer would use the formatting at the position where
    the last portion of hidden text ends, which may be in an earlier
    paragraph.
    
    Change-Id: I1e8c5676d7330c383521755c736828fdc42e4217
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183622
    Reviewed-by: Michael Stahl <michael.st...@allotropia.de>
    Tested-by: Jenkins
    (cherry picked from commit e8cbd4f14a3086e0f148f0b39f32f7101c25b012)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/183629
    Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com>

diff --git a/sw/qa/extras/layout/data/merge_hidden_redline_lineheight.rtf 
b/sw/qa/extras/layout/data/merge_hidden_redline_lineheight.rtf
new file mode 100644
index 000000000000..ea6698fa15d2
--- /dev/null
+++ b/sw/qa/extras/layout/data/merge_hidden_redline_lineheight.rtf
@@ -0,0 +1,56 @@
+{ 
tf1deflang1025nsinsicpg1252\uc1deff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang2057\deflangfe1028
       hemelang2057    hemelangfe1028  hemelangcs1025
+{onttbl{1bidi swisscharset0prq2{\*\panose 020b0604020202020204}Arial{\*
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{\*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;}}
+
+\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
+\jexpandiewkind1iewscale75\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+\pnucltr\pnqc\pnstart1\pnindent720\pnhang {\pntxta 
.}}{\*\pnseclvl3\pndec\pnqc\pnstart1\pnindent720\pnhang {\pntxta 
.}}{\*\pnseclvl4\pnlcltr\pnqc\pnstart1\pnindent720\pnhang {\pntxta 
)}}{\*\pnseclvl5\pndec\pnqc\pnstart1\pnindent720\pnhang {\pntxtb (}
+{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnqc\pnstart1\pnindent720\pnhang {\pntxtb 
(}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnqc\pnstart1\pnindent720\pnhang {\pntxtb 
(}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnqc\pnstart1\pnindent720\pnhang {\pntxtb 
(}{\pntxta )}}
+{\*\pnseclvl9\pnlcrm\pnqc\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta 
)}}\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+\hichf31506\dbchf31505\loch
31506 hidden}{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 
 }{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 24/11}{ 
tlchcs1 f31507 
+\ltrchcs0 
+\par }{ tlchcs1 f31507 \ltrchcs0 s48
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par }\pard \ltrpar\ql \li0 
i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 
2 empty hidden 
+\hichf31506\dbchf31505\loch31506 1\hichf31506\dbchf31505\loch31506 
1\hichf31506\dbchf31505\loch31506 /24
+\par }\pard \ltrpar\ql \li0 
i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0 { tlchcs1 f31507 \ltrchcs0 
+\par }{ tlchcs1 f31507 \ltrchcs0 s48
+\par }\pard \ltrpar\ql \li0 
i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 
3
+\hichf31506\dbchf31505\loch31506  \hichf31506\dbchf31505\loch31506 
nonempty\hichf31506\dbchf31505\loch31506  24\hichf31506\dbchf31505\loch
31506  hidden \hichf31506\dbchf31505\loch31506 11
+\hichf31506\dbchf31505\loch31506 /\hichf31506\dbchf31505\loch31506 11
+\par }\pard \ltrpar\ql \li0 
i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright 
in0\lin0\itap0 { tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch
31506 foo}{ tlchcs1 
+f31507 \ltrchcs0 
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par \hichf31506\dbchf31505\loch31506 4 nonempty 24 hidden 24/11
+\par }{ tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch31506 
foo
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par \hichf31506\dbchf31505\loch31506 5\hichf31506\dbchf31505\loch31506  
nonempty\hichf31506\dbchf31505\loch31506  24\hichf31506\dbchf31505\loch
31506  hidden \hichf31506\dbchf31505\loch31506 11/24
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par }{ tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch31506 
foo}{ tlchcs1 f31507 \ltrchcs0 s48
+\par }{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 
6\hichf31506\dbchf31505\loch31506  \hichf31506\dbchf31505\loch31506 
nonempty \hichf31506\dbchf31505\loch31506 24 
+\hichf31506\dbchf31505\loch31506 hidden \hichf31506\dbchf31505\loch31506 
11/11
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par }{ tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch31506 
foo}{ tlchcs1 f31507 \ltrchcs0 
+\par }{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 7}{ 
tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506  empty delete 
24/11}{ tlchcs1 f31507 \ltrchcs0 
+
+\par }{ tlchcs1 f31507 \ltrchcs0 \deleteds48 evauthdel1 evdttmdel1205079000
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par \hichf31506\dbchf31505\loch31506 8 empty delete 11/24
+\par }{ tlchcs1 f31507 \ltrchcs0 \deleted evauthdel1 evdttmdel1205079000
+\par }{ tlchcs1 f31507 \ltrchcs0 s48
+\par }{ tlchcs1 f31507 \ltrchcs0 
+\par }
+}
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index 9b0401f42aa9..d7fcbb7b5625 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -802,6 +802,74 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testHiddenParaBreaks)
     assertXPath(pXmlDoc, 
"/root/page[2]/body/txt[2]/SwParaPortion/SwLineLayout", "portion", u"End");
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testHiddenParaLineHeight)
+{
+    createSwDoc("merge_hidden_redline_lineheight.rtf");
+
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    SwViewOption aViewOptions(*pWrtShell->GetViewOptions());
+    aViewOptions.SetShowHiddenChar(true);
+    aViewOptions.SetViewMetaChars(true);
+    pWrtShell->ApplyViewOptions(aViewOptions);
+
+    {
+        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/infos/bounds", 
"height", u"269");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[7]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[11]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[12]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[16]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[17]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[18]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[19]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[20]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[21]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[22]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[23]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[24]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[25]/infos/bounds", 
"height", u"475");
+    }
+
+    aViewOptions.SetShowHiddenChar(false);
+    pWrtShell->ApplyViewOptions(aViewOptions);
+    dispatchCommand(mxComponent, u".uno:ShowTrackedChanges"_ustr, {});
+
+    {
+        xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/infos/bounds", 
"height", u"269");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[3]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[4]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[6]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[7]/infos/bounds", 
"height", u"475");
+        // 4: this was using wrong node's character properties (height 767)
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[8]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[9]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[10]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[11]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[12]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[13]/infos/bounds", 
"height", u"475");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[14]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[15]/infos/bounds", 
"height", u"450");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[16]/infos/bounds", 
"height", u"767");
+        assertXPath(pXmlDoc, "/root/page[1]/body/txt[17]/infos/bounds", 
"height", u"475");
+    }
+}
+
 CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testHiddenParaProps)
 {
     createSwDoc("merge_hidden_redline.docx");
diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
index bccc7377072d..aa3fd8353b0d 100644
--- a/sw/source/core/text/itratr.cxx
+++ b/sw/source/core/text/itratr.cxx
@@ -157,9 +157,7 @@ SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const 
nPosition) const
 
 bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, 
OutputDevice* pOut)
 {
-    std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
-        ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
-        : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
+    std::pair<SwTextNode const*, sal_Int32> const pos{SeekNewPos(nNewPos, 
nullptr)};
     bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == 
m_nPosition
         ? m_pFont->IsFntChg()
         : Seek( nNewPos );
@@ -332,12 +330,61 @@ void SwAttrIter::SeekToEnd()
     }
 }
 
+std::pair<SwTextNode const*, sal_Int32>
+SwAttrIter::SeekNewPos(TextFrameIndex const nNewPos, bool *const o_pIsToEnd)
+{
+    std::pair<SwTextNode const*, sal_Int32> newPos{ m_pMergedPara
+        ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
+        : std::make_pair(m_pTextNode, sal_Int32(nNewPos))};
+
+    bool isToEnd{false};
+    if (m_pMergedPara)
+    {
+        if (m_pMergedPara->extents.empty())
+        {
+            isToEnd = true;
+            assert(m_pMergedPara->pLastNode == newPos.first);
+        }
+        else
+        {
+            auto const& rLast{m_pMergedPara->extents.back()};
+            isToEnd = rLast.pNode == newPos.first && rLast.nEnd == 
newPos.second;
+            // for text formatting: use *last* node if all text is hidden
+            if (isToEnd
+                && m_pMergedPara->pLastNode != newPos.first // implies there 
is hidden text
+                && m_pViewShell->GetLayout()->GetParagraphBreakMode() == 
sw::ParagraphBreakMode::Hidden
+                && m_pTextNode->GetDoc().getIDocumentSettingAccess().get(
+                    
DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH))
+            {
+                TextFrameIndex nHiddenStart(COMPLETE_STRING);
+                TextFrameIndex nHiddenEnd(0);
+                m_pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex(0), 
nHiddenStart, nHiddenEnd);
+                if (TextFrameIndex(0) == nHiddenStart
+                    && TextFrameIndex(m_pMergedPara->mergedText.getLength()) 
<= nHiddenEnd)
+                {
+                    newPos.first = m_pMergedPara->pLastNode;
+                    newPos.second = m_pMergedPara->pLastNode->Len();
+                }
+            }
+        }
+    }
+    else
+    {
+        isToEnd = newPos.second == m_pTextNode->Len();
+    }
+    if (o_pIsToEnd)
+    {
+        *o_pIsToEnd = isToEnd;
+    }
+
+    return newPos;
+}
+
 bool SwAttrIter::Seek(TextFrameIndex const nNewPos)
 {
     // note: nNewPos isn't necessarily an index returned from GetNextAttr
-    std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara
-        ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
-        : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
+    bool isToEnd{false};
+    std::pair<SwTextNode const*, sal_Int32> const newPos{SeekNewPos(nNewPos, 
&isToEnd)};
 
     if ( m_pRedline && m_pRedline->ExtOn() )
         m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), 
newPos.second);
@@ -426,24 +473,6 @@ bool SwAttrIter::Seek(TextFrameIndex const nNewPos)
         }
     }
 
-    bool isToEnd{false};
-    if (m_pMergedPara)
-    {
-        if (!m_pMergedPara->extents.empty())
-        {
-            auto const& rLast{m_pMergedPara->extents.back()};
-            isToEnd = rLast.pNode == newPos.first && rLast.nEnd == 
newPos.second;
-        }
-        else
-        {
-            isToEnd = true;
-        }
-    }
-    else
-    {
-        isToEnd = newPos.second == m_pTextNode->Len();
-    }
-
     if (m_pTextNode->GetpSwpHints())
     {
         if (m_pMergedPara)
diff --git a/sw/source/core/text/itratr.hxx b/sw/source/core/text/itratr.hxx
index d61d112404eb..2e2b01d68492 100644
--- a/sw/source/core/text/itratr.hxx
+++ b/sw/source/core/text/itratr.hxx
@@ -61,6 +61,7 @@ private:
     const SwTextNode* m_pTextNode;
     sw::MergedPara const* m_pMergedPara;
 
+    std::pair<SwTextNode const*, sal_Int32> SeekNewPos(TextFrameIndex nNewPos, 
bool * o_pIsToEnd);
     void SeekFwd(sal_Int32 nOldPos, sal_Int32 nNewPos);
     void SeekToEnd();
     void SetFnt( SwFont* pNew ) { m_pFont = pNew; }

Reply via email to