tdf#119952 DOCX import: fix negative page margins

DOCX body text can overlap with header/footer, if top/bottom
page margin is negative. To support this, convert header/footer
text content to textbox anchored to header/footer, if needed.

Note: possible improvements:

1) Skip this hack, if the header is small enough to not overlap with
the body, calculate only the height of the header at the import time.

2) This hack does not fix the case when the top of the header
is under the top of the body. (A problem in DOC import, too.)
This could be achieved by repositioning the dummy header to the top,
and lower the textbox by the same amount. (This would still not
resolve the extreme situation, when the body start from 0 mm
(in LibreOffice, header must be at least 1 mm).

3) Import of VertOrientation::BOTTOM property seems to be bad,
or at least the footer loses this property after a DOCX round-trip,
resulting bad footer position.

4) after a round-trip, the 1 mm height of the dummy header
increases to 1 line height. Also the "Autofit height" and
"Use dynamic spacing" settings are changed, likely related
to their missing DOCX export.

Co-authored-by: Tibor Nagy (NISZ)

Change-Id: I8319c93c6c5a980878ee9956c8ab2953da60409e
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117842
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
(cherry picked from commit d656191ec308d4280b93c7169372e543a255d108)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118295
Tested-by: Jenkins
diff --git a/sw/qa/extras/ooxmlexport/data/tdf119952_negativeMargins.docx b/sw/qa/extras/ooxmlexport/data/tdf119952_negativeMargins.docx
new file mode 100644
index 0000000..9b5a98d
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf119952_negativeMargins.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
index d999827..9cfaf5aa 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx
@@ -1458,6 +1458,89 @@ DECLARE_OOXMLEXPORT_TEST(testTdf124678_case2, "tdf124678_with_leading_paragraph.
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Second page header text", OUString("HEADER"), parseDump("/root/page[2]/header/txt"));
}

static bool lcl_nearEqual(const sal_Int32 nNumber1, const sal_Int32 nNumber2, sal_Int32 nMaxDiff = 5)
{
    return std::abs(nNumber1 - nNumber2) < nMaxDiff;
}

