oox/inc/drawingml/scene3dhelper.hxx              |   13 +
 oox/qa/unit/data/Scene3d_material_highlight.pptx |binary
 oox/qa/unit/data/Scene3d_material_wireframe.pptx |binary
 oox/qa/unit/testscene3d.cxx                      |  168 +++++++++++++++--------
 oox/source/drawingml/scene3dhelper.cxx           |   93 ++++++++++++
 oox/source/drawingml/shape.cxx                   |    4 
 6 files changed, 223 insertions(+), 55 deletions(-)

New commits:
commit 2eabd56ff5f65733c6fa1bcef14d4a39c21dddc7
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Fri Mar 15 00:00:51 2024 +0100
Commit:     Regina Henschel <rb.hensc...@t-online.de>
CommitDate: Wed Mar 20 18:06:35 2024 +0100

    tdf#70039 import material of extruded shapes
    
    This patch continues the tdf#70039 related commits 98b06ed3 and 6e5529d7.
    It adds further property-value pairs to the property map which is later
    used in CustomShapeProperties.pushToPropSet() for property Extrusion.
    
    OOXML defines a set of 15 material presets in section 20.1.10.50
    ST_PresetMaterialType. The specification lists some values and it has
    example pictures. The example shape uses Bevel. That is not implemented,
    thus the examples are not really usable. Microsoft specifies the values
    it uses in section 2.1.1331 in MS-OI29500] - v20231113. The values used
    in the patch are based on these resources.
    
    MS Office defines the material by the properties 'Specular-',
    'Diffuse-', 'Ambient-' and 'Emissive-Color, 'Specular Power', 'Blinn
    Hightlight', 'Diffuse-' and 'Alpha-Fresnel', and 'Metal'. But ODF, API
    and current implementation have for material only the properties
    'Diffusion', 'Specularity', 'Shininess', 'Metal' and 'MetalType'. The
    patch tries to map the values as well as possible.
    
    The mapping works well for the legacy-foo materials which reflect the
    material in binary MS Office and is base of ODF and our implementation.
    But the preset has also material where transparency is added to the
    reflected light or the reflected light is blended or brightened with
    White. That is not possible currently and such shapes look different
    than in MS Office. These are especially the materials in the
    'Translucent' section in the UI of MS Office. The material type 'flat'
    uses 'Emissive Color'. Such is not available at all. So material 'flat'
    looks noticable different too.
    
    More details about the used values is contained in the attachment in
    the bug report.
    
    Change-Id: I16a242446cbe98efcbdf5452058e1a3bd88dcf56
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164853
    Tested-by: Jenkins
    Reviewed-by: Patrick Luby <guibomac...@gmail.com>
    Reviewed-by: Regina Henschel <rb.hensc...@t-online.de>

diff --git a/oox/inc/drawingml/scene3dhelper.hxx 
b/oox/inc/drawingml/scene3dhelper.hxx
index af72a0e0cf2a..0f79825b13ca 100644
--- a/oox/inc/drawingml/scene3dhelper.hxx
+++ b/oox/inc/drawingml/scene3dhelper.hxx
@@ -50,6 +50,19 @@ public:
     void setLightingProperties(const oox::drawingml::Shape3DPropertiesPtr 
p3DProperties,
                                const double& rRotZ, oox::PropertyMap& 
rPropertyMap);
 
