sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport25.cxx                          |   38 
++++++----
 sw/source/filter/ww8/wrtw8nds.cxx                                   |   17 ++++
 3 files changed, 43 insertions(+), 12 deletions(-)

New commits:
commit a8abe02aeab3722ad275fac8b9969e9f26cb958b
Author:     Justin Luth <[email protected]>
AuthorDate: Tue Jan 27 10:04:09 2026 -0500
Commit:     Miklos Vajna <[email protected]>
CommitDate: Thu Feb 5 09:27:22 2026 +0100

    tdf#170516 docx export: write flies before w:sdt starts
    
    If a run Pos start a runSdt content control (CH_TXTATR_BREAKWORD)
    AND it also anchors a floating shape,
    then the shape was being placed inside the sdtContent,
    and if the Sdt was plainText,
    then MS Word MIGHT consider that document to be corrupt.
    
    This patch follows plan B (see patchset notes).
    Plan A didn't pass CI unit tests (but it did pass locally...)
    
    For whatever reason, tdf#78333's original fix for corrupt SDTs
    skipped this situation when it happened in a floating table
            && !rNode.GetFlyFormat()
            ...
            {
                bPostponeWritingText = true ;
            }
    but the corruption is reported in that case too...
    
    (Floating shapes cannot anchor other flies,
    so all their images are inline [they have their own ].
    so only in a floating table is this situation likely to arise.
    Well, at least natively. ODT->DOCX can also trigger this.)
    
    make CppunitTest_sw_ooxmlexport25 \
        CPPUNIT_TEST_NAME=testTdf170516_drawingBeforePlainText
    
    Change-Id: I951fcef33fe54d71736f3036a43087f340927727
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198632
    Reviewed-by: Justin Luth <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git 
a/sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx 
b/sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx
new file mode 100644
index 000000000000..fa054c0c9ac3
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf170516_drawingBeforePlainText.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
index 9a2bf41b986a..c3c770857be3 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
@@ -147,18 +147,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf165478_bottomAligned, 
"tdf165478_bottomAligned.d
     CPPUNIT_ASSERT_EQUAL(sal_Int32(1887), nFlyTop);
 }
 
-CPPUNIT_TEST_FIXTURE(Test, testTdf170438_dropdown)
-{
-    createSwDoc("tdf170438_dropdown.odt");
-
-    saveAndReload(TestFilter::DOCX);
-
-    xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
-    // MS Word reports document as corrupt if displayText is empty
-    assertXPath(pXmlDoc, "//w:listItem[1]", "displayText", u" ");
-    assertXPath(pXmlDoc, "//w:listItem[1]", "value", u""); // value may be 
empty
-}
-
 CPPUNIT_TEST_FIXTURE(Test, testTdf165359_SdtWithInline)
 {
     createSwDoc("tdf165359_SdtWithInline.docx");
@@ -181,6 +169,32 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf165359_SdtWithDrawing)
     assertXPath(pXmlDoc, "//w:sdtPr/w:text", 0);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf170438_dropdown)
+{
+    createSwDoc("tdf170438_dropdown.odt");
+
+    saveAndReload(TestFilter::DOCX);
+
+    xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
+    // MS Word reports document as corrupt if displayText is empty
+    assertXPath(pXmlDoc, "//w:listItem[1]", "displayText", u" ");
+    assertXPath(pXmlDoc, "//w:listItem[1]", "value", u""); // value may be 
empty
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf170516_drawingBeforePlainText)
+{
+    createSwDoc("tdf170516_drawingBeforePlainText.docx");
+
+    saveAndReload(TestFilter::DOCX);
+
+    xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
+    assertXPath(pXmlDoc, "//w:sdt", 1); // only one sdt
+    assertXPath(pXmlDoc, "//w:tc/w:p/w:sdt", 1); // and it is inside a cell
+
+    assertXPath(pXmlDoc, "//mc:AlternateContent", 1); // only one drawing
+    assertXPath(pXmlDoc, "//w:tc/w:p/w:r/mc:AlternateContent", 1); // and it 
is not inside the sdt
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf170389_manyTabstops)
 {
     createSwDoc("tdf170389_manyTabstops.odt");
diff --git a/sw/source/filter/ww8/wrtw8nds.cxx 
b/sw/source/filter/ww8/wrtw8nds.cxx
index f795be4b6b0c..1912d25e7634 100644
--- a/sw/source/filter/ww8/wrtw8nds.cxx
+++ b/sw/source/filter/ww8/wrtw8nds.cxx
@@ -2512,9 +2512,11 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode 
)
             // Don't redline content-controls--Word doesn't do them.
             SwTextAttr* pAttr = rNode.GetTextAttrAt(nCurrentPos, 
RES_TXTATR_CONTENTCONTROL,
                                                     
sw::GetTextAttrMode::Default);
+            bool bIsStartOfContentControl = false;
             if (pAttr && pAttr->GetStart() == nCurrentPos)
             {
                 pRedlineData = nullptr;
+                bIsStartOfContentControl = true;
             }
 
             sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nCurrentPos );
@@ -2578,6 +2580,21 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode 
)
 
             OUString aSymbolFont;
             sal_Int32 nLen = nNextAttr - nCurrentPos;
+
+            if (!bPostponeWritingText && bIsStartOfContentControl
+                && nStateOfFlyFrame == FLY_PROCESSED
+                && GetExportFormat() == MSWordExportBase::ExportFormat::DOCX)
+            {
+                // FLY_PROCESSED: there is at least 1 fly already written
+
+                // assurance: this will not duplicate what is done for fields 
below...
+                assert(bTextAtr); // because SwTextContentControl always 
SetHasDummyChar(true)
+
+                // write flys in a separate run before Sdt content control
+                AttrOutput().EndRun(&rNode, nCurrentPos, /*nLen=*/-1, 
/*bLastRun=*/false);
+                AttrOutput().StartRun(pRedlineData, nCurrentPos, 
bSingleEmptyRun);
+            }
+
             if ( !bTextAtr && nLen )
             {
                 sal_Unicode ch = aStr[nCurrentPos];

Reply via email to