RTF filter: allow measuring page borders from the edge of the page

This is similar to commit 51942eafdb4439559b6d59f3becd4afab45277f0 (DOC
import: allow negative page border distances, 2022-06-08), except here
we map \pgbrdropt's 5th bit to the "from page edge" bool, and then the
rest of the import works already after the DOCX fixes.

Similarly, the export has to map the "from page edge" bool to \pgbrdropt
and has to call into editeng::BorderDistancesToWord(), but the rest of
the process works after the DOCX fixes.

Change-Id: Ic88f1ab17ac169025c38790ffa895748df0a76c0
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135502
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
diff --git a/sw/qa/extras/rtfexport/rtfexport3.cxx b/sw/qa/extras/rtfexport/rtfexport3.cxx
index 665682c..bd8a1c8 100644
--- a/sw/qa/extras/rtfexport/rtfexport3.cxx
+++ b/sw/qa/extras/rtfexport/rtfexport3.cxx
@@ -22,6 +22,12 @@
#include <comphelper/sequenceashashmap.hxx>
#include <tools/UnitConversion.hxx>

#include <unotxdoc.hxx>
#include <docsh.hxx>
#include <wrtsh.hxx>
#include <fmtpdsc.hxx>
#include <IDocumentContentOperations.hxx>

using namespace css;

class Test : public SwModelTestBase
@@ -434,6 +440,58 @@ CPPUNIT_TEST_FIXTURE(Test, testRtlGutter)
    verify();
}

CPPUNIT_TEST_FIXTURE(Test, testNegativePageBorder)
{
    {
        // Given a document with a top margin and a border which has more spacing than the margin on
        // its 2nd page:
        createSwDoc();
        SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
        SwDocShell* pDocShell = pTextDoc->GetDocShell();
        SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
        pWrtShell->Insert("first");
        pWrtShell->SplitNode();
        pWrtShell->Insert("second");
        SwPageDesc* pDesc = pWrtShell->FindPageDescByName("Left Page", true);
        SwPaM aPaM(*pWrtShell->GetCursor()->GetPoint());
        SwFormatPageDesc aFormatPageDesc(pDesc);
        pDocShell->GetDoc()->getIDocumentContentOperations().InsertPoolItem(aPaM, aFormatPageDesc);
        uno::Reference<beans::XPropertySet> xPageStyle(
            getStyles("PageStyles")->getByName("Left Page"), uno::UNO_QUERY);
        xPageStyle->setPropertyValue("TopMargin", uno::Any(static_cast<sal_Int32>(501)));
        table::BorderLine2 aBorder;
        aBorder.LineWidth = 159;
        aBorder.OuterLineWidth = 159;
        xPageStyle->setPropertyValue("TopBorder", uno::Any(aBorder));
        sal_Int32 nTopBorderDistance = -646;
        xPageStyle->setPropertyValue("TopBorderDistance", uno::Any(nTopBorderDistance));
        pDocShell->GetDoc()->dumpAsXml();
    }

    // When saving that document to RTF:
    reload(mpFilter, "negative-page-border.rtf");

    // Then make sure that the border distance is negative, so the first line of body text appears
    // on top of the page border:
    SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
    SwDocShell* pDocShell = pTextDoc->GetDocShell();
    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
    pWrtShell->Down(/*bSelect=*/false);
    OUString aPageStyle = pWrtShell->GetCurPageStyle();
    uno::Reference<beans::XPropertySet> xPageStyle(getStyles("PageStyles")->getByName(aPageStyle),
                                                   uno::UNO_QUERY);
    auto nTopMargin = xPageStyle->getPropertyValue("TopMargin").get<sal_Int32>();
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(501), nTopMargin);
    auto aTopBorder = xPageStyle->getPropertyValue("TopBorder").get<table::BorderLine2>();
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(159), aTopBorder.LineWidth);
    auto nTopBorderDistance = xPageStyle->getPropertyValue("TopBorderDistance").get<sal_Int32>();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: -646
    // - Actual  : 0
    // i.e. the border negative distance was lost.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-646), nTopBorderDistance);
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/qa/filter/ww8/ww8.cxx b/sw/qa/filter/ww8/ww8.cxx
index eb735b1..7ccb129 100644
--- a/sw/qa/filter/ww8/ww8.cxx
+++ b/sw/qa/filter/ww8/ww8.cxx
@@ -26,7 +26,7 @@ class Test : public SwModelTestBase
{
};