DECLARE_OOXMLEXPORT_TEST(testTdf119952_negativeMargins, "tdf119952_negativeMargins.docx")
{
    // With negative margins (in MS Word) one can set up header (or footer) that overlaps with the body.
    // LibreOffice unable to display that, so when importing negative margins,
    // the header (or footer) converted to a flyframe, anchored to the header..
    // that can overlap with the body, and will appear like in Word.
    // This conversion modifies the document [i.e. replacing header text with a textbox...]
    // but its DOCX export looks the same, as the original document in Word, too.
    xmlDocUniquePtr pDump = parseLayoutDump();

    //Check layout positions / sizes
    sal_Int32 nLeftHead = getXPath(pDump, "//page[1]/header/infos/bounds", "left").toInt32();
    sal_Int32 nLeftBody = getXPath(pDump, "//page[1]/body/infos/bounds", "left").toInt32();
    sal_Int32 nLeftFoot = getXPath(pDump, "//page[1]/footer/infos/bounds", "left").toInt32();
    sal_Int32 nLeftHFly = getXPath(pDump, "//page[1]/header/txt/anchored/fly/infos/bounds", "left").toInt32();
    sal_Int32 nLeftFFly = getXPath(pDump, "//page[1]/footer/txt/anchored/fly/infos/bounds", "left").toInt32();

    sal_Int32 nTopHead = getXPath(pDump, "//page[1]/header/infos/bounds", "top").toInt32();
    sal_Int32 nTopBody = getXPath(pDump, "//page[1]/body/infos/bounds", "top").toInt32();
    sal_Int32 nTopFoot = getXPath(pDump, "//page[1]/footer/infos/bounds", "top").toInt32();
    sal_Int32 nTopHFly = getXPath(pDump, "//page[1]/header/txt/anchored/fly/infos/bounds", "top").toInt32();
    sal_Int32 nTopFFly = getXPath(pDump, "//page[1]/footer/txt/anchored/fly/infos/bounds", "top").toInt32();

    sal_Int32 nHeightHead = getXPath(pDump, "//page[1]/header/infos/bounds", "height").toInt32();
    sal_Int32 nHeightBody = getXPath(pDump, "//page[1]/body/infos/bounds", "height").toInt32();
    sal_Int32 nHeightFoot = getXPath(pDump, "//page[1]/footer/infos/bounds", "height").toInt32();
    sal_Int32 nHeightHFly = getXPath(pDump, "//page[1]/header/txt/anchored/fly/infos/bounds", "height").toInt32();
    sal_Int32 nHeightFFly = getXPath(pDump, "//page[1]/footer/txt/anchored/fly/infos/bounds", "height").toInt32();
    sal_Int32 nHeightHFlyBound = getXPath(pDump, "//page[1]/header/infos/prtBounds", "height").toInt32();
    sal_Int32 nHeightFFlyBound = getXPath(pDump, "//page[1]/footer/infos/prtBounds", "height").toInt32();

    CPPUNIT_ASSERT(lcl_nearEqual(nLeftHead, nLeftBody));
    CPPUNIT_ASSERT(lcl_nearEqual(nLeftHead, nLeftFoot));
    CPPUNIT_ASSERT(lcl_nearEqual(nLeftHead, nLeftHFly));
    CPPUNIT_ASSERT(lcl_nearEqual(nLeftHead, nLeftFFly));

    CPPUNIT_ASSERT(lcl_nearEqual(nTopHead, 851));
    CPPUNIT_ASSERT(lcl_nearEqual(nTopBody, 1418));
    CPPUNIT_ASSERT(lcl_nearEqual(nTopFoot, 15875));
    CPPUNIT_ASSERT(lcl_nearEqual(nTopHFly, 851));

    // this seems to be an import bug
    if (!mbExported)
        CPPUNIT_ASSERT(lcl_nearEqual(nTopFFly, 14403));

    CPPUNIT_ASSERT(lcl_nearEqual(nHeightHead, 567));
    CPPUNIT_ASSERT(lcl_nearEqual(nHeightBody, 14457));
    CPPUNIT_ASSERT(lcl_nearEqual(nHeightFoot, 680));
    CPPUNIT_ASSERT(lcl_nearEqual(nHeightHFly, 2152));
    CPPUNIT_ASSERT(lcl_nearEqual(nHeightFFly, 2152));

    // after export these heights increase to like 567..
    // not sure if it is an other import, or export bug... or just the result of the modified document
    if (!mbExported)
    {
        CPPUNIT_ASSERT(lcl_nearEqual(nHeightHFlyBound, 57));
        CPPUNIT_ASSERT(lcl_nearEqual(nHeightFFlyBound, 57));
    }

    //Check text of header/ footer
    CPPUNIT_ASSERT_EQUAL(OUString("f1"), getXPath(pDump, "//page[1]/header/txt/anchored/fly/txt[1]/Text", "Portion"));
    CPPUNIT_ASSERT_EQUAL(OUString("                f8"), getXPath(pDump, "//page[1]/header/txt/anchored/fly/txt[8]/Text", "Portion"));
    CPPUNIT_ASSERT_EQUAL(OUString("                f8"), getXPath(pDump, "//page[1]/footer/txt/anchored/fly/txt[1]/Text", "Portion"));
    CPPUNIT_ASSERT_EQUAL(OUString("f1"), getXPath(pDump, "//page[1]/footer/txt/anchored/fly/txt[8]/Text", "Portion"));

    CPPUNIT_ASSERT_EQUAL(OUString("p1"), getXPath(pDump, "//page[2]/header/txt/anchored/fly/txt[1]/Text", "Portion"));
    CPPUNIT_ASSERT_EQUAL(OUString("p1"), getXPath(pDump, "//page[2]/footer/txt/anchored/fly/txt[1]/Text", "Portion"));

    CPPUNIT_ASSERT_EQUAL(OUString("  aaaa"), getXPath(pDump, "//page[3]/header/txt/anchored/fly/txt[1]/Text", "Portion"));
    CPPUNIT_ASSERT_EQUAL(OUString("      eeee"), getXPath(pDump, "//page[3]/header/txt/anchored/fly/txt[5]/Text", "Portion"));

    CPPUNIT_ASSERT_EQUAL(OUString("f1    f2      f3        f4          f5            f6              f7                f8"), parseDump("/root/page[1]/header/txt/anchored/fly"));
    CPPUNIT_ASSERT_EQUAL(OUString("                f8              f7            f6          f5        f4      f3    f2f1"), parseDump("/root/page[1]/footer/txt/anchored/fly"));
    CPPUNIT_ASSERT_EQUAL(OUString("p1"), parseDump("/root/page[2]/header/txt/anchored/fly"));
    CPPUNIT_ASSERT_EQUAL(OUString("p1"), parseDump("/root/page[2]/footer/txt/anchored/fly"));
    CPPUNIT_ASSERT_EQUAL(OUString("  aaaa   bbbb    cccc     dddd      eeee"), parseDump("/root/page[3]/header/txt/anchored/fly"));
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
index 55c4874..884a195 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
@@ -340,38 +340,6 @@ void lcl_adjustBorderDistance(TableInfo& rInfo, const table::BorderLine2& rLeftB
    rInfo.nRightBorderDistance = nActualR;
}

void lcl_fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties)
{
    // fill empty frame properties to create an invisible frame around the table:
    // hide frame borders and zero inner and outer frame margins
    beans::PropertyValue aValue;
    aValue.Name = getPropertyName( PROP_ANCHOR_TYPE );
    aValue.Value <<= text::TextContentAnchorType_AS_CHARACTER;
    rFrameProperties.push_back(aValue);

    table::BorderLine2 aEmptyBorder;
    static const std::vector<std::u16string_view> aBorderNames
        = { u"TopBorder", u"LeftBorder", u"BottomBorder", u"RightBorder" };
    for (size_t i = 0; i < aBorderNames.size(); ++i)
    {
        beans::PropertyValue aBorderValue;
        aBorderValue.Name = aBorderNames[i];
        aBorderValue.Value <<= aEmptyBorder;
        rFrameProperties.push_back(aBorderValue);
    }
    static const std::vector<std::u16string_view> aMarginNames
        = { u"TopBorderDistance", u"LeftBorderDistance",
            u"BottomBorderDistance", u"RightBorderDistance",
            u"TopMargin", u"LeftMargin", u"BottomMargin", u"RightMargin" };
    for (size_t i = 0; i < aMarginNames.size(); ++i)
    {
        beans::PropertyValue aMarginValue;
        aMarginValue.Name = aMarginNames[i];
        aMarginValue.Value <<= sal_Int32(10);
        rFrameProperties.push_back(aMarginValue);
    }
}

}

TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo & rInfo,
@@ -1440,8 +1408,10 @@ void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel, bool bTab
        uno::Reference<text::XTextRange> xStart;
        uno::Reference<text::XTextRange> xEnd;

        if ( bConvertToFloating )
            lcl_fillEmptyFrameProperties(aFrameProperties);
        // fill empty frame properties to create an invisible frame around the table:
        // hide frame borders and zero inner and outer frame margins
        if (bConvertToFloating)
            DomainMapper_Impl::fillEmptyFrameProperties(aFrameProperties, true);

        // OOXML table style may contain paragraph properties, apply these on cell paragraphs
        if ( m_aTableRanges[0].hasElements() && m_aTableRanges[0][0].hasElements() )
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index e02549d..9f12d3a 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -2600,6 +2600,64 @@ void DomainMapper_Impl::appendGlossaryEntry()
    appendTextSectionAfter(m_xGlossaryEntryStart);
}

void DomainMapper_Impl::fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar)
{
    if (bSetAnchorToChar)
        rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_ANCHOR_TYPE), text::TextContentAnchorType_AS_CHARACTER));

    uno::Any aEmptyBorder = uno::makeAny(table::BorderLine2());
    static const std::vector<PropertyIds> aBorderIds
        = { PROP_BOTTOM_BORDER, PROP_LEFT_BORDER, PROP_RIGHT_BORDER, PROP_TOP_BORDER };
    for (size_t i = 0; i < aBorderIds.size(); ++i)
        rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aBorderIds[i]), aEmptyBorder));

    static const std::vector<PropertyIds> aMarginIds
        = { PROP_BOTTOM_MARGIN, PROP_BOTTOM_BORDER_DISTANCE,
            PROP_LEFT_MARGIN,   PROP_LEFT_BORDER_DISTANCE,
            PROP_RIGHT_MARGIN,  PROP_RIGHT_BORDER_DISTANCE,
            PROP_TOP_MARGIN,    PROP_TOP_BORDER_DISTANCE };
    for (size_t i = 0; i < aMarginIds.size(); ++i)
        rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aMarginIds[i]), static_cast<sal_Int32>(0)));
}

