drawinglayer/source/tools/emfpbrush.cxx                            |   18 ++
 drawinglayer/source/tools/emfpbrush.hxx                            |    4 
 drawinglayer/source/tools/emfphelperdata.cxx                       |   69 
++++++++--
 drawinglayer/source/tools/emfppen.cxx                              |    4 
 drawinglayer/source/tools/emfppen.hxx                              |    2 
 drawinglayer/source/tools/primitive2dxmldump.cxx                   |   27 +++
 emfio/qa/cppunit/emf/EmfImportTest.cxx                             |   46 
++++++
 emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf |binary
 8 files changed, 155 insertions(+), 15 deletions(-)

New commits:
commit 6c05a6339c27038b14d2f35f8f4b2b94335d6491
Author:     Bartosz Kosiorek <[email protected]>
AuthorDate: Tue Feb 24 23:23:28 2026 +0100
Commit:     Bartosz Kosiorek <[email protected]>
CommitDate: Sat Feb 28 08:04:39 2026 +0100

    tdf#129342 tdf#170985 EMF+ Implement BrushTypeTextureFill for bitmaps
    
    This patch implements the missing TextureFill brush support for EMF+
    records. Previously, texture fills were ignored, resulting in missing
    backgrounds or shapes.
    
    Change-Id: I61f80a9851f3929651f54b68d6d4d21e65720aa6
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200547
    Tested-by: Jenkins
    Reviewed-by: Bartosz Kosiorek <[email protected]>

diff --git a/drawinglayer/source/tools/emfpbrush.cxx 
b/drawinglayer/source/tools/emfpbrush.cxx
index d64d76085dc5..51fce424b70a 100644
--- a/drawinglayer/source/tools/emfpbrush.cxx
+++ b/drawinglayer/source/tools/emfpbrush.cxx
@@ -60,7 +60,7 @@ namespace emfplushelper
         return u""_ustr;
     }
 
-    void EMFPBrush::Read(SvStream& s, EmfPlusHelperData const & rR)
+    void EMFPBrush::Read(SvMemoryStream& s, EmfPlusHelperData const & rR, 
sal_uInt32 dataSize, bool bUseWholeStream)
     {
         sal_uInt32 header;
 
@@ -99,7 +99,21 @@ namespace emfplushelper
             }
             case BrushTypeTextureFill:
             {
-                SAL_WARN("drawinglayer.emf", "EMF+     TODO: implement 
BrushTypeTextureFill brush");
+                s.ReadUInt32(additionalFlags).ReadInt32(wrapMode);
+                SAL_INFO("drawinglayer.emf", "EMF+                             
TextureFill, flags: 0x"
+                                                << std::hex << additionalFlags 
<< std::dec
+                                                << ", wrapMode: " << wrapMode);
+
+                if (additionalFlags & 0x02) // BrushDataTransform
+                {
+                    EmfPlusHelperData::readXForm(s, brush_transformation);
+                    hasTransformation = true;
+                    SAL_INFO("drawinglayer.emf",
+                             "EMF+                             Use brush 
transformation: " << brush_transformation);
+                }
+
+                textureImage = std::make_unique<EMFPImage>();
+                textureImage->Read(s, dataSize, bUseWholeStream);
                 break;
             }
             case BrushTypePathGradient:
diff --git a/drawinglayer/source/tools/emfpbrush.hxx 
b/drawinglayer/source/tools/emfpbrush.hxx
index aee3fe02f60e..d50df7176133 100644
--- a/drawinglayer/source/tools/emfpbrush.hxx
+++ b/drawinglayer/source/tools/emfpbrush.hxx
@@ -20,6 +20,7 @@
 #pragma once
 
 #include "emfphelperdata.hxx"
+#include "emfpimage.hxx"
 #include <tools/color.hxx>
 
 namespace emfplushelper
@@ -114,6 +115,7 @@ namespace emfplushelper
         std::unique_ptr<::Color[]> surroundColors;
         std::unique_ptr<EMFPPath> path;
         EmfPlusHatchStyle hatchStyle;
+        std::unique_ptr<EMFPImage> textureImage;
 
         EMFPBrush();
         virtual ~EMFPBrush() override;
@@ -121,7 +123,7 @@ namespace emfplushelper
         sal_uInt32 GetType() const { return type; }
         const ::Color& GetColor() const { return solidColor; }
 
-        void Read(SvStream& s, EmfPlusHelperData const & rR);
+        void Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 
dataSize, bool bUseWholeStream);
     };
 }
 
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx 
b/drawinglayer/source/tools/emfphelperdata.cxx
index e5bc612b4f40..0edcda846828 100644
--- a/drawinglayer/source/tools/emfphelperdata.cxx
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -28,11 +28,14 @@
 #include "emfpfont.hxx"
 #include "emfpstringformat.hxx"
 #include <wmfemfhelper.hxx>
