sw/CppunitTest_sw_writerfilter_dmapper.mk                   |   11 +
 sw/qa/core/doc/doc.cxx                                      |   37 ++++++
 sw/qa/core/text/data/floattable-heading-split.docx          |binary
 sw/qa/core/text/widorp.cxx                                  |   22 +++
 sw/qa/extras/ooxmlexport/data/floattable-anchorpos.docx     |binary
 sw/qa/extras/ooxmlexport/ooxmlexport25.cxx                  |   24 ++++
 sw/qa/extras/uiwriter/uiwriter3.cxx                         |    8 +
 sw/qa/writerfilter/dmapper/DomainMapperTableHandler.cxx     |   31 ++++-
 sw/qa/writerfilter/dmapper/data/floattable-redline.docx     |binary
 sw/qa/writerfilter/ooxml/data/floattable-anchorpos.docx     |binary
 sw/qa/writerfilter/ooxml/ooxml.cxx                          |   37 ++++++
 sw/source/core/doc/DocumentRedlineManager.cxx               |   11 +
 sw/source/core/text/widorp.cxx                              |    8 +
 sw/source/core/undo/unredln.cxx                             |    4 
 sw/source/filter/ww8/docxattributeoutput.cxx                |   41 +++++--
 sw/source/filter/ww8/docxexport.cxx                         |   70 ++++++++++++
 sw/source/filter/ww8/docxexport.hxx                         |    8 +
 sw/source/filter/ww8/wrtw8nds.cxx                           |    6 -
 sw/source/filter/ww8/wrtww8.hxx                             |    2 
 sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx |    5 
 sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx    |   19 +++
 21 files changed, 327 insertions(+), 17 deletions(-)

New commits:
commit c37b34b50835b95c434967642fc8cb0472b543e8
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Jul 14 09:28:52 2025 +0200
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Mon Jul 14 19:15:17 2025 +0200

    tdf#167222 sw floattable: allow split of fly and its keep-together anchor 
text
    
    Regression from commit 3eb6d764b3023500f2299d36bf1860bc8e67db9f (DOCX
    import: floating table with negative top margin has to be a fly frame,
    2022-01-21), open the bugdoc, notice that the floating table should be
    at the end of page 1 but is at the start of page 2.
    
    This happens because in general keep-together means no split should
    happen for all anchored objects and lines of a text frame, but Word
    allows floating tables to be split from the next regular paragraph in
    the document.
    
    Fix the problem by building on top of the work done in commit
    2d0a4ef1d83b8de6cb133970c2c35ae706fb058e (sw floattable: fix negative
    vertical offset handling on page boundary, 2023-06-20), which makes this
    split to "floating table" and "anchor text" pieces possible, but that
    didn't help here, since the anchor text node happened to be a heading,
    with both a split property set to false and a keep-with-next property
    set to true. Now we ignore both properties in case the paragraph is a
    floating table anchor, which means that in SwTextFrame::FormatAdjust(),
    rFrameBreak.IsBreakNow() can return true, resulting in a call to
    SplitFrame(), as needed.
    
    The behavior for other types of anchored objects is unchanged, those
    don't go to a separate page in Word, either.
    
    Change-Id: I48835f355fa5aeb09b1a92bd8cee7e06cd148240
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187845
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/sw/qa/core/text/data/floattable-heading-split.docx 
b/sw/qa/core/text/data/floattable-heading-split.docx
new file mode 100644
index 000000000000..adfd173c172e
Binary files /dev/null and b/sw/qa/core/text/data/floattable-heading-split.docx 
differ
diff --git a/sw/qa/core/text/widorp.cxx b/sw/qa/core/text/widorp.cxx
index 54d26aa2db07..7dd48bc9b879 100644
--- a/sw/qa/core/text/widorp.cxx
+++ b/sw/qa/core/text/widorp.cxx
@@ -60,6 +60,28 @@ CPPUNIT_TEST_FIXTURE(Test, testHideWhitespaceWidorp)
     // went to page 1, so it was not split anymore.
     CPPUNIT_ASSERT(pPara2->HasFollow());
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testFloattableHeadingSplit)
+{
+    // Given a document which ends with a floating table and a heading 
paragraph:
+    // When loading that document & laying it out:
+    createSwDoc("floattable-heading-split.docx");
+
+    // Then make sure that the floating table is on page 1 and the last 
heading is on page 2:
+    SwDocShell* pDocShell = getSwDocShell();
+    SwDoc* pDoc = pDocShell->GetDoc();
+    SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
+    auto pPage1 = pLayout->Lower()->DynCastPageFrame();
+    // Without the accompanying fix in place, this test would have failed, the 
floating table went
+    // to page 2, not to page 1.
+    CPPUNIT_ASSERT(pPage1->GetSortedObjs());
+    // Make sure that page 2 has no floating table and has the heading on the 
correct page.
+    auto pPage2 = pPage1->GetNext()->DynCastPageFrame();
+    CPPUNIT_ASSERT(!pPage2->GetSortedObjs());
+    SwLayoutFrame* pBody2 = pPage2->FindBodyCont();
+    SwTextFrame* pPage2Para1 = pBody2->ContainsContent()->DynCastTextFrame();
+    CPPUNIT_ASSERT_EQUAL(u"page 2"_ustr, pPage2Para1->GetText());
+}
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/widorp.cxx b/sw/source/core/text/widorp.cxx
index 4c0dd129da04..b6c5dd5b70aa 100644
--- a/sw/source/core/text/widorp.cxx
+++ b/sw/source/core/text/widorp.cxx
@@ -73,6 +73,14 @@ SwTextFrameBreak::SwTextFrameBreak( SwTextFrame *pNewFrame, 
const SwTwips nRst )
     }
     m_bKeep = m_bKeep || 