CPPUNIT_TEST_FIXTURE(Test, testSwAttrSet)
CPPUNIT_TEST_FIXTURE(Test, testNegativePageBorderDocImport)
{
    // Given a document with a border distance that is larger than the margin, when loading that
    // document:
diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx b/sw/source/filter/ww8/rtfattributeoutput.cxx
index 20e8e6f..9d634e7 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.cxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.cxx
@@ -1281,22 +1281,32 @@ void RtfAttributeOutput::SectionPageBorders(const SwFrameFormat* pFormat,
                                            const SwFrameFormat* /*pFirstPageFormat*/)
{
    const SvxBoxItem& rBox = pFormat->GetBox();
    editeng::WordBorderDistances aDistances;
    editeng::BorderDistancesToWord(rBox, m_aPageMargins, aDistances);

    if (aDistances.bFromEdge)
    {
        sal_uInt16 nOpt = (1 << 5);
        m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_PGBRDROPT);
        m_aSectionBreaks.append(static_cast<sal_Int32>(nOpt));
    }

    const editeng::SvxBorderLine* pLine = rBox.GetTop();
    if (pLine)
        m_aSectionBreaks.append(OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRT,
                                              rBox.GetDistance(SvxBoxItemLine::TOP)));
        m_aSectionBreaks.append(
            OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRT, aDistances.nTop));
    pLine = rBox.GetBottom();
    if (pLine)
        m_aSectionBreaks.append(OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRB,
                                              rBox.GetDistance(SvxBoxItemLine::BOTTOM)));
        m_aSectionBreaks.append(
            OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRB, aDistances.nBottom));
    pLine = rBox.GetLeft();
    if (pLine)
        m_aSectionBreaks.append(OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRL,
                                              rBox.GetDistance(SvxBoxItemLine::LEFT)));
        m_aSectionBreaks.append(
            OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRL, aDistances.nLeft));
    pLine = rBox.GetRight();
    if (pLine)
        m_aSectionBreaks.append(OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRR,
                                              rBox.GetDistance(SvxBoxItemLine::RIGHT)));
        m_aSectionBreaks.append(
            OutBorderLine(m_rExport, pLine, OOO_STRING_SVTOOLS_RTF_PGBRDRR, aDistances.nRight));
}