void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom)
{
    while (!m_aHeaderFooterTextAppendStack.empty())
    {
        auto aFooterHeader = m_aHeaderFooterTextAppendStack.top();
        if ((aFooterHeader.second && !bDynamicHeightTop) || (!aFooterHeader.second && !bDynamicHeightBottom))
        {
            uno::Reference< text::XTextAppend > xTextAppend = aFooterHeader.first.xTextAppend;
            uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor();
            uno::Reference< text::XTextRange > xRangeStart, xRangeEnd;

            xRangeStart = xCursor->getStart();
            xCursor->gotoEnd(false);
            xRangeEnd = xCursor->getStart();

            std::vector<beans::PropertyValue> aFrameProperties;

            aFrameProperties.push_back(comphelper::makePropertyValue("TextWrap", css::text::WrapTextMode_THROUGH));
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT));
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false));
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN));
            aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN));

            fillEmptyFrameProperties(aFrameProperties, false);

            // If it is a footer, then orient the frame to the bottom
            if (!aFooterHeader.second)
                aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), text::VertOrientation::BOTTOM));

            uno::Reference<text::XTextAppendAndConvert> xBodyText(
                xRangeStart->getText(), uno::UNO_QUERY);
            xBodyText->convertToTextFrame(xRangeStart, xRangeEnd,
                comphelper::containerToSequence(aFrameProperties));
        }
        m_aHeaderFooterTextAppendStack.pop();
    }
}

void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::PageType eType)
{
    m_bSaveParaHadField = m_bParaHadField;
@@ -2662,6 +2720,11 @@ void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::P
                m_bIsNewDoc
                    ? uno::Reference<text::XTextCursor>()
                    : xText->createTextCursorByRange(xText->getStart())));
            m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW),
                m_bIsNewDoc
                    ? uno::Reference<text::XTextCursor>()
                    : xText->createTextCursorByRange(xText->getStart())),
                bHeader));
            m_bDiscardHeaderFooter = false; // set only on success!
        }
        // If we have *hidden* header footer
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 5212653..845e902 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -467,6 +467,7 @@ private:
    std::stack<TextAppendContext>                                                   m_aTextAppendStack;
    std::stack<AnchoredContext>                                                     m_aAnchoredStack;
    std::stack<HeaderFooterContext>                                                 m_aHeaderFooterStack;
    std::stack<std::pair<TextAppendContext, bool>>                                  m_aHeaderFooterTextAppendStack;
    std::deque<FieldContextPtr> m_aFieldStack;
    bool m_bForceGenericFields;
    bool                                                                            m_bSetUserFieldContent;
@@ -820,6 +821,8 @@ public:

    void PopPageHeaderFooter();
    bool IsInHeaderFooter() const { return m_eInHeaderFooterImport != HeaderFooterImportState::none; }
    void ConvertHeaderFooterToTextFrame(bool, bool);
    static void fillEmptyFrameProperties(std::vector<css::beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar);

    bool IsInTOC() const { return m_bStartTOC; }

diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx
index 9b4c819..cacadef 100644
--- a/writerfilter/source/dmapper/PropertyMap.cxx
+++ b/writerfilter/source/dmapper/PropertyMap.cxx
@@ -425,6 +425,8 @@ SectionPropertyMap::SectionPropertyMap( bool bIsFirstSection )
    , m_nLnc(NS_ooxml::LN_Value_ST_LineNumberRestart_newPage)
    , m_ndxaLnn( 0 )
    , m_nLnnMin( 0 )
    , m_bDynamicHeightTop( true )
    , m_bDynamicHeightBottom( true )
    , m_bDefaultHeaderLinkToPrevious( true )
    , m_bEvenPageHeaderLinkToPrevious( true )
    , m_bFirstPageHeaderLinkToPrevious( true )