!m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetSplit().GetValue() ||
         
m_pFrame->GetTextNodeForParaProps()->GetSwAttrSet().GetKeep().GetValue();
+
+    if (m_bKeep && m_pFrame->HasSplitFlyDrawObjs())
+    {
+        // Ignore keep-together and keep-with-next if this is an anchor for a 
floating table. It's
+        // OK to split the text frame into two, to separate the floating table 
and the anchor text.
+        m_bKeep = false;
+    }
+
     m_bBreak = false;
 
     if( !m_nRstHeight && !m_pFrame->IsFollow() && m_pFrame->IsInFootnote() && 
m_pFrame->HasPara() )
commit c8a27b430dc9c0af37ced47a99390d2e39008227
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Thu Jul 10 14:52:18 2025 +0200
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Mon Jul 14 19:15:09 2025 +0200

    tdf#167379 sw floattable: ignore dummy anchor nodes in DOCX export
    
    Open the bugdoc in Writer, looks OK, save to DOCX, open in Word: an
    unexpected paragraph appears between the two floating tables.
    
    What happens is that the DOCX import inserts dummy anchor paragraphs
    between floating tables, so we can maintain the invariant that each text
    node has at most one floating table anchored to it (which simplifies
    layout code), but then these dummy text nodes are not filtered out on
    the export side.
    
    Fix the problem by scanning the nodes array for such dummy nodes once at
    the start of the exporter, omitting such dummy nodes from the export
    result and write the affected floating tables when the next text node is
    written in the output.
    
    An alternative I considered is to leave
    MSWordExportBase::OutputContentNode() unchanged and just make some of
    the m_pSerializer calls conditional, so the dummy anchor node is missing
    from the export result. The problem is that it needed ~12 conditions and
    it would be easy to change the code in the future in a way that some
    part of the dummy node markup would be still emitted. So instead skip
    the entire text node and write the table with the next node.
    
    Change-Id: Ic15342d431a5c1e3086dc2029df6455ad675e13e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187638
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/sw/qa/extras/ooxmlexport/data/floattable-anchorpos.docx 
b/sw/qa/extras/ooxmlexport/data/floattable-anchorpos.docx
new file mode 100644
index 000000000000..d93e5772aa3f
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/floattable-anchorpos.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
index 88769ef361ac..0ab86ddaf8b3 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
@@ -125,6 +125,30 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf166620)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testFloatingTableAnchorPosExport)
+{
+    // Given a document with two floating tables after each other:
+    // When saving that document to DOCX:
+    loadAndSave("floattable-anchorpos.docx");
+
+    // Then make sure that the dummy anchor of the first floating table is not 
written to the export
+    // result:
+    xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
+    // Check the order of the floating tables: C is from the previous node, A 
is normal floating
+    // table.
+    CPPUNIT_ASSERT_EQUAL(u"C"_ustr,
+                         getXPathContent(pXmlDoc, 
"//w:body/w:tbl[1]/w:tr/w:tc/w:p/w:r/w:t"));
+    CPPUNIT_ASSERT_EQUAL(u"A"_ustr,
+                         getXPathContent(pXmlDoc, 
"//w:body/w:tbl[2]/w:tr/w:tc/w:p/w:r/w:t"));
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 1
+    // - Actual  : 2
+    // i.e. the dummy anchor node was written to DOCX, leading to a Writer vs 
Word layout
+    // difference.
+    CPPUNIT_ASSERT_EQUAL(1, countXPathNodes(pXmlDoc, "//w:body/w:p"));
+    CPPUNIT_ASSERT_EQUAL(u"D"_ustr, getXPathContent(pXmlDoc, 
"//w:body/w:p/w:r/w:t"));
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 72ae0ba83790..77023858b809 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -460,6 +460,7 @@ static void 
checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu
 {
     const auto& rExport = rDocxAttributeOutput.GetExport();
     // iterate though all SpzFrameFormats and check whether they are anchored 
to the current text node
+    std::vector<ww8::Frame> aFrames;
     for( sal_uInt16 nCnt = rExport.m_rDoc.GetSpzFrameFormats()->size(); nCnt; )
     {
         const SwFrameFormat* pFrameFormat = 
(*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
@@ -469,7 +470,22 @@ static void 
checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu
         if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode())
             continue;
 
-        if (*pAnchorNode != *rExport.m_pCurPam->GetPointNode().GetTextNode())
+        bool bAnchorMatchesNode = *pAnchorNode == 
*rExport.m_pCurPam->GetPointNode().GetTextNode();
+        bool bAnchorIsPreviousNode = false;
+        if (!bAnchorMatchesNode)
+        {
+            // The anchor doesn't match, but see if the previous node is a 
dummy anchor, we should
+            // emit floating tables to that anchor here, too.
+            SwNodeIndex aNodeIndex(rExport.m_pCurPam->GetPointNode());
+            --aNodeIndex;
+            if (*pAnchorNode == aNodeIndex.GetNode() && 
rExport.IsDummyFloattableAnchor(aNodeIndex.GetNode()))
+            {
+                bAnchorMatchesNode = true;
+                bAnchorIsPreviousNode = true;
+            }
+        }
+
+        if (!bAnchorMatchesNode)
             continue;
 
         const SwNodeIndex* pStartNode = 
pFrameFormat->GetContent().GetContentIdx();
@@ -500,19 +516,26 @@ static void 
checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu
         const SfxGrabBagItem* pTableGrabBag = 
pTableFormat->GetAttrSet().GetItem<SfxGrabBagItem>(RES_FRMATR_GRABBAG);
         const std::map<OUString, css::uno::Any> & rTableGrabBag = 
pTableGrabBag->GetGrabBag();
         // no grabbag?
-        if (rTableGrabBag.find(u"TablePosition"_ustr) == rTableGrabBag.end())
+        if (rTableGrabBag.find(u"TablePosition"_ustr) == rTableGrabBag.end() 
&& !pFrameFormat->GetFlySplit().GetValue())
         {
-            if (pFrameFormat->GetFlySplit().GetValue())
-            {
-                ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
-                rDocxAttributeOutput.WriteFloatingTable(&aFrame);
-            }
             continue;
         }
 
-        // write table to docx
+        // write table to docx: first tables from previous node, then from 
this node.
         ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
-        rDocxAttributeOutput.WriteFloatingTable(&aFrame);
+        if (bAnchorIsPreviousNode)
+        {
+            aFrames.insert(aFrames.begin(), aFrame);
+        }
+        else
+        {
+            aFrames.push_back(aFrame);
+        }
+    }
+
+    for (const auto& rFrame : aFrames)
+    {
+        rDocxAttributeOutput.WriteFloatingTable(&rFrame);
     }
 }
 
diff --git a/sw/source/filter/ww8/docxexport.cxx 
b/sw/source/filter/ww8/docxexport.cxx
index 02ce025e6abe..5b72f3e5fa2b 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -101,6 +101,8 @@
 #include <unotools/ucbstreamhelper.hxx>
 #include <comphelper/diagnose_ex.hxx>
 #include <unotxdoc.hxx>
+#include <formatflysplit.hxx>
+#include <fmtanchr.hxx>
 
 using namespace sax_fastparser;
 using namespace ::comphelper;
@@ -524,6 +526,67 @@ void DocxExport::OutputDML(uno::Reference<drawing::XShape> 
const & xShape)
     aExport.WriteShape(xShape);
 }
 
