sw/qa/core/text/data/clearing-break.fodt | 28 ++++++++++++++++++++++++++++ sw/qa/core/text/text.cxx | 31 +++++++++++++++++++++++++++++++ sw/source/core/inc/txtfly.hxx | 11 +++++++++++ sw/source/core/text/itrform2.cxx | 6 +++++- sw/source/core/text/porlay.cxx | 15 +++++++++++---- sw/source/core/text/porrst.cxx | 27 ++++++++++++++++++++++++++- sw/source/core/text/porrst.hxx | 9 ++++++++- sw/source/core/text/txtfly.cxx | 24 ++++++++++++++++++++++++ 8 files changed, 144 insertions(+), 7 deletions(-)
New commits: commit fd7db6e5be7b36a217246517f1c4990d5879e42d Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Mar 4 10:36:38 2022 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue Mar 29 08:46:23 2022 +0200 sw clearing breaks: initial layout support - add a SwTextFly::GetMaxBottom() to know which fly frame has the largest bottom position from the current paragraph - add a SwBreakPortion::m_eClear to track if the break portion is a clearing break - consider height of breaking clear portions in SwLineLayout::CalcLine() - increase the break portion height in SwBreakPortion::Format() if this is a clearing break and we have a fly frame that has a large enough bottom position, so the next line can "jump down", below the fly frame (so the line can be full width, which is the whole point of a clearing break) (cherry picked from commit 9df80f6e41b0e69fce2afc6647adb78b0dd1df44) Conflicts: sw/source/core/text/xmldump.cxx Change-Id: Ia21984b9cf9d04287cc19f98495c5b44b880020a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132163 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Miklos Vajna <vmik...@collabora.com> diff --git a/sw/qa/core/text/data/clearing-break.fodt b/sw/qa/core/text/data/clearing-break.fodt new file mode 100644 index 000000000000..a2f12850642c --- /dev/null +++ b/sw/qa/core/text/data/clearing-break.fodt @@ -0,0 +1,28 @@ +<?xml version='1.0' encoding='UTF-8'?> +<office:document xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:automatic-styles> + <style:style style:name="fr1" style:family="graphic"> + <style:graphic-properties fo:margin-left="0.318cm" fo:margin-right="0.318cm" fo:margin-top="0cm" fo:margin-bottom="0cm" style:wrap="parallel" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph"/> + </style:style> + </office:automatic-styles> + <office:body> + <office:text> + <text:p><draw:frame draw:style-name="fr1" draw:name="Picture 1" text:anchor-type="char" svg:x="0cm" svg:y="0cm" svg:width="1.806cm" svg:height="1.806cm" draw:z-index="0"><draw:image draw:mime-type="image/png"><office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAABGdBTUEAALGPC/xhBQAAAAFz + UkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAA + AAJiS0dEAACqjSMyAAAACW9GRnMAAAAGAAAAAAAMc1XTAAAACXBIWXMAAA3XAAAN1wFCKJt4 + AAAACXZwQWcAAABMAAAAQACdMTgbAAABzUlEQVRo3u3ZPU/CQBjA8X+Jxs3ESUDj4iK+LA5+ + BBfjqBE1cXB2MlFAEqMgxvhNNL4sLsK3UPQL6ObkoAETz+FKW2mxCPRYnucWUu76/OC59C49 + cGOCKqrD9kHRc6ddPv7oW2WCwMh0nF63Myz7Tm8hPTNu0pgHMER3scepTbgK6enJNND83RLn + /878yRaPmgBZFDuMsNLeWB9gmFQHP77MIg9gsYciR50NFKvtjIy10yk84pSZA7DYpwR8scmF + QQCMuoQMpzbh0iAARrlnVn90CWHTsZcAiHPPdINQAuqsc2MQAAnKDUKWEhZ10twaBEDSJWQo + YlFj7S9CzwEegkXWIbQsRAQASFJhpplwbRAACS+hANRJBxMiAkDcJeQ4sQkBhYgMoJ+Ozlwo + 2YQ7AJ6CRxyiUGnVy3hVKb0Af9v7hUG2Wy9TEQCUelFTDULB2S+YKYGOMcpM6UIccOQnRA6A + cSp6ibfI+wkGADBGpTEd8xz1AaAfTQ7huA8AvUw5hVjuA0D/C5OaMN8XACRZ8F0zCggKAQhA + AAIQgAAEIAABCEAAAhCAAAQgAAH4zg3feY4w3Xs44M5+oW0qvCWoGcvaIlM3x/f/ab+O738A + hOCNQr34oD4AAAAldEVYdGNyZWF0ZS1kYXRlADIwMTAtMTItMjBUMTc6MDg6MzYrMDE6MDB6 + 5RscAAAAJXRFWHRtb2RpZnktZGF0ZQAyMDEwLTEyLTIwVDE3OjA4OjM3KzAxOjAwgyNmnAAA + AABJRU5ErkJggg== + </office:binary-data></draw:image></draw:frame>AB</text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/core/text/text.cxx b/sw/qa/core/text/text.cxx index 4358fe03df75..f9864cbc722f 100644 --- a/sw/qa/core/text/text.cxx +++ b/sw/qa/core/text/text.cxx @@ -27,6 +27,7 @@ #include <porlay.hxx> #include <pormulti.hxx> +#include <formatlinebreak.hxx> constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/core/text/data/"; @@ -232,6 +233,36 @@ CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testEmptyNumberingPageSplit) dispatchCommand(mxComponent, ".uno:InsertGraphic", aArgs); } +CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreak) +{ + // Given a document with a fly frame and two characters wrapped around it: + createSwDoc(DATA_DIRECTORY, "clearing-break.fodt"); + // Insert a clearing break between "A" and "B": + uno::Reference<text::XTextDocument> xDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + xCursor->gotoEnd(/*bSelect=*/false); + xCursor->goLeft(/*nCount=*/1, /*bSelect=*/false); + uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextContent> xLineBreak( + xFactory->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY); + auto eClear = static_cast<sal_Int16>(SwLineBreakClear::ALL); + xLineBreakProps->setPropertyValue("Clear", uno::makeAny(eClear)); + xText->insertTextContent(xCursor, xLineBreak, /*bAbsorb=*/false); + + // When laying out that document: + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + // Then make sure that the second line "jumps down", below the fly frame: + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1024 + // - Actual : 276 + // i.e. the line height wasn't the twips value of the 1.806 cm from the file, but was based on + // the font size of the text, which is only correct for non-clearing breaks. + assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "height", "1024"); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/inc/txtfly.hxx b/sw/source/core/inc/txtfly.hxx index e5346fe3745b..82ee5f085d6a 100644 --- a/sw/source/core/inc/txtfly.hxx +++ b/sw/source/core/inc/txtfly.hxx @@ -126,6 +126,7 @@ class SwTextFly std::unique_ptr<SwAnchoredObjList> mpAnchoredObjList; tools::Long m_nMinBottom; + mutable tools::Long m_nMaxBottom; tools::Long m_nNextTop; /// Stores the upper edge of the "next" frame SwNodeOffset m_nCurrFrameNodeIndex; @@ -201,6 +202,7 @@ class SwTextFly const bool bInFooterOrHeader ); SwTwips CalcMinBottom() const; + SwTwips CalcMaxBottom() const; const SwTextFrame* GetMaster_(); @@ -228,6 +230,10 @@ public: bool Relax(); SwTwips GetMinBottom() const; + + /// Gets the maximum of the fly frame bottoms. + SwTwips GetMaxBottom() const; + const SwTextFrame* GetMaster() const; // This temporary variable needs to be manipulated in const methods @@ -339,6 +345,11 @@ inline SwTwips SwTextFly::GetMinBottom() const return mpAnchoredObjList ? m_nMinBottom : CalcMinBottom(); } +inline SwTwips SwTextFly::GetMaxBottom() const +{ + return mpAnchoredObjList ? m_nMaxBottom : CalcMaxBottom(); +} + inline const SwTextFrame* SwTextFly::GetMaster() const { return m_pMaster ? m_pMaster : const_cast<SwTextFly*>(this)->GetMaster_(); diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 6a91d3c37dfc..fdd99ccf75f5 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1461,7 +1461,11 @@ SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf ) pPor = NewTabPortion( rInf, false ); break; case CH_BREAK: - pPor = new SwBreakPortion( *rInf.GetLast() ); break; + { + SwTextAttr* pHint = GetAttr(rInf.GetIdx()); + pPor = new SwBreakPortion(*rInf.GetLast(), pHint); + break; + } case CHAR_SOFTHYPHEN: // soft hyphen pPor = new SwSoftHyphPortion; break; diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx index bd9d6a0e143c..05619f5591ff 100644 --- a/sw/source/core/text/porlay.cxx +++ b/sw/source/core/text/porlay.cxx @@ -448,10 +448,15 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, SwTextFormatInfo &rInf ) else if( !bHasFlyPortion && ( pPos->IsFlyCntPortion() || pPos->IsFlyPortion() ) ) bHasFlyPortion = true; - // To prevent that a paragraph-end-character does not change - // the line height through a Descent and thus causing the line - // to reformat. - if ( !pPos->IsBreakPortion() || !Height() ) + // A line break portion only influences the height of the line in case it's the only + // portion in the line, except when it's a clearing break. + bool bClearingBreak = false; + if (pPos->IsBreakPortion()) + { + auto pBreakPortion = static_cast<SwBreakPortion*>(pPos); + bClearingBreak = pBreakPortion->GetClear() != SwLineBreakClear::NONE; + } + if (!(pPos->IsBreakPortion() && !bClearingBreak) || !Height()) { if (!pPos->IsPostItsPortion()) bOnlyPostIts = false; @@ -750,6 +755,8 @@ void SwLineLayout::dumpAsXml(xmlTextWriterPtr pWriter) const { (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwLineLayout")); (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this); + (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("height"), + BAD_CAST(OString::number(Height()).getStr())); const SwLinePortion* pFirstPor = GetFirstPortion(); pFirstPor->SwLinePortion::dumpAsXml(pWriter); diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx index c762056c30fe..002b2f5f56e9 100644 --- a/sw/source/core/text/porrst.cxx +++ b/sw/source/core/text/porrst.cxx @@ -39,6 +39,7 @@ #include "redlnitr.hxx" #include "atrhndl.hxx" #include <rootfrm.hxx> +#include <textlinebreak.hxx> #include <IDocumentRedlineAccess.hxx> #include <IDocumentSettingAccess.hxx> @@ -93,12 +94,18 @@ void SwTmpEndPortion::Paint( const SwTextPaintInfo &rInf ) const const_cast<SwTextPaintInfo&>(rInf).SetFont(const_cast<SwFont*>(pOldFnt)); } -SwBreakPortion::SwBreakPortion( const SwLinePortion &rPortion ) +SwBreakPortion::SwBreakPortion( const SwLinePortion &rPortion, const SwTextAttr* pAttr ) : SwLinePortion( rPortion ) { mnLineLength = TextFrameIndex(1); m_eRedline = RedlineType::None; SetWhichPor( PortionType::Break ); + + m_eClear = SwLineBreakClear::NONE; + if (pAttr && pAttr->Which() == RES_TXTATR_LINEBREAK) + { + m_eClear = pAttr->GetLineBreak().GetValue(); + } } TextFrameIndex SwBreakPortion::GetModelPositionForViewPoint(const sal_uInt16) const @@ -156,6 +163,22 @@ bool SwBreakPortion::Format( SwTextFormatInfo &rInf ) const SwLinePortion *pRoot = rInf.GetRoot(); Width( 0 ); Height( pRoot->Height() ); + + // See if this is a clearing break. If so, calculate how much we need to "jump down" so the next + // line can again use the full text width. + if (m_eClear != SwLineBreakClear::NONE) + { + SwTextFly& rTextFly = rInf.GetTextFly(); + if (rTextFly.IsOn()) + { + SwTwips nHeight = rTextFly.GetMaxBottom() - rInf.Y(); + if (nHeight > Height()) + { + Height(nHeight, /*bText=*/false); + } + } + } + SetAscent( pRoot->GetAscent() ); if (rInf.GetIdx() + TextFrameIndex(1) == TextFrameIndex(rInf.GetText().getLength())) rInf.SetNewLine( true ); @@ -167,6 +190,8 @@ void SwBreakPortion::HandlePortion( SwPortionHandler& rPH ) const rPH.Text( GetLen(), GetWhichPor() ); } +SwLineBreakClear SwBreakPortion::GetClear() const { return m_eClear; } + SwKernPortion::SwKernPortion( SwLinePortion &rPortion, short nKrn, bool bBG, bool bGK ) : m_nKern( nKrn ), m_bBackground( bBG ), m_bGridKern( bGK ) diff --git a/sw/source/core/text/porrst.hxx b/sw/source/core/text/porrst.hxx index 9bc1efbf0711..362a16dec0b4 100644 --- a/sw/source/core/text/porrst.hxx +++ b/sw/source/core/text/porrst.hxx @@ -53,12 +53,17 @@ public: virtual void Paint( const SwTextPaintInfo &rInf ) const override; }; +enum class SwLineBreakClear; + class SwBreakPortion : public SwLinePortion { RedlineType m_eRedline; + /// Tracks the type of the breaking clear from SwTextLineBreak, if there is one. + SwLineBreakClear m_eClear; + public: - explicit SwBreakPortion( const SwLinePortion &rPortion ); + explicit SwBreakPortion(const SwLinePortion& rPortion, const SwTextAttr* pAttr); // Returns 0 if we have no usable data virtual SwLinePortion *Compress() override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; @@ -71,6 +76,8 @@ public: static constexpr OUStringLiteral S_NOBREAK_FOR_REDLINE = u"\u00A0"; void SetRedline( const RedlineType eRedline ) { m_eRedline = eRedline; } + + SwLineBreakClear GetClear() const; }; class SwKernPortion : public SwLinePortion diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx index db792e4a1ebf..aa193c50220f 100644 --- a/sw/source/core/text/txtfly.cxx +++ b/sw/source/core/text/txtfly.cxx @@ -309,6 +309,7 @@ SwTextFly::SwTextFly() , m_pCurrFrame(nullptr) , m_pMaster(nullptr) , m_nMinBottom(0) + , m_nMaxBottom(0) , m_nNextTop(0) , m_nCurrFrameNodeIndex(0) , m_bOn(false) @@ -339,6 +340,7 @@ SwTextFly::SwTextFly( const SwTextFly& rTextFly ) m_bOn = rTextFly.m_bOn; m_bTopRule = rTextFly.m_bTopRule; m_nMinBottom = rTextFly.m_nMinBottom; + m_nMaxBottom = rTextFly.m_nMaxBottom; m_nNextTop = rTextFly.m_nNextTop; m_nCurrFrameNodeIndex = rTextFly.m_nCurrFrameNodeIndex; mbIgnoreCurrentFrame = rTextFly.mbIgnoreCurrentFrame; @@ -369,6 +371,7 @@ void SwTextFly::CtorInitTextFly( const SwTextFrame *pFrame ) m_bOn = m_pPage->GetSortedObjs() != nullptr; m_bTopRule = true; m_nMinBottom = 0; + m_nMaxBottom = 0; m_nNextTop = 0; m_nCurrFrameNodeIndex = NODE_OFFSET_MAX; } @@ -955,6 +958,8 @@ SwAnchoredObjList* SwTextFly::InitAnchoredObjList() mpAnchoredObjList.reset( new SwAnchoredObjList ); } + CalcMaxBottom(); + // #i68520# return mpAnchoredObjList.get(); } @@ -993,6 +998,25 @@ SwTwips SwTextFly::CalcMinBottom() const return nRet; } +SwTwips SwTextFly::CalcMaxBottom() const +{ + SwTwips nRet = 0; + size_t nCount(m_bOn ? GetAnchoredObjList()->size() : 0); + SwRectFnSet aRectFnSet(m_pCurrFrame); + for (size_t i = 0; i < nCount; ++i) + { + const SwAnchoredObject* pAnchoredObj = (*mpAnchoredObjList)[i]; + SwRect aRect(pAnchoredObj->GetObjRectWithSpaces()); + SwTwips nBottom = aRectFnSet.GetBottom(aRect); + if (nBottom > nRet) + { + nRet = nBottom; + } + } + m_nMaxBottom = nRet; + return nRet; +} + bool SwTextFly::ForEach( const SwRect &rRect, SwRect* pRect, bool bAvoid ) const { SwSwapIfSwapped swap(const_cast<SwTextFrame *>(m_pCurrFrame));