@@ -982,28 +984,25 @@ void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage )
    bool bCopyFirstToFollow = bFirstPage && m_bTitlePage && m_aFollowPageStyle.is();

    sal_Int32 nTopMargin = m_nTopMargin;
    sal_Int32 nHeaderTop = m_nHeaderTop;
    sal_Int32 nHeaderHeight = m_nHeaderTop;
    if ( HasHeader( bFirstPage ) )
    {
        nTopMargin = nHeaderTop;
        if ( m_nTopMargin > 0 && m_nTopMargin > nHeaderTop )
            nHeaderTop = m_nTopMargin - nHeaderTop;
        else
            nHeaderTop = 0;
        nTopMargin = m_nHeaderTop;
        nHeaderHeight = m_nTopMargin - m_nHeaderTop;

        // minimum header height 1mm
        if ( nHeaderTop < MIN_HEAD_FOOT_HEIGHT )
            nHeaderTop = MIN_HEAD_FOOT_HEIGHT;
        if ( nHeaderHeight < MIN_HEAD_FOOT_HEIGHT )
            nHeaderHeight = MIN_HEAD_FOOT_HEIGHT;
    }

    Insert(PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::makeAny(m_bDynamicHeightTop));
    Insert(PROP_HEADER_DYNAMIC_SPACING, uno::makeAny(m_bDynamicHeightTop));
    Insert(PROP_HEADER_BODY_DISTANCE, uno::makeAny(nHeaderHeight - MIN_HEAD_FOOT_HEIGHT));
    Insert(PROP_HEADER_HEIGHT, uno::makeAny(nHeaderHeight));
    // looks like PROP_HEADER_HEIGHT = height of the header + space between the header, and the body

    if ( m_nTopMargin >= 0 ) //fixed height header -> see WW8Par6.hxx
    if ( m_bDynamicHeightTop ) //fixed height header -> see WW8Par6.hxx
    {
        Insert( PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::makeAny( true ) );
        Insert( PROP_HEADER_DYNAMIC_SPACING, uno::makeAny( true ) );
        Insert( PROP_HEADER_BODY_DISTANCE, uno::makeAny( nHeaderTop - MIN_HEAD_FOOT_HEIGHT ) );// ULSpace.Top()
        Insert( PROP_HEADER_HEIGHT, uno::makeAny( nHeaderTop ) );

        if (bCopyFirstToFollow && HasHeader(/*bFirstPage=*/true))
        {
            m_aFollowPageStyle->setPropertyValue("HeaderDynamicSpacing",
@@ -1012,36 +1011,25 @@ void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage )
                                                 getProperty(PROP_HEADER_HEIGHT)->second);
        }
    }
    else
    {
        //todo: old filter fakes a frame into the header/footer to support overlapping
        //current setting is completely wrong!
        Insert( PROP_HEADER_HEIGHT, uno::makeAny( nHeaderTop ) );
        Insert( PROP_HEADER_BODY_DISTANCE, uno::makeAny( m_nTopMargin - nHeaderTop ) );
        Insert( PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::makeAny( false ) );
        Insert( PROP_HEADER_DYNAMIC_SPACING, uno::makeAny( false ) );
    }

    sal_Int32 nBottomMargin = m_nBottomMargin;
    sal_Int32 nHeaderBottom = m_nHeaderBottom;
    sal_Int32 nFooterHeight = m_nHeaderBottom;
    if ( HasFooter( bFirstPage ) )
    {
        nBottomMargin = nHeaderBottom;
        if ( m_nBottomMargin > 0 && m_nBottomMargin > nHeaderBottom )
            nHeaderBottom = m_nBottomMargin - nHeaderBottom;
        else
            nHeaderBottom = 0;
        if ( nHeaderBottom < MIN_HEAD_FOOT_HEIGHT )
            nHeaderBottom = MIN_HEAD_FOOT_HEIGHT;
        nBottomMargin = m_nHeaderBottom;
        nFooterHeight = m_nBottomMargin - m_nHeaderBottom;

        // minimum footer height 1mm
        if ( nFooterHeight < MIN_HEAD_FOOT_HEIGHT )
            nFooterHeight = MIN_HEAD_FOOT_HEIGHT;
    }

    if ( m_nBottomMargin >= 0 ) //fixed height footer -> see WW8Par6.hxx
    Insert(PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::makeAny(m_bDynamicHeightBottom));
    Insert(PROP_FOOTER_DYNAMIC_SPACING, uno::makeAny(m_bDynamicHeightBottom));
    Insert(PROP_FOOTER_BODY_DISTANCE, uno::makeAny(nFooterHeight - MIN_HEAD_FOOT_HEIGHT));
    Insert(PROP_FOOTER_HEIGHT, uno::makeAny(nFooterHeight));
    if (m_bDynamicHeightBottom) //fixed height footer -> see WW8Par6.hxx
    {
        Insert( PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::makeAny( true ) );
        Insert( PROP_FOOTER_DYNAMIC_SPACING, uno::makeAny( true ) );
        Insert( PROP_FOOTER_BODY_DISTANCE, uno::makeAny( nHeaderBottom - MIN_HEAD_FOOT_HEIGHT ) );
        Insert( PROP_FOOTER_HEIGHT, uno::makeAny( nHeaderBottom ) );

        if (bCopyFirstToFollow && HasFooter(/*bFirstPage=*/true))
        {
            m_aFollowPageStyle->setPropertyValue("FooterDynamicSpacing",
@@ -1050,15 +1038,6 @@ void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage )
                                                 getProperty(PROP_FOOTER_HEIGHT)->second);
        }
    }
    else
    {
        //todo: old filter fakes a frame into the header/footer to support overlapping
        //current setting is completely wrong!
        Insert( PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::makeAny( false ) );
        Insert( PROP_FOOTER_DYNAMIC_SPACING, uno::makeAny( false ) );
        Insert( PROP_FOOTER_HEIGHT, uno::makeAny( m_nBottomMargin - nHeaderBottom ) );
        Insert( PROP_FOOTER_BODY_DISTANCE, uno::makeAny( nHeaderBottom ) );
    }

    //now set the top/bottom margin for the follow page style
    Insert( PROP_TOP_MARGIN, uno::makeAny( std::max<sal_Int32>(nTopMargin, 0) ) );
@@ -1134,6 +1113,13 @@ void SectionPropertyMap::HandleMarginsHeaderFooter( bool bFirstPage, DomainMappe
    */
    CopyLastHeaderFooter( bFirstPage, rDM_Impl );
    PrepareHeaderFooterProperties( bFirstPage );

    // tdf#119952: If top/bottom margin was negative during docx import,
    // then the header/footer and the body could be on top of each other
    // writer is unable to display both of them in the same position, but can be simulated
    // by moving the header/footer text into a flyframe anchored to the header/footer,
    // leaving an empty dummy header/footer.
    rDM_Impl.ConvertHeaderFooterToTextFrame(m_bDynamicHeightTop, m_bDynamicHeightBottom);
}

bool SectionPropertyMap::FloatingTableConversion( const DomainMapper_Impl& rDM_Impl, FloatingTableInfo& rInfo )
diff --git a/writerfilter/source/dmapper/PropertyMap.hxx b/writerfilter/source/dmapper/PropertyMap.hxx
index d4f981c5..9178499 100644
--- a/writerfilter/source/dmapper/PropertyMap.hxx
+++ b/writerfilter/source/dmapper/PropertyMap.hxx
@@ -270,6 +270,9 @@ private:
    sal_Int32                                       m_ndxaLnn;
    sal_Int32                                       m_nLnnMin;

    bool                                            m_bDynamicHeightTop;
    bool                                            m_bDynamicHeightBottom;

    std::vector<css::uno::Reference<css::drawing::XShape>>    m_xRelativeWidthShapes;

    // The "Link To Previous" flag indicates whether the header/footer
@@ -378,8 +381,8 @@ public:
    sal_Int32 GetLeftMargin() const        { return m_nLeftMargin; }
    void SetRightMargin( sal_Int32 nSet )  { m_nRightMargin = nSet; }
    sal_Int32 GetRightMargin() const       { return m_nRightMargin; }
    void SetTopMargin( sal_Int32 nSet )    { m_nTopMargin = nSet; }
    void SetBottomMargin( sal_Int32 nSet ) { m_nBottomMargin = nSet; }
    void SetTopMargin(sal_Int32 nSet)      { m_bDynamicHeightTop = nSet >= 0; m_nTopMargin = std::abs(nSet); }
    void SetBottomMargin( sal_Int32 nSet ) { m_bDynamicHeightBottom = nSet >= 0; m_nBottomMargin = std::abs(nSet); }
    void SetHeaderTop( sal_Int32 nSet )    { m_nHeaderTop = nSet; }
    void SetHeaderBottom( sal_Int32 nSet ) { m_nHeaderBottom = nSet; }
    void SetGutterMargin( sal_Int32 nGutterMargin ) { m_nGutterMargin = nGutterMargin; }