+void DocxExport::CollectFloatingTables()
+{
+    if (!m_rDoc.GetSpzFrameFormats())
+    {
+        return;
+    }
+
+    sw::FrameFormats<sw::SpzFrameFormat*>& rSpzFormats = 
*m_rDoc.GetSpzFrameFormats();
+    for (sw::SpzFrameFormat* pFormat : rSpzFormats)
+    {
+        const SwFormatFlySplit& rFlySplit = pFormat->GetFlySplit();
+        if (!rFlySplit.GetValue())
+        {
+            continue;
+        }
+
+        const SwFormatAnchor& rAnchor = pFormat->GetAnchor();
+        const SwPosition* pContentAnchor = rAnchor.GetContentAnchor();
+        if (!pContentAnchor)
+        {
+            continue;
+        }
+
+        SwNode& rNode = pContentAnchor->GetNode();
+        SwTextNode* pTextNode = rNode.GetTextNode();
+        if (!pTextNode)
+        {
+            continue;
+        }
+
+        SwNodeIndex aNodeIndex(*pTextNode);
+        ++aNodeIndex;
+        if (!aNodeIndex.GetNode().GetTextNode())
+        {
+            // Only text nodes know to look for floating tables from previous 
text nodes.
+            continue;
+        }
+
+        if (!pTextNode->HasSwAttrSet())
+        {
+            continue;
+        }
+
+        const SwAttrSet& rAttrSet = pTextNode->GetSwAttrSet();
+        const SvxLineSpacingItem& rLineSpacing = rAttrSet.GetLineSpacing();
+        if (rLineSpacing.GetLineSpaceRule() != SvxLineSpaceRule::Fix)
+        {
+            continue;
+        }
+
+        if (rLineSpacing.GetLineHeight() != 0)
+        {
+            continue;
+        }
+
+        // This is text node which is effectively invisible in Writer and has 
a floating table
+        // anchored to it; omit such nodes from the DOCX output.
+        m_aDummyFloatingTableAnchors.insert(&pContentAnchor->GetNode());
+    }
+}
+
 ErrCode DocxExport::ExportDocument_Impl()
 {
     // Set the 'Reviewing' flags in the settings structure
@@ -540,6 +603,8 @@ ErrCode DocxExport::ExportDocument_Impl()
     // Make sure images are counted from one, even when exporting multiple 
documents.
     rGraphicExportCache.push();
 
+    CollectFloatingTables();
+
     WriteMainText();
 
     WriteFootnotesEndnotes();
@@ -1931,6 +1996,11 @@ bool DocxExport::isMirroredMargin()
     return bMirroredMargins;
 }
 