+#include <drawinglayer/attribute/fillgraphicattribute.hxx>
+#include <drawinglayer/attribute/fontattribute.hxx>
 #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
 #include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
 #include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
-#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
 #include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
@@ -40,7 +43,6 @@
 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
 #include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
-#include <drawinglayer/attribute/fontattribute.hxx>
 #include <basegfx/color/bcolor.hxx>
 #include <basegfx/color/bcolormodifier.hxx>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
@@ -273,14 +275,14 @@ namespace emfplushelper
             {
                 EMFPBrush *brush = new EMFPBrush();
                 maEMFPObjects[index].reset(brush);
-                brush->Read(rObjectStream, *this);
+                brush->Read(rObjectStream, *this, dataSize, bUseWholeStream);
                 break;
             }
             case EmfPlusObjectTypePen:
             {
                 EMFPPen *pen = new EMFPPen();
                 maEMFPObjects[index].reset(pen);
-                pen->Read(rObjectStream, *this);
+                pen->Read(rObjectStream, *this, dataSize, bUseWholeStream);
                 pen->penWidth = unitToPixel(pen->penWidth, pen->penUnit, 
Direction::horizontal);
                 break;
             }
@@ -771,12 +773,65 @@ namespace emfplushelper
                         polygon,
                         fillColor.getBColor()));
             }
