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{1bidi swisscharset0prq2{\*\panose 020b0604020202020204}Arial{\* alt Arial};} +{dbminor31505bidi nilcharset136prq2{\*\panose 02010601000101010101}PMingLiU{\*alt Arial Unicode MS};} +{himinor31506bidi swisscharset0prq2{\*\panose 020f0502020204030204}Calibri;}{biminor31507bidi swisscharset0 prq2{\*\panose 020b0604020202020204}Arial{\*alt Arial};} +} +{\*\defchp s22\lochf31506\hichf31506\dbchf31505 }{\*\defpap \ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0 } oqfpromote {\stylesheet{ +\ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnum aautodjustright in0\lin0\itap0 tlchcs1 f31507fs22lang1025 \ltrchcs0 s22\lang2057\langfe1028\loch31506\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\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0 tlchcs1 f31507fs22lang1025 \ltrchcs0 s22\lang2057\langfe1028\loch 31506\hichf31506\dbchf31505+Normal Table;}} + +\paperw11906\paperh16838\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrltnbjenddoc 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 oindnmbrtselnbrelev 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\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0 tlchcs1 f31507fs22lang1025 \ltrchcs0 + s22\lang2057\langfe1028\lochf31506\hichf31506\dbchf31505+\hichf31506\dbchf31505\loch 31506 hidden}{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 }{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 24/11}{ tlchcs1 f31507 +\ltrchcs0 +\par }{ tlchcs1 f31507 \ltrchcs0 s48 +\par }{ tlchcs1 f31507 \ltrchcs0 +\par }\pard \ltrpar\ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 2 empty hidden +\hichf31506\dbchf31505\loch31506 1\hichf31506\dbchf31505\loch31506 1\hichf31506\dbchf31505\loch31506 /24 +\par }\pard \ltrpar\ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0 { tlchcs1 f31507 \ltrchcs0 +\par }{ tlchcs1 f31507 \ltrchcs0 s48 +\par }\pard \ltrpar\ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 3 +\hichf31506\dbchf31505\loch31506 \hichf31506\dbchf31505\loch31506 nonempty\hichf31506\dbchf31505\loch31506 24\hichf31506\dbchf31505\loch 31506 hidden \hichf31506\dbchf31505\loch31506 11 +\hichf31506\dbchf31505\loch31506 /\hichf31506\dbchf31505\loch31506 11 +\par }\pard \ltrpar\ql \li0 i0\sa160\sl259\slmult1\widctlpar\wrapdefaultspalphaspnumaautodjustright in0\lin0\itap0 { tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch 31506 foo}{ tlchcs1 +f31507 \ltrchcs0 +\par }{ tlchcs1 f31507 \ltrchcs0 +\par \hichf31506\dbchf31505\loch31506 4 nonempty 24 hidden 24/11 +\par }{ tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch31506 foo +\par }{ tlchcs1 f31507 \ltrchcs0 +\par \hichf31506\dbchf31505\loch31506 5\hichf31506\dbchf31505\loch31506 nonempty\hichf31506\dbchf31505\loch31506 24\hichf31506\dbchf31505\loch 31506 hidden \hichf31506\dbchf31505\loch31506 11/24 +\par }{ tlchcs1 f31507 \ltrchcs0 +\par }{ tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch31506 foo}{ tlchcs1 f31507 \ltrchcs0 s48 +\par }{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 6\hichf31506\dbchf31505\loch31506 \hichf31506\dbchf31505\loch31506 nonempty \hichf31506\dbchf31505\loch31506 24 +\hichf31506\dbchf31505\loch31506 hidden \hichf31506\dbchf31505\loch31506 11/11 +\par }{ tlchcs1 f31507 \ltrchcs0 +\par }{ tlchcs1 f31507 \ltrchcs0 s48\hichf31506\dbchf31505\loch31506 foo}{ tlchcs1 f31507 \ltrchcs0 +\par }{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 7}{ tlchcs1 f31507 \ltrchcs0 \hichf31506\dbchf31505\loch31506 empty delete 24/11}{ tlchcs1 f31507 \ltrchcs0 + +\par }{ tlchcs1 f31507 \ltrchcs0 \deleteds48 evauthdel1 evdttmdel1205079000 +\par }{ tlchcs1 f31507 \ltrchcs0 +\par \hichf31506\dbchf31505\loch31506 8 empty delete 11/24 +\par }{ tlchcs1 f31507 \ltrchcs0 \deleted evauthdel1 evdttmdel1205079000 +\par }{ tlchcs1 f31507 \ltrchcs0 s48 +\par }{ tlchcs1 f31507 \ltrchcs0 +\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; }