sw/inc/IDocumentSettingAccess.hxx | 1 sw/source/core/doc/DocumentSettingManager.cxx | 10 ++++ sw/source/core/inc/DocumentSettingManager.hxx | 1 sw/source/core/inc/txtfrm.hxx | 7 +- sw/source/core/layout/frmtool.cxx | 6 ++ sw/source/core/text/inftxt.cxx | 24 ++++++---- sw/source/core/text/inftxt.hxx | 6 ++ sw/source/core/text/itratr.cxx | 40 ++++++++++++++++- sw/source/core/text/itratr.hxx | 2 sw/source/core/text/itrpaint.cxx | 1 sw/source/core/text/itrtxt.cxx | 18 +++++++ sw/source/core/text/redlnitr.cxx | 54 ++++++++++++++++++----- sw/source/core/text/txtfrm.cxx | 21 ++++++-- sw/source/filter/ww8/ww8par.cxx | 1 sw/source/uibase/uno/SwXDocumentSettings.cxx | 18 +++++++ sw/source/writerfilter/dmapper/SettingsTable.cxx | 2 16 files changed, 180 insertions(+), 32 deletions(-)
New commits: commit 0849ddd0b1b3c384c5f3de8fe0bbb9df558fa786 Author: Michael Stahl <[email protected]> AuthorDate: Wed Oct 8 19:29:46 2025 +0200 Commit: Michael Stahl <[email protected]> CommitDate: Thu Oct 9 13:07:17 2025 +0200 sw: text formatting: implement per-line paragraph properties like Word This doesn't make a whole lot of sense. Add compatibility setting "HiddenParagraphMarkPerLineProperties" for RTF and DOCX compatibilityMode < 15. Apparently what Word's "Compatibility Mode" is doing in case a paragraph mark has hidden formatting is that it merges the last line of the first paragraph and the first line of the second paragraph together, and applies the first paragraph's properties to the line (and the preceding lines); but for the second line of the second paragraph, it applies the second paragraph's properties. This is now implemented here; firstly, by adding a flag to the MergedPara's Extents so that the situation can be distinguished (if the paragraphs are joined by a delete redline, Word does something different of course). Because it's possible that the hidden paragraph break is on a paragraph that doesn't have any extents, but a preceding paragraph has extents that are affected, this sometimes requires 0-length dummy extents. FindParaPropsNodeIgnoreHidden() sets the last paragraph as pParaPropsNode, which is used for all non-per-line properties. Note that it's somewhat likely that the various Update functions in txtfrm.cxx don't maintain the isHiddenParaMerge flag on extents correctly, so it may look different than Word when editing. Currently it affects these properties: * line spacing * tab stops These are now set per line by SwTextIter::Next() calling SwAttrIter::GetTextNodeForLinePropsWordCompat() and a factored out SwLineInfo::InitLineInfo(). Evidently Word also does adjustment this way, but it's not implemented here. Change-Id: I216d9e2afdac9ab6f97c0ea822d4d501689df7a6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192077 Tested-by: Jenkins Reviewed-by: Michael Stahl <[email protected]> diff --git a/sw/inc/IDocumentSettingAccess.hxx b/sw/inc/IDocumentSettingAccess.hxx index 79884c24b17b..8ade13a9ba52 100644 --- a/sw/inc/IDocumentSettingAccess.hxx +++ b/sw/inc/IDocumentSettingAccess.hxx @@ -101,6 +101,7 @@ enum class DocumentSettingId JUSTIFY_LINES_WITH_SHRINKING, APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, + HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES, DO_NOT_MIRROR_RTL_DRAW_OBJS, // COMPATIBILITY FLAGS END BROWSE_MODE, diff --git a/sw/source/core/doc/DocumentSettingManager.cxx b/sw/source/core/doc/DocumentSettingManager.cxx index e448e8c0fda2..9a689fa9d005 100644 --- a/sw/source/core/doc/DocumentSettingManager.cxx +++ b/sw/source/core/doc/DocumentSettingManager.cxx @@ -269,6 +269,8 @@ bool sw::DocumentSettingManager::get(/*[in]*/ DocumentSettingId id) const return mbApplyTextAttrToEmptyLineAtEndOfParagraph; case DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH: return mbApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph; + case DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES: + return mbHiddenParagraphMarkPerLineProperties; case DocumentSettingId::DO_NOT_BREAK_WRAPPED_TABLES: return mbDoNotBreakWrappedTables; case DocumentSettingId::ALLOW_TEXT_AFTER_FLOATING_TABLE_BREAK: @@ -488,6 +490,9 @@ void sw::DocumentSettingManager::set(/*[in]*/ DocumentSettingId id, /*[in]*/ boo mbApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph = value; break; + case DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES: + mbHiddenParagraphMarkPerLineProperties = value; + break; case DocumentSettingId::DO_NOT_MIRROR_RTL_DRAW_OBJS: mbDoNotMirrorRtlDrawObjs = value; @@ -1200,6 +1205,11 @@ void sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const BAD_CAST(OString::boolean(mbApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph).getStr())); (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbHiddenParagraphMarkPerLineProperties")); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), + BAD_CAST(OString::boolean(mbHiddenParagraphMarkPerLineProperties).getStr())); + (void)xmlTextWriterEndElement(pWriter); + (void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbDoNotMirrorRtlDrawObjs")); (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::boolean(mbDoNotMirrorRtlDrawObjs).getStr())); diff --git a/sw/source/core/inc/DocumentSettingManager.hxx b/sw/source/core/inc/DocumentSettingManager.hxx index 54d916350bdc..f9be48ea8c3a 100644 --- a/sw/source/core/inc/DocumentSettingManager.hxx +++ b/sw/source/core/inc/DocumentSettingManager.hxx @@ -180,6 +180,7 @@ class DocumentSettingManager final : bool mbJustifyLinesWithShrinking = false; bool mbApplyTextAttrToEmptyLineAtEndOfParagraph = false; // this was a mistake bool mbApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph = false; + bool mbHiddenParagraphMarkPerLineProperties = false; bool mbIgnoreHiddenCharsForLineCalculation = true; bool mbDoNotMirrorRtlDrawObjs = false; // If this is on as_char flys wrapping will be handled the same like in Word diff --git a/sw/source/core/inc/txtfrm.hxx b/sw/source/core/inc/txtfrm.hxx index 8a51b206101b..b68ec8d00c1a 100644 --- a/sw/source/core/inc/txtfrm.hxx +++ b/sw/source/core/inc/txtfrm.hxx @@ -92,11 +92,12 @@ struct Extent SwTextNode * /*const logically, but need assignment for std::vector*/ pNode; sal_Int32 nStart; sal_Int32 nEnd; - Extent(SwTextNode *const p, sal_Int32 const s, sal_Int32 const e) - : pNode(p), nStart(s), nEnd(e) + bool isHiddenParaMerge; //< for Word Compatibility Mode + Extent(SwTextNode *const p, sal_Int32 const s, sal_Int32 const e, bool const b) + : pNode(p), nStart(s), nEnd(e), isHiddenParaMerge(b) { assert(pNode); - assert(nStart != nEnd); + assert(nStart != nEnd || isHiddenParaMerge); } }; diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx index 4183cd482587..227092ae2f55 100644 --- a/sw/source/core/layout/frmtool.cxx +++ b/sw/source/core/layout/frmtool.cxx @@ -1142,7 +1142,11 @@ static bool IsShown(SwNodeOffset const nIndex, } for (auto iter = *pIter; iter != *pEnd; ++iter) { - assert(iter->nStart != iter->nEnd); // TODO possible? + if (iter->nStart == iter->nEnd) + { + assert(iter->isHiddenParaMerge); + continue; + } assert(iter->pNode->GetIndex() == nIndex); if (rAnch.GetAnchorContentOffset() < iter->nStart) { diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index 9e7d36ed5fde..567ab6ab8425 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -114,11 +114,11 @@ SwLineInfo::~SwLineInfo() { } -void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, - const SwTextNode& rTextNode ) +void SwLineInfo::InitLineInfo(SwTextNode const& rTextNodeForLineProps) { - m_oRuler.emplace( rAttrSet.GetTabStops() ); - if ( rTextNode.GetListTabStopPosition( m_nListTabStopPosition ) ) + SwAttrSet const& rAttrSetForLineProps{rTextNodeForLineProps.GetSwAttrSet()}; + m_oRuler.emplace(rAttrSetForLineProps.GetTabStops()); + if (rTextNodeForLineProps.GetListTabStopPosition(m_nListTabStopPosition)) { m_bListTabStopIncluded = true; @@ -139,7 +139,7 @@ void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, } } - if ( !rTextNode.getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ) + if (!rTextNodeForLineProps.getIDocumentSettingAccess()->get(DocumentSettingId::TABS_RELATIVE_TO_INDENT)) { // remove default tab stop at position 0 for ( sal_uInt16 i = 0; i < m_oRuler->Count(); i++ ) @@ -153,7 +153,13 @@ void SwLineInfo::CtorInitLineInfo( const SwAttrSet& rAttrSet, } } - m_pSpace = &rAttrSet.GetLineSpacing(); + m_pSpace = &rAttrSetForLineProps.GetLineSpacing(); +} + +void SwLineInfo::CtorInitLineInfo(const SwAttrSet& rAttrSet, + const SwTextNode& rTextNodeForLineProps) +{ + InitLineInfo(rTextNodeForLineProps); m_nVertAlign = rAttrSet.GetParaVertAlign().GetValue(); m_nDefTabStop = std::numeric_limits<SwTwips>::max(); } @@ -530,6 +536,7 @@ SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf, const OUString* p m_aPos( rInf.GetPos() ), m_aPaintRect( rInf.GetPaintRect() ), m_nSpaceIdx( rInf.GetSpaceIdx() ) + , m_pLineInfo(rInf.m_pLineInfo) { } SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf ) @@ -543,6 +550,7 @@ SwTextPaintInfo::SwTextPaintInfo( const SwTextPaintInfo &rInf ) m_aPos( rInf.GetPos() ), m_aPaintRect( rInf.GetPaintRect() ), m_nSpaceIdx( rInf.GetSpaceIdx() ) + , m_pLineInfo(rInf.m_pLineInfo) { } SwTextPaintInfo::SwTextPaintInfo( SwTextFrame *pFrame, const SwRect &rPaint ) @@ -796,8 +804,8 @@ void SwTextPaintInfo::CalcRect( const SwLinePortion& rPor, SwRect* pRect, SwRect* pIntersect, const bool bInsideBox ) const { - const SwAttrSet& rAttrSet = GetTextFrame()->GetTextNodeForParaProps()->GetSwAttrSet(); - const SvxLineSpacingItem& rSpace = rAttrSet.GetLineSpacing(); + assert(m_pLineInfo); + SvxLineSpacingItem const& rSpace{*m_pLineInfo->GetLineSpacing()}; tools::Long nPropLineSpace = rSpace.GetPropLineSpace(); SwTwips nHeight = rPor.Height(); diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx index afac3f32ef8d..fb6b2435df52 100644 --- a/sw/source/core/text/inftxt.hxx +++ b/sw/source/core/text/inftxt.hxx @@ -66,8 +66,9 @@ class SwLineInfo bool m_bListTabStopIncluded; tools::Long m_nListTabStopPosition; + void InitLineInfo(SwTextNode const& rTextNodeForLineProps); void CtorInitLineInfo( const SwAttrSet& rAttrSet, - const SwTextNode& rTextNode ); + const SwTextNode& rTextNodeForLineProps); SW_DLLPUBLIC SwLineInfo(); SW_DLLPUBLIC ~SwLineInfo(); @@ -365,6 +366,8 @@ class SwTextPaintInfo : public SwTextSizeInfo SwRect m_aPaintRect; // Original paint rect (from Layout paint) sal_uInt16 m_nSpaceIdx; + SwLineInfo const* m_pLineInfo{nullptr}; // hack: need this to get line props + void DrawText_(const OUString &rText, const SwLinePortion &rPor, const TextFrameIndex nIdx, const TextFrameIndex nLen, const bool bKern, const bool bWrong = false, @@ -481,6 +484,7 @@ public: void SetSmartTags(sw::WrongListIterator *const pNew) { m_pSmartTags = pNew; } sw::WrongListIterator* GetSmartTags() const { return m_pSmartTags; } + void SetLineInfo(SwLineInfo const*const pLineInfo) { m_pLineInfo = pLineInfo; } }; class SwTextFormatInfo : public SwTextPaintInfo diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index 4fb47e279bba..a25152138f9e 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -340,13 +340,14 @@ SwAttrIter::SeekNewPos(TextFrameIndex const nNewPos, bool *const o_pIsToEnd) bool isToEnd{false}; if (m_pMergedPara) { - if (m_pMergedPara->extents.empty()) + if (m_pMergedPara->mergedText.isEmpty()) { isToEnd = true; assert(m_pMergedPara->pLastNode == newPos.first); } else { + assert(!m_pMergedPara->extents.empty()); 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 @@ -955,6 +956,43 @@ TextFrameIndex SwAttrIter::GetNextLayoutBreakAttr() const return TextFrameIndex{ nNext }; } +SwTextNode const& +SwAttrIter::GetTextNodeForLinePropsWordCompat(TextFrameIndex const nStart) +{ + if (m_pMergedPara) + { + // skip any hidden to find the first non-hidden character on the line + TextFrameIndex nHiddenStart{COMPLETE_STRING}; + TextFrameIndex nHiddenEnd{0}; + m_pScriptInfo->GetBoundsOfHiddenRange(nStart, nHiddenStart, nHiddenEnd); + sal_Int32 nIndex(::std::max(nStart, nHiddenEnd)); + // now, find the hidden paragraph break that follows the first + // non-hidden character on the line + for (auto it{m_pMergedPara->extents.begin()}; it != m_pMergedPara->extents.end(); ++it) + { + if (nIndex < (it->nEnd - it->nStart)) + { + nIndex = 0; + } + if (nIndex == 0) + { + if (it->isHiddenParaMerge) + { + return *it->pNode; + } + } + else + { + nIndex = nIndex - (it->nEnd - it->nStart); + } + } + // no hidden paragraph break => use default + assert(nIndex == 0 && "view index out of bounds"); + return *m_pMergedPara->pParaPropsNode; + } + return *m_pTextNode; +} + namespace { class SwMinMaxArgs diff --git a/sw/source/core/text/itratr.hxx b/sw/source/core/text/itratr.hxx index d721ca46c826..9bf688e496e0 100644 --- a/sw/source/core/text/itratr.hxx +++ b/sw/source/core/text/itratr.hxx @@ -113,6 +113,8 @@ public: void SetPropFont( const sal_uInt8 nNew ) { m_nPropFont = nNew; } SwAttrHandler& GetAttrHandler() { return m_aAttrHandler; } + + SwTextNode const& GetTextNodeForLinePropsWordCompat(TextFrameIndex nStart); }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx index d9d5ec7f3ac2..293bca3a9747 100644 --- a/sw/source/core/text/itrpaint.cxx +++ b/sw/source/core/text/itrpaint.cxx @@ -70,6 +70,7 @@ void SwTextPainter::CtorInitTextPainter( SwTextFrame *pNewFrame, SwTextPaintInfo SwFont *pMyFnt = GetFnt(); GetInfo().SetFont( pMyFnt ); m_bPaintDrop = false; + GetInfo().SetLineInfo(&GetLineInfo()); } SwLinePortion *SwTextPainter::CalcPaintOfst(const SwRect &rPaint, bool& rbSkippedNumPortions) diff --git a/sw/source/core/text/itrtxt.cxx b/sw/source/core/text/itrtxt.cxx index 1d1eed3e0837..84df842f3af5 100644 --- a/sw/source/core/text/itrtxt.cxx +++ b/sw/source/core/text/itrtxt.cxx @@ -24,6 +24,8 @@ #include <editeng/paravertalignitem.hxx> #include "pormulti.hxx" +#include <IDocumentSettingAccess.hxx> +#include <rootfrm.hxx> #include <pagefrm.hxx> #include <tgrditem.hxx> #include "porfld.hxx" @@ -42,10 +44,17 @@ void SwTextIter::CtorInitTextIter( SwTextFrame *pNewFrame, SwTextInfo *pNewInf ) m_pFrame = pNewFrame; m_pInf = pNewInf; - m_aLineInf.CtorInitLineInfo( pNode->GetSwAttrSet(), *pNode ); + m_nFrameStart = m_pFrame->getFrameArea().Pos().Y() + m_pFrame->getFramePrintArea().Pos().Y(); SwTextIter::Init(); + SwTextNode const& rTextNodeForLineProps{ + (pNode->getIDocumentSettingAccess()->get(DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES) + && m_pFrame->getRootFrame()->GetParagraphBreakMode() == ::sw::ParagraphBreakMode::Hidden) + ? GetTextNodeForLinePropsWordCompat(m_nStart) + : *pNode}; + m_aLineInf.CtorInitLineInfo(pNode->GetSwAttrSet(), rTextNodeForLineProps); + // Order is important: only execute FillRegister if GetValue!=0 m_bRegisterOn = pNode->GetSwAttrSet().GetRegister().GetValue() && m_pFrame->FillRegister( m_nRegStart, m_nRegDiff ); @@ -116,6 +125,13 @@ const SwLineLayout *SwTextIter::Next() if( m_pCurr->GetLen() || ( m_nLineNr>1 && !m_pCurr->IsDummy() ) ) ++m_nLineNr; m_pCurr = m_pCurr->GetNext(); + if (m_pFrame->GetDoc().getIDocumentSettingAccess().get( + DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES) + && m_pFrame->getRootFrame()->GetParagraphBreakMode() == ::sw::ParagraphBreakMode::Hidden) + { + SwTextNode const& rNode{GetTextNodeForLinePropsWordCompat(m_nStart)}; + m_aLineInf.InitLineInfo(rNode); + } return m_pCurr; } else diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx index 87564e2a3fbb..99710e5b20d7 100644 --- a/sw/source/core/text/redlnitr.cxx +++ b/sw/source/core/text/redlnitr.cxx @@ -36,6 +36,7 @@ #include <IDocumentRedlineAccess.hxx> #include <IDocumentLayoutAccess.hxx> #include <IDocumentMarkAccess.hxx> +#include <IDocumentSettingAccess.hxx> #include <IMark.hxx> #include <bookmark.hxx> #include <rootfrm.hxx> @@ -83,6 +84,10 @@ private: public: SwPosition const* GetStartPos() const { return m_pStartPos; } SwPosition const* GetEndPos() const { return m_pEndPos; } + bool IsHiddenParagraphBreak() const + { // only if it is merged *by* hidden break (it could be deleted at the same time) + return m_oParagraphBreak.has_value(); + } HideIterator(const SwTextNode & rTextNode, bool const isHideRedlines, sw::FieldmarkMode const eMode, @@ -106,6 +111,7 @@ public: // Note: caller is responsible for checking for immediately adjacent hides bool Next() { + m_oParagraphBreak.reset(); SwPosition const* pNextRedlineHide(nullptr); assert(m_pEndPos); if (m_isHideRedlines) @@ -293,23 +299,36 @@ void FindParaPropsNodeIgnoreHidden(sw::MergedPara & rMerged, pScriptInfo->GetBoundsOfHiddenRange(TextFrameIndex{0}, nHiddenStart, nHiddenEnd); if (TextFrameIndex{0} == nHiddenStart) { - if (nHiddenEnd == TextFrameIndex{rMerged.mergedText.getLength()}) + // Word compatibilityMode < 15 changes properties per line, so just set the last node here + if (rMerged.pLastNode->getIDocumentSettingAccess()->get( + DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES)) { 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; + else // Word compatibilityMode 15 works differently! + { // (and this is just an approximation of what it does) + 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()) + for (auto const& it : rMerged.extents) { // para props from first node that isn't empty (OOo/LO compat) - rMerged.pParaPropsNode = rMerged.extents.begin()->pNode; + if (it.nStart != it.nEnd) // filter isHiddenParaMerge dummy extents + { + rMerged.pParaPropsNode = it.pNode; + return; + } + else assert(it.isHiddenParaMerge); } - 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); @@ -344,11 +363,22 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, assert(pNode != &rTextNode || &pStart->GetNode() == &rTextNode); // detect calls with wrong start node if (pStart->GetContentIndex() != nLastEnd) // not 0 so we eliminate adjacent deletes { - extents.emplace_back(pNode, nLastEnd, pStart->GetContentIndex()); + extents.emplace_back(pNode, nLastEnd, pStart->GetContentIndex(), false); mergedText.append(pNode->GetText().subView(nLastEnd, pStart->GetContentIndex() - nLastEnd)); } if (&pEnd->GetNode() != pNode) { + if (iter.IsHiddenParagraphBreak()) + { + if (!extents.empty() && extents.back().pNode == pNode) + { + extents.back().isHiddenParaMerge = true; + } + else + { // dummy extent - must have "true" on one that has pNode! + extents.emplace_back(pNode, pNode->Len(), pNode->Len(), true); + } + } if (pNode == &rTextNode) { pNode->SetRedlineMergeFlag(SwNode::Merge::First); @@ -465,7 +495,7 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, } if (nLastEnd != pNode->Len()) { - extents.emplace_back(pNode, nLastEnd, pNode->Len()); + extents.emplace_back(pNode, nLastEnd, pNode->Len(), false); mergedText.append(pNode->GetText().subView(nLastEnd, pNode->Len() - nLastEnd)); } if (extents.empty()) // there was no text anywhere @@ -474,7 +504,9 @@ CheckParaRedlineMerge(SwTextFrame & rFrame, SwTextNode & rTextNode, } else { - assert(!mergedText.isEmpty()); + assert(!mergedText.isEmpty() + || ::std::all_of(extents.begin(), extents.end(), + [](auto const& it){ return it.isHiddenParaMerge; })); } auto pRet{std::make_unique<sw::MergedPara>(rFrame, std::move(extents), mergedText.makeStringAndClear(), &rTextNode, nodes.back())}; diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx index b097a5ee5ea0..30b49c04cdb2 100644 --- a/sw/source/core/text/txtfrm.cxx +++ b/sw/source/core/text/txtfrm.cxx @@ -839,6 +839,7 @@ void SwTextFrame::dumpAsXml(xmlTextWriterPtr writer) const (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "txtNodeIndex" ), "%" SAL_PRIdINT32, sal_Int32(e.pNode->GetIndex()) ); (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "start" ), "%" SAL_PRIdINT32, e.nStart ); (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "end" ), "%" SAL_PRIdINT32, e.nEnd ); + (void)xmlTextWriterWriteFormatAttribute( writer, BAD_CAST( "isHPM" ), "%d", e.isHiddenParaMerge ? 1 : 0 ); (void)xmlTextWriterEndElement( writer ); } (void)xmlTextWriterEndElement( writer ); @@ -1126,7 +1127,7 @@ static TextFrameIndex UpdateMergedParaForInsert(MergedPara & rMerged, // assert((bFoundNode || rMerged.extents.empty()) && "text node not found - why is it sending hints to us"); if (!bInserted) { // must be in a gap - rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen); + rMerged.extents.emplace(itInsert, const_cast<SwTextNode*>(&rNode), nIndex, nIndex + nLen, false); text.insert(nTFIndex, rNode.GetText().subView(nIndex, nLen)); nInserted = nLen; // called from SwRangeRedline::InvalidateRange() @@ -1235,7 +1236,7 @@ TextFrameIndex UpdateMergedParaForDelete(MergedPara & rMerged, sal_Int32 const nOldEnd(it->nEnd); it->nEnd = nIndex; it = rMerged.extents.emplace(it+1, - it->pNode, nIndex + nDeleteHere, nOldEnd); + it->pNode, nIndex + nDeleteHere, nOldEnd, it->isHiddenParaMerge); } assert(nDeleteHere == nToDelete); } @@ -1301,7 +1302,7 @@ MapViewToModel(MergedPara const& rMerged, TextFrameIndex const i_nIndex) nIndex = nIndex - (pExtent->nEnd - pExtent->nStart); } assert(nIndex == 0 && "view index out of bounds"); - return pExtent + return (pExtent && pExtent->nStart != pExtent->nEnd) // skip isHiddenParaMerge dummys ? std::make_pair(pExtent->pNode, pExtent->nEnd) //1-past-the-end index : std::make_pair(const_cast<SwTextNode*>(rMerged.pLastNode), rMerged.pLastNode->Len()); } @@ -1443,9 +1444,17 @@ SwTextNode const* SwTextFrame::GetTextNodeForFirstText() const { sw::MergedPara const*const pMerged(GetMergedPara()); if (pMerged) - return pMerged->extents.empty() - ? pMerged->pFirstNode - : pMerged->extents.front().pNode; + { + for (auto const& it : pMerged->extents) + { + if (it.nStart != it.nEnd) // skip isHiddenParaMerge dummy extents + { + return it.pNode; + } + else assert(it.isHiddenParaMerge); + } + return pMerged->pFirstNode; + } else return static_cast<SwTextNode const*>(SwFrame::GetDep()); } diff --git a/sw/source/filter/ww8/ww8par.cxx b/sw/source/filter/ww8/ww8par.cxx index 83a90939f3ca..3492f8662343 100644 --- a/sw/source/filter/ww8/ww8par.cxx +++ b/sw/source/filter/ww8/ww8par.cxx @@ -1971,6 +1971,7 @@ void SwWW8ImplReader::ImportDop() m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::CONTINUOUS_ENDNOTES, true); // rely on default for HYPHENATE_URLS=false m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, true); + m_rDoc.getIDocumentSettingAccess().set(DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES, true); // rely on default for IGNORE_HIDDEN_CHARS_FOR_LINE_CALCULATION=true IDocumentSettingAccess& rIDSA = m_rDoc.getIDocumentSettingAccess(); diff --git a/sw/source/uibase/uno/SwXDocumentSettings.cxx b/sw/source/uibase/uno/SwXDocumentSettings.cxx index 9bb0e6940b2e..192c142a81b9 100644 --- a/sw/source/uibase/uno/SwXDocumentSettings.cxx +++ b/sw/source/uibase/uno/SwXDocumentSettings.cxx @@ -162,6 +162,7 @@ enum SwDocumentSettingsPropertyHandles HANDLE_USE_VARIABLE_WIDTH_NBSP, HANDLE_APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, HANDLE_APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, + HANDLE_HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES, HANDLE_DO_NOT_MIRROR_RTL_DRAW_OBJS, HANDLE_PAINT_HELL_OVER_HEADER_FOOTER, HANDLE_MIN_ROW_HEIGHT_INCL_BORDER, @@ -279,6 +280,7 @@ static rtl::Reference<MasterPropertySetInfo> lcl_createSettingsInfo() { u"UseVariableWidthNBSP"_ustr, HANDLE_USE_VARIABLE_WIDTH_NBSP, cppu::UnoType<bool>::get(), 0 }, { u"ApplyTextAttrToEmptyLineAtEndOfParagraph"_ustr, HANDLE_APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, cppu::UnoType<bool>::get(), 0 }, { u"ApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph"_ustr, HANDLE_APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH, cppu::UnoType<bool>::get(), 0 }, + { u"HiddenParagraphMarkPerLineProperties"_ustr, HANDLE_HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES, cppu::UnoType<bool>::get(), 0 }, { u"DoNotMirrorRtlDrawObjs"_ustr, HANDLE_DO_NOT_MIRROR_RTL_DRAW_OBJS, cppu::UnoType<bool>::get(), 0 }, { u"PaintHellOverHeaderFooter"_ustr, HANDLE_PAINT_HELL_OVER_HEADER_FOOTER, cppu::UnoType<bool>::get(), 0 }, { u"MinRowHeightInclBorder"_ustr, HANDLE_MIN_ROW_HEIGHT_INCL_BORDER, cppu::UnoType<bool>::get(), 0 }, @@ -1124,6 +1126,16 @@ void SwXDocumentSettings::_setSingleValue( const comphelper::PropertyInfo & rInf } } break; + case HANDLE_HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES: + { + bool bTmp; + if (rValue >>= bTmp) + { + mpDoc->getIDocumentSettingAccess().set( + DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES, bTmp); + } + } + break; case HANDLE_DO_NOT_MIRROR_RTL_DRAW_OBJS: { bool bTmp; @@ -1794,6 +1806,12 @@ void SwXDocumentSettings::_getSingleValue( const comphelper::PropertyInfo & rInf DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH); } break; + case HANDLE_HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES: + { + rValue <<= mpDoc->getIDocumentSettingAccess().get( + DocumentSettingId::HIDDEN_PARAGRAPH_MARK_PER_LINE_PROPERTIES); + } + break; case HANDLE_DO_NOT_MIRROR_RTL_DRAW_OBJS: { rValue <<= mpDoc->getIDocumentSettingAccess().get( diff --git a/sw/source/writerfilter/dmapper/SettingsTable.cxx b/sw/source/writerfilter/dmapper/SettingsTable.cxx index e4b10814efa0..64f8eba21b13 100644 --- a/sw/source/writerfilter/dmapper/SettingsTable.cxx +++ b/sw/source/writerfilter/dmapper/SettingsTable.cxx @@ -636,6 +636,8 @@ void SettingsTable::ApplyProperties(rtl::Reference<SwXTextDocument> const& xDoc) xDocumentSettings->setPropertyValue(u"MsWordCompMinLineHeightByFly"_ustr, uno::Any(true)); xDocumentSettings->setPropertyValue(u"TabOverMargin"_ustr, uno::Any(true)); xDocumentSettings->setPropertyValue(u"AddFrameOffsets"_ustr, uno::Any(true)); // tdf#138782 + xDocumentSettings->setPropertyValue(u"HiddenParagraphMarkPerLineProperties"_ustr, + uno::Any(true)); } // Show changes value
