diff options
25 files changed, 831 insertions, 182 deletions
diff --git a/filter/source/msfilter/escherex.cxx b/filter/source/msfilter/escherex.cxx index 9ea8ab91a7e0..3f817a72bcb1 100644 --- a/filter/source/msfilter/escherex.cxx +++ b/filter/source/msfilter/escherex.cxx @@ -91,6 +91,7 @@ #include <sal/log.hxx> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/numeric/ftools.hxx> #include <osl/diagnose.h> #include <algorithm> @@ -2858,7 +2859,11 @@ void EscherPropertyContainer::CreateCustomShapeProperties( const MSO_SPT eShapeT { double fExtrusionShininess = 0; if ( rrProp.Value >>= fExtrusionShininess ) - AddOpt( DFF_Prop_c3DShininess, static_cast<sal_Int32>( fExtrusionShininess * 655.36 ) ); + { + // ODF to MS Office conversion invers to msdffimp.cxx + fExtrusionShininess = basegfx::fround(fExtrusionShininess / 10.0); + AddOpt( DFF_Prop_c3DShininess, static_cast<sal_Int32>(fExtrusionShininess) ); + } } else if ( rrProp.Name == "Skew" ) { @@ -2875,7 +2880,7 @@ void EscherPropertyContainer::CreateCustomShapeProperties( const MSO_SPT eShapeT { double fExtrusionSpecularity = 0; if ( rrProp.Value >>= fExtrusionSpecularity ) - AddOpt( DFF_Prop_c3DSpecularAmt, static_cast<sal_Int32>( fExtrusionSpecularity * 1333 ) ); + AddOpt( DFF_Prop_c3DSpecularAmt, static_cast<sal_Int32>( fExtrusionSpecularity * 655.36 ) ); } else if ( rrProp.Name == "ProjectionMode" ) { diff --git a/filter/source/msfilter/msdffimp.cxx b/filter/source/msfilter/msdffimp.cxx index a0ecad074ac6..76813d38df24 100644 --- a/filter/source/msfilter/msdffimp.cxx +++ b/filter/source/msfilter/msdffimp.cxx @@ -149,6 +149,7 @@ #include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp> #include <com/sun/star/beans/PropertyValues.hpp> #include <com/sun/star/beans/XPropertySetInfo.hpp> #include <com/sun/star/beans/XPropertySet.hpp> @@ -1675,14 +1676,20 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt aExtrusionPropVec.push_back( aProp ); // "Brightness" + // MS Office default 0x00004E20 16.16 FixedPoint, 20000/65536=0.30517, ODF default 33%. + // Thus must set value even if default. + double fBrightness = 20000.0; if ( IsProperty( DFF_Prop_c3DAmbientIntensity ) ) { - double fBrightness = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DAmbientIntensity, 0 )); - fBrightness /= 655.36; - aProp.Name = "Brightness"; - aProp.Value <<= fBrightness; - aExtrusionPropVec.push_back( aProp ); + // Value must be in range 0.0 to 1.0 in MS Office binary specification, but larger + // values are in fact interpreted. + fBrightness = GetPropertyValue( DFF_Prop_c3DAmbientIntensity, 0 ); } + fBrightness /= 655.36; + aProp.Name = "Brightness"; + aProp.Value <<= fBrightness; + aExtrusionPropVec.push_back( aProp ); + // "Depth" in 1/100mm if ( IsProperty( DFF_Prop_c3DExtrudeBackward ) || IsProperty( DFF_Prop_c3DExtrudeForward ) ) { @@ -1700,14 +1707,17 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt aExtrusionPropVec.push_back( aProp ); } // "Diffusion" + // ODF default is 0%, MS Office default is 100%. Thus must set value even if default. + double fDiffusion = 100; if ( IsProperty( DFF_Prop_c3DDiffuseAmt ) ) { - double fDiffusion = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DDiffuseAmt, 0 )); + fDiffusion = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DDiffuseAmt, 0 )); fDiffusion /= 655.36; - aProp.Name = "Diffusion"; - aProp.Value <<= fDiffusion; - aExtrusionPropVec.push_back( aProp ); } + aProp.Name = "Diffusion"; + aProp.Value <<= fDiffusion; + aExtrusionPropVec.push_back( aProp ); + // "NumberOfLineSegments" if ( IsProperty( DFF_Prop_c3DTolerance ) ) { @@ -1730,24 +1740,35 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt aProp.Name = "SecondLightHarsh"; aProp.Value <<= bExtrusionSecondLightHarsh; aExtrusionPropVec.push_back( aProp ); + // "FirstLightLevel" + // MS Office default 0x00009470 16.16 FixedPoint, 38000/65536 = 0.5798, ODF default 66%. + // Thus must set value even if default. + double fFirstLightLevel = 38000.0; if ( IsProperty( DFF_Prop_c3DKeyIntensity ) ) { - double fFirstLightLevel = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DKeyIntensity, 0 )); - fFirstLightLevel /= 655.36; - aProp.Name = "FirstLightLevel"; - aProp.Value <<= fFirstLightLevel; - aExtrusionPropVec.push_back( aProp ); + // value<0 and value>1 are allowed in MS Office. Clamp such in ODF export, not here. + fFirstLightLevel = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DKeyIntensity, 0 )); } + fFirstLightLevel /= 655.36; + aProp.Name = "FirstLightLevel"; + aProp.Value <<= fFirstLightLevel; + aExtrusionPropVec.push_back( aProp ); + // "SecondLightLevel" + // MS Office default 0x00009470 16.16 FixedPoint, 38000/65536 = 0.5798, ODF default 66%. + // Thus must set value even if default. + double fSecondLightLevel = 38000.0; if ( IsProperty( DFF_Prop_c3DFillIntensity ) ) { - double fSecondLightLevel = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DFillIntensity, 0 )); - fSecondLightLevel /= 655.36; - aProp.Name = "SecondLightLevel"; - aProp.Value <<= fSecondLightLevel; - aExtrusionPropVec.push_back( aProp ); + // value<0 and value>1 are allowed in MS Office. Clamp such in ODF export, not here. + fSecondLightLevel = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DFillIntensity, 0 )); } + fSecondLightLevel /= 655.36; + aProp.Name = "SecondLightLevel"; + aProp.Value <<= fSecondLightLevel; + aExtrusionPropVec.push_back( aProp ); + // "FirstLightDirection" if ( IsProperty( DFF_Prop_c3DKeyX ) || IsProperty( DFF_Prop_c3DKeyY ) || IsProperty( DFF_Prop_c3DKeyZ ) ) { @@ -1776,6 +1797,10 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt aProp.Name = "Metal"; aProp.Value <<= bExtrusionMetal; aExtrusionPropVec.push_back( aProp ); + aProp.Name = "MetalType"; + aProp.Value <<= css::drawing::EnhancedCustomShapeMetalType::MetalMSCompatible; + aExtrusionPropVec.push_back(aProp); + // "ShadeMode" if ( IsProperty( DFF_Prop_c3DRenderMode ) ) { @@ -1788,7 +1813,7 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt aProp.Value <<= eExtrusionShadeMode; aExtrusionPropVec.push_back( aProp ); } - // "RotateAngle" in Grad + // "RotateAngle" in Degree if ( IsProperty( DFF_Prop_c3DXRotationAngle ) || IsProperty( DFF_Prop_c3DYRotationAngle ) ) { double fAngleX = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DXRotationAngle, 0 ))) / 65536.0; @@ -1821,34 +1846,44 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt } } // "Shininess" + // MS Office default 5, ODF default 50%. if ( IsProperty( DFF_Prop_c3DShininess ) ) { double fShininess = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DShininess, 0 )); - fShininess /= 655.36; + fShininess *= 10.0; // error in [MS ODRAW] (2021), type is not FixedPoint but long. aProp.Name = "Shininess"; aProp.Value <<= fShininess; aExtrusionPropVec.push_back( aProp ); } + // "Skew" + // MS Office angle file value is 16.16 FixedPoint, default 0xFF790000, + // -8847360/65536=-135, ODF default 45. Thus must set value even if default. + double fSkewAngle = -135.0; + // MS Office amount file value is signed integer in range 0xFFFFFF9C to 0x00000064, + // default 0x00000032, ODF default 50.0 + double fSkewAmount = 50.0; if ( IsProperty( DFF_Prop_c3DSkewAmount ) || IsProperty( DFF_Prop_c3DSkewAngle ) ) { - double fSkewAmount = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DSkewAmount, 50 )); - double fSkewAngle = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DSkewAngle, sal::static_int_cast< sal_uInt32 >(-135 * 65536) ))) / 65536.0; + fSkewAmount = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DSkewAmount, 50 )); + fSkewAngle = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DSkewAngle, sal::static_int_cast< sal_uInt32 >(-135 * 65536) )); + fSkewAngle /= 65536.0; + } + EnhancedCustomShapeParameterPair aSkewPair; + aSkewPair.First.Value <<= fSkewAmount; + aSkewPair.First.Type = EnhancedCustomShapeParameterType::NORMAL; + aSkewPair.Second.Value <<= fSkewAngle; + aSkewPair.Second.Type = EnhancedCustomShapeParameterType::NORMAL; + aProp.Name = "Skew"; + aProp.Value <<= aSkewPair; + aExtrusionPropVec.push_back( aProp ); - EnhancedCustomShapeParameterPair aSkewPair; - aSkewPair.First.Value <<= fSkewAmount; - aSkewPair.First.Type = EnhancedCustomShapeParameterType::NORMAL; - aSkewPair.Second.Value <<= fSkewAngle; - aSkewPair.Second.Type = EnhancedCustomShapeParameterType::NORMAL; - aProp.Name = "Skew"; - aProp.Value <<= aSkewPair; - aExtrusionPropVec.push_back( aProp ); - } // "Specularity" + // Type Fixed point 16.16, percent in API if ( IsProperty( DFF_Prop_c3DSpecularAmt ) ) { double fSpecularity = static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DSpecularAmt, 0 )); - fSpecularity /= 1333; + fSpecularity /= 655.36; aProp.Name = "Specularity"; aProp.Value <<= fSpecularity; aExtrusionPropVec.push_back( aProp ); @@ -1860,16 +1895,22 @@ void DffPropertyReader::ApplyCustomShapeGeometryAttributes( SvStream& rIn, SfxIt aExtrusionPropVec.push_back( aProp ); // "ViewPoint" in 1/100mm + // MS Office default 1250000 EMU=3472.222 Hmm, ODF default 3.5cm + // Thus must set value even if default. + double fViewX = 1250000.0 / 360.0; + double fViewY = -1250000.0 / 360.0;; + double fViewZ = 9000000.0 / 360.0; if ( IsProperty( DFF_Prop_c3DXViewpoint ) || IsProperty( DFF_Prop_c3DYViewpoint ) || IsProperty( DFF_Prop_c3DZViewpoint ) ) { - double fViewX = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DXViewpoint, 1250000 ))) / 360.0; - double fViewY = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DYViewpoint, sal_uInt32(-1250000) )))/ 360.0; - double fViewZ = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DZViewpoint, 9000000 ))) / 360.0; - css::drawing::Position3D aExtrusionViewPoint( fViewX, fViewY, fViewZ ); - aProp.Name = "ViewPoint"; - aProp.Value <<= aExtrusionViewPoint; - aExtrusionPropVec.push_back( aProp ); + fViewX = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DXViewpoint, 1250000 ))) / 360.0; + fViewY = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DYViewpoint, sal_uInt32(-1250000) )))/ 360.0; + fViewZ = static_cast<double>(static_cast<sal_Int32>(GetPropertyValue( DFF_Prop_c3DZViewpoint, 9000000 ))) / 360.0; } + css::drawing::Position3D aExtrusionViewPoint( fViewX, fViewY, fViewZ ); + aProp.Name = "ViewPoint"; + aProp.Value <<= aExtrusionViewPoint; + aExtrusionPropVec.push_back( aProp ); + // "Origin" if ( IsProperty( DFF_Prop_c3DOriginX ) || IsProperty( DFF_Prop_c3DOriginY ) ) { diff --git a/include/xmloff/xmltoken.hxx b/include/xmloff/xmltoken.hxx index c17ffe8af69a..7e278b4cffc3 100644 --- a/include/xmloff/xmltoken.hxx +++ b/include/xmloff/xmltoken.hxx @@ -2455,6 +2455,7 @@ namespace xmloff::token { XML_EXTRUSION_FIRST_LIGHT_DIRECTION, XML_EXTRUSION_SECOND_LIGHT_DIRECTION, XML_EXTRUSION_METAL, + XML_EXTRUSION_METAL_TYPE, XML_EXTRUSION_ROTATION_ANGLE, XML_EXTRUSION_ROTATION_CENTER, XML_EXTRUSION_SHININESS, diff --git a/offapi/UnoApi_offapi.mk b/offapi/UnoApi_offapi.mk index 2f4240e75e33..28b3a41f280b 100644 --- a/offapi/UnoApi_offapi.mk +++ b/offapi/UnoApi_offapi.mk @@ -2295,6 +2295,7 @@ $(eval $(call gb_UnoApi_add_idlfiles,offapi,com/sun/star/drawing,\ EnhancedCustomShapeSegmentCommand \ EnhancedCustomShapeTextFrame \ EnhancedCustomShapeTextPathMode \ + EnhancedCustomShapeMetalType \ EscapeDirection \ FillStyle \ FlagSequence \ diff --git a/offapi/com/sun/star/drawing/EnhancedCustomShapeExtrusion.idl b/offapi/com/sun/star/drawing/EnhancedCustomShapeExtrusion.idl index bfe99f4029fd..4b589c4aa1d8 100644 --- a/offapi/com/sun/star/drawing/EnhancedCustomShapeExtrusion.idl +++ b/offapi/com/sun/star/drawing/EnhancedCustomShapeExtrusion.idl @@ -94,11 +94,20 @@ service EnhancedCustomShapeExtrusion */ [optional, property] boolean Metal; + /** Specifies in case of Metal=true the way the rendering of the shape is modified. + <p>Note: Currently not usable in ODF strict.</p> + + @see EnhancedCustomShapeMetalType + + @since LibreOffice 7.4 + */ + [optional, property] short MetalType; + /** This property defines the shade mode. */ [optional, property] ::com::sun::star::drawing::ShadeMode ShadeMode; - /** This attributes specifies the rotation angle about the x-axis in grad. + /** This attributes specifies the rotation angle about the x-axis in degrees. The order of rotation is: z-axis, y-axis and then x-axis. The z-axis is specified by the draw:rotate-angle. */ diff --git a/offapi/com/sun/star/drawing/EnhancedCustomShapeMetalType.idl b/offapi/com/sun/star/drawing/EnhancedCustomShapeMetalType.idl new file mode 100755 index 000000000000..aea502ccd31b --- /dev/null +++ b/offapi/com/sun/star/drawing/EnhancedCustomShapeMetalType.idl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#ifndef __com_sun_star_drawing_EnhancedCustomShapeMetalType_idl__ +#define __com_sun_star_drawing_EnhancedCustomShapeMetalType_idl__ + + + module com { module sun { module star { module drawing { + +/** These constants define the way the attribute Metal of service + EnhancedCustomShapeExtrusion is interpreted for rendering the shape. + @since LibreOffice 7.4 + */ +constants EnhancedCustomShapeMetalType +{ + /** The rendering of the shape is modified as specified in the ODF standard. + */ + const short MetalODF = 0; + + /** The rendering of the shape is modified to get a similar rendering as in Microsoft Office for objects, which have the fc3DMetallic flag in Rich Text Format or binary MS Office format set. + */ + const short MetalMSCompatible = 1; +}; + + +}; }; }; }; + + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
\ No newline at end of file diff --git a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng index ce941afd1b77..2df8d41c5eb9 100644 --- a/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng +++ b/schema/libreoffice/OpenDocument-v1.3+libreoffice-schema.rng @@ -2395,6 +2395,15 @@ xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1. </rng:optional> </rng:define> + <!-- https://issues.oasis-open.org/browse/OFFICE-4123 --> + <rng:define name="draw-enhanced-geometry-attlist" combine="interleave"> + <rng:optional> + <rng:attribute name="loext:extrusion-metal-type"> + <rng:ref name="namespacedToken"/> + </rng:attribute> + </rng:optional> + </rng:define> + <!-- TODO no proposal --> <rng:define name="draw-custom-shape-attlist" combine="interleave"> <rng:ref name="common-draw-rel-size-attlist"/> diff --git a/svx/qa/unit/customshapes.cxx b/svx/qa/unit/customshapes.cxx index b420ecebe2e4..081a6b7789e0 100644 --- a/svx/qa/unit/customshapes.cxx +++ b/svx/qa/unit/customshapes.cxx @@ -14,16 +14,18 @@ #include <test/bootstrapfixture.hxx> #include <unotest/macros_test.hxx> #include <rtl/ustring.hxx> -#include <editeng/unoprnms.hxx> #include <basegfx/polygon/b2dpolypolygon.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/point/b2dpoint.hxx> +#include <comphelper/propertyvalue.hxx> +#include <editeng/unoprnms.hxx> #include <sfx2/request.hxx> #include <sfx2/viewfrm.hxx> #include <sfx2/viewsh.hxx> #include <svl/intitem.hxx> #include <svx/EnhancedCustomShape2d.hxx> #include <svx/extrusionbar.hxx> +#include <svx/graphichelper.hxx> #include <svx/svdoashp.hxx> #include <svx/svdopath.hxx> #include <svx/svdview.hxx> @@ -31,13 +33,16 @@ #include <svx/unoapi.hxx> #include <unotools/mediadescriptor.hxx> #include <unotools/tempfile.hxx> +#include <vcl/filter/PngImageReader.hxx> +#include <vcl/BitmapReadAccess.hxx> #include <cppunit/TestAssert.h> +#include <com/sun/star/awt/Rectangle.hpp> #include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/GraphicExportFilter.hpp> #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> #include <com/sun/star/drawing/XDrawPage.hpp> -#include <com/sun/star/awt/Rectangle.hpp> #include <com/sun/star/frame/Desktop.hpp> #include <com/sun/star/frame/XStorable.hpp> @@ -127,6 +132,106 @@ void lcl_AssertRectEqualWithTolerance(std::string_view sInfo, const tools::Recta std::abs(rExpected.GetHeight() - rActual.GetHeight()) <= nTolerance); } +CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145700_3D_NonUI) +{ + // The document contains first light soft, no ambient color, no second light and shininess 6. + // Such settings are not available in the UI. It tests the actual color, not the geometry. + // Load document + OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145700_3D_NonUI.doc"; + mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument"); + + // Generate bitmap from shape + uno::Reference<drawing::XShape> xShape = getShape(0); + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL()); + + // Read bitmap and test color + // The expected values are taken from an image generated by Word + // Without the changed methods the colors were in range RGB(17,11,17) to RGB(87,55,89). + SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ); + vcl::PngImageReader aPNGReader(aFileStream); + BitmapEx aBMPEx = aPNGReader.read(); + Bitmap aBMP = aBMPEx.GetBitmap(); + Bitmap::ScopedReadAccess pRead(aBMP); + Size aSize = aBMP.GetSizePixel(); + // GetColor(Y,X) + Color aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.125); + Color aExpectedColor(107, 67, 109); + sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor); + CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance); + // The current solution for soft light still can be improved. nColorDistance is high. + aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.45); + aExpectedColor = Color(179, 113, 183); + nColorDistance = aExpectedColor.GetColorError(aActualColor); + CPPUNIT_ASSERT_LESS(sal_uInt16(54), nColorDistance); + // This point tests whether shininess is read and used. With default shininess it would be white. + aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.72); + aExpectedColor = Color(255, 231, 255); + nColorDistance = aExpectedColor.GetColorError(aActualColor); + CPPUNIT_ASSERT_LESS(sal_uInt16(14), nColorDistance); +} + +CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145700_3D_FrontLightDim) +{ + // This tests the actual color, not the geometry. + // Load document + OUString aURL = m_directories.getURLFromSrc(sDataDirectory) + "tdf145700_3D_FrontLightDim.doc"; + mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument"); + + // Generate bitmap from shape + uno::Reference<drawing::XShape> xShape = getShape(0); + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL()); + + // Read bitmap and test color + // The expected values are taken from an image generated by Word + // Without the changed methods the nColorDistance was 476 and 173 respecitively. + SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ); + vcl::PngImageReader aPNGReader(aFileStream); + BitmapEx aBMPEx = aPNGReader.read(); + Bitmap aBMP = aBMPEx.GetBitmap(); + Bitmap::ScopedReadAccess pRead(aBMP); + Size aSize = aBMP.GetSizePixel(); + // GetColor(Y,X) + Color aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.4); + Color aExpectedColor(240, 224, 229); + sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor); + CPPUNIT_ASSERT_LESS(sal_uInt16(9), nColorDistance); + aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() * 0.9); + aExpectedColor = Color(96, 90, 92); + nColorDistance = aExpectedColor.GetColorError(aActualColor); + CPPUNIT_ASSERT_LESS(sal_uInt16(6), nColorDistance); +} + +CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145700_3D_FirstLightHarsh) +{ + // Load document + OUString aURL + = m_directories.getURLFromSrc(sDataDirectory) + "tdf145700_3D_FirstLightHarsh.doc"; + mxComponent = loadFromDesktop(aURL, "com.sun.star.text.TextDocument"); + + // Generate bitmap from shape + uno::Reference<drawing::XShape> xShape = getShape(0); + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + GraphicHelper::SaveShapeAsGraphicToPath(mxComponent, xShape, "image/png", aTempFile.GetURL()); + + // Read bitmap and test color in center + SvFileStream aFileStream(aTempFile.GetURL(), StreamMode::READ); + vcl::PngImageReader aPNGReader(aFileStream); + BitmapEx aBMPEx = aPNGReader.read(); + Bitmap aBMP = aBMPEx.GetBitmap(); + Bitmap::ScopedReadAccess pRead(aBMP); + Size aSize = aBMP.GetSizePixel(); + // GetColor(Y,X) + const Color aActualColor = pRead->GetColor(aSize.Height() / 2, aSize.Width() / 2); + const Color aExpectedColor(211, 247, 255); // from image generated by Word + sal_uInt16 nColorDistance = aExpectedColor.GetColorError(aActualColor); + CPPUNIT_ASSERT_LESS(sal_uInt16(3), nColorDistance); +} + CPPUNIT_TEST_FIXTURE(CustomshapesTest, testTdf145956_Origin_Relative_BoundRect) { // The ViewPoint is relative to point Origin. The coordinates of point Origin are fractions of diff --git a/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc b/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc Binary files differnew file mode 100644 index 000000000000..28b8b018d4e6 --- /dev/null +++ b/svx/qa/unit/data/tdf145700_3D_FirstLightHarsh.doc diff --git a/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc b/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc Binary files differnew file mode 100644 index 000000000000..5849e3eac6ba --- /dev/null +++ b/svx/qa/unit/data/tdf145700_3D_FrontLightDim.doc diff --git a/svx/qa/unit/data/tdf145700_3D_NonUI.doc b/svx/qa/unit/data/tdf145700_3D_NonUI.doc Binary files differnew file mode 100644 index 000000000000..d62d57cf02cc --- /dev/null +++ b/svx/qa/unit/data/tdf145700_3D_NonUI.doc diff --git a/svx/qa/unit/svdraw.cxx b/svx/qa/unit/svdraw.cxx index cab6b56b0ae7..275f75507d64 100644 --- a/svx/qa/unit/svdraw.cxx +++ b/svx/qa/unit/svdraw.cxx @@ -398,8 +398,9 @@ CPPUNIT_TEST_FIXTURE(SvdrawTest, testFontWorks) assertXPath(pXmlDoc, "//scene", "projectionMode", "Perspective"); assertXPath(pXmlDoc, "//scene/extrude3D[1]/fill", "color", "#ff0000"); assertXPath(pXmlDoc, "//scene/extrude3D[1]/object3Dattributes/material", "color", "#ff0000"); + // ODF default 50% is repesented by Specular Intensity = 2^5. The relationship is not linear. assertXPath(pXmlDoc, "//scene/extrude3D[1]/object3Dattributes/material", "specularIntensity", - "20"); + "32"); } CPPUNIT_TEST_FIXTURE(SvdrawTest, testSurfaceMetal) @@ -411,12 +412,12 @@ CPPUNIT_TEST_FIXTURE(SvdrawTest, testSurfaceMetal) xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage); - // ODF specifies specular color as rgb(200,200,200) and adding 15 to specularity for metal=true - // without patch the specular color was #ffffff - assertXPath(pXmlDoc, "(//material)[1]", "specular", "#c8c8c8"); - // specularIntensity = 100 - (80 + 15), with nominal value 80 in the file - // without patch specularIntensity was 15 - assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "5"); + // ODF specifies for metal = true specular color as rgb(200,200,200) and adding 15 to specularity + // Together with extrusion-first-light-level 67% and extrusion-specularity 80% factor is + // 0.67*0.8 * 200/255 = 0.42 and color #6b6b6b + assertXPath(pXmlDoc, "(//material)[1]", "specular", "#6b6b6b"); + // 3D specularIntensity = 2^(50/10) + 15 = 47, with default extrusion-shininess 50% + assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "47"); } CPPUNIT_TEST_FIXTURE(SvdrawTest, testExtrusionPhong) @@ -442,16 +443,20 @@ CPPUNIT_TEST_FIXTURE(SvdrawTest, testSurfaceMattePPT) xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage); - // The preset 'matte' in the PPT user interface sets the specularity of material to 0. To get the - // same effect in LO, specular of the lights need to be false in addition. Without patch the - // lights 1, 2, 3 are used, with patch lights 2, 3, 4. Thereby light 4 has the same color and - // direction as light 1, but without being specular. The dump has in both cases three lights, but - // without number. So we test as ersatz, that the third of them has the color of light 1. Being - // not light 1, it is never specular in LO, so no need to test. - // 'color' was "#464646" without patch. - assertXPath(pXmlDoc, "(//light)[3]", "color", "#aaaaaa"); - // 'specularIntensity' was "15" without patch. specularIntensity = 100 - specularity of material. - assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "100"); + // The preset 'matte' sets the specularity of material to 0. But that alone does not make the + // rendering 'matte' in LO. To get a 'matte' effect in LO, specularity of the light need to be + // false in addition. To get this, first light is set off and values from first light are copied + // to forth light, as only first light is specular. Because first and third lights are off, the + // forth light is the second one in the dump. The gray color corresponding to + // FirstLightLevel = 38000/2^16 is #949494. + assertXPath(pXmlDoc, "(//material)[1]", "specular", "#000000"); + assertXPath(pXmlDoc, "(//light)[2]", "color", "#949494"); + // To make the second light soft, part of its intensity is moved to lights 5,6,7 and 8. + assertXPath(pXmlDoc, "(//light)[1]", "color", "#1e1e1e"); + assertXPath(pXmlDoc, "(//light)[3]", "color", "#3b3b3b"); + // The 3D property specularIntensity is not related to 'extrusion-specularity' but to + // 'extrusion-shininess'. specularIntensity = 2^(shininess/10), here default 32. + assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "32"); } CPPUNIT_TEST_FIXTURE(SvdrawTest, testMaterialSpecular) @@ -465,10 +470,17 @@ CPPUNIT_TEST_FIXTURE(SvdrawTest, testMaterialSpecular) xmlDocUniquePtr pXmlDoc = lcl_dumpAndParseFirstObjectWithAssert(pSdrPage); CPPUNIT_ASSERT(pXmlDoc); - // The material property 'draw:extrusion-specularity' was not applied to the object but to the - // scene. Without patch the object has always a default value 15 of specularIntensity. The file - // has specularity=77%. It should be specularIntensity = 100-77=23 with patch. - assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "23"); + // 3D specular color is derived from properties 'extrusion-specularity' and 'extrusion-first-light + // -level'. 3D specularIntensity is dervied from property 'draw:extrusion-shininess'. Both are + // object properties, not scene properties. Those were wrong in various forms before the patch. + // Specularity = 77% * first-light-level 67% = 0.5159, which corresponds to gray color #848484. + assertXPath(pXmlDoc, "(//material)[1]", "specular", "#848484"); + // extrusion-shinines 50% corresponds to 3D specularIntensity 32, use 2^(50/10). + assertXPath(pXmlDoc, "(//material)[1]", "specularIntensity", "32"); + // extrusion-first-light-level 67% corresponds to gray color #ababab, use 255 * 0.67. + assertXPath(pXmlDoc, "(//light)[1]", "color", "#ababab"); + // The first light is harsh, the second light soft. So the 3D scene should have 6 lights (1+1+4). + assertXPath(pXmlDoc, "//light", 6); } } diff --git a/svx/source/customshapes/EnhancedCustomShape3d.cxx b/svx/source/customshapes/EnhancedCustomShape3d.cxx index c967261cfd22..0d8078fe6bd3 100644 --- a/svx/source/customshapes/EnhancedCustomShape3d.cxx +++ b/svx/source/customshapes/EnhancedCustomShape3d.cxx @@ -45,6 +45,7 @@ #include <com/sun/star/drawing/ShadeMode.hpp> #include <svx/sdr/properties/properties.hxx> #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp> #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/range/b2drange.hxx> #include <sdr/primitive2d/sdrattributecreator.hxx> @@ -53,6 +54,7 @@ #include <svx/xlnwtit.hxx> #include <svx/xlntrit.hxx> #include <svx/xfltrit.hxx> +#include <basegfx/color/bcolor.hxx> using namespace com::sun::star; using namespace com::sun::star::uno; @@ -90,6 +92,9 @@ void GetSkew( const SdrCustomShapeGeometryItem& rItem, double& rSkewAmount, doub if ( ! ( pAny && ( *pAny >>= aSkewParaPair ) && ( aSkewParaPair.First.Value >>= rSkewAmount ) && ( aSkewParaPair.Second.Value >>= rSkewAngle ) ) ) { rSkewAmount = 50; + // ODF default is 45, but older ODF documents expect -135 as default. For intermediate + // solution see tdf#141301 and tdf#141127. + // MS Office default -135 is set in msdffimp.cxx to make import independent from setting here. rSkewAngle = -135; } rSkewAngle = basegfx::deg2rad(rSkewAngle); @@ -170,6 +175,56 @@ drawing::Direction3D GetDirection3D( const SdrCustomShapeGeometryItem& rItem, co return aRetValue; } +sal_Int16 GetMetalType(const SdrCustomShapeGeometryItem& rItem, const sal_Int16 eDefault) +{ + sal_Int16 aRetValue(eDefault); + const Any* pAny = rItem.GetPropertyValueByName("Extrusion", "MetalType"); + if (pAny) + *pAny >>= aRetValue; + return aRetValue; +} + +// Calculates the light directions for the additional lights, which are used to emulate soft +// lights of MS Office. Method needs to be documented in the Wiki +// https://wiki.documentfoundation.org/Development/ODF_Implementer_Notes in part +// List_of_LibreOffice_ODF_implementation-defined_items +// The method expects vector rLight to be normalized and results normalized vectors. +void lcl_SoftLightsDirection(const basegfx::B3DVector& rLight, basegfx::B3DVector& rSoftUp, + basegfx::B3DVector& rSoftDown, basegfx::B3DVector& rSoftRight, + basegfx::B3DVector& rSoftLeft) +{ + constexpr double fAngle = basegfx::deg2rad(60); // angle between regular light and soft light + + // We first create directions around (0|0|1) and then rotate them to the light position. + rSoftUp = basegfx::B3DVector(0.0, sin(fAngle), cos(fAngle)); + rSoftDown = basegfx::B3DVector(0.0, -sin(fAngle), cos(fAngle)); + rSoftRight = basegfx::B3DVector(sin(fAngle), 0.0, cos(fAngle)); + rSoftLeft = basegfx::B3DVector(-sin(fAngle), 0.0, cos(fAngle)); + + basegfx::B3DHomMatrix aRotateMat; + aRotateMat.rotate(0.0, 0.0, M_PI_4); + if (rLight.getX() == 0.0 && rLight.getZ() == 0.0) + { + // Special case with light from top or bottom + if (rLight.getY() >= 0.0) + aRotateMat.rotate(-M_PI_2, 0.0, 0.0); + else + aRotateMat.rotate(M_PI_2, 0.0, 0.0); + } + else + { + // Azimuth from z-axis to x-axis. (0|0|1) to (1|0|0) is 90deg. + double fAzimuth = atan2(rLight.getX(), rLight.getZ()); + // Elevation from xz-plane to y-axis. (0|0|1) to (0|1|0) is 90deg. + double fElevation = atan2(rLight.getY(), std::hypot(rLight.getX(), rLight.getZ())); + aRotateMat.rotate(-fElevation, fAzimuth, 0.0); + } + + rSoftUp = aRotateMat * rSoftUp; + rSoftDown = aRotateMat * rSoftDown; + rSoftRight = aRotateMat * rSoftRight; + rSoftLeft = aRotateMat * rSoftLeft; +} } SdrObject* EnhancedCustomShape3d::Create3DObject( @@ -256,7 +311,7 @@ SdrObject* EnhancedCustomShape3d::Create3DObject( pScene->GetProperties().SetObjectItem( Svx3DShadeModeItem(static_cast<sal_uInt16>(eShadeMode))); aSet.Put( makeSvx3DPercentDiagonalItem( 0 ) ); aSet.Put( Svx3DTextureModeItem( 1 ) ); - // SPECIFIC needed for ShadeMode_SMOOTH and ShadeMode_PHONG, otherwise FLAT is faster + // SPECIFIC needed for ShadeMode_SMOOTH and ShadeMode_PHONG, otherwise FLAT is faster. if (eShadeMode == drawing::ShadeMode_SMOOTH || eShadeMode == drawing::ShadeMode_PHONG) aSet.Put( Svx3DNormalsKindItem(static_cast<sal_uInt16>(drawing::NormalsKind_SPECIFIC))); else @@ -703,105 +758,259 @@ SdrObject* EnhancedCustomShape3d::Create3DObject( pScene->SetLogicRect(a2DProjectionResult.GetBoundRect()); - // light - - - drawing::Direction3D aFirstLightDirectionDefault( 50000, 0, 10000 ); - drawing::Direction3D aFirstLightDirection( GetDirection3D( rGeometryItem, "FirstLightDirection", aFirstLightDirectionDefault ) ); - if ( aFirstLightDirection.DirectionZ == 0.0 ) - aFirstLightDirection.DirectionZ = 1.0; - - double fLightIntensity = GetDouble( rGeometryItem, "FirstLightLevel", 43712.0 / 655.36 ) / 100.0; + // light and material - bool bFirstLightHarsh = GetBool( rGeometryItem, "FirstLightHarsh", true ); + // "LightFace" has nothing corresponding in 3D rendering engine. + /* bool bLightFace = */ GetBool(rGeometryItem, "LightFace", true); // default in ODF - drawing::Direction3D aSecondLightDirectionDefault( -50000, 0, 10000 ); - drawing::Direction3D aSecondLightDirection( GetDirection3D( rGeometryItem, "SecondLightDirection", aSecondLightDirectionDefault ) ); - if ( aSecondLightDirection.DirectionZ == 0.0 ) - aSecondLightDirection.DirectionZ = -1; + // Light directions - double fLight2Intensity = GetDouble( rGeometryItem, "SecondLightLevel", 43712.0 / 655.36 ) / 100.0; + drawing::Direction3D aFirstLightDirectionDefault(50000.0, 0.0, 10000.0); + drawing::Direction3D aFirstLightDirection(GetDirection3D( rGeometryItem, "FirstLightDirection", aFirstLightDirectionDefault)); + if (aFirstLightDirection.DirectionX == 0.0 && aFirstLightDirection.DirectionY == 0.0 + && aFirstLightDirection.DirectionZ == 0.0) + aFirstLightDirection.DirectionZ = 1.0; + basegfx::B3DVector aLight1Vector(aFirstLightDirection.DirectionX, -aFirstLightDirection.DirectionY, aFirstLightDirection.DirectionZ); + aLight1Vector.normalize(); + + drawing::Direction3D aSecondLightDirectionDefault(-50000.0, 0.0, 10000.0); + drawing::Direction3D aSecondLightDirection(GetDirection3D( rGeometryItem, "SecondLightDirection", aSecondLightDirectionDefault)); + if (aSecondLightDirection.DirectionX == 0.0 && aSecondLightDirection.DirectionY == 0.0 + && aSecondLightDirection.DirectionZ == 0.0) + aSecondLightDirection.DirectionZ = 1.0; + basegfx::B3DVector aLight2Vector(aSecondLightDirection.DirectionX, -aSecondLightDirection.DirectionY, aSecondLightDirection.DirectionZ); + aLight2Vector.normalize(); + + // Light Intensity + + // For "FirstLight" the 3D-Scene light "1" is regulary used. In case of surface "Matte" + // the light 4 is used instead. For "SecondLight" the 3D-Scene light "2" is regulary used. + // In case first or second light is not harsh, the lights 5 to 8 are used in addition + // to get a soft light appearance. + // The 3D-Scene light "3" is currently not used. + + // ODF default 66%. MS Office default 38000/65536=0.579 is set in import filter. + double fLight1Intensity = GetDouble(rGeometryItem, "FirstLightLevel", 66) / 100.0; + // ODF and MS Office have both default 'true'. + bool bFirstLightHarsh = GetBool(rGeometryItem, "FirstLightHarsh", true); + // ODF default 66%. MS Office default 38000/65536=0.579 is set in import filter + double fLight2Intensity = GetDouble(rGeometryItem, "SecondLightLevel", 66) / 100.0; + // ODF has default 'true'. MS Office default 'false' is set in import. + bool bSecondLightHarsh = GetBool(rGeometryItem, "SecondLightHarsh", true); + + // ODF default 33%. MS Office default 20000/65536=0.305 is set in import filter. + double fAmbientIntensity = GetDouble(rGeometryItem, "Brightness", 33) / 100.0; + + double fLight1IntensityForSpecular(fLight1Intensity); // remember original value + if (!bFirstLightHarsh || !bSecondLightHarsh) // might need softing lights + { + bool bNeedSoftLights(false); // catch case of lights with zero intensity. + basegfx::B3DVector aLight5Vector; + basegfx::B3DVector aLight6Vector; + basegfx::B3DVector aLight7Vector; + basegfx::B3DVector aLight8Vector; + // The needed light intensities depend on the angle between regular light and + // additional lights, currently for 60deg. + Color aHoriSoftLightColor; + Color aVertSoftLightColor; + + if (!bSecondLightHarsh && fLight2Intensity > 0.0 + && (bFirstLightHarsh || fLight1Intensity == 0.0)) // only second light soft + { + // That is default for shapes generated in the UI, for LO and MS Office as well. + bNeedSoftLights = true; + double fLight2SoftIntensity = fLight2Intensity * 0.40; + aHoriSoftLightColor = Color(basegfx::BColor(fLight2SoftIntensity).clamp()); + aVertSoftLightColor = aHoriSoftLightColor; + fLight2Intensity *= 0.2; + + lcl_SoftLightsDirection(aLight2Vector, aLight5Vector, aLight6Vector, + aLight7Vector, aLight8Vector); + } + else if (!bFirstLightHarsh && fLight1Intensity > 0.0 + && (bSecondLightHarsh || fLight2Intensity == 0.0)) // only first light soft + { + bNeedSoftLights = true; + double fLight1SoftIntensity = fLight1Intensity * 0.40; + aHoriSoftLightColor = Color(basegfx::BColor(fLight1SoftIntensity).clamp()); + aVertSoftLightColor = aHoriSoftLightColor; + fLight1Intensity *= 0.2; + + lcl_SoftLightsDirection(aLight1Vector, aLight5Vector, aLight6Vector, + aLight7Vector, aLight8Vector); + } + else if (!bFirstLightHarsh && fLight1Intensity > 0.0 && !bSecondLightHarsh + && fLight2Intensity > 0.0) // both lights soft + { + bNeedSoftLights = true; + // We do not hat enough lights. We use two soft lights for FirstLight and two for + // SecondLight and double intensity. + double fLight1SoftIntensity = fLight1Intensity * 0.8; + fLight1Intensity *= 0.4; + aHoriSoftLightColor = Color(basegfx::BColor(fLight1SoftIntensity).clamp()); + basegfx::B3DVector aDummy1, aDummy2; + lcl_SoftLightsDirection(aLight1Vector, aDummy1, aDummy2, aLight7Vector, + aLight8Vector); + + double fLight2SoftIntensity = fLight2Intensity * 0.8; + aVertSoftLightColor = Color(basegfx::BColor(fLight2SoftIntensity).clamp()); + fLight2Intensity *= 0.4; + lcl_SoftLightsDirection(aLight2Vector, aLight5Vector, aLight6Vector, aDummy1, + aDummy2); + } - /* sal_Bool bLight2Harsh = */ GetBool( rGeometryItem, "SecondLightHarsh", false ); - /* sal_Bool bLightFace = */ GetBool( rGeometryItem, "LightFace", false ); + if (bNeedSoftLights) + { + pScene->GetProperties().SetObjectItem( + makeSvx3DLightDirection5Item(aLight5Vector)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightcolor5Item(aVertSoftLightColor)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff5Item(true)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightDirection6Item(aLight6Vector)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightcolor6Item(aVertSoftLightColor)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff6Item(true)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightDirection7Item(aLight7Vector)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightcolor7Item(aHoriSoftLightColor)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff7Item(true)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightDirection8Item(aLight8Vector)); + pScene->GetProperties().SetObjectItem( + makeSvx3DLightcolor8Item(aHoriSoftLightColor)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff8Item(true)); + } + } - double fAmbientIntensity = GetDouble( rGeometryItem, "Brightness", 22178.0 / 655.36 ) / 100.0; - bool bMetal = GetBool( rGeometryItem, "Metal", false ); + // ToDo: MSO seems to add half of the surplus to ambient color. ODF restricts value to <1. + if (fLight1Intensity > 1.0) + { + fAmbientIntensity += (fLight1Intensity - 1.0) / 2.0; + } - // Currently needed for import from binary MS Office. - // ToDo: Create a solution in the filters. - // MS Office adds black to diffuse and ambient color in case of metal. Use an - // approximating ersatz. - if (bMetal) + // ToDo: How to handle fAmbientIntensity larger 1.0 ? Perhaps lighten object color? + + // Now set the regulary 3D-scene light attributes. + Color aAmbientColor(basegfx::BColor(fAmbientIntensity).clamp()); + pScene->GetProperties().SetObjectItem(makeSvx3DAmbientcolorItem(aAmbientColor)); + + pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection1Item(aLight1Vector)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff1Item(fLight1Intensity > 0.0)); + Color aLight1Color(basegfx::BColor(fLight1Intensity).clamp()); + pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor1Item(aLight1Color)); + + pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection2Item(aLight2Vector)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff2Item(fLight2Intensity > 0.0)); + Color aLight2Color(basegfx::BColor(fLight2Intensity).clamp()); + pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor2Item(aLight2Color)); + + // Object reactions on light + // Diffusion, Specular-Color and -Intensity are object properties, not scene properties. + // Surface flag "Metal" is an object property too. + + // Property "Diffusion" would correspond to style attribute "drd3:diffuse-color". + // But that is not implemented. We cannot ignore the attribute because MS Office sets + // attribute c3DDiffuseAmt to 43712 (Type Fixed 16.16, approx 66,9%) instead of MSO + // default 65536 (100%), if the user sets surface 'Metal' in the UI of MS Office. + // We will change the material color of the 3D object as ersatz. + // ODF data type is percent with default 0%. MSO default is set in import filter. + double fDiffusion = GetDouble(rGeometryItem, "Diffusion", 0.0) / 100.0; + + // ODF standard specifies for value true: "the specular color for the shading of an + // extruded shape is gray (red, green and blue values of 200) instead of white and 15% is + // added to the specularity." + // Neither 'specularity' nor 'specular color' is clearly defined in the standard. ODF term + // 'specularity' seems to correspond to UI field 'Specular Intensity' for 3D scenes. + // MS Office uses current material color in case 'Metal' is set. To detect, whether + // rendering similar to MS Office has to be used the property 'MetalType' is used. It is + // set on import and in the extrusion bar. + bool bMetal = GetBool(rGeometryItem, "Metal", false); + sal_Int16 eMetalType( + GetMetalType(rGeometryItem, drawing::EnhancedCustomShapeMetalType::MetalODF)); + bool bMetalMSCompatible + = eMetalType == drawing::EnhancedCustomShapeMetalType::MetalMSCompatible; + + // Property "Specularity" corresponds to 3D object style attribute dr3d:specular-color. + double fSpecularity = GetDouble(rGeometryItem, "Specularity", 0) / 100.0; + + if (bMetal && !bMetalMSCompatible) { - fAmbientIntensity -= 0.15; // Estimated value. Adapt it if necessary. - fAmbientIntensity = std::clamp(fAmbientIntensity, 0.0, 1.0); - fLight2Intensity -= 0.15; - fLight2Intensity = std::clamp(fLight2Intensity, 0.0, 1.0); + fSpecularity *= 200.0 / 255.0; } - sal_uInt16 nAmbientColor = static_cast<sal_uInt16>( fAmbientIntensity * 255.0 ); - if ( nAmbientColor > 255 ) - nAmbientColor = 255; - Color aGlobalAmbientColor( static_cast<sal_uInt8>(nAmbientColor), static_cast<sal_uInt8>(nAmbientColor), static_cast<sal_uInt8>(nAmbientColor) ); - pScene->GetProperties().SetObjectItem( makeSvx3DAmbientcolorItem( aGlobalAmbientColor ) ); - - sal_uInt8 nSpotLight1 = static_cast<sal_uInt8>( fLightIntensity * 255.0 ); - basegfx::B3DVector aSpotLight1( aFirstLightDirection.DirectionX, - ( aFirstLightDirection.DirectionY ), -( aFirstLightDirection.DirectionZ ) ); - aSpotLight1.normalize(); - pScene->GetProperties().SetObjectItem( makeSvx3DLightOnOff1Item( true ) ); - Color aAmbientSpot1Color( nSpotLight1, nSpotLight1, nSpotLight1 ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightcolor1Item( aAmbientSpot1Color ) ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightDirection1Item( aSpotLight1 ) ); - - sal_uInt8 nSpotLight2 = static_cast<sal_uInt8>( fLight2Intensity * 255.0 ); - basegfx::B3DVector aSpotLight2( aSecondLightDirection.DirectionX, -aSecondLightDirection.DirectionY, -aSecondLightDirection.DirectionZ ); - aSpotLight2.normalize(); - pScene->GetProperties().SetObjectItem( makeSvx3DLightOnOff2Item( true ) ); - Color aAmbientSpot2Color( nSpotLight2, nSpotLight2, nSpotLight2 ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightcolor2Item( aAmbientSpot2Color ) ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightDirection2Item( aSpotLight2 ) ); - - // Currently needed for import from binary MS Office. - // ToDo: Create a solution in the filters. - // Binary MS Office creates brighter shapes than our 3D engine with same values. - sal_uInt8 nSpotLight3 = 70; - basegfx::B3DVector aSpotLight3( 0.0, 0.0, 1.0 ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightOnOff3Item( true ) ); - Color aAmbientSpot3Color( nSpotLight3, nSpotLight3, nSpotLight3 ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightcolor3Item( aAmbientSpot3Color ) ); - pScene->GetProperties().SetObjectItem( makeSvx3DLightDirection3Item( aSpotLight3 ) ); - - double fSpecular = GetDouble( rGeometryItem, "Specularity", 0 ); - // ODF specifies 'white', OOXML uses shape fill color in some presets - Color aSpecularCol(255, 255, 255); - if ( bMetal ) + // MS Office seems to render as if 'Specular Color' = Specularity * Light1Intensity. + double fShadingFactor = fLight1IntensityForSpecular * fSpecularity; + Color aSpecularCol(basegfx::BColor(fShadingFactor).clamp()); + // In case of bMetalMSCompatible the color will be recalculated in the below loop. + + // Shininess ODF default 50 (unit %). MS Office default 5, import filter makes *10. + // Shininess corresponds to "Specular Intensity" with the nonlinear relationship + // "Specular Intensity" = 2^c3DShininess = 2^("Shininess" / 10) + double fShininess = GetDouble(rGeometryItem, "Shininess", 50) / 10.0; + fShininess = std::clamp<double>(pow(2, fShininess), 0.0, 100.0); + sal_uInt16 nIntensity = static_cast<sal_uInt16>(basegfx::fround(fShininess)); + if (bMetal && !bMetalMSCompatible) { - // values as specified in ODF - aSpecularCol = Color( 200, 200, 200 ); - fSpecular += 15.0; + nIntensity += 15; // as specified in ODF + nIntensity = std::clamp<sal_uInt16>(nIntensity, 0, 100); } - sal_Int32 nIntensity = 100 - static_cast<sal_Int32>(fSpecular); - nIntensity = std::clamp<sal_Int32>(nIntensity, 0, 100); - // specularity is an object property, not a scene property - SdrObjListIter aSceneIter( *pScene, SdrIterMode::DeepNoGroups ); + SdrObjListIter aSceneIter(*pScene, SdrIterMode::DeepNoGroups); while (aSceneIter.IsMore()) { const SdrObject* pNext = aSceneIter.Next(); + + // Change material color as ersatz for missing style attribute "drd3:diffuse-color". + // For this ersatz we exclude case fDiffusion == 0.0, because for older documents this + // attribute is not written out to draw:extrusion-diffusion and ODF default 0 would + // produce black objects. + const Color& rMatColor + = pNext->GetProperties().GetItem(XATTR_FILLCOLOR).GetColorValue(); + Color aOldMatColor(rMatColor); + if (basegfx::fTools::more(fDiffusion, 0.0) + && !basegfx::fTools::equal(fDiffusion, 1.0)) + { + // Occurs e.g. with MS surface preset 'Metal'. + sal_uInt16 nHue; + sal_uInt16 nSaturation; + sal_uInt16 nBrightness; + rMatColor.RGBtoHSB(nHue, nSaturation, nBrightness); + nBrightness + = static_cast<sal_uInt16>(static_cast<double>(nBrightness) * fDiffusion); + nBrightness = std::clamp<sal_uInt16>(nBrightness, 0, 100); + Color aNewMatColor = Color::HSBtoRGB(nHue, nSaturation, nBrightness); + pNext->GetProperties().SetObjectItem(XFillColorItem("", aNewMatColor)); + } + + // Using material color instead of gray in case of MS Office compatible rendering. + if (bMetal && bMetalMSCompatible) + { + sal_uInt16 nHue; + sal_uInt16 nSaturation; + sal_uInt16 nBrightness; + aOldMatColor.RGBtoHSB(nHue, nSaturation, nBrightness); + nBrightness = static_cast<sal_uInt16>(static_cast<double>(nBrightness) + * fShadingFactor); + nBrightness = std::clamp<sal_uInt16>(nBrightness, 0, 100); + aSpecularCol = Color::HSBtoRGB(nHue, nSaturation, nBrightness); + } + pNext->GetProperties().SetObjectItem(makeSvx3DMaterialSpecularItem(aSpecularCol)); - pNext->GetProperties().SetObjectItem(makeSvx3DMaterialSpecularIntensityItem(static_cast<sal_uInt16>(nIntensity))); + pNext->GetProperties().SetObjectItem( + makeSvx3DMaterialSpecularIntensityItem(nIntensity)); } - // fSpecular = 0 is used to indicate surface preset "matte". - if (!bFirstLightHarsh || basegfx::fTools::equalZero(fSpecular, 0.0001)) + // fSpecularity = 0 is used to indicate surface preset "Matte". + if (basegfx::fTools::equalZero(fSpecularity)) { // First light in LO 3D engine is always specular, all other lights are never specular. // We copy light1 values to light4 and use it instead of light1 in the 3D scene. pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff1Item(false)); pScene->GetProperties().SetObjectItem(makeSvx3DLightOnOff4Item(true)); - pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor4Item(aAmbientSpot1Color)); - pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection4Item(aSpotLight1)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightcolor4Item(aLight1Color)); + pScene->GetProperties().SetObjectItem(makeSvx3DLightDirection4Item(aLight1Vector)); } // removing placeholder objects diff --git a/svx/source/tbxctrls/extrusioncontrols.cxx b/svx/source/tbxctrls/extrusioncontrols.cxx index c54004b24cec..b0d8a1238610 100644 --- a/svx/source/tbxctrls/extrusioncontrols.cxx +++ b/svx/source/tbxctrls/extrusioncontrols.cxx @@ -829,11 +829,13 @@ ExtrusionSurfaceWindow::ExtrusionSurfaceWindow(svt::PopupWindowController* pCont , mxMatt(m_xBuilder->weld_radio_button("matt")) , mxPlastic(m_xBuilder->weld_radio_button("plastic")) , mxMetal(m_xBuilder->weld_radio_button("metal")) + , mxMetalMSO(m_xBuilder->weld_radio_button("metalMSO")) { mxWireFrame->connect_toggled(LINK(this, ExtrusionSurfaceWindow, SelectHdl)); mxMatt->connect_toggled(LINK(this, ExtrusionSurfaceWindow, SelectHdl)); mxPlastic->connect_toggled(LINK(this, ExtrusionSurfaceWindow, SelectHdl)); mxMetal->connect_toggled(LINK(this, ExtrusionSurfaceWindow, SelectHdl)); + mxMetalMSO->connect_toggled(LINK(this, ExtrusionSurfaceWindow, SelectHdl)); AddStatusListener( g_sExtrusionSurface ); } @@ -853,6 +855,8 @@ void ExtrusionSurfaceWindow::implSetSurface( int nSurface, bool bEnabled ) mxPlastic->set_sensitive(bEnabled); mxMetal->set_active(nSurface == 3 && bEnabled); mxMetal->set_sensitive(bEnabled); + mxMetalMSO->set_active(nSurface == 4 && bEnabled); + mxMetalMSO->set_sensitive(bEnabled); } void ExtrusionSurfaceWindow::statusChanged( @@ -886,8 +890,10 @@ IMPL_LINK(ExtrusionSurfaceWindow, SelectHdl, weld::Toggleable&, rButton, void) nSurface = 1; else if (mxPlastic->get_active()) nSurface = 2; - else + else if (mxMetal->get_active()) nSurface = 3; + else + nSurface = 4; Sequence< PropertyValue > aArgs{ comphelper::makePropertyValue( OUString(g_sExtrusionSurface).copy(5), nSurface) }; diff --git a/svx/source/tbxctrls/extrusioncontrols.hxx b/svx/source/tbxctrls/extrusioncontrols.hxx index 7e0043487274..8b7c2e8afcc7 100644 --- a/svx/source/tbxctrls/extrusioncontrols.hxx +++ b/svx/source/tbxctrls/extrusioncontrols.hxx @@ -188,6 +188,7 @@ private: std::unique_ptr<weld::RadioButton> mxMatt; std::unique_ptr<weld::RadioButton> mxPlastic; std::unique_ptr<weld::RadioButton> mxMetal; + std::unique_ptr<weld::RadioButton> mxMetalMSO; DECL_LINK( SelectHdl, weld::Toggleable&, void ); diff --git a/svx/source/toolbars/extrusionbar.cxx b/svx/source/toolbars/extrusionbar.cxx index 659af238d77e..26ac4805cde0 100644 --- a/svx/source/toolbars/extrusionbar.cxx +++ b/svx/source/toolbars/extrusionbar.cxx @@ -18,6 +18,7 @@ */ +#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> #include <com/sun/star/drawing/ShadeMode.hpp> @@ -126,9 +127,9 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r { css::uno::Any* pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, sExtrusion ); + bool bOn(false); if( pAny ) { - bool bOn(false); (*pAny) >>= bOn; bOn = !bOn; (*pAny) <<= bOn; @@ -139,6 +140,23 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r aPropValue.Name = sExtrusion; aPropValue.Value <<= true; rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); + bOn = true; + } + // draw:extrusion-diffusion has default 0% and c3DDiffuseAmt has default 100%. We set property + // "Diffusion" with value 100% here if it does not exist already. This forces, that the + // property is written to file in case an extrusion is newly created, and users of old + // documents, which usually do not have this property, can force the value to 100% by toggling + // the extrusion off and on. + if (bOn) + { + pAny = rGeometryItem.GetPropertyValueByName(sExtrusion, u"Diffusion"); + if (!pAny) + { + css::beans::PropertyValue aPropValue; + aPropValue.Name = u"Diffusion"; + aPropValue.Value <<= 100.0; + rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); + } } } break; @@ -335,52 +353,75 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r break; case 1: // matte case 2: // plastic - case 3: // metal + case 3: // metal ODF + case 4: // metal MS Office if (eOldShadeMode == ShadeMode_DRAFT) eShadeMode = ShadeMode_FLAT; // ODF default break; } - bool bMetal = nSurface == 3; - // ODF has no dedicated property for 'surface'. MS Office binary format uses attribute // c3DSpecularAmt to distinguish between 'matte' (=0) and 'plastic'. - // From point of ODF, using not harsh light has similar effect. + // We do the same. double fOldSpecularity = 0.0; pAny = rGeometryItem.GetPropertyValueByName(sExtrusion, u"Specularity"); if (pAny) *pAny >>= fOldSpecularity; double fSpecularity = fOldSpecularity; - bool bOldIsFirstLightHarsh = true; - pAny = rGeometryItem.GetPropertyValueByName(sExtrusion, u"FirstLightHarsh"); - if (pAny) - *pAny >>= bOldIsFirstLightHarsh; - bool bIsFirstLightHarsh = bOldIsFirstLightHarsh; switch( nSurface ) { case 0: // wireframe break; case 1: // matte fSpecularity = 0.0; - bIsFirstLightHarsh = false; break; case 2: // plastic - case 3: // metal + case 3: // metal ODF + case 4: // metal MS Office if (basegfx::fTools::equalZero(fOldSpecularity, 0.0001)) - fSpecularity = 80; // estimated value, can be changed if necessary - if (!bOldIsFirstLightHarsh) - bIsFirstLightHarsh = true; + // MS Office uses 80000/65536. That is currently not allowed in ODF. + // But the ODF error will be catched in xmloff. + fSpecularity = 80000.0 / 655.36; // interpreted as % break; } + // MS Office binary format uses attribute c3DDiffuseAmt with value =43712 (Fixed 16.16) in + // addition to the 'metal' flag. For other surface kinds default = 65536 is used. + // We toggle between 100 and 43712.0 / 655.36 here, to get better ODF -> MSO binary. + // We keep other values, those might be set outside regular UI, e.g by macro. + double fOldDiffusion = 100.0; + pAny = rGeometryItem.GetPropertyValueByName(sExtrusion, u"Diffusion"); + if (pAny) + *pAny >>= fOldDiffusion; + double fDiffusion = fOldDiffusion; + if (nSurface == 4) + { + if (fOldDiffusion == 100.0) + fDiffusion = 43712.0 / 655.36; // interpreted as % + } + else + { + if (basegfx::fTools::equalZero(fOldDiffusion - 43712.0 / 655.36, 0.0001)) + fDiffusion = 100.0; + } + css::beans::PropertyValue aPropValue; aPropValue.Name = "ShadeMode"; aPropValue.Value <<= eShadeMode; rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); aPropValue.Name = "Metal"; - aPropValue.Value <<= bMetal; - rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); + aPropValue.Value <<= nSurface == 3 || nSurface == 4; + rGeometryItem.SetPropertyValue(sExtrusion, aPropValue); + + if (nSurface == 3 || nSurface == 4) + { + aPropValue.Name = "MetalType"; + aPropValue.Value <<= nSurface == 4 + ? EnhancedCustomShapeMetalType::MetalMSCompatible + : EnhancedCustomShapeMetalType::MetalODF; + rGeometryItem.SetPropertyValue(sExtrusion, aPropValue); + } if (!basegfx::fTools::equalZero(fOldSpecularity - fSpecularity, 0.0001)) { @@ -389,10 +430,10 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r rGeometryItem.SetPropertyValue(sExtrusion, aPropValue); } - if (bOldIsFirstLightHarsh != bIsFirstLightHarsh) + if (!basegfx::fTools::equalZero(fOldDiffusion - fDiffusion, 0.0001)) { - aPropValue.Name = "FirstLightHarsh"; - aPropValue.Value <<= bIsFirstLightHarsh; + aPropValue.Name = "Diffusion"; + aPropValue.Value <<= fDiffusion; rGeometryItem.SetPropertyValue(sExtrusion, aPropValue); } } @@ -404,16 +445,17 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r { sal_Int32 nLevel = rReq.GetArgs()->GetItem<SfxInt32Item>(SID_EXTRUSION_LIGHTING_INTENSITY)->GetValue(); - double fBrightness; - double fLevel1; - double fLevel2; + double fBrightness; // c3DAmbientIntensity in MS Office + double fLevel1; // c3DKeyIntensity in MS Office + double fLevel2; // c3DFillIntensity in MS Office + // ToDo: "bright" values are different from MS Office. Should they be kept? switch( nLevel ) { case 0: // bright - fBrightness = 34.0; - fLevel1 = 66.0; - fLevel2 = 66.0; + fBrightness = 33.0; // ODF default. + fLevel1 = 66.0; // ODF default + fLevel2 = 66.0; // ODF default break; case 1: // normal fBrightness = 15.0; @@ -432,10 +474,6 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r aPropValue.Value <<= fBrightness; rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); - aPropValue.Name = "SecondLightHarsh"; - aPropValue.Value <<= false; - rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); - aPropValue.Name = "FirstLightLevel"; aPropValue.Value <<= fLevel1; rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); @@ -443,6 +481,12 @@ static void impl_execute( SfxRequest const & rReq, SdrCustomShapeGeometryItem& r aPropValue.Name = "SecondLightLevel"; aPropValue.Value <<= fLevel2; rGeometryItem.SetPropertyValue( sExtrusion, aPropValue ); + + // If a user sets light preset 'Dim' in MS Office, MS Office sets second light to harsh. + // In other cases it is soft. + aPropValue.Name = "SecondLightHarsh"; + aPropValue.Value <<= nLevel == 2; + rGeometryItem.SetPropertyValue(sExtrusion, aPropValue); } } break; @@ -665,8 +709,8 @@ static void getExtrusionDirectionState( SdrView const * pSdrView, SfxItemSet& rS } bool bParallel = true; - Position3D aViewPoint( 3472, -3472, 25000 ); - double fSkewAngle = -135; + Position3D aViewPoint( 3472, -3472, 25000 ); // MSO default + double fSkewAngle = -135; // MSO default pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, "ProjectionMode" ); sal_Int16 nProjectionMode = sal_Int16(); @@ -869,25 +913,33 @@ static void getExtrusionSurfaceState( SdrView const * pSdrView, SfxItemSet& rSet sal_Int32 nSurface = 0; // wire frame ShadeMode eShadeMode( ShadeMode_FLAT ); - pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, "ShadeMode" ); + pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, u"ShadeMode" ); if( pAny ) *pAny >>= eShadeMode; if (eShadeMode != ShadeMode_DRAFT) { bool bMetal = false; - pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, "Metal" ); + pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, u"Metal" ); if( pAny ) *pAny >>= bMetal; if( bMetal ) { - nSurface = 3; // metal + nSurface = 3; // metal ODF + sal_Int16 eMetalType; + pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, u"MetalType" ); + if (pAny) + { + *pAny >>= eMetalType; + if (eMetalType == EnhancedCustomShapeMetalType::MetalMSCompatible) + nSurface = 4; // metal MS Office + } } else { double fSpecularity = 0; - pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, "Specularity" ); + pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, u"Specularity" ); if( pAny ) *pAny >>= fSpecularity; @@ -951,7 +1003,7 @@ static void getExtrusionDepthState( SdrView const * pSdrView, SfxItemSet& rSet ) continue; } - double fDepth = 1270.0; + double fDepth = 1270.0; // =36pt ODF default pAny = rGeometryItem.GetPropertyValueByName( sExtrusion, "Depth" ); if( pAny ) { diff --git a/svx/uiconfig/ui/surfacewindow.ui b/svx/uiconfig/ui/surfacewindow.ui index e7f612b8e7a9..52688d176f47 100644 --- a/svx/uiconfig/ui/surfacewindow.ui +++ b/svx/uiconfig/ui/surfacewindow.ui @@ -22,6 +22,11 @@ <property name="can_focus">False</property> <property name="icon_name">svx/res/wireframe_16.png</property> </object> + <object class="GtkImage" id="image5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">svx/res/metal_16.png</property> + </object> <object class="GtkPopover" id="SurfaceWindow"> <property name="can_focus">False</property> <property name="no_show_all">True</property> @@ -89,7 +94,7 @@ </child> <child> <object class="GtkRadioButton" id="metal"> - <property name="label" translatable="yes" context="surfacewindow|RID_SVXSTR_METAL">Me_tal</property> + <property name="label" translatable="yes" context="surfacewindow|RID_SVXSTR_METAL">Me_tal ODF</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">False</property> @@ -105,6 +110,24 @@ <property name="position">3</property> </packing> </child> + <child> + <object class="GtkRadioButton" id="metalMSO"> + <property name="label" translatable="yes" context="surfacewindow|RID_SVXSTR_METALMSO">Meta_l MS compatible</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="image">image5</property> + <property name="use_underline">True</property> + <property name="always_show_image">True</property> + <property name="draw_indicator">True</property> + <property name="group">wireframe</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> </object> </child> </object> diff --git a/xmloff/inc/EnhancedCustomShapeToken.hxx b/xmloff/inc/EnhancedCustomShapeToken.hxx index 328704f9b889..7fd86f5a5126 100644 --- a/xmloff/inc/EnhancedCustomShapeToken.hxx +++ b/xmloff/inc/EnhancedCustomShapeToken.hxx @@ -47,6 +47,7 @@ namespace xmloff::EnhancedCustomShapeToken { EAS_extrusion_first_light_direction, EAS_extrusion_second_light_direction, EAS_extrusion_metal, + EAS_extrusion_metal_type, EAS_shade_mode, EAS_extrusion_rotation_angle, EAS_extrusion_rotation_center, @@ -115,6 +116,7 @@ namespace xmloff::EnhancedCustomShapeToken { EAS_FirstLightDirection, EAS_SecondLightDirection, EAS_Metal, + EAS_MetalType, EAS_ShadeMode, EAS_RotateAngle, EAS_RotationCenter, diff --git a/xmloff/qa/unit/data/tdf145700_3D_metal_type_MSCompatible.doc b/xmloff/qa/unit/data/tdf145700_3D_metal_type_MSCompatible.doc Binary files differnew file mode 100644 index 000000000000..99c433654dcd --- /dev/null +++ b/xmloff/qa/unit/data/tdf145700_3D_metal_type_MSCompatible.doc diff --git a/xmloff/qa/unit/draw.cxx b/xmloff/qa/unit/draw.cxx index fc07053cbacf..04c7178ca6bf 100644 --- a/xmloff/qa/unit/draw.cxx +++ b/xmloff/qa/unit/draw.cxx @@ -16,15 +16,19 @@ #include <com/sun/star/frame/Desktop.hpp> #include <com/sun/star/frame/XStorable.hpp> #include <com/sun/star/packages/zip/ZipFileAccess.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp> #include <com/sun/star/drawing/XDrawPagesSupplier.hpp> #include <com/sun/star/drawing/XMasterPageTarget.hpp> #include <com/sun/star/util/Color.hpp> #include <com/sun/star/text/XTextRange.hpp> #include <com/sun/star/text/XTextTable.hpp> +#include <comphelper/configuration.hxx> +#include <officecfg/Office/Common.hxx> #include <unotools/mediadescriptor.hxx> #include <unotools/tempfile.hxx> #include <unotools/ucbstreamhelper.hxx> +#include <unotools/saveopt.hxx> using namespace ::com::sun::star; @@ -44,6 +48,7 @@ public: void registerNamespaces(xmlXPathContextPtr& pXmlXpathCtx) override; uno::Reference<lang::XComponent>& getComponent() { return mxComponent; } void save(const OUString& rFilterName, utl::TempFile& rTempFile); + uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex); }; void XmloffDrawTest::setUp() @@ -76,6 +81,17 @@ void XmloffDrawTest::save(const OUString& rFilterName, utl::TempFile& rTempFile) validate(rTempFile.GetFileName(), test::ODF); } +uno::Reference<drawing::XShape> XmloffDrawTest::getShape(sal_uInt8 nShapeIndex) +{ + uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent, + uno::UNO_QUERY_THROW); + uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages()); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW); + uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex), + uno::UNO_QUERY_THROW); + return xShape; +} + CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testTextBoxLoss) { // Load a document that has a shape with a textbox in it. Save it to ODF and reload. @@ -242,6 +258,90 @@ CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testTableInShape) CPPUNIT_ASSERT_EQUAL(OUString("A1"), xCell->getString()); } +// Tests for save/load of new (LO 7.4) attribute loext:extrusion-metal-type +namespace +{ +void lcl_assertMetalProperties(std::string_view sInfo, uno::Reference<drawing::XShape>& rxShape) +{ + uno::Reference<beans::XPropertySet> xShapeProps(rxShape, uno::UNO_QUERY); + uno::Sequence<beans::PropertyValue> aGeoPropSeq; + xShapeProps->getPropertyValue("CustomShapeGeometry") >>= aGeoPropSeq; + comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq); + uno::Sequence<beans::PropertyValue> aExtrusionSeq; + aGeoPropMap.getValue("Extrusion") >>= aExtrusionSeq; + comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq); + + bool bIsMetal(false); + aExtrusionPropMap.getValue("Metal") >>= bIsMetal; + OString sMsg = OString::Concat(sInfo) + " Metal"; + CPPUNIT_ASSERT_MESSAGE(sMsg.getStr(), bIsMetal); + + sal_Int16 nMetalType(-1); + aExtrusionPropMap.getValue("MetalType") >>= nMetalType; + sMsg = OString::Concat(sInfo) + " MetalType"; + CPPUNIT_ASSERT_EQUAL_MESSAGE( + sMsg.getStr(), css::drawing::EnhancedCustomShapeMetalType::MetalMSCompatible, nMetalType); +} +} + +CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testExtrusionMetalTypeExtended) +{ + // import + getComponent() = loadFromDesktop(m_directories.getURLFromSrc(DATA_DIRECTORY) + + "tdf145700_3D_metal_type_MSCompatible.doc", + "com.sun.star.text.TextDocument"); + // verify properties + uno::Reference<drawing::XShape> xShape(getShape(0)); + lcl_assertMetalProperties("from doc", xShape); + + // Test, that new attribute is written with loext namespace. Adapt when attribute is added to ODF. + utl::TempFile aTempFile; + // The file has set c3DSpecularAmt="65536" to prevent validation error in attribute + // draw:extrusion-specularity. The error, that 122% is written in case of c3DSpecularAmt="80000" is + // not yet fixed. + save("writer8", aTempFile); + + // assert XML. + std::unique_ptr<SvStream> pStream = parseExportStream(aTempFile, "content.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + assertXPath(pXmlDoc, "//draw:enhanced-geometry", "extrusion-metal", "true"); + assertXPath(pXmlDoc, + "//draw:enhanced-geometry[@loext:extrusion-metal-type='loext:MetalMSCompatible']"); + + // reload + getComponent()->dispose(); + getComponent() = loadFromDesktop(aTempFile.GetURL(), "com.sun.star.text.TextDocument"); + // verify properties + uno::Reference<drawing::XShape> xShapeReload(getShape(0)); + lcl_assertMetalProperties("from ODF 1.3 extended", xShapeReload); +} + +CPPUNIT_TEST_FIXTURE(XmloffDrawTest, testExtrusionMetalTypeStrict) +{ + // import + getComponent() = loadFromDesktop(m_directories.getURLFromSrc(DATA_DIRECTORY) + + "tdf145700_3D_metal_type_MSCompatible.doc", + "com.sun.star.text.TextDocument"); + + // save ODF 1.3 strict and test, that new attribute is not written. Adapt when attribute is + // added to ODF. + const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion(GetODFDefaultVersion()); + SetODFDefaultVersion(SvtSaveOptions::ODFVER_013); + // The file has set c3DSpecularAmt="65536" to prevent validation error in attribute + // draw:extrusion-specularity. The error, that 122% is written in case of c3DSpecularAmt="80000" is + // not yet fixed. + utl::TempFile aTempFile; + save("writer8", aTempFile); + + // assert XML. + std::unique_ptr<SvStream> pStream = parseExportStream(aTempFile, "content.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + assertXPath(pXmlDoc, "//draw:enhanced-geometry", "extrusion-metal", "true"); + assertXPath(pXmlDoc, "//draw:enhanced-geometry[@loext:extrusion-metal-type]", 0); + + SetODFDefaultVersion(nCurrentODFVersion); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/xmloff/source/core/xmltoken.cxx b/xmloff/source/core/xmltoken.cxx index 298dd431a0fe..f3ae86566e43 100644 --- a/xmloff/source/core/xmltoken.cxx +++ b/xmloff/source/core/xmltoken.cxx @@ -2473,6 +2473,7 @@ namespace xmloff::token { TOKEN( "extrusion-first-light-direction" , XML_EXTRUSION_FIRST_LIGHT_DIRECTION ), TOKEN( "extrusion-second-light-direction" , XML_EXTRUSION_SECOND_LIGHT_DIRECTION ), TOKEN( "extrusion-metal" , XML_EXTRUSION_METAL ), + TOKEN( "extrusion-metal-type" , XML_EXTRUSION_METAL_TYPE ), TOKEN( "extrusion-rotation-angle" , XML_EXTRUSION_ROTATION_ANGLE ), TOKEN( "extrusion-rotation-center" , XML_EXTRUSION_ROTATION_CENTER ), TOKEN( "extrusion-shininess" , XML_EXTRUSION_SHININESS ), diff --git a/xmloff/source/draw/EnhancedCustomShapeToken.cxx b/xmloff/source/draw/EnhancedCustomShapeToken.cxx index 38ca0df48e6a..700e29fc71fd 100644 --- a/xmloff/source/draw/EnhancedCustomShapeToken.cxx +++ b/xmloff/source/draw/EnhancedCustomShapeToken.cxx @@ -59,6 +59,7 @@ const TokenTable pTokenTableArray[] = { "extrusion-first-light-direction", EAS_extrusion_first_light_direction }, { "extrusion-second-light-direction", EAS_extrusion_second_light_direction }, { "extrusion-metal", EAS_extrusion_metal }, + { "extrusion-metal-type", EAS_extrusion_metal_type }, { "shade-mode", EAS_shade_mode }, { "extrusion-rotation-angle", EAS_extrusion_rotation_angle }, { "extrusion-rotation-center", EAS_extrusion_rotation_center }, @@ -127,6 +128,7 @@ const TokenTable pTokenTableArray[] = { "FirstLightDirection", EAS_FirstLightDirection }, { "SecondLightDirection", EAS_SecondLightDirection }, { "Metal", EAS_Metal }, + { "MetalType", EAS_MetalType }, { "ShadeMode", EAS_ShadeMode }, { "RotateAngle", EAS_RotateAngle }, { "RotationCenter", EAS_RotationCenter }, diff --git a/xmloff/source/draw/shapeexport.cxx b/xmloff/source/draw/shapeexport.cxx index f1c23d3ed0b2..ea33330994b1 100644 --- a/xmloff/source/draw/shapeexport.cxx +++ b/xmloff/source/draw/shapeexport.cxx @@ -46,6 +46,7 @@ #include <com/sun/star/drawing/EnhancedCustomShapeGluePointType.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeTextFrame.hpp> @@ -4453,6 +4454,25 @@ static void ImpExportEnhancedGeometry( SvXMLExport& rExport, const uno::Referenc bExtrusionMetal ? GetXMLToken( XML_TRUE ) : GetXMLToken( XML_FALSE ) ); } break; + case EAS_MetalType : + { + // export only if ODF extensions are enabled + sal_Int16 eMetalType; + if (rProp.Value >>= eMetalType) + { + SvtSaveOptions::ODFSaneDefaultVersion eVersion = rExport.getSaneDefaultVersion(); + if (eVersion > SvtSaveOptions::ODFSVER_013 + && (eVersion & SvtSaveOptions::ODFSVER_EXTENDED)) + { + if (eMetalType == drawing::EnhancedCustomShapeMetalType::MetalMSCompatible) + aStr = "loext:MetalMSCompatible"; + else + aStr = "draw:MetalODF"; + rExport.AddAttribute(XML_NAMESPACE_LO_EXT, XML_EXTRUSION_METAL_TYPE, aStr); + } + } + } + break; case EAS_ShadeMode : { // shadeMode diff --git a/xmloff/source/draw/ximpcustomshape.cxx b/xmloff/source/draw/ximpcustomshape.cxx index c8dbf70ba143..df86901b9635 100644 --- a/xmloff/source/draw/ximpcustomshape.cxx +++ b/xmloff/source/draw/ximpcustomshape.cxx @@ -40,6 +40,7 @@ #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeMetalType.hpp> #include <com/sun/star/drawing/ProjectionMode.hpp> #include <com/sun/star/drawing/Position3D.hpp> #include <sax/tools/converter.hxx> @@ -982,6 +983,18 @@ void XMLEnhancedCustomShapeContext::startFastElement( case EAS_extrusion_metal : GetBool( maExtrusion, aIter.toView(), EAS_Metal ); break; + case EAS_extrusion_metal_type : + { + OUString rValue = aIter.toString(); + sal_Int16 eMetalType(drawing::EnhancedCustomShapeMetalType::MetalODF); + if (rValue == "loext:MetalMSCompatible") + eMetalType = drawing::EnhancedCustomShapeMetalType::MetalMSCompatible; + beans::PropertyValue aProp; + aProp.Name = EASGet(EAS_MetalType); + aProp.Value <<= eMetalType; + maExtrusion.push_back(aProp); + } + break; case EAS_shade_mode : { drawing::ShadeMode eShadeMode( drawing::ShadeMode_FLAT ); diff --git a/xmloff/source/token/tokens.txt b/xmloff/source/token/tokens.txt index a23326c71f17..f610bfbff47e 100644 --- a/xmloff/source/token/tokens.txt +++ b/xmloff/source/token/tokens.txt @@ -2320,6 +2320,7 @@ extrusion-second-light-level extrusion-first-light-direction extrusion-second-light-direction extrusion-metal +extrusion-metal-type extrusion-rotation-angle extrusion-rotation-center extrusion-shininess |