+bool DocxExport::IsDummyFloattableAnchor(SwNode& rNode) const
+{
+    return GetDummyFloatingTableAnchors().contains(&rNode);
+}
+
 void DocxExport::WriteDocumentBackgroundFill()
 {
     const std::unique_ptr<SvxBrushItem> pBrush = getBackground();
diff --git a/sw/source/filter/ww8/docxexport.hxx 
b/sw/source/filter/ww8/docxexport.hxx
index 43cdccce8a5c..ad339ad3bde1 100644
--- a/sw/source/filter/ww8/docxexport.hxx
+++ b/sw/source/filter/ww8/docxexport.hxx
@@ -129,6 +129,8 @@ class DocxExport : public MSWordExportBase
     /// Storage for sdt data which need to be written to other XMLs
     std::vector<SdtData> m_SdtData;
 
+    std::set<SwNode*> m_aDummyFloatingTableAnchors;
+
 public:
 
     DocxExportFilter& GetFilter() { return m_rFilter; };
@@ -254,6 +256,8 @@ private:
     /// Write comments.xml
     void WritePostitFields();
 
+    void CollectFloatingTables();
+
     /// Write the numbering table.
     virtual void WriteNumbering() override;
 
@@ -326,6 +330,10 @@ public:
     /// return true if Page Layout is set as Mirrored
     bool isMirroredMargin();
 
+    const std::set<SwNode*>& GetDummyFloatingTableAnchors() const { return 
m_aDummyFloatingTableAnchors; }
+
+    bool IsDummyFloattableAnchor(SwNode& rNode) const override;
+
 private:
     DocxExport( const DocxExport& ) = delete;
 
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx 
b/sw/source/filter/ww8/wrtw8nds.cxx
index fe499c5f3dc8..3e0fd1bd50f8 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -3755,7 +3755,11 @@ void MSWordExportBase::OutputContentNode( SwContentNode& 
rNode )
     switch ( rNode.GetNodeType() )
     {
         case SwNodeType::Text:
-            OutputTextNode( *rNode.GetTextNode() );
+            // Skip dummy anchors: the next node will emit their floating 
tables.
+            if (!IsDummyFloattableAnchor(*rNode.GetTextNode()))
+            {
+                OutputTextNode(*rNode.GetTextNode());
+            }
             break;
         case SwNodeType::Grf:
             OutputGrfNode( *rNode.GetGrfNode() );
diff --git a/sw/source/filter/ww8/wrtww8.hxx b/sw/source/filter/ww8/wrtww8.hxx
index bd116fa83f12..fa34838426f2 100644
--- a/sw/source/filter/ww8/wrtww8.hxx
+++ b/sw/source/filter/ww8/wrtww8.hxx
@@ -930,6 +930,8 @@ protected:
 
     std::vector<const Graphic*> m_vecBulletPic; ///< Vector to record all the 
graphics of bullets
 
+    virtual bool IsDummyFloattableAnchor(SwNode& /*rNode*/) const { return 
false; }
+
 public:
     MSWordExportBase(SwDoc& rDocument, std::shared_ptr<SwUnoCursor> & 
pCurrentPam, SwPaM* pOriginalPam);
     virtual ~MSWordExportBase();
commit d57cd07263418f6dca54ec2ebceec8e91a571f62
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Wed Jul 9 13:37:02 2025 +0200
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Mon Jul 14 19:14:22 2025 +0200

    tdf#167379 sw floattable: make dummy paragraph from DOCX import less visible
    
    Open the bugdoc, the text C and D appears to have the same vertical
    position in Word, but D is shifted down in Writer.
    
    What happens is that since commit
    01ad8ec4bb5425446e95dbada81de435646824b4 (sw floattable: fix lost tables
    around a floating table from DOCX, 2023-06-05), an empty paragraph is
    inserted between two floating tables immediately following each other,
    to have a suitable anchor point for all floating tables. But it wasn't
    considered that the anchor point takes vertical space, which has an
    impact on layout.
    
    Fix the problem by keeping the empty paragraph in the model & layout
    (the split fly frame needs those), but setting the paragraph properties
    (spacing, line spacing) in a way that the created text frame has 0
    height, so it has no negative impact on the position of the text after
    the text frame.
    
    This is similar to what the DOC import does in
    WW8TabDesc::CreateSwTable(), though there we set the font size of the
    paragraph to 1pt, which improves the layout but still has a small
    impact. An alternative I considered is to set the "hidden" character
    property on the text node's item set, but that would create no text
    frame at all, so we would again have no anchor point for the floating
    table, so that would not work.
    
    Change-Id: Iceb2f51ad42028434822cb9c0701a7e6d6545845
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187566
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx 
b/sw/qa/extras/uiwriter/uiwriter3.cxx
index de126574ca78..466a2664e065 100644
--- a/sw/qa/extras/uiwriter/uiwriter3.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter3.cxx
@@ -844,7 +844,13 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf134227)
 
     CPPUNIT_ASSERT_EQUAL(4, getShapes());
 
-    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
+    // Go to the end of the document, outside the dummy paragraph that just 
serves as an anchor for
+    // the floating table.
+    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
+    uno::Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(
+        xModel->getCurrentController(), uno::UNO_QUERY);
+    uno::Reference<text::XTextViewCursor> xViewCursor = 
xTextViewCursorSupplier->getViewCursor();
+    xViewCursor->gotoEnd(/*bExpand=*/false);
     dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});
 
     // Without the fix in place, it would have crashed here
