sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007image.docx |binary sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007imageInline.docx |binary sw/qa/extras/ooxmlimport/ooxmlimport2.cxx | 49 +++++++++ sw/source/filter/ww8/docxattributeoutput.cxx | 51 --------- sw/source/filter/ww8/docxexport.cxx | 52 ++++++++++ sw/source/filter/ww8/docxexport.hxx | 3 sw/source/filter/ww8/docxsdrexport.cxx | 27 +++-- writerfilter/source/dmapper/GraphicImport.cxx | 31 +++++ writerfilter/source/dmapper/GraphicImport.hxx | 1 9 files changed, 159 insertions(+), 55 deletions(-)
New commits: commit 67f2a99229101757af4f40118f4d3c83ba38648b Author: Regina Henschel <rb.hensc...@t-online.de> AuthorDate: Sun Jul 25 18:04:53 2021 +0200 Commit: Regina Henschel <rb.hensc...@t-online.de> CommitDate: Tue Jul 27 01:34:02 2021 +0200 tdf#143475 consider Word 2007 rotated image speciality Usually Word relates effectExtent to a rectangle with swapped width and height, if the object rotation is between 45deg and 135deg. But Word 2007 (=version 12) makes an exception for images (bug?). The patch determines the version from compatibility setting and calculates wrap margins and effectExtent values considering this special feature of Word 2007. I have moved the part for getting the Word version from InteropGrabBag from the local function lcl_getWordCompatibilityMode to DocxExport, because I need it exactly the same way in docxsdrexport.cxx. Change-Id: Icc2f3d0710e29207413fb3810d281a0fd7d82002 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/119482 Tested-by: Jenkins Reviewed-by: Regina Henschel <rb.hensc...@t-online.de> diff --git a/sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007image.docx b/sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007image.docx new file mode 100644 index 000000000000..48506f4ec53a Binary files /dev/null and b/sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007image.docx differ diff --git a/sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007imageInline.docx b/sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007imageInline.docx new file mode 100644 index 000000000000..42c4894255ca Binary files /dev/null and b/sw/qa/extras/ooxmlimport/data/tdf143475_rotatedWord2007imageInline.docx differ diff --git a/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx b/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx index 257ffb234b6e..86f91c91fcaf 100644 --- a/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx +++ b/sw/qa/extras/ooxmlimport/ooxmlimport2.cxx @@ -48,6 +48,55 @@ public: } }; +CPPUNIT_TEST_FIXTURE(Test, testTdf143475rotatedWord2007imageInline) +{ + // Given a docx document with compatibility to Word version 12 (2007), which has a shape + // rotated by 75deg. Similar to testTdf143475rotatedWord2007image but with inline anchored + // shape, as in bug report. + load(mpTestDocumentPath, "tdf143475_rotatedWord2007imageInline.docx"); + + // Word 2007 does not swap width and height for rotated images as done in later versions. + // This was not considered and lead to wrong distance to text on import and wrong effectExtent + // on export. + // Import fails without fix with left: expected 1258 actual -743 ; right expected 1256 actual -743; + // top: expected 14 actual 2013; bottom: expected 0 actual 1960; + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(1258), getProperty<sal_Int32>(getShape(1), "LeftMargin"), + 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(1256), + getProperty<sal_Int32>(getShape(1), "RightMargin"), 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(14), getProperty<sal_Int32>(getShape(1), "TopMargin"), + 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(0), getProperty<sal_Int32>(getShape(1), "BottomMargin"), + 1); + + // Because LO made the same error on export, which inverts the import error, import-export-cycle + // does not fail without the patch. Therefore no export test. +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf143475rotatedWord2007image) +{ + // Given a docx document with compatibility to Word version 12 (2007), which has a shape + // rotated by 75deg. + load(mpTestDocumentPath, "tdf143475_rotatedWord2007image.docx"); + + // Word 2007 does not swap width and height for rotated images as done in later versions. + // This was not considered and lead to wrong distance to text on import and wrong effectExtent + // on export. + // Import fails without fix with left: expected 1252 actual -746 ; right expected 1256 actual -743; + // top: expected 12 actual 2013; bottom: expected 0 actual 1960; + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(1252), getProperty<sal_Int32>(getShape(1), "LeftMargin"), + 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(1256), + getProperty<sal_Int32>(getShape(1), "RightMargin"), 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(12), getProperty<sal_Int32>(getShape(1), "TopMargin"), + 1); + CPPUNIT_ASSERT_DOUBLES_EQUAL(sal_Int32(0), getProperty<sal_Int32>(getShape(1), "BottomMargin"), + 1); + + // Because LO made the same error on export, which inverts the import error, import-export-cycle + // does not fail without the patch. Therefore no export test. +} + CPPUNIT_TEST_FIXTURE(Test, testTdf143219ContourWrapRotate) { load(mpTestDocumentPath, "tdf143219_ContourWrap_rotate.docx"); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 514e42f0da99..bb8334bda259 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -3842,55 +3842,12 @@ OString lcl_padStartToLength(OString const & aString, sal_Int32 nLen, char cFill //Keep this function in-sync with the one in writerfilter/.../SettingsTable.cxx //Since this is not import code, "-1" needs to be handled as the mode that LO will save as. //To identify how your code should handle a "-1", look in DocxExport::WriteSettings(). -sal_Int32 lcl_getWordCompatibilityMode( const SwDoc& rDoc ) +sal_Int32 lcl_getWordCompatibilityMode(const DocxExport& rDocExport) { - uno::Reference< beans::XPropertySet > xPropSet( rDoc.GetDocShell()->GetBaseModel(), uno::UNO_QUERY_THROW ); - uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); - - sal_Int32 nWordCompatibilityMode = -1; - if ( xPropSetInfo->hasPropertyByName( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) ) - { - uno::Sequence< beans::PropertyValue > propList; - xPropSet->getPropertyValue( UNO_NAME_MISC_OBJ_INTEROPGRABBAG ) >>= propList; - - for ( const auto& rProp : std::as_const(propList) ) - { - if ( rProp.Name == "CompatSettings" ) - { - css::uno::Sequence< css::beans::PropertyValue > aCurrentCompatSettings; - rProp.Value >>= aCurrentCompatSettings; - - for ( const auto& rCurrentCompatSetting : std::as_const(aCurrentCompatSettings) ) - { - uno::Sequence< beans::PropertyValue > aCompatSetting; - rCurrentCompatSetting.Value >>= aCompatSetting; - - OUString sName; - OUString sUri; - OUString sVal; - - for ( const auto& rPropVal : std::as_const(aCompatSetting) ) - { - if ( rPropVal.Name == "name" ) rPropVal.Value >>= sName; - if ( rPropVal.Name == "uri" ) rPropVal.Value >>= sUri; - if ( rPropVal.Name == "val" ) rPropVal.Value >>= sVal; - } - - if ( sName == "compatibilityMode" && sUri == "http://schemas.microsoft.com/office/word" ) - { - const sal_Int32 nValidMode = sVal.toInt32(); - // if repeated, highest mode wins in MS Word. 11 is the first valid mode. - if ( nValidMode > 10 && nValidMode > nWordCompatibilityMode ) - nWordCompatibilityMode = nValidMode; - - } - } - } - } - } + sal_Int32 nWordCompatibilityMode = rDocExport.getWordCompatibilityModeFromGrabBag(); // TODO: this is duplicated, better store it in DocxExport member? - if (!rDoc.getIDocumentSettingAccess().get(DocumentSettingId::ADD_EXT_LEADING)) + if (!rDocExport.m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::ADD_EXT_LEADING)) { if (nWordCompatibilityMode == -1 || 14 < nWordCompatibilityMode) { @@ -4217,7 +4174,7 @@ void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t // tdf#106742: since MS Word 2013 (compatibilityMode >= 15), top-level tables are handled the same as nested tables; // if no compatibilityMode is defined (which now should only happen on a new export to .docx), // LO uses a higher compatibility than 2010's 14. - sal_Int32 nMode = lcl_getWordCompatibilityMode( m_rExport.m_rDoc ); + sal_Int32 nMode = lcl_getWordCompatibilityMode(m_rExport); const SwFrameFormat* pFrameFormat = pTableTextNodeInfoInner->getTableBox()->GetFrameFormat(); if ((0 < nMode && nMode <= 14) && m_tableReference->m_nTableDepth == 0) diff --git a/sw/source/filter/ww8/docxexport.cxx b/sw/source/filter/ww8/docxexport.cxx index 3eefc919eed8..f13a1d2f290d 100644 --- a/sw/source/filter/ww8/docxexport.cxx +++ b/sw/source/filter/ww8/docxexport.cxx @@ -1782,6 +1782,58 @@ sal_Int32 DocxExport::WriteOutliner(const OutlinerParaObject& rParaObj, sal_uInt return nParaId; } +//Keep this function in-sync with the one in writerfilter/.../SettingsTable.cxx +//Since this is not import code, "-1" needs to be handled as the mode that LO will save as. +//To identify how your code should handle a "-1", look in DocxExport::WriteSettings(). +sal_Int32 DocxExport::getWordCompatibilityModeFromGrabBag() const +{ + sal_Int32 nWordCompatibilityMode = -1; + uno::Reference< beans::XPropertySet > xPropSet(m_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 (const auto& rProp : std::as_const(propList)) + { + if (rProp.Name == "CompatSettings") + { + css::uno::Sequence< css::beans::PropertyValue > aCurrentCompatSettings; + rProp.Value >>= aCurrentCompatSettings; + + for (const auto& rCurrentCompatSetting : std::as_const(aCurrentCompatSettings)) + { + uno::Sequence< beans::PropertyValue > aCompatSetting; + rCurrentCompatSetting.Value >>= aCompatSetting; + + OUString sName; + OUString sUri; + OUString sVal; + + for (const auto& rPropVal : std::as_const(aCompatSetting)) + { + if ( rPropVal.Name == "name" ) rPropVal.Value >>= sName; + if ( rPropVal.Name == "uri" ) rPropVal.Value >>= sUri; + if ( rPropVal.Name == "val" ) rPropVal.Value >>= sVal; + } + + if (sName == "compatibilityMode" && sUri == "http://schemas.microsoft.com/office/word") + { + const sal_Int32 nValidMode = sVal.toInt32(); + // if repeated, highest mode wins in MS Word. 11 is the first valid mode. + if (nValidMode > 10 && nValidMode > nWordCompatibilityMode) + nWordCompatibilityMode = nValidMode; + + } + } + } + } + } + + return nWordCompatibilityMode; +} + void DocxExport::SetFS( ::sax_fastparser::FSHelperPtr const & pFS ) { mpFS = pFS; diff --git a/sw/source/filter/ww8/docxexport.hxx b/sw/source/filter/ww8/docxexport.hxx index 4bbd2ea9cb0c..91ca7c82d154 100644 --- a/sw/source/filter/ww8/docxexport.hxx +++ b/sw/source/filter/ww8/docxexport.hxx @@ -303,6 +303,9 @@ public: // Get author id to remove personal info size_t GetInfoID( const OUString sPersonalInfo ) const { return m_pAuthorIDs->GetInfoID(sPersonalInfo); } + // needed in docxsdrexport.cxx and docxattributeoutput.cxx + sal_Int32 getWordCompatibilityModeFromGrabBag() const; + private: DocxExport( const DocxExport& ) = delete; diff --git a/sw/source/filter/ww8/docxsdrexport.cxx b/sw/source/filter/ww8/docxsdrexport.cxx index 5a8fb68c284e..bba707b56235 100644 --- a/sw/source/filter/ww8/docxsdrexport.cxx +++ b/sw/source/filter/ww8/docxsdrexport.cxx @@ -13,6 +13,7 @@ #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> #include <editeng/lrspitem.hxx> #include <editeng/ulspitem.hxx> +#include <editeng/unoprnms.hxx> #include <editeng/shaditem.hxx> #include <editeng/opaqitem.hxx> #include <editeng/boxitem.hxx> @@ -141,6 +142,7 @@ bool lcl_IsRotateAngleValid(const SdrObject& rObj) { case OBJ_GRUP: case OBJ_LINE: + case OBJ_PLIN: case OBJ_PATHLINE: case OBJ_PATHFILL: return false; @@ -150,7 +152,8 @@ bool lcl_IsRotateAngleValid(const SdrObject& rObj) } void lcl_calculateMSOBaseRectangle(const SdrObject& rObj, double& rfMSOLeft, double& rfMSORight, - double& rfMSOTop, double& rfMSOBottom) + double& rfMSOTop, double& rfMSOBottom, + const bool bIsWord2007Image) { // Word rotates around shape center, LO around left/top. Thus logic rectangle of LO is not // directly usable as 'base rectangle'. @@ -159,10 +162,12 @@ void lcl_calculateMSOBaseRectangle(const SdrObject& rObj, double& rfMSOLeft, dou double fHalfWidth = rObj.GetLogicRect().getWidth() / 2.0; double fHalfHeight = rObj.GetLogicRect().getHeight() / 2.0; - // MSO swaps width and height depending on rotation angle. + // MSO swaps width and height depending on rotation angle; exception: Word 2007 (vers 12) never + // swaps width and height for images. double fRotation = lcl_IsRotateAngleValid(rObj) ? toDegrees(NormAngle36000(rObj.GetRotateAngle())) : 0.0; - if ((fRotation > 45.0 && fRotation <= 135.0) || (fRotation > 225.0 && fRotation <= 315.0)) + if (((fRotation > 45.0 && fRotation <= 135.0) || (fRotation > 225.0 && fRotation <= 315.0)) + && !bIsWord2007Image) { rfMSOLeft = fCenterX - fHalfHeight; rfMSORight = fCenterX + fHalfHeight; @@ -180,16 +185,16 @@ void lcl_calculateMSOBaseRectangle(const SdrObject& rObj, double& rfMSOLeft, dou void lcl_calculateRawEffectExtent(sal_Int32& rLeft, sal_Int32& rTop, sal_Int32& rRight, sal_Int32& rBottom, const SdrObject& rObj, - const bool bUseBoundRect) + const bool bUseBoundRect, const bool bIsWord2007Image) { // This method calculates the extent needed, to let Word use the same outer area for the object // as LO. Word uses as 'base rectangle' the unrotated shape rectangle, maybe having swapped width - // and height depending on rotation angle. + // and height depending on rotation angle and version of Word. double fMSOLeft; double fMSORight; double fMSOTop; double fMSOBottom; - lcl_calculateMSOBaseRectangle(rObj, fMSOLeft, fMSORight, fMSOTop, fMSOBottom); + lcl_calculateMSOBaseRectangle(rObj, fMSOLeft, fMSORight, fMSOTop, fMSOBottom, bIsWord2007Image); tools::Rectangle aLORect = bUseBoundRect ? rObj.GetCurrentBoundRect() : rObj.GetSnapRect(); rLeft = fMSOLeft - aLORect.Left(); @@ -618,11 +623,16 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons } else // other objects than frames. pObj exists. { + // Word 2007 makes no width-hight-swap for images. Detect this situation. + sal_Int32 nMode = m_pImpl->getExport().getWordCompatibilityModeFromGrabBag(); + bool bIsWord2007Image(nMode > 0 && nMode < 14 && pObj->GetObjIdentifier() == OBJ_GRAF); + // Word cannot handle negative EffectExtent although allowed in OOXML, the 'dist' attributes // may not be negative. Take care of that. if (isAnchor) { - lcl_calculateRawEffectExtent(nLeftExt, nTopExt, nRightExt, nBottomExt, *pObj, true); + lcl_calculateRawEffectExtent(nLeftExt, nTopExt, nRightExt, nBottomExt, *pObj, true, + bIsWord2007Image); // We have calculated the effectExtent from boundRect, therefore half stroke width is // already contained. // ToDo: The other half of the stroke width needs to be subtracted from padding. @@ -656,7 +666,8 @@ void DocxSdrExport::startDMLAnchorInline(const SwFrameFormat* pFrameFormat, cons } else { - lcl_calculateRawEffectExtent(nLeftExt, nTopExt, nRightExt, nBottomExt, *pObj, false); + lcl_calculateRawEffectExtent(nLeftExt, nTopExt, nRightExt, nBottomExt, *pObj, false, + bIsWord2007Image); // nDistT,... contain the needed distances from import or set by user. But Word // ignores Dist attributes of inline shapes. So we move all needed distances to // effectExtent and force effectExtent to non-negative. diff --git a/writerfilter/source/dmapper/GraphicImport.cxx b/writerfilter/source/dmapper/GraphicImport.cxx index 940780ca9705..8ed707c2917f 100644 --- a/writerfilter/source/dmapper/GraphicImport.cxx +++ b/writerfilter/source/dmapper/GraphicImport.cxx @@ -552,6 +552,26 @@ static bool lcl_bHasGroupSlantedChild(const SdrObject* pObj) return false; } +void GraphicImport::lcl_correctWord2007EffectExtent(const sal_Int32 nMSOAngle) +{ + // Word versions older than 14 do not swap width and height (see lcl_doMSOWidthHeightSwap) + // and therefore generate different effectExtent. We correct them here. + sal_Int16 nAngleDeg = (nMSOAngle / 60000) % 180; + if (nAngleDeg >= 45 && nAngleDeg < 135) + { + sal_Int32 nDiff = o3tl::convert((m_pImpl->getXSize() - m_pImpl->getYSize()) / 2.0, + o3tl::Length::mm100, o3tl::Length::emu); + if (m_pImpl->m_oEffectExtentLeft) + *m_pImpl->m_oEffectExtentLeft += nDiff; + if (m_pImpl->m_oEffectExtentRight) + *m_pImpl->m_oEffectExtentRight += nDiff; + if (m_pImpl->m_oEffectExtentTop) + *m_pImpl->m_oEffectExtentTop -= nDiff; + if (m_pImpl->m_oEffectExtentBottom) + *m_pImpl->m_oEffectExtentBottom -= nDiff; + } +} + static void lcl_doMSOWidthHeightSwap(awt::Point& rLeftTop, awt::Size& rSize, const sal_Int32 nMSOAngle) { @@ -965,6 +985,17 @@ void GraphicImport::lcl_attribute(Id nName, Value& rValue) // snap rectangle. // Margin correction + + // tdf#143475: Word 2007 (vers 12) calculates effectExtent for rotated images + // based on the unrotated image without width-height-swap. We correct this to + // those values, which would be calculated if width-height-swap was used. + if (m_pImpl->rDomainMapper.GetSettingsTable()->GetWordCompatibilityMode() < 14 + && xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape") + && nOOXAngle != 0) + { + lcl_correctWord2007EffectExtent(nOOXAngle); + } + if (m_pImpl->eGraphicImportType == IMPORT_AS_DETECTED_INLINE) { if (nOOXAngle == 0) diff --git a/writerfilter/source/dmapper/GraphicImport.hxx b/writerfilter/source/dmapper/GraphicImport.hxx index bea911eef8bb..7ca4e09ed30d 100644 --- a/writerfilter/source/dmapper/GraphicImport.hxx +++ b/writerfilter/source/dmapper/GraphicImport.hxx @@ -126,6 +126,7 @@ public: void handleWrapTextValue(sal_uInt32 nVal); void lcl_expandRectangleByEffectExtent(css::awt::Point& rLeftTop, css::awt::Size& rSize); + void lcl_correctWord2007EffectExtent(const sal_Int32 nMSOAngle); }; typedef tools::SvRef<GraphicImport> GraphicImportPtr; _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits