tdf#106742: OOXML import/export: treat "tblInd" properly.

Since MS Word 2013+ if you change cell margin at the table, the border won't be shifted.

The decision is to do the same ( see https://bugs.documentfoundation.org/show_bug.cgi?id=106742 ).

Change-Id: Ia58693c44f63ed21dca2cd99591002ba68927b65
Reviewed-on: https://gerrit.libreoffice.org/36084
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
diff --git a/sw/qa/extras/ooxmlexport/data/table-position.docx b/sw/qa/extras/ooxmlexport/data/table-cell-margin.docx
similarity index 100%
rename from sw/qa/extras/ooxmlexport/data/table-position.docx
rename to sw/qa/extras/ooxmlexport/data/table-cell-margin.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/data/table-position.docx b/sw/qa/extras/ooxmlexport/data/table-position-14.docx
similarity index 100%
copy from sw/qa/extras/ooxmlexport/data/table-position.docx
copy to sw/qa/extras/ooxmlexport/data/table-position-14.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/data/table-position-15.docx b/sw/qa/extras/ooxmlexport/data/table-position-15.docx
new file mode 100644
index 0000000..d3dcaec
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/table-position-15.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx
index a0bd7e4..3b4445f 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport2.cxx
@@ -363,41 +363,6 @@ DECLARE_OOXMLEXPORT_TEST(testTable, "table.odt")
    assertXPath(parseExport("word/styles.xml"), "//w:style[@w:styleId='Normal']/w:qFormat", 1);
}

DECLARE_OOXMLEXPORT_TEST(testTablePosition, "table-position.docx")
{
    sal_Int32 aXCoordsFromOffice[] = { 2500, -1000, 0, 0 };
    sal_Int32 cellLeftMarginFromOffice[] = { 250, 100, 0, 0 };

    uno::Reference<text::XTextTablesSupplier> xTablesSupplier(mxComponent, uno::UNO_QUERY);
    uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY);
    uno::Reference<container::XIndexAccess> xTables(xTablesSupplier->getTextTables( ), uno::UNO_QUERY);

    for (int i=0; i<4; i++) {
        uno::Reference<text::XTextTable> xTable1 (xTables->getByIndex(i), uno::UNO_QUERY);
        // Verify X coord
        uno::Reference<view::XSelectionSupplier> xCtrl(xModel->getCurrentController(), uno::UNO_QUERY);
        xCtrl->select(uno::makeAny(xTable1));
        uno::Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(xCtrl, uno::UNO_QUERY);
        uno::Reference<text::XTextViewCursor> xCursor(xTextViewCursorSupplier->getViewCursor(), uno::UNO_QUERY);
        awt::Point pos = xCursor->getPosition();
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Incorrect X coord computed from docx",
            aXCoordsFromOffice[i], pos.X, 1);

        // Verify left margin of 1st cell :
        //  * Office left margins are measured relative to the right of the border
        //  * LO left spacing is measured from the center of the border
        uno::Reference<table::XCell> xCell = xTable1->getCellByName("A1");
        uno::Reference< beans::XPropertySet > xPropSet(xCell, uno::UNO_QUERY_THROW);
        sal_Int32 aLeftMargin = -1;
        xPropSet->getPropertyValue("LeftBorderDistance") >>= aLeftMargin;
        uno::Any aLeftBorder = xPropSet->getPropertyValue("LeftBorder");
        table::BorderLine2 aLeftBorderLine;
        aLeftBorder >>= aLeftBorderLine;
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Incorrect left spacing computed from docx cell margin",
            cellLeftMarginFromOffice[i], aLeftMargin - 0.5 * aLeftBorderLine.LineWidth, 1);
    }
}