diff --git a/sw/qa/writerfilter/ooxml/data/floattable-anchorpos.docx 
b/sw/qa/writerfilter/ooxml/data/floattable-anchorpos.docx
new file mode 100644
index 000000000000..d93e5772aa3f
Binary files /dev/null and 
b/sw/qa/writerfilter/ooxml/data/floattable-anchorpos.docx differ
diff --git a/sw/qa/writerfilter/ooxml/ooxml.cxx 
b/sw/qa/writerfilter/ooxml/ooxml.cxx
index c5f9af177a13..9a9d2745ba7c 100644
--- a/sw/qa/writerfilter/ooxml/ooxml.cxx
+++ b/sw/qa/writerfilter/ooxml/ooxml.cxx
@@ -17,6 +17,14 @@
 
 #include <fmtcntnt.hxx>
 #include <ndtxt.hxx>
+#include <docsh.hxx>
+#include <wrtsh.hxx>
+#include <rootfrm.hxx>
+#include <pagefrm.hxx>
+#include <txtfrm.hxx>
+#include <sortedobjs.hxx>
+#include <anchoredobject.hxx>
+#include <flyfrm.hxx>
 
 using namespace ::com::sun::star;
 
@@ -109,6 +117,35 @@ CPPUNIT_TEST_FIXTURE(Test, testNestedRuns)
     // Without the accompanying fix in place, this test would have failed, the 
shape was empty.
     CPPUNIT_ASSERT_EQUAL(u"Test text box"_ustr, pTextNode->GetText());
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testFloatingTableAnchorPos)
+{
+    // Given a document with a floating table (text: C) and a visually first 
body paragraph (text:
+    // D):
+    // When loading that document:
+    createSwDoc("floattable-anchorpos.docx");
+
+    // Then make sure that D is not shifted down vertically, compared to C:
+    SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell();
+    SwRootFrame* pLayout = pWrtShell->GetLayout();
+    SwPageFrame* pPage = pLayout->GetLower()->DynCastPageFrame();
+    SwTextFrame* pBodyPara1 = 
pPage->FindFirstBodyContent()->DynCastTextFrame();
+    CPPUNIT_ASSERT(pBodyPara1->HasSplitFlyDrawObjs());
+    SwSortedObjs& rFlys1 = *pBodyPara1->GetDrawObjs();
+    SwFlyFrame* pFly1 = rFlys1[0]->DynCastFlyFrame();
+    SwTextFrame* pFly1Text = pFly1->ContainsContent()->DynCastTextFrame();
+    CPPUNIT_ASSERT_EQUAL(u"C"_ustr, pFly1Text->GetText());
+    SwTwips nFlyTop = pFly1Text->getFrameArea().Top();
+    SwTextFrame* pBodyPara2 = pBodyPara1->GetNext()->DynCastTextFrame();
+    CPPUNIT_ASSERT_EQUAL(u"D"_ustr, pBodyPara2->GetText());
+    SwTwips nBodyTop = pBodyPara2->getFrameArea().Top();
+    SwTwips nDiff = std::abs(nBodyTop - nFlyTop);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected less or equal than: 1
+    // - Actual  : 243
+    // i.e. the vertical position of D was too big.
+    CPPUNIT_ASSERT_LESSEQUAL(static_cast<SwTwips>(1), nDiff);
+}
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx 
b/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx
index 046d8c6aacb0..d8754cc6d2a6 100644
--- a/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx
+++ b/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx
@@ -1618,6 +1618,25 @@ void 
OOXMLFastContextHandlerTextTable::lcl_startFastElement
         // subset of '<resource name="CT_P" resource="Stream">' in model.xml:
         startParagraphGroup();
         sendTableDepth();
+
+        OOXMLPropertySet::Pointer_t pSprms(new OOXMLPropertySet);
+        {
+            // Configure the dummy paragraph to have a zero height, so it has 
no impact on the
+            // layout.
+            OOXMLPropertySet::Pointer_t pAttributes(new OOXMLPropertySet);
+            OOXMLValue pSBVal = OOXMLValue::createInteger(0);
+            pAttributes->add(NS_ooxml::LN_CT_Spacing_before, pSBVal, 
OOXMLProperty::ATTRIBUTE);
+            OOXMLValue pSAVal = OOXMLValue::createInteger(0);
+            pAttributes->add(NS_ooxml::LN_CT_Spacing_after, pSAVal, 
OOXMLProperty::ATTRIBUTE);
+            OOXMLValue pSLRVal = 
OOXMLValue::createInteger(NS_ooxml::LN_Value_doc_ST_LineSpacingRule_exact);
+            pAttributes->add(NS_ooxml::LN_CT_Spacing_lineRule, pSLRVal, 
OOXMLProperty::ATTRIBUTE);
+            OOXMLValue pSLVal = OOXMLValue::createInteger(0);
+            pAttributes->add(NS_ooxml::LN_CT_Spacing_line, pSLVal, 
OOXMLProperty::ATTRIBUTE);
+            OOXMLValue pSprm = OOXMLValue::createPropertySet(pAttributes);
+            pSprms->add(NS_ooxml::LN_CT_PPrBase_spacing, pSprm, 
OOXMLProperty::SPRM);
+        }
+        mpStream->props(pSprms.get());
+
         endOfParagraph();
     }
 
commit 6d829c23b569b891b10d6bfaf06c97909b4e00dd
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Jul 8 09:50:08 2025 +0200
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Mon Jul 14 19:14:22 2025 +0200

    tdf#167229 sw floattable: fix missing cell background with redlines
    
    Open the DOCX file, the 3rd table should have a gray background in its
    B2 cell, but its background is white.
    
    It seems this happens because there are 2 floating tables before the
    table and the first floating table has a redline. Commit
    288db6eb47fbbd2b3ca34ffea0686d8ed8ed9be9 (tdf#132271 DOCX: import change
    tracking in floating tables, 2020-09-07) added special handling of
    redlines inside floating tables, and I moved that code around for
    SwFormatFlySplit in c50bf5a5daaae3d40f89ea0784a75a8a571c208d (sw
    floattable: remove no longer needed DOCX import heuristics, 2023-04-12).
    The trouble is that once these pending redlines are processed, nobody
    clears rFramedRedlines, so it gets processed also for a next floating
    table, which pollutes the state of the importer, which leads to missing
    cell properties for later tables.
    
    Fix the problem by clearing the redline list in
    DomainMapperTableHandler::endTable(), which restores the invariant that
    once the redline list is processed, it's cleared -- that was there in
    the old SectionPropertyMap::CloseSectionGroup() code before the
    SwFormatFlySplit work.
    
    An alternative I've considered is to just catch exceptions in
    BeforeConvertToTextFrame() when getting the TextTable and Cell
    properties, but that would still result in this unwanted behavior that a
    floattable-with-redlines has an influance on the next floattable.
    
    Change-Id: I9998a46d72a72172dffcf85bf96b3478d63c9269
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187530
    Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com>
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/sw/CppunitTest_sw_writerfilter_dmapper.mk 
b/sw/CppunitTest_sw_writerfilter_dmapper.mk
index 366cca5dd239..de6f844faeec 100644
--- a/sw/CppunitTest_sw_writerfilter_dmapper.mk
+++ b/sw/CppunitTest_sw_writerfilter_dmapper.mk
@@ -45,6 +45,17 @@ $(eval $(call 
gb_CppunitTest_use_libraries,sw_writerfilter_dmapper, \
     utl \
     tl \
     vcl \
+    svl \
+    sw \
+    swqahelper \
+))
+
+$(eval $(call gb_CppunitTest_set_include,sw_writerfilter_dmapper,\
+    -I$(SRCDIR)/sw/inc \
+    -I$(SRCDIR)/sw/source/core/inc \
+    -I$(SRCDIR)/sw/source/uibase/inc \
+    -I$(SRCDIR)/sw/qa/inc \
+    $$(INCLUDE) \
 ))
 
 $(eval $(call gb_CppunitTest_use_sdk_api,sw_writerfilter_dmapper))
diff --git a/sw/qa/writerfilter/dmapper/DomainMapperTableHandler.cxx 
b/sw/qa/writerfilter/dmapper/DomainMapperTableHandler.cxx
index 03c4727737b7..e36ae88a7728 100644
--- a/sw/qa/writerfilter/dmapper/DomainMapperTableHandler.cxx
+++ b/sw/qa/writerfilter/dmapper/DomainMapperTableHandler.cxx
@@ -7,7 +7,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-#include <test/unoapixml_test.hxx>
+#include <swmodeltestbase.hxx>
 
 #include <com/sun/star/beans/XPropertySet.hpp>
 #include <com/sun/star/table/BorderLine2.hpp>
@@ -21,16 +21,19 @@
 #include <com/sun/star/text/XPageCursor.hpp>
 #include <com/sun/star/qa/XDumper.hpp>
 