+    /** Puts the material properties of the 3D-shape into the property map
+        @details OOXML and MSO describe the material properties using Specular 
Color (RGBA), Diffuse
+            Color (RGBA), Ambient Color (RGBA), Emissive Color (RGBA), 
Specular Power (positive int),
+            Diffuse Fresnel (int), Alpha Fresnel (int), Metal (bool) and Blinn 
Highlight (bool). ODF
+            and API have Diffusion (double), Specularity (double), Shininess 
(double), Metal (bool),
+            MetalType (enum). Thus a directly use of the MSO values is not 
possible and some material
+            types cannot be rendered. The method uses workarounds and 
approximations.
+        @param [in] p3DProperties contains besides other properties the 
material preset name
+        @param [in, out] rPropertyMap has properties directly usable in
+            CustomShapeProperties.pushToPropSet() for property Extrusion.*/
+    static void setMaterialProperties(const 
oox::drawingml::Shape3DPropertiesPtr p3DProperties,
+                                      oox::PropertyMap& rPropertyMap);
+
 private:
     /** Calculates angles suitable for API from OOXML scene3d angles.
         @param [in] nLat, nLon, nRev in unit 1/60000 deg with same orientation 
as the attributes lat,
diff --git a/oox/qa/unit/data/Scene3d_material_highlight.pptx 
b/oox/qa/unit/data/Scene3d_material_highlight.pptx
new file mode 100644
index 000000000000..c299c21bc68c
Binary files /dev/null and b/oox/qa/unit/data/Scene3d_material_highlight.pptx 
differ
diff --git a/oox/qa/unit/data/Scene3d_material_wireframe.pptx 
b/oox/qa/unit/data/Scene3d_material_wireframe.pptx
new file mode 100644
index 000000000000..f72baeb25178
Binary files /dev/null and b/oox/qa/unit/data/Scene3d_material_wireframe.pptx 
differ
diff --git a/oox/qa/unit/testscene3d.cxx b/oox/qa/unit/testscene3d.cxx
index 5ca452ea3d21..48b968ba23cd 100644
--- a/oox/qa/unit/testscene3d.cxx
+++ b/oox/qa/unit/testscene3d.cxx
@@ -43,7 +43,7 @@ protected:
     uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex, sal_uInt8 
nPageIndex);
     // Converts the shape 0 on page 0 to a bitmap and returns this bitmap.
     // It assumes, that shape 0 on page 0 is the only shape.
-    void getShapeAsBitmap(BitmapEx& rBMP);
+    void getShapeAsBitmap(BitmapEx& rBMP, sal_uInt8 nShapeIndex);
 };
 
 uno::Reference<drawing::XShape> TestScene3d::getShape(sal_uInt8 nShapeIndex, 
sal_uInt8 nPageIndex)
@@ -60,20 +60,20 @@ uno::Reference<drawing::XShape> 
TestScene3d::getShape(sal_uInt8 nShapeIndex, sal
     return xShape;
 }
 
-void TestScene3d::getShapeAsBitmap(BitmapEx& rBMP)
+void TestScene3d::getShapeAsBitmap(BitmapEx& rBMP, sal_uInt8 nShapeIndex)
 {
     SfxViewShell* pViewShell = SfxViewShell::Current();
     SdrView* pSdrView = pViewShell->GetDrawView();
 
     // Mark object and convert it to bitmap
-    uno::Reference<drawing::XShape> xShape3D(getShape(0, 0));
+    uno::Reference<drawing::XShape> xShape3D(getShape(nShapeIndex, 0));
     SdrObject* pSdrShape(SdrObject::getSdrObjectFromXShape(xShape3D));
     pSdrView->MarkObj(pSdrShape, pSdrView->GetSdrPageView());
     dispatchCommand(mxComponent, ".uno:ConvertIntoBitmap", {});
     pSdrView->UnmarkAll();
 
     // Get graphic
-    uno::Reference<drawing::XShape> xShapeBmp(getShape(0, 0));
+    uno::Reference<drawing::XShape> xShapeBmp(getShape(nShapeIndex, 0));
     SdrGrafObj* pGrafObj = 
dynamic_cast<SdrGrafObj*>(SdrObject::getSdrObjectFromXShape(xShapeBmp));
     CPPUNIT_ASSERT_MESSAGE("No image object created", pGrafObj);
     const Graphic& rGraphic = pGrafObj->GetGraphic();
@@ -81,6 +81,28 @@ void TestScene3d::getShapeAsBitmap(BitmapEx& rBMP)
     CPPUNIT_ASSERT_MESSAGE("No bitmap", !rBMP.IsEmpty());
 }
 
+namespace
+{
+void lcl_AssertColorsApproximateEqual(const ::Color& aExpected, const ::Color& 
aActual)
+{
+    // Currently (March 2024), the import of lighting and material is only 
approximately possible.
+    // Thus colors are not identical. When the import will be improved, the 
tolerances should be
+    // reduced. The test uses HSB instead of RGB, because differences in hue 
are more irritating and
+    // should be detected as a priority. That is not possible with 
GetColorError() method.
+    sal_uInt16 nExpH;
+    sal_uInt16 nExpS;
+    sal_uInt16 nExpB;
+    sal_uInt16 nActH;
+    sal_uInt16 nActS;
+    sal_uInt16 nActB;
+    aExpected.RGBtoHSB(nExpH, nExpS, nExpB);
+    aActual.RGBtoHSB(nActH, nActS, nActB);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Hue", nExpH, nActH, 2);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Saturation", nExpS, nActS, 13);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Brightness", nExpB, nActB, 11);
+}
+} // end anonymous namespace
+
 CPPUNIT_TEST_FIXTURE(TestScene3d, test_isometricRightUp)
 {
     // Given a document with a scene3d element on a shape. Without the fix, 
the shape was imported as
@@ -357,21 +379,17 @@ CPPUNIT_TEST_FIXTURE(TestScene3d, 
test_lightRig_modernCamera)
     // The test assumes rendering with ShadeMode_FLAT.
     loadFromFile(u"Scene3d_lightRig_modernCamera.pptx");
     BitmapEx aBMP;
-    getShapeAsBitmap(aBMP);
+    getShapeAsBitmap(aBMP, 0);
 
     // Size in pixel depends on dpi. Thus calculate positions relative to size.
     // Color in center
     sal_Int32 nX = 0.5 * aBMP.GetSizePixel().Width();
     sal_Int32 nY = 0.5 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedCenter(248, 226, 212);
-    CPPUNIT_ASSERT_MESSAGE("center color wrong",
-                           
aExpectedCenter.GetColorError(aBMP.GetPixelColor(nX, nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(247, 225, 211), 
aBMP.GetPixelColor(nX, nY));
     // Color left
     nX = 0.046122 * aBMP.GetSizePixel().Width();
     nY = 0.5 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedLeft(0, 105, 48);
-    CPPUNIT_ASSERT_MESSAGE("left color wrong",
-                           aExpectedLeft.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(0, 103, 47), 
aBMP.GetPixelColor(nX, nY));
 }
 
 CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_legacyCamera)
@@ -383,21 +401,17 @@ CPPUNIT_TEST_FIXTURE(TestScene3d, 
test_lightRig_legacyCamera)
     // The test assumes rendering with ShadeMode_FLAT.
     loadFromFile(u"Scene3d_lightRig_legacyCamera.pptx");
     BitmapEx aBMP;
-    getShapeAsBitmap(aBMP);
+    getShapeAsBitmap(aBMP, 0);
 
     // Size in pixel depends on dpi. Thus calculate positions relative to size.
     // Color in center
     sal_Int32 nX = 0.5 * aBMP.GetSizePixel().Width();
     sal_Int32 nY = 0.5 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedCenter(96, 88, 82);
-    CPPUNIT_ASSERT_MESSAGE("center color wrong",
-                           
aExpectedCenter.GetColorError(aBMP.GetPixelColor(nX, nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(94, 86, 80), 
aBMP.GetPixelColor(nX, nY));
     // Color left
     nX = 0.046122 * aBMP.GetSizePixel().Width();
     nY = 0.5 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedLeft(0, 180, 82);
-    CPPUNIT_ASSERT_MESSAGE("left color wrong",
-                           aExpectedLeft.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(5, 185, 87), 
aBMP.GetPixelColor(nX, nY));
 }
 
 CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_default)
@@ -408,27 +422,21 @@ CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_default)
     // The test assumes rendering with ShadeMode_FLAT.
     loadFromFile(u"Scene3d_lightRig_default.pptx");
     BitmapEx aBMP;
-    getShapeAsBitmap(aBMP);
+    getShapeAsBitmap(aBMP, 0);
 
     // Size in pixel depends on dpi. Thus calculate positions relative to size.
     // Front color
     sal_Int32 nX = 0.93811 * aBMP.GetSizePixel().Width();
     sal_Int32 nY = 0.49904 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedFront(165, 187, 150);
-    CPPUNIT_ASSERT_MESSAGE("front color wrong",
-                           aExpectedFront.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(165, 187, 150), 
aBMP.GetPixelColor(nX, nY));
     // Left color
     nX = 0.078176 * aBMP.GetSizePixel().Width();
     nY = 0.49904 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedLeft(255, 189, 74);
-    CPPUNIT_ASSERT_MESSAGE("left color wrong",
-                           aExpectedLeft.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(255, 189, 74), 
aBMP.GetPixelColor(nX, nY));
     // Top color
     nX = 0.48860 * aBMP.GetSizePixel().Width();
     nY = 0.069098 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedTop(189, 100, 39);
-    CPPUNIT_ASSERT_MESSAGE("top color wrong",
-                           aExpectedTop.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(189, 100, 39), 
aBMP.GetPixelColor(nX, nY));
 }
 
 CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_dir_rotation)
@@ -439,27 +447,21 @@ CPPUNIT_TEST_FIXTURE(TestScene3d, 
test_lightRig_dir_rotation)
     // and MSO. The test assumes rendering with ShadeMode_FLAT.
     loadFromFile(u"Scene3d_lightRig_dir_rotation.pptx");
     BitmapEx aBMP;
-    getShapeAsBitmap(aBMP);
+    getShapeAsBitmap(aBMP, 0);
 
     // Size in pixel depends on dpi. Thus calculate positions relative to size.
     // Front color
     sal_Int32 nX = 0.93811 * aBMP.GetSizePixel().Width();
     sal_Int32 nY = 0.49904 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedFront(165, 187, 150);
-    CPPUNIT_ASSERT_MESSAGE("front color wrong",
-                           aExpectedFront.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(165, 187, 150), 
aBMP.GetPixelColor(nX, nY));
     // Left color
     nX = 0.078176 * aBMP.GetSizePixel().Width();
     nY = 0.49904 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedLeft(206, 108, 42);
-    CPPUNIT_ASSERT_MESSAGE("left color wrong",
-                           aExpectedLeft.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(206, 108, 42), 
aBMP.GetPixelColor(nX, nY));
     // Top color
     nX = 0.48860 * aBMP.GetSizePixel().Width();
     nY = 0.069098 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedTop(255, 189, 74);
-    CPPUNIT_ASSERT_MESSAGE("top color wrong",
-                           aExpectedTop.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(255, 189, 74), 
aBMP.GetPixelColor(nX, nY));
 }
 
 CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_rot_rotation)
@@ -469,32 +471,88 @@ CPPUNIT_TEST_FIXTURE(TestScene3d, 
test_lightRig_rot_rotation)
     // The test assumes rendering with ShadeMode_FLAT.
     loadFromFile(u"Scene3d_lightRig_rot_rotation.pptx");
     BitmapEx aBMP;
-    getShapeAsBitmap(aBMP);
+    getShapeAsBitmap(aBMP, 0);
 
     // Size in pixel depends on dpi. Thus calculate positions relative to size.
     // Front color, same as in MS Office
     sal_Int32 nX = 0.93811 * aBMP.GetSizePixel().Width();
     sal_Int32 nY = 0.49904 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedFront(88, 100, 80);
-    CPPUNIT_ASSERT_MESSAGE("center color wrong",
-                           aExpectedFront.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
-    // Left color, different from MS Office
-    // The rotation brings the second light in a position, that it contributes 
to the left face.
-    // Because the light is specular in MS Office, but current LibreOffice 
cannot make a second
-    // light specular, the colors in MS Office and LibreOffice differ 
noticeably. MS Office has
-    // here rgb(255, 214, 99). The expected color is the color in LibreOffice 
as of March 2024.
-    // The test needs to be updated, when LibreOffice rendering is improved.
+    lcl_AssertColorsApproximateEqual(::Color(88, 100, 80), 
aBMP.GetPixelColor(nX, nY));
+    // Left color
     nX = 0.078176 * aBMP.GetSizePixel().Width();
     nY = 0.49904 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedLeft(255, 191, 75);
-    CPPUNIT_ASSERT_MESSAGE("left color wrong",
-                           aExpectedLeft.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
-    // Top color, same as in MS Office
+    lcl_AssertColorsApproximateEqual(::Color(255, 214, 99), 
aBMP.GetPixelColor(nX, nY));
+    // Top color
     nX = 0.48860 * aBMP.GetSizePixel().Width();
     nY = 0.069098 * aBMP.GetSizePixel().Height();
-    ::Color aExpectedTop(106, 56, 22);
-    CPPUNIT_ASSERT_MESSAGE("top color wrong",
-                           aExpectedTop.GetColorError(aBMP.GetPixelColor(nX, 
nY)) < 7);
+    lcl_AssertColorsApproximateEqual(::Color(106, 56, 22), 
aBMP.GetPixelColor(nX, nY));
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_material_highlight)
+{
+    // The file contains six shapes with same geometry and fill and line 
color. The scenes use the
+    // camera 'orthographicFront' and the lightRig 'twoPt'. The test looks at 
an area of highlight
+    // and at an area outside the hightlight.
+
+    loadFromFile(u"Scene3d_material_highlight.pptx");
+
+    BitmapEx aBMP;
+    getShapeAsBitmap(aBMP, 0); // material legacyPlastic
+    sal_Int32 nX = 0.75 * aBMP.GetSizePixel().Width();
+    sal_Int32 nYhigh = 0.25 * aBMP.GetSizePixel().Height();
+    sal_Int32 nYsoft = 0.75 * aBMP.GetSizePixel().Height();
+    lcl_AssertColorsApproximateEqual(::Color(255, 255, 255), 
aBMP.GetPixelColor(nX, nYhigh));
+    lcl_AssertColorsApproximateEqual(::Color(130, 95, 70), 
aBMP.GetPixelColor(nX, nYsoft));
+
+    // same geometry, thus nX, nYhigh and nYsoft unchanged
+    getShapeAsBitmap(aBMP, 1); // material warmMatte
+    lcl_AssertColorsApproximateEqual(::Color(253, 200, 164), 
aBMP.GetPixelColor(nX, nYhigh));
+    lcl_AssertColorsApproximateEqual(::Color(132, 96, 71), 
aBMP.GetPixelColor(nX, nYsoft));
+
+    getShapeAsBitmap(aBMP, 2); // material metal
+    lcl_AssertColorsApproximateEqual(::Color(255, 255, 255), 
aBMP.GetPixelColor(nX, nYhigh));
+    lcl_AssertColorsApproximateEqual(::Color(132, 96, 71), 
aBMP.GetPixelColor(nX, nYsoft));
+
+    getShapeAsBitmap(aBMP, 3); // material matte
+    lcl_AssertColorsApproximateEqual(::Color(190, 138, 102), 
aBMP.GetPixelColor(nX, nYhigh));
+    lcl_AssertColorsApproximateEqual(::Color(130, 95, 70), 
aBMP.GetPixelColor(nX, nYsoft));
+
+    getShapeAsBitmap(aBMP, 4); // material dkEdge
+    lcl_AssertColorsApproximateEqual(::Color(255, 255, 255), 
aBMP.GetPixelColor(nX, nYhigh));
+    lcl_AssertColorsApproximateEqual(::Color(115, 84, 62), 
aBMP.GetPixelColor(nX, nYsoft));
+
+    getShapeAsBitmap(aBMP, 5); // material legacyMetal
+    lcl_AssertColorsApproximateEqual(::Color(255, 255, 220), 
aBMP.GetPixelColor(nX, nYhigh));
+    lcl_AssertColorsApproximateEqual(::Color(86, 63, 46), 
aBMP.GetPixelColor(nX, nYsoft));
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_material_wireframe)
+{
+    // Given a document with a shape in 3D mode with material legacyWireframe.
+    // It uses a projection "Oblique: Top Left".
+    loadFromFile(u"Scene3d_material_wireframe.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(0, 0));
+
+    // Make sure the export to ODF has the corresponding attributes.
+    save("impress8");
+    xmlDocUniquePtr pXmlDoc = parseExport("content.xml");
+
+    assertXPath(pXmlDoc,
+                
"/office:document-content/office:body/office:presentation/draw:page/"
+                "draw:custom-shape/draw:enhanced-geometry"_ostr,
+                "extrusion-origin"_ostr, "-0.5 -0.5");
+    assertXPath(pXmlDoc,
+                
"/office:document-content/office:body/office:presentation/draw:page/"
+                "draw:custom-shape/draw:enhanced-geometry"_ostr,
+                "extrusion-skew"_ostr, "30 -45");
+    assertXPath(pXmlDoc,
+                
"/office:document-content/office:body/office:presentation/draw:page/"
+                "draw:custom-shape/draw:enhanced-geometry"_ostr,
+                "projection"_ostr, "parallel");
+    assertXPath(pXmlDoc,
+                
"/office:document-content/office:body/office:presentation/draw:page/"
+                "draw:custom-shape/draw:enhanced-geometry"_ostr,
+                "shade-mode"_ostr, "draft");
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/oox/source/drawingml/scene3dhelper.cxx 
b/oox/source/drawingml/scene3dhelper.cxx
index d2b03ae97485..eff649a71cd7 100644
--- a/oox/source/drawingml/scene3dhelper.cxx
+++ b/oox/source/drawingml/scene3dhelper.cxx
@@ -20,12 +20,15 @@
 
 #include <com/sun/star/drawing/Direction3D.hpp>
 #include <com/sun/star/drawing/EnhancedCustomShapeParameter.hpp>
+#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/Position3D.hpp>
 #include <com/sun/star/drawing/ProjectionMode.hpp>
 #include <com/sun/star/drawing/ShadeMode.hpp>
 
+#include <cmath>
+
 namespace oox
 {
 /** This struct is used to hold values from the OOXML camera preset types.*/