struct SingleLineBorders {
    sal_Int16 top, bottom, left, right;
    SingleLineBorders(int t=0, int b=0, int l=0, int r=0)
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx
index 5b62695..d751899 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport8.cxx
@@ -47,6 +47,7 @@
#include <com/sun/star/style/ParagraphAdjust.hpp>
#include <com/sun/star/table/ShadowFormat.hpp>
#include <com/sun/star/view/XFormLayerAccess.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <com/sun/star/table/BorderLine2.hpp>
#include <com/sun/star/table/TableBorder2.hpp>
#include <com/sun/star/text/SizeType.hpp>
@@ -2096,6 +2097,81 @@ DECLARE_OOXMLEXPORT_TEST(testTdf99140, "tdf99140.docx")
    CPPUNIT_ASSERT_EQUAL(text::HoriOrientation::LEFT_AND_WIDTH, getProperty<sal_Int16>(xTableProperties, "HoriOrient"));
}

DECLARE_OOXMLEXPORT_TEST( testTableCellMargin, "table-cell-margin.docx" )
{
    sal_Int32 cellLeftMarginFromOffice[] = { 250, 100, 0, 0 };

    uno::Reference< text::XTextTablesSupplier > xTablesSupplier( mxComponent, uno::UNO_QUERY );
    uno::Reference< frame::XModel >             xModel( mxComponent, uno::UNO_QUERY );
    uno::Reference< container::XIndexAccess >   xTables( xTablesSupplier->getTextTables(), uno::UNO_QUERY );

    for ( int i = 0; i < 4; i++ )
    {
        uno::Reference< text::XTextTable > xTable1( xTables->getByIndex( i ), uno::UNO_QUERY );

        // Verify left margin of 1st cell :
        //  * Office left margins are measured relative to the right of the border
        //  * LO left spacing is measured from the center of the border
        uno::Reference< table::XCell > xCell = xTable1->getCellByName( "A1" );
        uno::Reference< beans::XPropertySet > xPropSet( xCell, uno::UNO_QUERY_THROW );
        sal_Int32 aLeftMargin = -1;
        xPropSet->getPropertyValue( "LeftBorderDistance" ) >>= aLeftMargin;
        uno::Any aLeftBorder = xPropSet->getPropertyValue( "LeftBorder" );
        table::BorderLine2 aLeftBorderLine;
        aLeftBorder >>= aLeftBorderLine;
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Incorrect left spacing computed from docx cell margin",
            cellLeftMarginFromOffice[i], aLeftMargin - 0.5 * aLeftBorderLine.LineWidth, 1 );
    }
}

// tdf#106742 for DOCX with compatibility level <= 14 (MS Word up to and incl. ver.2010), we should use cell margins when calculating table left border position
DECLARE_OOXMLEXPORT_TEST( testTablePosition14, "table-position-14.docx" )
{
    sal_Int32 aXCoordsFromOffice[] = { 2500, -1000, 0, 0 };

    uno::Reference< text::XTextTablesSupplier > xTablesSupplier( mxComponent, uno::UNO_QUERY );
    uno::Reference< frame::XModel >             xModel( mxComponent, uno::UNO_QUERY );
    uno::Reference< container::XIndexAccess >   xTables( xTablesSupplier->getTextTables(), uno::UNO_QUERY );

    for ( int i = 0; i < 4; i++ )
    {
        uno::Reference< text::XTextTable > xTable1( xTables->getByIndex( i ), uno::UNO_QUERY );

        // Verify X coord
        uno::Reference< view::XSelectionSupplier > xCtrl( xModel->getCurrentController(), uno::UNO_QUERY );
        xCtrl->select( uno::makeAny( xTable1 ) );
        uno::Reference< text::XTextViewCursorSupplier > xTextViewCursorSupplier( xCtrl, uno::UNO_QUERY );
        uno::Reference< text::XTextViewCursor > xCursor( xTextViewCursorSupplier->getViewCursor(), uno::UNO_QUERY );
        awt::Point pos = xCursor->getPosition();
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Incorrect X coord computed from docx",
            aXCoordsFromOffice[i], pos.X, 1 );
    }
}