+#include <frmatr.hxx>
+#include <swtable.hxx>
+
 using namespace ::com::sun::star;
 
 namespace
 {
 /// Tests for sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx.
-class Test : public UnoApiXmlTest
+class Test : public SwModelTestBase
 {
 public:
     Test()
-        : UnoApiXmlTest(u"/sw/qa/writerfilter/dmapper/data/"_ustr)
+        : SwModelTestBase(u"/sw/qa/writerfilter/dmapper/data/"_ustr)
     {
     }
 };
@@ -277,6 +280,28 @@ CPPUNIT_TEST_FIXTURE(Test, 
testDOCXFloatingTableNestedLayout)
     CPPUNIT_ASSERT_GREATER(nTableTop, nFlyTop);
     CPPUNIT_ASSERT_LESS(nTableBottom, nFlyBottom);
 }
+
+CPPUNIT_TEST_FIXTURE(Test, testDOCXFloatingTableRedline)
+{
+    // Given a document with 3 floating tables, the last table has a last cell 
with a custom cell
+    // background:
+    // When importing that document from DOCX:
+    createSwDoc("floattable-redline.docx");
+
+    // Then make sure that the cell background is not lost:
+    SwDoc* pDoc = getSwDoc();
+    sw::TableFrameFormats& rTableFormats = *pDoc->GetTableFrameFormats();
+    SwTableFormat* pTableFormat = rTableFormats[2];
+    SwTable* pTable = SwTable::FindTable(pTableFormat);
+    const SwTableBox* pCell = pTable->GetTableBox(u"B2"_ustr);
+    const SwAttrSet& rCellSet = pCell->GetFrameFormat()->GetAttrSet();
+    const SvxBrushItem& rCellBackground = rCellSet.GetBackground();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: rgba[d0d8e8ff]
+    // - Actual  : rgba[ffffff00]
+    // i.e. the cell background was white, should be gray.
+    CPPUNIT_ASSERT_EQUAL(Color(0xD0D8E8), rCellBackground.GetColor());
+}
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/qa/writerfilter/dmapper/data/floattable-redline.docx 
b/sw/qa/writerfilter/dmapper/data/floattable-redline.docx
new file mode 100644
index 000000000000..9b45768ca9d2
Binary files /dev/null and 
b/sw/qa/writerfilter/dmapper/data/floattable-redline.docx differ
diff --git a/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx 
b/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx
index a98cd7f56403..74c25f9ad9ea 100644
--- a/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx
+++ b/sw/source/writerfilter/dmapper/DomainMapperTableHandler.cxx
@@ -1668,7 +1668,7 @@ void DomainMapperTableHandler::endTable(unsigned int 
nestedTableLevel)
             bool bRecordChanges = 
m_rDMapper_Impl.GetSettingsTable()->GetRecordChanges();
             if (xTextAppendAndConvert.is() && !(bInFootnote && bRecordChanges))
             {
-                const std::deque<StoredRedline>& rFramedRedlines = 
m_rDMapper_Impl.m_aStoredRedlines[StoredRedlines::FRAME];
+                std::deque<StoredRedline>& rFramedRedlines = 
m_rDMapper_Impl.m_aStoredRedlines[StoredRedlines::FRAME];
                 std::vector<sal_Int32> redPos, redLen;
                 std::vector<OUString> redCell;
                 std::vector<OUString> redTable;
@@ -1722,6 +1722,9 @@ void DomainMapperTableHandler::endTable(unsigned int 
nestedTableLevel)
                 }
 
                 AfterConvertToTextFrame(m_rDMapper_Impl, rFramedRedlines, 
redPos, redLen, redCell, redTable);
+
+                // These redlines are not relevant for the next floating 
table, clear them.
+                rFramedRedlines.clear();
             }
 
             if (xFrameAnchor.is() && eBreakType != style::BreakType_NONE)
commit f5761c003dfc99ec4df123a935285f9bf65de474
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Mon Jul 7 10:52:26 2025 +0200
Commit:     Andras Timar <andras.ti...@collabora.com>
CommitDate: Mon Jul 14 19:14:22 2025 +0200

    tdf#166319 sw interdependent redlines: fix redo of accept of ins-then-fmt's 
fmt
    
    The bugdoc has <ins>AA<format>BB</format>CC</ins>, go to middle of BB,
    accept, undo, redo: there is no format redline in the document, but
    there should be one.
    
    What happens is that the "accept" action creates an SwUndoAcceptRedline
    with the default depth = 0, but then it carefully calls
    lcl_DeleteInnerRedline() in
    sw::DocumentRedlineManager::AcceptRedlineRange() instead of a plain
    accept.
    
    The undo action already knows how to only accept the underlying redline,
    so fix the problem in sw::DocumentRedlineManager::AcceptRedlineRange()
    by correctly recording the UI action with depth = 1, which gives us both
    working undo & redo.
    
    The exec of the undo action's redo calls
    sw::DocumentRedlineManager::AcceptRedline(), which now calls the same
    lcl_DeleteInnerRedline() as the original UI action, so this looks
    consistent now.
    
    Change-Id: Ibb21c0745f3dd0aacec7cf7448c7c85245dbee69
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187467
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com>

