sw/qa/extras/layout/data/cell-vertical-align-middle-in-fly-at-page.fodt |   35 
++++++++++
 sw/qa/extras/layout/layout5.cxx                                         |   18 
+++++
 sw/source/core/inc/pagefrm.hxx                                          |    8 
++
 sw/source/core/layout/layact.cxx                                        |   10 
++
 4 files changed, 69 insertions(+), 2 deletions(-)

New commits:
commit 15c89cf1b72a7556af238d9b0a7d34017c6ec3ee
Author:     Mike Kaganski <[email protected]>
AuthorDate: Sun Nov 2 13:28:36 2025 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Sun Nov 2 11:32:11 2025 +0100

    tdf#169158: Call FormatObjsAtFrame again, when at-page fly needs that
    
    Formatting a cell with non-default vertical alignment is done in two
    steps. In the end of the first step, SwContentNotify::ImplDestroy
    detects the difference of size/position of the table content, and the
    non-default vertical alignment, and invalidates the cell and its page.
    Then the next formatting would correct the size/position mismatch; it
    obviously relies on the second pass of formatting.
    
    In SwLayAction::InternalAction, the formatting loop did a single call
    to SwObjectFormatter::FormatObjsAtFrame, which formatted at-page fly
    objects, and then an inner loop which formatted the rest of the page
    content. In that inner loop, it performed validation of all flags,
    then called FormatContent, and checked the validity. That meant, that
    any invalidation that could happen in at-page objects formatting time
    would be lost. Then, there was nothing to indicate that the at-page
    objects could need a second formatting attempt; the outer loop could
    only run once, even when the at-page formatting needed another run.
    
    This change introduces a dedicated flag for at-page fly invalid state.
    It is checked in SwLayAction::InternalAction in a new loop, to make
    sure that SwObjectFormatter::FormatObjsAtFrame is repeated as needed.
    
    Change-Id: Id6f4fef10cc031af95c38f0074178405027c513d
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193298
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>

