crashtesting: fix PDF export of fdo83057-2.docx

Loading this document and laying it out resulted in an assertion failure
since commit de90c192cb8f1f03a4028493d8bfe9a127a76b2a (sw content
controls, plain text: enable DOCX filter with data binding, 2022-09-19).

Writer has two relevant invariants: First, if a draw shape is anchored
into a header, then the "master" shape (typically on the first page) and
its "virtual" shapes (on other pages) have their indexes / ZOrders set
so that the master shape has the highest index. The second is that in
case a draw+fly format is paired into a "textbox", then the fly index is
always the draw index + 1. The assert fails because in this case the
virtual draw shape's index is not smaller than the master one.

What seems to happen here is that first SwDrawContact::ConnectToLayout()
iterates the frames of the text node in the header, and happens to first
visit page 1 and then page 2. So the master SdrObjects go to the first
page, the virtual ones go to the second page. Then later
SwDrawContact::ConnectToLayout() is called again, which removes the
master and the virtual draw SdrObject from the layout and the virtual
draw SdrObject from the model. Then it visits the text frames of the
header text node again, but this time it happens to find the second
page, and only then the first page (for reasons unclear to me, this
didn't happen before the above commit). The result is that both
invariants are failing: the master draw+fly is no longer on the same
page and the indexes are also out of order, so one of the pages don't
show shape text for the shape from the header.

Fix the problem by two tweaks:

- Improve SwDrawContact::ConnectToLayout(), so that in case it just
  appends the draw object to the layout for the master, then it adjusts
  its ZOrder so the matching fly will be the draw one + 1.

  This was working in the virtual case already, since there we call
  AddVirtObj() already, which calls SwDrawVirtObj::AddToDrawingPage(),
  which already knows how to maintain this invariant.

  This fixes master draw SdrObjects to be directly before their fly
  counterparts, even if the page order changes.

- Improve SwDrawVirtObj::AddToDrawingPage(), so that in case the virtual
  draw SdrObject finds its fly, but that fly has an incorrect index (due
  to the changed page visit order), then we first fix up the fly and
  only then set the index of the virtual draw SdrObject.

The result is a doc model like this:

<SdrObject ptr="0x52ecd30" symbol="13SwDrawVirtObj" name="" title="" description="" nOrdNum="0" aOutRect="-16000, -16000, 0, 0"/>
<SwVirtFlyDrawObj ptr="0x4e4bfa0" fly-frame="5">
  <SdrObject ptr="0x4e4bfa0" symbol="16SwVirtFlyDrawObj" name="" title="" description="" nOrdNum="1" aOutRect="0, 0, 0, 0"/>
<SdrObject ptr="0x4ea4300" symbol="17SdrObjCustomShape" name="Rectangle 2" title="" description="" nOrdNum="2" aOutRect="10418, 2441, 1775, 485">
<SwVirtFlyDrawObj ptr="0x52ec630" fly-frame="14">
  <SdrObject ptr="0x52ec630" symbol="16SwVirtFlyDrawObj" name="" title="" description="" nOrdNum="3" aOutRect="0, 0, 0, 0"/>

I.e. each draw SdrObject is followed by its fly and the masters have the
highest indexes.

This all stems from the behavior that 1) re-connecting a draw contact to
the layout removes the draw shapes but not the flys from the layout, 2)
the order of frames in an SwIterator visiting is not something we can
depend on and 3) textboxes are implemented using a pair of draw+fly
formats.

Change-Id: Ia03f0ec70cf67626660349accc63770fe2afef5d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/140688
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
diff --git a/sw/qa/core/draw/data/sdt-textbox-header.docx b/sw/qa/core/draw/data/sdt-textbox-header.docx
new file mode 100644
index 0000000..a8b2d96
--- /dev/null
+++ b/sw/qa/core/draw/data/sdt-textbox-header.docx
Binary files differ
diff --git a/sw/qa/core/draw/draw.cxx b/sw/qa/core/draw/draw.cxx
index c251480..feb347a 100644
--- a/sw/qa/core/draw/draw.cxx
+++ b/sw/qa/core/draw/draw.cxx
@@ -141,6 +141,17 @@ CPPUNIT_TEST_FIXTURE(SwCoreDrawTest, testTdf107727FrameBorder)
    CPPUNIT_ASSERT_EQUAL(Color(0x0000ff), Color(ColorTransparency, aBorder.Color));
}