// tdf#106742 for DOCX with compatibility level > 14 (MS Word since ver.2013), we should NOT use cell margins when calculating table left border position
DECLARE_OOXMLEXPORT_TEST( testTablePosition15, "table-position-15.docx" )
{
    sal_Int32 aXCoordsFromOffice[] = { 2751, -899, 1, 106 };

    uno::Reference< text::XTextTablesSupplier > xTablesSupplier( mxComponent, uno::UNO_QUERY );
    uno::Reference< frame::XModel >             xModel( mxComponent, uno::UNO_QUERY );
    uno::Reference< container::XIndexAccess >   xTables( xTablesSupplier->getTextTables(), uno::UNO_QUERY );

    for ( int i = 0; i < 4; i++ )
    {
        uno::Reference< text::XTextTable > xTable1( xTables->getByIndex( i ), uno::UNO_QUERY );

        // Verify X coord
        uno::Reference< view::XSelectionSupplier > xCtrl( xModel->getCurrentController(), uno::UNO_QUERY );
        xCtrl->select( uno::makeAny( xTable1 ) );
        uno::Reference< text::XTextViewCursorSupplier > xTextViewCursorSupplier( xCtrl, uno::UNO_QUERY );
        uno::Reference< text::XTextViewCursor > xCursor( xTextViewCursorSupplier->getViewCursor(), uno::UNO_QUERY );
        awt::Point pos = xCursor->getPosition();
        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( "Incorrect X coord computed from docx",
            aXCoordsFromOffice[i], pos.X, 1 );
    }
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index fe8ec15..a1be342 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -3206,6 +3206,51 @@ OString lcl_padStartToLength(OString const & aString, sal_Int32 nLen, sal_Char c
        return aString;
}

sal_Int32 lcl_getWordCompatibilityMode( const SwDoc& rDoc )
{
    uno::Reference< beans::XPropertySet >     xPropSet( rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW );
    uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();

    if ( xPropSetInfo->hasPropertyByName( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) )
    {
        uno::Sequence< beans::PropertyValue > propList;
        xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= propList;

        for ( sal_Int32 i = 0; i < propList.getLength(); ++i )
        {
            if ( propList[i].Name == "CompatSettings" )
            {
                css::uno::Sequence< css::beans::PropertyValue > aCurrentCompatSettings;
                propList[i].Value >>= aCurrentCompatSettings;

                for ( sal_Int32 j = 0; j < aCurrentCompatSettings.getLength(); ++j )
                {
                    uno::Sequence< beans::PropertyValue > aCompatSetting;
                    aCurrentCompatSettings[j].Value >>= aCompatSetting;

                    OUString sName;
                    OUString sUri;
                    OUString sVal;

                    for ( sal_Int32 k = 0; k < aCompatSetting.getLength(); ++k )
                    {
                        if ( aCompatSetting[k].Name == "name" ) aCompatSetting[k].Value >>= sName;
                        if ( aCompatSetting[k].Name == "uri" )  aCompatSetting[k].Value >>= sUri;
                        if ( aCompatSetting[k].Name == "val" )  aCompatSetting[k].Value >>= sVal;
                    }

                    if ( sName == "compatibilityMode" && sUri == "http://schemas.microsoft.com/office/word" )
                    {
                        return sVal.toInt32();
                    }
                }
            }
        }
    }

    return -1; // Word compatibility mode not found
}

}