diff --git 
a/sw/qa/extras/layout/data/cell-vertical-align-middle-in-fly-at-page.fodt 
b/sw/qa/extras/layout/data/cell-vertical-align-middle-in-fly-at-page.fodt
new file mode 100644
index 000000000000..e4be99c21a8b
--- /dev/null
+++ b/sw/qa/extras/layout/data/cell-vertical-align-middle-in-fly-at-page.fodt
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" 
office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:styles>
+  <style:style style:name="Frame" style:family="graphic"/>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="Table1.1" style:family="table-row">
+   <style:table-row-properties style:min-row-height="3cm"/>
+  </style:style>
+  <style:style style:name="Table1.A1" style:family="table-cell">
+   <style:table-cell-properties style:vertical-align="middle" fo:padding="1mm" 
fo:border="0.06pt solid #000000"/>
+  </style:style>
+  <style:style style:name="fr1" style:family="graphic" 
style:parent-style-name="Frame">
+   <style:graphic-properties style:vertical-pos="from-top" 
style:vertical-rel="page" style:horizontal-pos="from-left" 
style:horizontal-rel="page" fo:padding="0" fo:border="none"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:body>
+  <office:text>
+   <draw:frame draw:style-name="fr1" draw:name="Frame1" 
text:anchor-type="page" text:anchor-page-number="1" svg:x="3cm" svg:y="3cm" 
svg:width="5cm" svg:height="3cm">
+    <draw:text-box>
+     <table:table table:name="Table1">
+      <table:table-column/>
+      <table:table-row table:style-name="Table1.1">
+       <table:table-cell table:style-name="Table1.A1" 
office:value-type="string">
+        <text:p>foo</text:p>
+       </table:table-cell>
+      </table:table-row>
+     </table:table>
+    </draw:text-box>
+   </draw:frame>
+   <text:p/>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index 8abae48b6c81..c9868574d12d 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -2244,6 +2244,24 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf164718)
     CPPUNIT_ASSERT_LESS(sal_Int32(4000), height);
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf169158)
+{
+    // A table with vertical-align="middle" in a fly anchored at page:
+    createSwDoc("cell-vertical-align-middle-in-fly-at-page.fodt");
+    auto pXmlDoc = parseLayoutDump(); // Intentionally no 
Scheduler::ProcessEventsToIdle
+
+    // The problem was, that layout didn't reformat objects at page, when the 
non-default
+    // alignment invalidated cells in tables in at-page fly in 
SwContentNotify::ImplDestroy.
+
+    // Check that the text in the cell is aligned correctly:
+    assertXPath(pXmlDoc, "//page", 1);
+    assertXPath(pXmlDoc, "//page/anchored/fly/tab/row/cell/infos/bounds", 
"top", u"1985");
+    // Without the fix, this would fail with
+    // - Expected: 2698
+    // - Actual  : 2043
+    assertXPath(pXmlDoc, "//page/anchored/fly/tab/row/cell/txt/infos/bounds", 
"top", u"2698");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/inc/pagefrm.hxx b/sw/source/core/inc/pagefrm.hxx
index 3f4967a65e62..aa2af7e0ae4c 100644
--- a/sw/source/core/inc/pagefrm.hxx
+++ b/sw/source/core/inc/pagefrm.hxx
@@ -81,6 +81,8 @@ class SW_DLLPUBLIC SwPageFrame final: public 
SwFootnoteBossFrame
     bool m_bInvalidAutoCmplWrds :1; // Update auto complete word list
     bool m_bInvalidWordCount    :1;
     bool m_bHasGrid             :1; // Grid for Asian layout
+    mutable bool m_bInAtPageFlyFormatting : 1 = false;
+    mutable bool m_bInvalidAtPageFly : 1 = false; // Disambiguate at-page 
invalidation
 
     static const sal_Int8 snShadowPxWidth;
 
@@ -229,6 +231,7 @@ public:
     inline void ValidateSmartTags() const;
     inline void ValidateAutoCompleteWords() const;
     inline void ValidateWordCount() const;
+    void ValidateAtPageFly() const { m_bInvalidAtPageFly = false; }
     inline bool IsInvalid() const;
     inline bool IsInvalidFly() const;
     bool IsRightShadowNeeded() const;
@@ -242,6 +245,9 @@ public:
     bool IsInvalidSmartTags() const { return m_bInvalidSmartTags; }
     bool IsInvalidAutoCompleteWords() const { return m_bInvalidAutoCmplWrds; }
     bool IsInvalidWordCount() const { return m_bInvalidWordCount; }
+    bool IsInvalidAtPageFly() const { return m_bInvalidAtPageFly; }
+    bool IsInAtPageFlyFormatting() const { return m_bInAtPageFlyFormatting; }
+    void SetInAtPageFlyFormatting(bool val) const { m_bInAtPageFlyFormatting = 
val; }
 
     /** SwPageFrame::GetDrawBackgroundColor
 
@@ -373,6 +379,8 @@ inline const SwContentFrame 
*SwPageFrame::FindLastBodyContent() const
 inline void SwPageFrame::InvalidateFlyLayout() const
 {
     const_cast<SwPageFrame*>(this)->m_bInvalidFlyLayout = true;
+    if (m_bInAtPageFlyFormatting)
+        m_bInvalidAtPageFly = true;
 }
 inline void SwPageFrame::InvalidateFlyContent() const
 {
diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx
index de8e0055e592..923d22b52dd4 100644
--- a/sw/source/core/layout/layact.cxx
+++ b/sw/source/core/layout/layact.cxx
@@ -570,12 +570,18 @@ void SwLayAction::InternalAction(OutputDevice* 
pRenderContext)
 
         if (!bTakeShortcut)
         {
+            bool bAtPageObjectsAreInvalid = false;
             while ( !IsInterrupt() && !IsNextCycle() &&
-                    ((pPage->GetSortedObjs() && pPage->IsInvalidFly()) || 
pPage->IsInvalid()) )
+                    ((pPage->GetSortedObjs() && pPage->IsInvalidFly()) || 
pPage->IsInvalid() || bAtPageObjectsAreInvalid))
             {
                 unlockPositionOfObjects( pPage );
 
-                SwObjectFormatter::FormatObjsAtFrame( *pPage, *pPage, this );
+                pPage->ValidateAtPageFly();
+                pPage->SetInAtPageFlyFormatting(true);
+                SwObjectFormatter::FormatObjsAtFrame(*pPage, *pPage, this);
+                pPage->SetInAtPageFlyFormatting(false);
+                bAtPageObjectsAreInvalid = pPage->IsInvalidAtPageFly();
+
                 if ( !pPage->GetSortedObjs() )
                 {
                     // If there are no (more) Flys, the flags are superfluous.

Reply via email to