CPPUNIT_TEST_FIXTURE(SwCoreDrawTest, testSdtTextboxHeader)
{
    // Given a 2 page document, same header on both pages, content control in the header and
    // shape+fly pair (textbox) anchored in the same header:
    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "sdt-textbox-header.docx";

    // When loading that document, then make sure that layout doesn't fail with an assertion because
    // the "master SdrObj should have the highest index" invariant doesn't hold:
    mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument", {});
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/draw/dcontact.cxx b/sw/source/core/draw/dcontact.cxx
index 369c46e4..fe19106 100644
--- a/sw/source/core/draw/dcontact.cxx
+++ b/sw/source/core/draw/dcontact.cxx
@@ -1954,6 +1954,36 @@ void SwDrawContact::ConnectToLayout( const SwFormatAnchor* pAnch )
                        {
                            // append 'master' drawing object
                            pAnchorFrameOfMaster = pFrame;

                            const SwFrameFormat* pFlyFormat = nullptr;
                            if (!maAnchoredDrawObj.GetDrawObj()->IsGroupObject())
                            {
                                pFlyFormat = SwTextBoxHelper::getOtherTextBoxFormat(GetFormat(), RES_DRAWFRMFMT);
                            }

                            if (pFlyFormat)
                            {
                                // This is a master draw object and it has an associated fly format.
                                // See if a fly frame is already inserted to the layout: if so, this
                                // master draw object should be ordered directly before the fly one.
                                if (const SwSortedObjs* pObjs = pFrame->GetDrawObjs())
                                {
                                    for (const SwAnchoredObject* pAnchoredObj : *pObjs)
                                    {
                                        if (&pAnchoredObj->GetFrameFormat() == pFlyFormat)
                                        {
                                            SdrPage* pDrawPage = pAnchoredObj->GetDrawObj()->getSdrPageFromSdrObject();
                                            if (pDrawPage)
                                            {
                                                sal_uInt32 nOrdNum = pAnchoredObj->GetDrawObj()->GetOrdNum();
                                                pDrawPage->SetObjectOrdNum(maAnchoredDrawObj.GetDrawObj()->GetOrdNumDirect(), nOrdNum);
                                                break;
                                            }
                                        }
                                    }
                                }
                            }

                            pFrame->AppendDrawObj( maAnchoredDrawObj );
                        }
                        else
@@ -2328,6 +2358,17 @@ void SwDrawVirtObj::AddToDrawingPage(SwFrame const& rAnchorFrame)
                if (&pAnchoredObj->GetFrameFormat() == pFlyFormat)
                {
                    assert(dynamic_cast<SwFlyFrame const*>(pAnchoredObj));

                    if (pAnchoredObj->GetDrawObj()->GetOrdNum() >= GetReferencedObj().GetOrdNum())
                    {
                        // This virtual draw object has an associated fly one, but the fly's index
                        // is not below the masters, fix it up.
                        if (pDrawPg)
                        {
                            pDrawPg->SetObjectOrdNum(pAnchoredObj->GetDrawObj()->GetOrdNumDirect(), GetReferencedObj().GetOrdNum());
                        }
                    }

                    nOrdNum = pAnchoredObj->GetDrawObj()->GetOrdNum();
                    // the master SdrObj should have the highest index
                    assert(nOrdNum < GetReferencedObj().GetOrdNum());
diff --git a/sw/source/core/draw/dflyobj.cxx b/sw/source/core/draw/dflyobj.cxx
index 467df88..9e2b96c 100644
--- a/sw/source/core/draw/dflyobj.cxx
+++ b/sw/source/core/draw/dflyobj.cxx
@@ -1302,4 +1302,17 @@ bool SwVirtFlyDrawObj::IsTextBox() const
    return SwTextBoxHelper::isTextBox(GetFormat(), RES_FLYFRMFMT, this);
}

void SwVirtFlyDrawObj::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SwVirtFlyDrawObj"));
    (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
    (void)xmlTextWriterWriteAttribute(
        pWriter, BAD_CAST("fly-frame"),
        BAD_CAST(OString::number(m_pFlyFrame->GetFrameId()).getStr()));

    SdrVirtObj::dumpAsXml(pWriter);

    (void)xmlTextWriterEndElement(pWriter);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/inc/dflyobj.hxx b/sw/source/core/inc/dflyobj.hxx
index 3858f89..db853d2 100644
--- a/sw/source/core/inc/dflyobj.hxx
+++ b/sw/source/core/inc/dflyobj.hxx
@@ -139,6 +139,7 @@ public:
    virtual bool HasLimitedRotation() const override;

    virtual bool IsTextBox() const override;
    void dumpAsXml(xmlTextWriterPtr pWriter) const override;
};

#endif