void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner )
@@ -3428,17 +3473,24 @@ void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t
                pJcVal = "left";
            else
                pJcVal = "start";
            nIndent = sal_Int32( pTableFormat->GetLRSpace( ).GetLeft( ) );
            nIndent = sal_Int32( pTableFormat->GetLRSpace().GetLeft() );

            // Table indentation has different meaning in Word, depending if the table is nested or not.
            // If nested, tblInd is added to parent table's left spacing and defines left edge position
            // If not nested, text position of left-most cell must be at absolute X = tblInd
            // so, table_spacing + table_spacing_to_content = tblInd
            if (m_tableReference->m_nTableDepth == 0)

            // tdf#106742: since MS Word 2013 (compatibilityMode >= 15), top-level tables are handled the same as nested tables;
            // this is also the default behavior in LO when DOCX doesn't define "compatibilityMode" option
            sal_Int32 nMode = lcl_getWordCompatibilityMode( *m_rExport.m_pDoc );

            if ( nMode > 0 && nMode <= 14 && m_tableReference->m_nTableDepth == 0 )
            {
                const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox();
                const SwFrameFormat * pFrameFormat = pTabBox->GetFrameFormat();
                nIndent += sal_Int32( pFrameFormat->GetBox( ).GetDistance( SvxBoxItemLine::LEFT ) );
                const SwTableBox*    pTabBox = pTableTextNodeInfoInner->getTableBox();
                const SwFrameFormat* pFrameFormat = pTabBox->GetFrameFormat();
                nIndent += sal_Int32( pFrameFormat->GetBox().GetDistance( SvxBoxItemLine::LEFT ) );
            }

            break;
        }
    }
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
index c4d26e5..8fa604a 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
@@ -313,6 +313,34 @@ void lcl_DecrementHoriOrientPosition(std::vector<beans::PropertyValue>& rFramePr
    }
}

sal_Int32 lcl_getWordCompatibilityMode( const css::uno::Sequence< css::beans::PropertyValue >& rCompatSettings )
{
    for ( int i = 0; i < rCompatSettings.getLength(); ++i )
    {
        const css::beans::PropertyValue& rProp = rCompatSettings[i];
        if ( rProp.Name == "compatSetting" )
        {
            css::uno::Sequence< css::beans::PropertyValue > aCurrentCompatSettings;
            rProp.Value >>= aCurrentCompatSettings;

            OUString sName;
            OUString sUri;
            OUString sVal;

            aCurrentCompatSettings[0].Value >>= sName;
            aCurrentCompatSettings[1].Value >>= sUri;
            aCurrentCompatSettings[2].Value >>= sVal;

            if ( sName == "compatibilityMode" && sUri == "http://schemas.microsoft.com/office/word" )
            {
                return sVal.toInt32();
            }
        }
    }

    return -1; // Word compatibility mode not found
}

TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo & rInfo, std::vector<beans::PropertyValue>& rFrameProperties)
{
    // will receive the table style if any
@@ -546,13 +574,18 @@ TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo
        // - top level tables: the goal is to have in-cell text starting at table indent pos (tblInd),
        //   so table's position depends on table's cells margin
        // - nested tables: the goal is to have left-most border starting at table_indent pos
        if (rInfo.nNestLevel > 1)

        // tdf#106742: since MS Word 2013 (compatibilityMode >= 15), top-level tables are handled the same as nested tables;
        // this is also the default behavior in LO when DOCX doesn't define "compatibilityMode" option
        sal_Int32 nMode = lcl_getWordCompatibilityMode( m_rDMapper_Impl.GetSettingsTable()->GetCompatSettings() );

        if ( nMode > 0 && nMode <= 14 && rInfo.nNestLevel == 1 )
        {
            m_aTableProperties->Insert( PROP_LEFT_MARGIN, uno::makeAny( nLeftMargin - nGapHalf ));
            m_aTableProperties->Insert( PROP_LEFT_MARGIN, uno::makeAny( nLeftMargin - nGapHalf - rInfo.nLeftBorderDistance ) );
        }
        else
        {
            m_aTableProperties->Insert( PROP_LEFT_MARGIN, uno::makeAny( nLeftMargin - nGapHalf - rInfo.nLeftBorderDistance ));
            m_aTableProperties->Insert( PROP_LEFT_MARGIN, uno::makeAny( nLeftMargin - nGapHalf ) );
        }

        m_aTableProperties->getValue( TablePropertyMap::TABLE_WIDTH, nTableWidth );