tdf#121509 DOCX export: fix corrupt shape anchoring in textbox

MSO doesn't support shapes anchored to character in a textbox.
Convert these shapes by re-anchoring them to the anchor
point of the textbox (also recalculating their positions in
simple cases), so Word can now open the exported document.

Change-Id: I28b244975981d69c50e5d4a46249918af089bae5
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117163
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/extras/layout/data/Tdf121509.odt b/sw/qa/extras/layout/data/Tdf121509.odt
new file mode 100644
index 0000000..856f60c
--- /dev/null
+++ b/sw/qa/extras/layout/data/Tdf121509.odt
Binary files differ
diff --git a/sw/qa/extras/layout/layout2.cxx b/sw/qa/extras/layout/layout2.cxx
index 72cdc90..1db5f1e 100644
--- a/sw/qa/extras/layout/layout2.cxx
+++ b/sw/qa/extras/layout/layout2.cxx
@@ -49,6 +49,9 @@
#include <svx/svdpage.hxx>
#include <svx/svdotext.hxx>
#include <dcontact.hxx>
#include <frameformats.hxx>
#include <fmtcntnt.hxx>
#include <unotextrange.hxx>

constexpr OUStringLiteral DATA_DIRECTORY = u"/sw/qa/extras/layout/data/";

@@ -1442,6 +1445,61 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf127118)
    assertXPath(pXmlDoc, "/root/page[2]/body/tab/row[1]/cell[1]/txt[1]", "WritingMode", "VertBTLR");
}

CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf121509)
{
    auto pDoc = createSwDoc(DATA_DIRECTORY, "Tdf121509.odt");
    CPPUNIT_ASSERT(pDoc);

    // Get all shape/frame formats
    auto vFrameFormats = pDoc->GetSpzFrameFormats();
    // Get the textbox
    auto xTextFrame = SwTextBoxHelper::getUnoTextFrame(getShape(1));
    // Get The triangle
    auto pTriangleShapeFormat = vFrameFormats->GetFormat(2);
    CPPUNIT_ASSERT(xTextFrame);
    CPPUNIT_ASSERT(pTriangleShapeFormat);

    // Get the position inside the textbox
    auto xTextContentStart = xTextFrame->getText()->getStart();
    SwUnoInternalPaM aCursor(*pDoc);
    CPPUNIT_ASSERT(sw::XTextRangeToSwPaM(aCursor, xTextContentStart));

    // Put the triangle into the textbox
    SwFormatAnchor aNewAnch(pTriangleShapeFormat->GetAnchor());
    aNewAnch.SetAnchor(aCursor.Start());
    CPPUNIT_ASSERT(pTriangleShapeFormat->SetFormatAttr(aNewAnch));

    // Reload (docx)
    auto aTemp = utl::TempFile();
    save("Office Open XML Text", aTemp);

    // The second part: check if the reloaded doc has flys inside a fly
    uno::Reference<lang::XComponent> xComponent
        = loadFromDesktop(aTemp.GetURL(), "com.sun.star.text.TextDocument");
    uno::Reference<text::XTextDocument> xTextDoc(xComponent, uno::UNO_QUERY);
    auto pTextDoc = dynamic_cast<SwXTextDocument*>(xTextDoc.get());
    CPPUNIT_ASSERT(pTextDoc);
    auto pSecondDoc = pTextDoc->GetDocShell()->GetDoc();
    auto pSecondFormats = pSecondDoc->GetSpzFrameFormats();

    bool bFlyInFlyFound = false;
    for (auto secondformat : *pSecondFormats)
    {
        auto& pNd = secondformat->GetAnchor().GetContentAnchor()->nNode.GetNode();
        if (pNd.FindFlyStartNode())
        {
            // So there is a fly inside another -> problem.
            bFlyInFlyFound = true;
            break;
        }
    }
    // Drop the tempfile
    aTemp.CloseStream();

    // With the fix this cannot be true, if it is, that means Word unable to read the file..
    CPPUNIT_ASSERT_MESSAGE("Corrupt exported docx file!", !bFlyInFlyFound);
}

CPPUNIT_TEST_FIXTURE(SwLayoutWriter2, testTdf134685)
{
    createSwDoc(DATA_DIRECTORY, "tdf134685.docx");
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index 7d185f5..19dc42a 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -6082,7 +6082,7 @@ void DocxAttributeOutput::WritePostponedDMLDrawing()
    m_pPostponedOLEs = std::move(pPostponedOLEs);
}

void DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame &rFrame, const Point& /*rNdTopLeft*/ )
void DocxAttributeOutput::WriteFlyFrame(const ww8::Frame& rFrame)
{
    m_pSerializer->mark(Tag_OutputFlyFrame);

@@ -6254,6 +6254,71 @@ void DocxAttributeOutput::OutputFlyFrame_Impl( const ww8::Frame &rFrame, const P
    m_pSerializer->mergeTopMarks(Tag_OutputFlyFrame);
}

void DocxAttributeOutput::OutputFlyFrame_Impl(const ww8::Frame& rFrame, const Point& /*rNdTopLeft*/)
{
    /// The old OutputFlyFrame_Impl() moved to WriteFlyFrame().
    /// Now if a frame anchored inside another frame, it will
    /// not be exported immediately, because OOXML does not
    /// support that feature, instead it postponed and exported
    /// later when the original shape closed.

    if (rFrame.GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR
        || rFrame.IsInline())
    {
        m_nEmbedFlyLevel++;
        WriteFlyFrame(rFrame);
        m_nEmbedFlyLevel--;
        return;
    }

    if (m_nEmbedFlyLevel == 0)
    {
        if (m_vPostponedFlys.empty())
        {
            m_nEmbedFlyLevel++;
            WriteFlyFrame(rFrame);
            m_nEmbedFlyLevel--;
        }
        else
            for (auto it = m_vPostponedFlys.begin(); it != m_vPostponedFlys.end();)
            {
                m_nEmbedFlyLevel++;
                WriteFlyFrame(*it);
                it = m_vPostponedFlys.erase(it);
                m_nEmbedFlyLevel--;
            }
    }
    else
    {
        bool bFound = false;
        for (const auto& i : m_vPostponedFlys)
        {
            if (i.RefersToSameFrameAs(rFrame))
            {
                bFound = true;
                break;
            }
        }
        if (!bFound)
        {
            if (auto pParentFly = rFrame.GetContentNode()->GetFlyFormat())
            {
                auto aHori(rFrame.GetFrameFormat().GetHoriOrient());
                aHori.SetPos(aHori.GetPos() + pParentFly->GetHoriOrient().GetPos());
                auto aVori(rFrame.GetFrameFormat().GetVertOrient());
                aVori.SetPos(aVori.GetPos() + pParentFly->GetVertOrient().GetPos());

                const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aHori);
                const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(aVori);
                const_cast<SwFrameFormat&>(rFrame.GetFrameFormat()).SetFormatAttr(pParentFly->GetAnchor());

                m_vPostponedFlys.push_back(rFrame);
            }

        }
    }
}

void DocxAttributeOutput::WriteOutliner(const OutlinerParaObject& rParaObj)
{
    const EditTextObject& rEditObj = rParaObj.GetTextObject();
@@ -9993,6 +10058,7 @@ DocxAttributeOutput::DocxAttributeOutput( DocxExport &rExport, const FSHelperPtr
      m_sFieldBkm( ),
      m_nNextBookmarkId( 0 ),
      m_nNextAnnotationMarkId( 0 ),
      m_nEmbedFlyLevel(0),
      m_pCurrentFrame( nullptr ),
      m_bParagraphOpened( false ),
      m_bParagraphFrameOpen( false ),
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx b/sw/source/filter/ww8/docxattributeoutput.hxx
index b068818..7f880cc 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -718,6 +718,7 @@ private:
    void WritePostponedOLE();
    void WritePostponedDMLDrawing();
    void WritePostponedCustomShape();
    void WriteFlyFrame(const ww8::Frame& rFrame);

    void WriteSdtBlock(sal_Int32& nSdtPrToken,
                       rtl::Reference<sax_fastparser::FastAttributeList>& pSdtPrTokenChildren,
@@ -802,6 +803,14 @@ private:

    OUString m_sRawText;

    /// The first frame (anchored to the main text) is 0.
    /// The second frame what is anchored to the previous in, is 1
    /// The third anchored inside the second is the 2 etc.
    sal_uInt32 m_nEmbedFlyLevel;

    /// Stores the flys what are anchored inside a fly
    std::vector<ww8::Frame> m_vPostponedFlys;

    /// Bookmarks to output
    std::vector<OUString> m_rBookmarksStart;
    std::vector<OUString> m_rBookmarksEnd;