-            else if (brush->type == BrushTypeTextureFill)
+            else if (brush->type == BrushTypeTextureFill && 
brush->textureImage)
             {
-                SAL_WARN("drawinglayer.emf", "EMF+     TODO: implement 
BrushTypeTextureFill brush");
+                if (brush->textureImage->type != ImageDataTypeBitmap)
+                {
+                    SAL_WARN("drawinglayer.emf", "EMF+ TextureFill: Metafiles 
type is not supported");
+                    return;
+                }
+
+                const Size aSizePx = 
brush->textureImage->graphic.GetSizePixel();
+                if (aSizePx.Width() <= 0 || aSizePx.Height() <= 0)
+                    return;
+
+                basegfx::B2DHomMatrix aBrushMatrix;
+                if (brush->additionalFlags & 0x02) // 0x02 = 
BrushDataTransform flag
+                    aBrushMatrix = brush->brush_transformation;
+
+                // Create a scaling matrix to map the normalized 1x1 range
+                // to the actual pixel dimensions of the texture.
+
+                const basegfx::B2DHomMatrix aTextureScale(
+                        /* Row 0, Column 0 */ aSizePx.Width(),
+                        /* Row 0, Column 1 */ 0.0,
+                        /* Row 0, Column 2 */ 0.0,
+                        /* Row 1, Column 0 */ 0.0,
+                        /* Row 1, Column 1 */ aSizePx.Height(),
+                        /* Row 1, Column 2 */ 0.0);
+
+                const basegfx::B2DHomMatrix aTotalMatrix = maMapTransform * 
aBrushMatrix * aTextureScale;
+
+                // Invert the matrix to transform the fill polygon from 
document space
+                // back into the local normalized texture space (0.0 to 1.0).
+                basegfx::B2DHomMatrix aInvTotalMatrix = aTotalMatrix;
+                if (!aInvTotalMatrix.invert())
+                {
+                    SAL_WARN("drawinglayer.emf", "EMF+ TextureFill: Failed to 
invert transformation matrix");
+                    return;
+                }
+                basegfx::B2DPolyPolygon aLocalPolygon = polygon;
+                aLocalPolygon.transform(aInvTotalMatrix);
+
+                const bool bIsTiled = (brush->wrapMode != WrapModeClamp);
+
+                const drawinglayer::attribute::FillGraphicAttribute 
aFillAttribute(
+                    brush->textureImage->graphic,
+                    basegfx::B2DRange(0.0, 0.0, 1.0, 1.0),
+                    bIsTiled);
+
+                drawinglayer::primitive2d::Primitive2DReference xFillPrimitive 
=
+                    new 
drawinglayer::primitive2d::PolyPolygonGraphicPrimitive2D(
+                        aLocalPolygon,
+                        basegfx::B2DRange(0.0, 0.0, 1.0, 1.0),
+                        aFillAttribute);
+
+                mrTargetHolders.Current().append(
+                    new drawinglayer::primitive2d::TransformPrimitive2D(
+                        aTotalMatrix,
+                        drawinglayer::primitive2d::Primitive2DContainer({ 
xFillPrimitive })));
             }
             else if (brush->type == BrushTypePathGradient || brush->type == 
BrushTypeLinearGradient)
-
             {
                 if (brush->type == BrushTypePathGradient && 
!(brush->additionalFlags & 0x1))
                 {
diff --git a/drawinglayer/source/tools/emfppen.cxx 
b/drawinglayer/source/tools/emfppen.cxx
index bef1df4d8480..789239d62e8b 100644
--- a/drawinglayer/source/tools/emfppen.cxx
+++ b/drawinglayer/source/tools/emfppen.cxx
@@ -178,7 +178,7 @@ namespace emfplushelper
         return drawinglayer::attribute::StrokeAttribute();
     }
 
-    void EMFPPen::Read(SvStream& s, EmfPlusHelperData const & rR)
+    void EMFPPen::Read(SvMemoryStream& s, EmfPlusHelperData const & rR, 
sal_uInt32 dataSize, bool bUseWholeStream)
     {
         sal_Int32 lineJoin = EmfPlusLineJoinTypeMiter;
         sal_uInt32 graphicsVersion, penType;
@@ -373,7 +373,7 @@ namespace emfplushelper
             customEndCapLen = 0;
         }
 
-        EMFPBrush::Read(s, rR);
+        EMFPBrush::Read(s, rR, dataSize, bUseWholeStream);
     }
 }
 
diff --git a/drawinglayer/source/tools/emfppen.hxx 
b/drawinglayer/source/tools/emfppen.hxx
index bdb5ebf0599b..b6d30f411057 100644
--- a/drawinglayer/source/tools/emfppen.hxx
+++ b/drawinglayer/source/tools/emfppen.hxx
@@ -122,7 +122,7 @@ namespace emfplushelper
 
         virtual ~EMFPPen() override;
 
-        void Read(SvStream& s, EmfPlusHelperData const & rR);
+        void Read(SvMemoryStream& s, EmfPlusHelperData const & rR, sal_uInt32 
dataSize, bool bUseWholeStream);
 
         drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const 
double aTransformation) const;
     };
diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx 
b/drawinglayer/source/tools/primitive2dxmldump.cxx
index 4d113d509c35..a1fffa4cbee9 100644
--- a/drawinglayer/source/tools/primitive2dxmldump.cxx
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -33,8 +33,9 @@
 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
 #include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
-#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
 #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
 #include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
@@ -68,6 +69,7 @@
 #include <drawinglayer/attribute/sdrfillattribute.hxx>
 #include <drawinglayer/attribute/fillhatchattribute.hxx>
 #include <drawinglayer/attribute/fillgradientattribute.hxx>
+#include <drawinglayer/attribute/fillgraphicattribute.hxx>
 #include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
 #include <drawinglayer/attribute/materialattribute3d.hxx>
 #include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
@@ -733,6 +735,15 @@ void Primitive2dXmlDump::decomposeAndWrite(
                 rWriter.endElement();
             }
             break;
