sw/qa/extras/layout/data/tdf167526.docx                  |binary
 sw/qa/extras/layout/layout5.cxx                          |   50 +++++++++++++
 sw/source/filter/ww8/attributeoutputbase.hxx             |    3 
 sw/source/filter/ww8/docxattributeoutput.cxx             |   57 +++------------
 sw/source/filter/ww8/docxattributeoutput.hxx             |    3 
 sw/source/filter/ww8/docxexport.cxx                      |    8 --
 sw/source/filter/ww8/wrtw8nds.cxx                        |   13 ++-
 sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx |    3 
 8 files changed, 81 insertions(+), 56 deletions(-)

New commits:
commit a84e0fd218ad6824326b980a63806d4684af29b0
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Thu Jul 17 18:01:35 2025 +0500
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Mon Aug 11 12:11:14 2025 +0200

    tdf#167535: Ignore dummy anchor nodes in line numbering (take 2)
    
    Commit 441aed20b95ee40dec1df72fb8e8167d0e48c0c4 (tdf#167379 sw floattable:
    make dummy paragraph from DOCX import less visible, 2025-07-10) introduced
    dummy nodes as invisible anchors for floating tables without normal anchor
    nodes. These nodes are not part of actual content; use suppressLineNumbers
    property in OOXMLFastContextHandlerTextTable::lcl_startFastElement, which
    avoids their use in line numbering.
    
    Change-Id: Id1703c10ede6960499e08758c250d6d561d1f07f
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188011
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Tested-by: Jenkins
    Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189300

diff --git a/sw/qa/extras/layout/data/tdf167526.docx 
b/sw/qa/extras/layout/data/tdf167526.docx
index 050f081ff8e3..47c0fe5d2a01 100644
Binary files a/sw/qa/extras/layout/data/tdf167526.docx and 
b/sw/qa/extras/layout/data/tdf167526.docx differ
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index e55f419287f1..beb4b01bfcd5 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -1873,6 +1873,18 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf167526)
         // Without a fix, this would fail, because the third element was the 
wrongly emitted <w:p>
         assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[3]", "tbl");
     }
+
+    // tdf#167535: check line numbering; the dummy node must not interfere 
with it
+    {
+        // Dump the rendering of the first page as an XML file.
+        SwDocShell* pShell = getSwDocShell();
+        std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
+        MetafileXmlDump dumper;
+        xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
+        // Without a fix, this was 3
+        assertXPathContent(
+            pXmlDoc, "/metafile/push/push/push/textarray[@index=0 and 
@length=1][2]/text", u"2");
+    }
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx 
b/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx
index b7381fc84c3b..83c6b55e700c 100644
--- a/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx
+++ b/sw/source/writerfilter/ooxml/OOXMLFastContextHandler.cxx
@@ -1636,6 +1636,9 @@ void 
OOXMLFastContextHandlerTextTable::lcl_startFastElement
             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);
+            // Do not count it in line numbering
+            pSprms->add(NS_ooxml::LN_CT_PPrBase_suppressLineNumbers,
+                        OOXMLValue::createBoolean(true), OOXMLProperty::SPRM);
         }
         mpStream->props(pSprms.get());
 
commit 40ce124cc68248527b0c1ea5b5c7662132c0600a
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Wed Jul 16 22:23:07 2025 +0500
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Mon Aug 11 12:11:00 2025 +0200

    tdf#167526: write floating tables anchored to dummy nodes immediately
    
    Commit 441aed20b95ee40dec1df72fb8e8167d0e48c0c4 (tdf#167379 sw floattable:
    make dummy paragraph from DOCX import less visible, 2025-07-10) introduced
    dummy nodes as invisible anchors for floating tables without normal anchor
    nodes. Commit c9851022d102a2abfc16c033c0249f24573300e7 took care to avoid
    their export to DOCX, not calling OutputTextNode in OutputContentNode for
    them, and handling their tables as soon as the next text node is handled.
    
    However, this didn't work, when the next node wasn't a text node. In that
    case, the dummy paragraph wasn't detected as such in CollectFloatingTables,
    and got exported as normal; and after reload, it appeared after its table.
    
    Fix that by making sure to write floating tables as soon as the dummy node
    is handled.
    
    1. Make previously static checkAndWriteFloatingTables a virtual method of
    AttributeOutputBase, implemented in DocxAttributeOutput.
    
    2. Let MSWordExportBase::OutputTextNode check if it handles a dummy node,
    and if so, call AttrOutput().CheckAndWriteFloatingTables, and return.
    
    3. Revert to calling OutputTextNode in MSWordExportBase::OutputContentNode
    unconditionally.
    
    This simplifies the logic a bit, avoiding the need to care about postponed
    actions, potentially in multiple places.
    
    Change-Id: I41b57103fc8b4522e3b5b1810389db078719b9b1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187975
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189299

