tdf#140158 tdf#138598 tdf#140598 sw: fix sync of AS_CHAR textboxes

Textboxes anchored "As Character" fell apart, when
typing before some characters or inserting a page break.

By fixing that, the tdf#138598 bug also have fixed which
was a regression from commit b6850bbe95418ecfde404be1696548f18d200c9b
(tdf#106153 sw compatibility: fix textboxes exceeding the page).

In addition, tdf140598 is also fixed, which was
a regression from commit c96c386c5db45dc4d5e358915caad7474e373068
(tdf#136516 add positioning to SwTextBoxHelper::syncProperty()).

Change-Id: Ifeadd8b2055ce52a019d651369ca41185de7bbe3
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111338
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
Signed-off-by: Xisco Fauli <xiscofauli@libreoffice.org>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111796
Tested-by: Jenkins
diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx
index 45d8a6c..2043b1f 100644
--- a/sw/inc/textboxhelper.hxx
+++ b/sw/inc/textboxhelper.hxx
@@ -16,6 +16,8 @@

#include <com/sun/star/uno/Any.h>
#include <com/sun/star/uno/Type.h>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <svx/swframetypes.hxx>

#include "swdllapi.h"

@@ -71,6 +73,9 @@ public:
    static void getProperty(SwFrameFormat const* pShape, sal_uInt16 nWID, sal_uInt8 nMemberID,
                            css::uno::Any& rValue);

    /// There are two types of enum of anchor type, so this function maps this.
    static css::text::TextContentAnchorType mapAnchorType(const RndStdIds& rAnchorID);

    /// Similar to syncProperty(), but used by the internal API (e.g. for UI purposes).
    static void syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& rSet);

diff --git a/sw/qa/extras/uiwriter/data3/AsCharTxBxTest.docx b/sw/qa/extras/uiwriter/data3/AsCharTxBxTest.docx
new file mode 100755
index 0000000..7603b80
--- /dev/null
+++ b/sw/qa/extras/uiwriter/data3/AsCharTxBxTest.docx
Binary files differ
diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx b/sw/qa/extras/uiwriter/uiwriter3.cxx
index e568e695..018a96a 100644
--- a/sw/qa/extras/uiwriter/uiwriter3.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter3.cxx
@@ -725,6 +725,58 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf134253)
    CPPUNIT_ASSERT_EQUAL(6, getPages());
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, TestAsCharTextBox)
{
    // Releated tickets:
    // tdf#138598 Replace vertical alignment of As_char textboxes in footer
    // tdf#140158 Remove horizontal positioning of As_char textboxes, because
    // the anchor moving does the same for it.

    load(DATA_DIRECTORY, "AsCharTxBxTest.docx");
    SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
    CPPUNIT_ASSERT(pTextDoc);

    // Add 3x tab to the doc
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_TAB);
    Scheduler::ProcessEventsToIdle();

    auto pExportDump = parseLayoutDump();
    CPPUNIT_ASSERT(pExportDump);

    // Check if the texbox fallen apart due to the tabs
    const double nLeftSideOfShape1
        = getXPath(pExportDump, "/root/page/body/txt/anchored/SwAnchoredDrawObject/bounds", "left")
              .toDouble();
    const double nLeftSideOfTxBx1
        = getXPath(pExportDump, "/root/page/body/txt/anchored/fly/infos/bounds", "left").toDouble();

    CPPUNIT_ASSERT(nLeftSideOfShape1 < nLeftSideOfTxBx1);

    // Another test is for the tdf#138598: Check footer textbox
    const double nLeftSideOfShape2
        = getXPath(pExportDump, "/root/page[2]/footer/txt/anchored/SwAnchoredDrawObject/bounds",
                   "left")
              .toDouble();
    const double nLeftSideOfTxBx2
        = getXPath(pExportDump, "/root/page[2]/footer/txt/anchored/fly/infos/bounds", "left")
              .toDouble();

    CPPUNIT_ASSERT(nLeftSideOfShape2 < nLeftSideOfTxBx2);

    const double nTopSideOfShape2
        = getXPath(pExportDump, "/root/page[2]/footer/txt/anchored/SwAnchoredDrawObject/bounds",
                   "top")
              .toDouble();
    const double nTopSideOfTxBx2
        = getXPath(pExportDump, "/root/page[2]/footer/txt/anchored/fly/infos/bounds", "top")
              .toDouble();

    CPPUNIT_ASSERT(nTopSideOfShape2 < nTopSideOfTxBx2);
    // Without the fix in place the two texboxes has been fallen apart, and  asserts will broken.
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf76636)
{
    load(DATA_DIRECTORY, "tdf76636.doc");
diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx
index 4d44d1c..5257d44 100644
--- a/sw/source/core/doc/textboxhelper.cxx
+++ b/sw/source/core/doc/textboxhelper.cxx
@@ -33,6 +33,7 @@
#include <editeng/memberids.h>
#include <svx/svdoashp.hxx>
#include <svx/svdpage.hxx>
#include <svx/swframetypes.hxx>
#include <svl/itemiter.hxx>
#include <comphelper/sequenceashashmap.hxx>
#include <sal/log.hxx>
@@ -686,10 +687,39 @@ void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_u
                    if (aValue.get<text::TextContentAnchorType>()
                        == text::TextContentAnchorType::TextContentAnchorType_AS_CHARACTER)
                    {
                        xPropertySet->setPropertyValue(
                            UNO_NAME_ANCHOR_TYPE,
                            uno::makeAny(
                                text::TextContentAnchorType::TextContentAnchorType_AT_CHARACTER));
                        if (const auto aPos = pShape->GetAnchor().GetContentAnchor())
                        {
                            xPropertySet->setPropertyValue(
                                UNO_NAME_ANCHOR_TYPE,
                                uno::makeAny(text::TextContentAnchorType::
                                                 TextContentAnchorType_AT_CHARACTER));
                            xPropertySet->setPropertyValue(
                                UNO_NAME_HORI_ORIENT_RELATION,
                                uno::makeAny(text::RelOrientation::CHAR));

                            auto pAnch = pFormat->GetAnchor();
                            pAnch.SetAnchor(pShape->GetAnchor().GetContentAnchor());
                            tools::Rectangle aRect(getTextRectangle(pShape, false));

                            SwFormatHoriOrient aNewHOri(pFormat->GetHoriOrient());
                            aNewHOri.SetPos(aRect.getX());

                            SwFormatVertOrient aNewVOri(pFormat->GetVertOrient());
                            aNewVOri.SetPos(aRect.getY());

                            pFormat->SetFormatAttr(pAnch);
                            // tdf#140598: Do not apply wrong rectangle position.
                            if (aRect.TopLeft() != Point(0, 0))
                            {
                                pFormat->SetFormatAttr(aNewHOri);
                                pFormat->SetFormatAttr(aNewVOri);
                            }
                            else
                                SAL_WARN("sw.core",
                                         "SwTextBoxHelper::syncProperty: Repositioning failed!");
                        }

                        return;
                    }
                    else // Otherwise copy the anchor type of the shape
                    {
@@ -699,11 +729,55 @@ void SwTextBoxHelper::syncProperty(SwFrameFormat* pShape, sal_uInt16 nWID, sal_u
                    if (aValue.get<text::TextContentAnchorType>()
                        == text::TextContentAnchorType::TextContentAnchorType_AT_PAGE)
                    {
                        xPropertySet->setPropertyValue(
                            UNO_NAME_ANCHOR_PAGE_NO,
                            uno::makeAny(pShape->GetAnchor().GetPageNum()));
                        if (pShape->GetAnchor().GetPageNum())
                            xPropertySet->setPropertyValue(
                                UNO_NAME_ANCHOR_PAGE_NO,
                                uno::makeAny(pShape->GetAnchor().GetPageNum()));
                        else
                        {
                            SAL_WARN("sw.core", "SwTextBoxHelper::syncProperty: Invalid Page Num!");
                            return;
                        }
                    }

                    // At-Content Anchors have to be synced:
                    if (aValue.get<text::TextContentAnchorType>()
                            == text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH
                        || aValue.get<text::TextContentAnchorType>()
                               == text::TextContentAnchorType::TextContentAnchorType_AT_CHARACTER)
                    {
                        // If the shape has content...
                        if (auto aPos = pShape->GetAnchor().GetContentAnchor())
                        {
                            SwFormatAnchor aAnch(pFormat->GetAnchor());
                            // ...set it for the textframe too.
                            aAnch.SetAnchor(aPos);
                            pFormat->SetFormatAttr(aAnch);
                        }
                        else
                            SAL_WARN("sw.core",
                                     "SwTextBoxHelper::syncProperty: Anchor without content!");
                    }
                    // And the repositioning:
                    if (pShape->GetAnchor().GetAnchorId() != RndStdIds::FLY_AS_CHAR)
                    {
                        tools::Rectangle aRect(getTextRectangle(pShape, false));

                        // tdf#140598: Do not apply wrong rectangle position.
                        if (aRect.TopLeft() != Point(0, 0))
                        {
                            SwFormatHoriOrient aNewHOri(pShape->GetHoriOrient());
                            aNewHOri.SetPos(aNewHOri.GetPos() + aRect.getX());
                            SwFormatVertOrient aNewVOri(pShape->GetVertOrient());
                            aNewVOri.SetPos(aNewVOri.GetPos() + aRect.getY());

                            pFormat->SetFormatAttr(aNewHOri);
                            pFormat->SetFormatAttr(aNewVOri);
                        }
                        else
                            SAL_WARN("sw.core",
                                     "SwTextBoxHelper::syncProperty: Repositioning failed!");
                    }
                    return;
                }
                break;
@@ -834,12 +908,41 @@ void SwTextBoxHelper::restoreLinks(std::set<ZSortFly>& rOld, std::vector<SwFrame
    }
}

text::TextContentAnchorType SwTextBoxHelper::mapAnchorType(const RndStdIds& rAnchorID)
{
    text::TextContentAnchorType aAnchorType;
    switch (rAnchorID)
    {
        case RndStdIds::FLY_AS_CHAR:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AS_CHARACTER;
            break;
        case RndStdIds::FLY_AT_CHAR:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_CHARACTER;
            break;
        case RndStdIds::FLY_AT_PARA:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH;
            break;
        case RndStdIds::FLY_AT_PAGE:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PAGE;
            break;
        case RndStdIds::FLY_AT_FLY:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_FRAME;
            break;
        default:
            aAnchorType = text::TextContentAnchorType::TextContentAnchorType_AT_PARAGRAPH;
            SAL_WARN("sw.core", "SwTextBoxHelper::mapAnchorType: Unknown AnchorType!");
            break;
    }
    return aAnchorType;
}

void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& rSet)
{
    SwFrameFormat* pFormat = getOtherTextBoxFormat(&rShape, RES_DRAWFRMFMT);
    if (!pFormat)
        return;

    const bool bInlineAnchored = rShape.GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR;
    SfxItemSet aTextBoxSet(pFormat->GetDoc()->GetAttrPool(), aFrameFormatSetRange);

    SfxItemIter aIter(rSet);
@@ -861,6 +964,13 @@ void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& 
        {
            case RES_VERT_ORIENT:
            {
                // The new position can be with anchor changing so sync it!
                const text::TextContentAnchorType aNewAnchorType
                    = mapAnchorType(rShape.GetAnchor().GetAnchorId());
                syncProperty(&rShape, RES_ANCHOR, MID_ANCHOR_ANCHORTYPE, uno::Any(aNewAnchorType));
                if (bInlineAnchored)
                    return;

                auto& rOrient = static_cast<const SwFormatVertOrient&>(*pItem);
                SwFormatVertOrient aOrient(rOrient);

@@ -885,6 +995,8 @@ void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& 
            case RES_HORI_ORIENT:
            {
                auto& rOrient = static_cast<const SwFormatHoriOrient&>(*pItem);
                if (bInlineAnchored)
                    return;
                SwFormatHoriOrient aOrient(rOrient);

                tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false);
@@ -910,11 +1022,14 @@ void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& 
                tools::Rectangle aRect = getTextRectangle(&rShape, /*bAbsolute=*/false);
                if (!aRect.IsEmpty())
                {
                    aVertOrient.SetPos(aVertOrient.GetPos() + aRect.getY());
                    aTextBoxSet.Put(aVertOrient);
                    if (!bInlineAnchored)
                    {
                        aVertOrient.SetPos(aVertOrient.GetPos() + aRect.getY());
                        aHoriOrient.SetPos(aHoriOrient.GetPos() + aRect.getX());

                    aHoriOrient.SetPos(aHoriOrient.GetPos() + aRect.getX());
                    aTextBoxSet.Put(aHoriOrient);
                        aTextBoxSet.Put(aVertOrient);
                        aTextBoxSet.Put(aHoriOrient);
                    }

                    aSize.SetWidth(aRect.getWidth());
                    aSize.SetHeight(aRect.getHeight());
diff --git a/sw/source/core/text/porfly.cxx b/sw/source/core/text/porfly.cxx
index 24961c0..cafa70c 100644
--- a/sw/source/core/text/porfly.cxx
+++ b/sw/source/core/text/porfly.cxx
@@ -352,20 +352,30 @@ void SwFlyCntPortion::SetBase( const SwTextFrame& rFrame, const Point &rBase,
            // is relative to the print area of the anchor text frame.
            tools::Rectangle aTextRectangle = SwTextBoxHelper::getTextRectangle(pShape);

            SwFormatHoriOrient aHori(pTextBox->GetHoriOrient());
            aHori.SetHoriOrient(css::text::HoriOrientation::NONE);
            sal_Int32 nLeft = aTextRectangle.getX() - rFrame.getFrameArea().Left()
                              - rFrame.getFramePrintArea().Left();
            aHori.SetPos(nLeft);

            const auto aPos(pShape->GetAnchor().GetContentAnchor());
            SwFormatVertOrient aVert(pTextBox->GetVertOrient());
            aVert.SetVertOrient(css::text::VertOrientation::NONE);
            sal_Int32 const nTop = aTextRectangle.getY() - rFrame.getFrameArea().Top()
                                   - rFrame.getFramePrintArea().Top();
            aVert.SetPos(nTop);

            // tdf#138598 Replace vertical alignment of As_char textboxes in footer
            // tdf#140158 Remove horizontal positioning of As_char textboxes, because
            // the anchor moving does the same for it.
            if (!aPos->nNode.GetNode().FindFooterStartNode())
            {
                aVert.SetVertOrient(css::text::VertOrientation::NONE);
                sal_Int32 const nTop = aTextRectangle.getY() - rFrame.getFrameArea().Top()
                                       - rFrame.getFramePrintArea().Top();
                aVert.SetPos(nTop);
            }
            else
            {
                aVert.SetVertOrient(css::text::VertOrientation::NONE);
                aVert.SetPos(SwTextBoxHelper::getTextRectangle(pShape, false).getY());
            }

            SwFormatAnchor aNewTxBxAnchor(pTextBox->GetAnchor());
            aNewTxBxAnchor.SetAnchor(aPos);

            pTextBox->LockModify();
            pTextBox->SetFormatAttr(aHori);
            pTextBox->SetFormatAttr(aNewTxBxAnchor);
            pTextBox->SetFormatAttr(aVert);
            pTextBox->UnlockModify();
        }