+
+            case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
+            {
+                rWriter.startElement("fillgraphic");
+                runDecomposeAndRecurse(pBasePrimitive, rWriter);
+                rWriter.endElement();
+            }
+            break;
+
             case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D:
             {
                 const auto& rHiddenGeometryPrimitive2D
@@ -852,6 +863,20 @@ void Primitive2dXmlDump::decomposeAndWrite(
                 rWriter.endElement();
             }
             break;
+
+            case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
+            {
+                const auto& rPolyPolygonGraphicPrimitive2D
+                    = static_cast<const 
PolyPolygonGraphicPrimitive2D&>(*pBasePrimitive);
+                rWriter.startElement("polypolygongraphic");
+                rWriter.attribute(
+                    "transparency",
+                    std::lround(100 * 
rPolyPolygonGraphicPrimitive2D.getTransparency()));
+                runDecomposeAndRecurse(pBasePrimitive, rWriter);
+                rWriter.endElement();
+            }
+            break;
+
             case PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D:
             {
                 const auto& rPolyPolygonStrokePrimitive2D
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx 
b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index 144b0356af16..7643e44ce9fa 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -117,7 +117,7 @@ CPPUNIT_TEST_FIXTURE(Test, testPolyPolygon)
 
 CPPUNIT_TEST_FIXTURE(Test, testDrawImagePointsTypeBitmap)
 {
-    // tdf#142941 EMF+ file with ObjectTypeImage, FillRects, DrawImagePoints 
,records
+    // tdf#142941 EMF+ file with ObjectTypeImage, FillRects, DrawImagePoints
     // The test is checking the position of displaying bitmap with too large 
SrcRect
 
     Primitive2DSequence aSequence
@@ -147,6 +147,50 @@ CPPUNIT_TEST_FIXTURE(Test, testDrawImagePointsTypeBitmap)
         "b63539,b53335,ba3236,a2393e,1c0000");
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testFillRectsWithTextureBrush)
+{
+    // tdf#129342 tdf#170985 EMF+ file with ObjectTypeBrush, ObjectTypepen, 
FillRects
+    // The test is checking the position of displaying bitmap with too large 
SrcRect
+
+    Primitive2DSequence aSequence
+        = 
parseEmf(u"/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf");
+    CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
+    drawinglayer::Primitive2dXmlDump dumper;
+    xmlDocUniquePtr pDocument = 
dumper.dumpAndParse(Primitive2DContainer(aSequence));
+    CPPUNIT_ASSERT(pDocument);
+
+    assertXPath(pDocument, aXPathPrefix + "transform/polypolygongraphic", 
"transparency", u"0");
+    assertXPath(pDocument, aXPathPrefix + 
"transform/polypolygongraphic/mask/polypolygon", 1);
+
+    assertXPath(pDocument,
+                aXPathPrefix + 
"transform/polypolygongraphic/mask/fillgraphic/group/transform", 17);
+    assertXPath(pDocument,
+                aXPathPrefix
+                    + 
"transform/polypolygongraphic/mask/fillgraphic/group/transform/bitmap/data",
+                697);
+
+    assertXPath(
+        pDocument,
+        aXPathPrefix
+            + 
"transform/polypolygongraphic/mask/fillgraphic/group/transform[1]/bitmap/data",
+        41);
+    assertXPath(
+        pDocument,
+        aXPathPrefix
+            + 
"transform/polypolygongraphic/mask/fillgraphic/group/transform[1]/bitmap/data[30]",
+        "row",
+        
u"fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,"
+        
u"fefefe,fefefe,fefefe,fefefe,ffedfe,edf6f5,e6fffd,edfff8,e8f3e3,ffffef,fffbed,ffe8e1,"
+        
u"f5f6e6,fff7f3,fff0f6,ffeff4,f6ffff,e9ffff,f2ffff,f4ebfe,fefefe,fcfcfc,fefefe,ffffff,"
+        
u"ffffff,fdfdfd,fbfbfb,fdfdfd,fff6fd,fff9fc,fdfcfa,f6fdf6,f7fffb,f7fffa,f9fffa,f2f9f1,"
+        
u"fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,"
+        
u"fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe,fefefe");
+
+    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/line", "width", 
u"26");
+    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/polypolygon", 
"minx", u"397");
+    assertXPath(pDocument, aXPathPrefix + "polypolygonstroke/polypolygon", 
"miny", u"397");
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testDrawString)
 {
 #if HAVE_MORE_FONTS
diff --git a/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf 
b/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf
new file mode 100644
index 000000000000..e90e2af12a0f
Binary files /dev/null and 
b/emfio/qa/cppunit/emf/data/TestEmfPlusFillRectsWithTextureBrush.emf differ

Reply via email to