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           |   13 +++++++++----
 sw/source/core/text/porrst.cxx           |   27 ++++++++++++++++++++++++++-
 sw/source/core/text/porrst.hxx           |    9 ++++++++-
 sw/source/core/text/txtfly.cxx           |   24 ++++++++++++++++++++++++
 sw/source/core/text/xmldump.cxx          |    3 +++
 9 files changed, 145 insertions(+), 7 deletions(-)

New commits:
commit 9df80f6e41b0e69fce2afc6647adb78b0dd1df44
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Fri Mar 4 10:36:38 2022 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Fri Mar 4 14:45:56 2022 +0100

    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)
    
    Change-Id: Ia21984b9cf9d04287cc19f98495c5b44b880020a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/130961
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>
    Tested-by: Jenkins

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 5741fe656a81..39e33e01a580 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/";
 
@@ -259,6 +260,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 354c976e6e0b..ee8bec655455 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 1be31ad972bd..dcab6f1db4e9 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;
 
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));
diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx
index b7c05e716a5e..6bd2219e82e1 100644
--- a/sw/source/core/text/xmldump.cxx
+++ b/sw/source/core/text/xmldump.cxx
@@ -492,6 +492,9 @@ void SwFrame::dumpAsXml( xmlTextWriterPtr writer ) const
                 {
                     (void)xmlTextWriterStartElement(writer, 
BAD_CAST("SwLineLayout"));
                     (void)xmlTextWriterWriteFormatAttribute(writer, 
BAD_CAST("ptr"), "%p", pLine);
+                    (void)xmlTextWriterWriteAttribute(
+                        writer, BAD_CAST("height"),
+                        BAD_CAST(OString::number(pLine->Height()).getStr()));
                     const SwLinePortion* pPor = pLine->GetFirstPortion();
                     while (pPor)
                     {

Reply via email to