diff --git a/sw/qa/core/doc/doc.cxx b/sw/qa/core/doc/doc.cxx
index ba2520d4054e..96869044ce82 100644
--- a/sw/qa/core/doc/doc.cxx
+++ b/sw/qa/core/doc/doc.cxx
@@ -908,6 +908,43 @@ CPPUNIT_TEST_FIXTURE(SwCoreDocTest, testInsThenFormat)
     // Without the accompanying fix in place, this test would have failed, the 
text was AAABBBCCC,
     // just the format of BBB was dropped.
     CPPUNIT_ASSERT(pTextNode->GetText().isEmpty());
+
+    // And given a state where you're just after the undo for the accept of 
insert-then-format's
+    // format:
+    pWrtShell->Undo();
+    // Undo() creates a new cursor.
+    pCursor = pWrtShell->GetCursor();
+    pCursor->DeleteMark();
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    // Move inside "BBB".
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, 
/*bBasicCall=*/false);
+    nRedline = 0;
+    rRedlines.FindAtPosition(*pCursor->Start(), nRedline);
+    // A redline is found.
+    CPPUNIT_ASSERT_LESS(rRedlines.size(), nRedline);
+    pWrtShell->AcceptRedline(nRedline);
+    pWrtShell->Undo();
+
+    // When redoing that accept:
+    pWrtShell->Redo();
+
+    // Then make sure we have a single format redline as a result:
+    pCursor = pWrtShell->GetCursor();
+    pCursor->DeleteMark();
+    pWrtShell->SttEndDoc(/*bStt=*/true);
+    // Move inside "BBB".
+    pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 4, 
/*bBasicCall=*/false);
+    nRedline = 0;
+    pRedline = rRedlines.FindAtPosition(*pCursor->Start(), nRedline);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 1
+    // - Actual  : 0
+    // i.e. everything was accepted, even the format redline was gone from the 
document, instead of
+    // just accepting the underlying insert.
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rRedlines.size());
+    CPPUNIT_ASSERT(pRedline);
+    CPPUNIT_ASSERT_EQUAL(RedlineType::Format, pRedline->GetType());
+    CPPUNIT_ASSERT(!pRedline->GetRedlineData().Next());
 }
 
 CPPUNIT_TEST_FIXTURE(SwCoreDocTest, testDelThenFormat)
diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx 
b/sw/source/core/doc/DocumentRedlineManager.cxx
index a02f5b947a57..8d8358d0fe80 100644
--- a/sw/source/core/doc/DocumentRedlineManager.cxx
+++ b/sw/source/core/doc/DocumentRedlineManager.cxx
@@ -3358,15 +3358,22 @@ bool 
DocumentRedlineManager::AcceptRedlineRange(SwRedlineTable::size_type nPosOr
         }
         else if (pTmp->GetRedlineData(0).CanCombineForAcceptReject(aOrigData))
         {
+            bool bHierarchicalFormat = pTmp->GetType() == RedlineType::Format 
&& pTmp->GetStackCount() > 1;
             if (m_rDoc.GetIDocumentUndoRedo().DoesUndo())
             {
+                sal_Int8 nDepth = 0;
+                if (bHierarchicalFormat && pTmp->GetType(1) == 
RedlineType::Insert)
+                {
+                    // Only work with the underlying insert, so the undo 
action matches the UI
+                    // action below.
+                    nDepth = 1;
+                }
                 m_rDoc.GetIDocumentUndoRedo().AppendUndo(
-                    std::make_unique<SwUndoAcceptRedline>(*pTmp));
+                    std::make_unique<SwUndoAcceptRedline>(*pTmp, nDepth));
             }
             nPamEndNI = pTmp->Start()->GetNodeIndex();
             nPamEndCI = pTmp->Start()->GetContentIndex();
 
-            bool bHierarchicalFormat = pTmp->GetType() == RedlineType::Format 
&& pTmp->GetStackCount() > 1;
             if (bHierarchicalFormat && pTmp->GetType(1) == RedlineType::Insert)
             {
                 // This combination of 2 redline types prefers accepting the 
inner one first.
diff --git a/sw/source/core/undo/unredln.cxx b/sw/source/core/undo/unredln.cxx
index 7786b1a5cf42..77c15f282b7d 100644
--- a/sw/source/core/undo/unredln.cxx
+++ b/sw/source/core/undo/unredln.cxx
@@ -125,6 +125,10 @@ void SwUndoRedline::dumpAsXml(xmlTextWriterPtr pWriter) 
const
     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
                                 
BAD_CAST(OString::boolean(mbHierarchical).getStr()));
     (void)xmlTextWriterEndElement(pWriter);
+    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("depth"));
+    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
+                                BAD_CAST(OString::number(mnDepth).getStr()));
+    (void)xmlTextWriterEndElement(pWriter);
 
     (void)xmlTextWriterEndElement(pWriter);
 }

Reply via email to