void RtfAttributeOutput::SectionBiDi(bool bBiDi)
@@ -3166,15 +3176,29 @@ void RtfAttributeOutput::FormatLRSpace(const SvxLRSpaceItem& rLRSpace)
    {
        if (m_rExport.m_bOutPageDescs)
        {
            m_aPageMargins.nLeft = 0;
            m_aPageMargins.nRight = 0;

            if (const SvxBoxItem* pBoxItem = m_rExport.HasItem(RES_BOX))
            {
                m_aPageMargins.nLeft
                    = pBoxItem->CalcLineSpace(SvxBoxItemLine::LEFT, /*bEvenIfNoLine*/ true);
                m_aPageMargins.nRight
                    = pBoxItem->CalcLineSpace(SvxBoxItemLine::RIGHT, /*bEvenIfNoLine*/ true);
            }

            m_aPageMargins.nLeft += sal::static_int_cast<sal_uInt16>(rLRSpace.GetLeft());
            m_aPageMargins.nRight += sal::static_int_cast<sal_uInt16>(rLRSpace.GetRight());

            if (rLRSpace.GetLeft())
            {
                m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGLSXN);
                m_aSectionBreaks.append(static_cast<sal_Int32>(rLRSpace.GetLeft()));
                m_aSectionBreaks.append(static_cast<sal_Int32>(m_aPageMargins.nLeft));
            }
            if (rLRSpace.GetRight())
            {
                m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGRSXN);
                m_aSectionBreaks.append(static_cast<sal_Int32>(rLRSpace.GetRight()));
                m_aSectionBreaks.append(static_cast<sal_Int32>(m_aPageMargins.nRight));
            }
            if (rLRSpace.GetGutterMargin())
            {
@@ -3233,6 +3257,7 @@ void RtfAttributeOutput::FormatULSpace(const SvxULSpaceItem& rULSpace)
            {
                m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGTSXN);
                m_aSectionBreaks.append(static_cast<sal_Int32>(aDistances.m_DyaTop));
                m_aPageMargins.nTop = aDistances.m_DyaTop;
            }
            if (aDistances.HasHeader())
            {
@@ -3244,6 +3269,7 @@ void RtfAttributeOutput::FormatULSpace(const SvxULSpaceItem& rULSpace)
            {
                m_aSectionBreaks.append(OOO_STRING_SVTOOLS_RTF_MARGBSXN);
                m_aSectionBreaks.append(static_cast<sal_Int32>(aDistances.m_DyaBottom));
                m_aPageMargins.nBottom = aDistances.m_DyaBottom;
            }
            if (aDistances.HasFooter())
            {
diff --git a/sw/source/filter/ww8/rtfattributeoutput.hxx b/sw/source/filter/ww8/rtfattributeoutput.hxx
index aedef26..17de105 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.hxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.hxx
@@ -29,6 +29,7 @@
#include <wrtswtbl.hxx>

#include <rtl/strbuf.hxx>
#include <editeng/boxitem.hxx>

#include <optional>

@@ -640,6 +641,8 @@ private:
    /// If m_bParaBeforeAutoSpacing is set, value of \sa.
    sal_Int32 m_nParaAfterSpacing;

    editeng::WordPageMargins m_aPageMargins;

public:
    explicit RtfAttributeOutput(RtfExport& rExport);

diff --git a/writerfilter/qa/cppunittests/rtftok/data/negative-page-border.rtf b/writerfilter/qa/cppunittests/rtftok/data/negative-page-border.rtf
new file mode 100644
index 0000000..e5bec71
--- /dev/null
+++ b/writerfilter/qa/cppunittests/rtftok/data/negative-page-border.rtf
@@ -0,0 +1,7 @@
{\rtf1
\paperw11906\paperh16838\margl1134\margr1134\margt284\margb1134
\sectd\pgbrdropt32\pgbrdrt\brdrs\brdrw90\brsp560 \pgbrdrl\brdrs\brdrw90\brsp560 \pgbrdrb\brdrs\brdrw90\brsp560 \pgbrdrr\brdrs\brdrw90\brsp560
\pard\plain
In this example, the page top margin (0.5cm) is the same as the border top margin (0.5 cm).
\par
}
diff --git a/writerfilter/qa/cppunittests/rtftok/rtfdispatchvalue.cxx b/writerfilter/qa/cppunittests/rtftok/rtfdispatchvalue.cxx
index 662e65d..4479a0c 100644
--- a/writerfilter/qa/cppunittests/rtftok/rtfdispatchvalue.cxx
+++ b/writerfilter/qa/cppunittests/rtftok/rtfdispatchvalue.cxx
@@ -13,6 +13,7 @@
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/table/BorderLine2.hpp>

using namespace ::com::sun::star;

@@ -72,6 +73,35 @@ CPPUNIT_TEST_FIXTURE(Test, testFollowStyle)
    // i.e. \snext was ignored.
    CPPUNIT_ASSERT_EQUAL(OUString("Standard"), aFollowStyle);
}

CPPUNIT_TEST_FIXTURE(Test, testNegativePageBorder)
{
    // Given a document with a top margin and a border which has more spacing than the margin:
    OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "negative-page-border.rtf";

    // When loading that document:
    getComponent() = loadFromDesktop(aURL);

    // Then make sure that the border distance is negative, so it can appear at the correct
    // position:
    uno::Reference<style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(getComponent(),
                                                                         uno::UNO_QUERY);
    uno::Reference<container::XNameAccess> xStyleFamilies
        = xStyleFamiliesSupplier->getStyleFamilies();
    uno::Reference<container::XNameAccess> xStyleFamily(xStyleFamilies->getByName("PageStyles"),
                                                        uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> xStyle(xStyleFamily->getByName("Standard"), uno::UNO_QUERY);
    auto nTopMargin = xStyle->getPropertyValue("TopMargin").get<sal_Int32>();
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(501), nTopMargin);
    auto aTopBorder = xStyle->getPropertyValue("TopBorder").get<table::BorderLine2>();
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(159), aTopBorder.LineWidth);
    auto nTopBorderDistance = xStyle->getPropertyValue("TopBorderDistance").get<sal_Int32>();
    // Without the accompanying fix in place, this test would have failed with:
    // - Expected: -646
    // - Actual  : 342
    // i.e. the border negative distance was lost.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(-646), nTopBorderDistance);
}
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/writerfilter/source/rtftok/rtfdispatchvalue.cxx b/writerfilter/source/rtftok/rtfdispatchvalue.cxx
index eb53702..a77e22a 100644
--- a/writerfilter/source/rtftok/rtfdispatchvalue.cxx
+++ b/writerfilter/source/rtftok/rtfdispatchvalue.cxx
@@ -1803,6 +1803,19 @@ RTFError RTFDocumentImpl::dispatchValue(RTFKeyword nKeyword, int nParam)
            }
        }
        break;
        case RTFKeyword::PGBRDROPT:
        {
            sal_Int16 nOffsetFrom = (nParam & 0xe0) >> 5;
            bool bFromEdge = nOffsetFrom == 1;
            if (bFromEdge)
            {
                Id nId = NS_ooxml::LN_Value_doc_ST_PageBorderOffset_page;
                putNestedAttribute(m_aStates.top().getSectionSprms(),
                                   NS_ooxml::LN_EG_SectPrContents_pgBorders,
                                   NS_ooxml::LN_CT_PageBorders_offsetFrom, new RTFValue(nId));
            }
        }
        break;
        default:
        {
            SAL_INFO("writerfilter", "TODO handle value '" << keywordToString(nKeyword) << "'");