diff --git a/sw/qa/extras/layout/data/tdf167526.docx 
b/sw/qa/extras/layout/data/tdf167526.docx
new file mode 100644
index 000000000000..050f081ff8e3
Binary files /dev/null and b/sw/qa/extras/layout/data/tdf167526.docx differ
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index 055e6b908a07..e55f419287f1 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -1837,6 +1837,44 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf167541)
     createSwDoc("tdf167541.fodt");
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf167526)
+{
+    // Given a document with a floating table, immediately followed by a 
normal table:
+    createSwDoc("tdf167526.docx");
+
+    // check layout
+    {
+        // Make sure that the second node is a dummy node, and its line has 
height of 0
+        auto pXmlDoc = parseLayoutDump();
+        assertXPath(pXmlDoc, "//page['pass 1']", 1);
+        assertXPath(pXmlDoc, "//body['pass 1']/txt[2]/anchored", 1);
+        assertXPath(pXmlDoc, "//body['pass 
1']/txt[2]/SwParaPortion/SwLineLayout", "height", u"0");
+    }
+
+    // DOCX roundtrip:
+    saveAndReload(u"Office Open XML Text"_ustr);
+
+    // check layout
+    {
+        // Make sure that the second node is a dummy node, and its line has 
height of 0
+        auto pXmlDoc = parseLayoutDump();
+        assertXPath(pXmlDoc, "//page['pass 2']", 1);
+        assertXPath(pXmlDoc, "//body['pass 2']/txt[2]/anchored", 1);
+        // Without a fix, this would fail, because the paragraph was visible 
after reload;
+        // there were two SwLineLayout elements, each with non-0 height.
+        assertXPath(pXmlDoc, "//body['pass 
2']/txt[2]/SwParaPortion/SwLineLayout", "height", u"0");
+    }
+
+    // check DOCX export
+    {
+        auto pXmlDoc = parseExport(u"word/document.xml"_ustr);
+        assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[1]", "p");
+        assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[2]", "tbl");
+        // Without a fix, this would fail, because the third element was the 
wrongly emitted <w:p>
+        assertXPathNodeName(pXmlDoc, "/w:document/w:body/*[3]", "tbl");
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx 
b/sw/source/filter/ww8/attributeoutputbase.hxx
index 8318470f3959..fc99648ff619 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -351,6 +351,9 @@ public:
         const SvxBrushItem* pBrush, // #i120928 export graphic of bullet
         bool isLegal) = 0;
 
+    // Output the floating tables attached to the text node
+    virtual void CheckAndWriteFloatingTables(const SwNode& /*rNode*/) {}
+
 protected:
 
     static void GetNumberPara( OUString& rStr, const SwField& rField );
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 160b7056b6f0..20fbf987a350 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -457,36 +457,23 @@ void DocxAttributeOutput::WriteFloatingTable(ww8::Frame 
const* pParentFrame)
     m_rExport.SetFloatingTableFrame(nullptr);
 }
 
-static void checkAndWriteFloatingTables(DocxAttributeOutput& 
rDocxAttributeOutput)
+void DocxAttributeOutput::CheckAndWriteFloatingTables(const SwNode& rNode)
 {
-    const auto& rExport = rDocxAttributeOutput.GetExport();
+    // floating tables in shapes are not supported: exclude this case
+    if (m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
+        return;
+
     // 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; )
+    for( sal_uInt16 nCnt = m_rExport.m_rDoc.GetSpzFrameFormats()->size(); 
nCnt; )
     {
-        const SwFrameFormat* pFrameFormat = 
(*rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
+        const SwFrameFormat* pFrameFormat = 
(*m_rExport.m_rDoc.GetSpzFrameFormats())[ --nCnt ];
         const SwFormatAnchor& rAnchor = pFrameFormat->GetAnchor();
         const SwNode* pAnchorNode = rAnchor.GetAnchorNode();
 
-        if (!pAnchorNode || ! rExport.m_pCurPam->GetPointNode().GetTextNode())
+        if (!pAnchorNode || !rNode.GetTextNode())
             continue;
 
-        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)
+        if (*pAnchorNode != *rNode.GetTextNode())
             continue;
 
         const SwNodeIndex* pStartNode = 
pFrameFormat->GetContent().GetContentIdx();
@@ -522,21 +509,9 @@ static void 
checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu
             continue;
         }
 
-        // write table to docx: first tables from previous node, then from 
this node.
+        // write table to docx
         ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor());
-        if (bAnchorIsPreviousNode)
-        {
-            aFrames.insert(aFrames.begin(), aFrame);
-        }
-        else
-        {
-            aFrames.push_back(aFrame);
-        }
-    }
-
-    for (const auto& rFrame : aFrames)
-    {
-        rDocxAttributeOutput.WriteFloatingTable(&rFrame);
+        WriteFloatingTable(&aFrame);
     }
 }
 
@@ -597,13 +572,9 @@ sal_Int32 DocxAttributeOutput::StartParagraph(const 
ww8::WW8TableNodeInfo::Point
     }
 
     // look ahead for floating tables that were put into a frame during import
-    // floating tables in shapes are not supported: exclude this case
-    if (!m_rExport.SdrExporter().IsDMLAndVMLDrawingOpen())
-    {
-        // Do this after opening table/row/cell, so floating tables anchored 
at cell start go inside
-        // the cell, not outside.
-        checkAndWriteFloatingTables(*this);
-    }
+    // Do this after opening table/row/cell, so floating tables anchored at 
cell start go inside
+    // the cell, not outside.
+    CheckAndWriteFloatingTables(m_rExport.m_pCurPam->GetPointNode());
 
     // Look up the "sdt end before this paragraph" property early, when it
     // would normally arrive, it would be too late (would be after the
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index c09fc2f646b3..dd215d9aa7fe 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -434,6 +434,9 @@ public:
         const SvxBrushItem* pBrush,
         bool isLegal ) override;
 
+    /// Output the floating tables attached to the text node
+    virtual void CheckAndWriteFloatingTables(const SwNode& rNode) override;
+
     void WriteField_Impl(const SwField* pField, ww::eField eType,
             const OUString& rFieldCmd, FieldFlags nMode,
             OUString const* pBookmarkName = nullptr);
diff --git a/sw/source/filter/ww8/docxexport.cxx 
b/sw/source/filter/ww8/docxexport.cxx
index a4e80227666e..dcc1b0796e5e 100644
--- a/sw/source/filter/ww8/docxexport.cxx
+++ b/sw/source/filter/ww8/docxexport.cxx
@@ -556,14 +556,6 @@ void DocxExport::CollectFloatingTables()
             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;
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx 
b/sw/source/filter/ww8/wrtw8nds.cxx
index b5796ea19db9..9fdac1a4867e 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -2328,6 +2328,13 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode 
)
 {
     SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" );
 
+    if (IsDummyFloattableAnchor(rNode))
+    {
+        // Emit their floating tables
+        AttrOutput().CheckAndWriteFloatingTables(rNode);
+        return;
+    }
+
     SwWW8AttrIter aWatermarkAttrIter( *this, rNode );
 
     // export inline heading
@@ -3757,11 +3764,7 @@ void MSWordExportBase::OutputContentNode( SwContentNode& 
rNode )
     switch ( rNode.GetNodeType() )
     {
         case SwNodeType::Text:
-            // Skip dummy anchors: the next node will emit their floating 
tables.
-            if (!IsDummyFloattableAnchor(*rNode.GetTextNode()))
-            {
-                OutputTextNode(*rNode.GetTextNode());
-            }
+            OutputTextNode(*rNode.GetTextNode());
             break;
         case SwNodeType::Grf:
             OutputGrfNode( *rNode.GetGrfNode() );

Reply via email to