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() );