@@ -941,6 +944,96 @@ void Scene3DHelper::setLightingProperties(const 
oox::drawingml::Shape3DPropertie
         rPropertyMap.setProperty(oox::PROP_SecondLightLevel, 0.0); // prevent 
defaults.
 }
 
+namespace
+/** This struct is used to hold material values for extruded custom shapes. 
Because we cannot yet
+    render all material propertes MS Office uses, the values are adapted to 
our current abilities.*/
+{
+struct MaterialValues
+{
+    std::u16string_view msMaterialPrstName; // identifies the material type
+    // Corresponds to MS Office 'Diffuse Color' and 'Ambient Color'.
+    double fDiffusion;
+    double fSpecularity; // Corresponds to MS Office 'Specular Color'.
+    // Corresponds to our 'Shininess' as 2^(Shininess/10) = nSpecularPower.
+    sal_uInt8 nSpecularPower;
+    bool bMetal; // Corresponds to MS Office 'Metal'
+    // constants com::sun::star::drawing::EnhancedCustomShapeMetalType
+    // MetalMSCompatible belongs to 'legacyMetal' material type.
+    std::optional<sal_Int16> oMetalType; // MetalODF = 0, MetalMSCompatible = 1
+    // MS Office properties 'Emissive Color', 'Diffuse Fresnel', 'Alpha 
Fresnel' and 'Blinn Highlight'
+    // are not contained.
+};
+} // end anonymous namespace
+
+// OOXML standard has a fixed amount of 15 material types. The type 
'legacyWireframe' is special and
+// thus is handled separately. A spreadsheet with further remarks is attached 
to tdf#70039.
+constexpr sal_uInt16 nPrstMaterialCount(14);
+constexpr MaterialValues aPrstMaterialArray[nPrstMaterialCount]
+    = { { u"clear", 100, 60, 20, false, {} },
+        { u"dkEdge", 70, 100, 35, false, {} },
+        { u"flat", 100, 80, 50, false, {} },
+        { u"legacyMatte", 100, 0, 0, false, {} },
+        { u"legacyMetal", 66.69921875, 122.0703125, 32, true, { 1 } },
+        { u"legacyPlastic", 100, 122.0703125, 32, false, {} },
+        { u"matte", 100, 0, 0, false, {} },
+        { u"metal", 100, 100, 12, true, { 0 } },
+        { u"plastic", 100, 60, 12, true, { 0 } },
+        { u"powder", 100, 30, 10, false, {} },
+        { u"softEdge", 100, 100, 35, false, {} },
+        { u"softmetal", 100, 100, 8, true, { 0 } },
+        { u"translucentPowder", 100, 30, 10, true, { 0 } },
+        { u"warmMatte", 100, 30, 8, false, {} } };
+
+void Scene3DHelper::setMaterialProperties(const 
oox::drawingml::Shape3DPropertiesPtr p3DProperties,
+                                          oox::PropertyMap& rPropertyMap)
+{
+    if (!p3DProperties)
+        return;
+
+    // PowerPoint does not write aus prstMaterial="warmMatte", but handles it 
as default.
+    const sal_Int32 nMaterialID = 
(*p3DProperties).mnMaterial.value_or(XML_warmMatte); // token
+
+    // special handling for 'legacyWireframe'
+    if (nMaterialID == XML_legacyWireframe)
+    {
+        // This is handled via shade mode of the scene.
+        rPropertyMap.setProperty(oox::PROP_ShadeMode, 
css::drawing::ShadeMode_DRAFT);
+        // Notice, the color of the strokes will be different from MS Office, 
because LO uses the
+        // shape line color even if the line style is 'none', whereas MS 
Office uses contour color or
+        // Black.
+        return;
+    }
+
+    sal_Int16 nIdx(0); // Index into aPrstMaterialArray
+    while (nIdx < nPrstMaterialCount
+           && aPrstMaterialArray[nIdx].msMaterialPrstName
+                  != 
oox::drawingml::Generic3DProperties::getPresetMaterialTypeString(nMaterialID))
+        ++nIdx;
+    if (nIdx >= nPrstMaterialCount)
+        return; // error in document
+
+    // extrusion-diffuse, extrusion-specularity-loext
+    rPropertyMap.setProperty(oox::PROP_Diffusion, 
aPrstMaterialArray[nIdx].fDiffusion);
+    rPropertyMap.setProperty(oox::PROP_Specularity, 
aPrstMaterialArray[nIdx].fSpecularity);
+
+    // extrusion-shininess
+    double fShininess = 0.0;
+    // Conversion 2^(fShininess/10) = nSpecularPower
+    if (aPrstMaterialArray[nIdx].nSpecularPower > 0)
+        fShininess = 10.0 * std::log2(aPrstMaterialArray[nIdx].nSpecularPower);
+    rPropertyMap.setProperty(oox::PROP_Shininess, fShininess);
+
+    // extrusion-metal, extrusion-metal-type
+    rPropertyMap.setProperty(oox::PROP_Metal, aPrstMaterialArray[nIdx].bMetal);
+    if (aPrstMaterialArray[nIdx].bMetal)
+    {
+        sal_Int16 eMetalType = aPrstMaterialArray[nIdx].oMetalType.value_or(0) 
== 1
+                                   ? 
css::drawing::EnhancedCustomShapeMetalType::MetalMSCompatible
+                                   : 
css::drawing::EnhancedCustomShapeMetalType::MetalODF;
+        rPropertyMap.setProperty(oox::PROP_MetalType, eMetalType);
+    }
+}
+
 } // end namespace oox
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index 195b85f893df..963391806c40 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -1002,8 +1002,12 @@ Reference< XShape > const & Shape::createAndInsert(
     }
 
     if (bHas3DEffect)
+    {
         aScene3DHelper.setLightingProperties(mp3DPropertiesPtr, 
fShapeRotateInclCamera,
                                              
getCustomShapeProperties()->getExtrusionPropertyMap());
+        oox::Scene3DHelper::setMaterialProperties(
+            mp3DPropertiesPtr, 
getCustomShapeProperties()->getExtrusionPropertyMap());
+    }
 
     if (bIsCroppedGraphic || bIs3DGraphic)
     {

Reply via email to