emfio/README.md                                    |    2 
 emfio/inc/mtftools.hxx                             |    1 
 emfio/qa/cppunit/emf/EmfImportTest.cxx             |   54 ++++++++++--
 emfio/qa/cppunit/emf/data/TestSmallTextOut.emf     |binary
 emfio/qa/cppunit/emf/data/TestSmallTextOutAnsi.emf |binary
 emfio/source/reader/emfreader.cxx                  |   88 ++++++++++++++++++---
 6 files changed, 124 insertions(+), 21 deletions(-)

New commits:
commit c0a0abdbb9b6e84ae3ebeb1f4a1e8287fd121d45
Author:     Andras Timar <[email protected]>
AuthorDate: Sun Feb 8 12:09:12 2026 +0100
Commit:     Xisco Fauli <[email protected]>
CommitDate: Tue Feb 10 14:44:01 2026 +0100

    tdf#142226 emfio: implement EMR_SMALLTEXTOUT
    
    Change-Id: Ib031c0d771045b80c99274974de8267e759e8557
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198903
    Tested-by: Jenkins
    Reviewed-by: Andras Timar <[email protected]>
    (cherry picked from commit aad62f783bd1bced7a6f850058be9c833f998ec1)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198947
    Reviewed-by: Xisco Fauli <[email protected]>

diff --git a/emfio/README.md b/emfio/README.md
index f9f33a2ba4e1..b1a0de61c888 100644
--- a/emfio/README.md
+++ b/emfio/README.md
@@ -82,7 +82,7 @@ EMR_SETPALETTEENTRIES EMR_RESIZEPALETTE
 EMR_EXTFLOODFILL EMR_ANGLEARC EMR_SETCOLORADJUSTMENT EMR_POLYDRAW16
 EMR_CREATECOLORSPACE EMR_SETCOLORSPACE EMR_DELETECOLORSPACE
 EMR_GLSRECORD EMR_GLSBOUNDEDRECORD EMR_PIXELFORMAT EMR_DRAWESCAPE
-EMR_EXTESCAPE EMR_STARTDOC EMR_SMALLTEXTOUT EMR_FORCEUFIMAPPING
+EMR_EXTESCAPE EMR_STARTDOC EMR_FORCEUFIMAPPING
 EMR_NAMEDESCAPE EMR_COLORCORRECTPALETTE EMR_SETICMPROFILEA
 EMR_SETICMPROFILEW EMR_TRANSPARENTBLT EMR_TRANSPARENTDIB
 EMR_GRADIENTFILL EMR_SETLINKEDUFIS EMR_SETMAPPERFLAGS EMR_SETICMMODE
diff --git a/emfio/inc/mtftools.hxx b/emfio/inc/mtftools.hxx
index 47c556b16038..7d1c99923b24 100644
--- a/emfio/inc/mtftools.hxx
+++ b/emfio/inc/mtftools.hxx
@@ -264,6 +264,7 @@ namespace emfio
         ETO_RTLREADING  = 0x0080,
         /* _WIN32_WINNT >= 0x0500 */
         ETO_NO_RECT     = 0x0100,
+        ETO_SMALL_CHARS = 0x0200,
         ETO_PDY         = 0x2000
     };
 
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx 
b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index c11f6b41c7c2..7eef19cea5b5 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -1702,6 +1702,38 @@ CPPUNIT_TEST_FIXTURE(Test, testAlignRtlReading)
     assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[3]", "rtl", 
u"true");
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testSmallTextOut)
+{
+    // EMR_SMALLTEXTOUT with Unicode text (no ETO_SMALL_CHARS), ETO_NO_RECT.
+    // Verifies the text "SmallTextOut" is correctly imported.
+    OUString aUrl = 
m_directories.getURLFromSrc(u"/emfio/qa/cppunit/emf/data/TestSmallTextOut.emf");
+    SvFileStream aFileStream(aUrl, StreamMode::READ);
+    GDIMetaFile aGDIMetaFile;
+    ReadWindowMetafile(aFileStream, aGDIMetaFile);
+
+    MetafileXmlDump dumper;
+    xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile);
+    CPPUNIT_ASSERT(pDoc);
+
+    assertXPathContent(pDoc, "/metafile/push[2]/textarray/text", 
u"SmallTextOut");
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testSmallTextOutAnsi)
+{
+    // EMR_SMALLTEXTOUT with ETO_SMALL_CHARS (8-bit text) and bounds rectangle.
+    OUString aUrl
+        = 
m_directories.getURLFromSrc(u"/emfio/qa/cppunit/emf/data/TestSmallTextOutAnsi.emf");
+    SvFileStream aFileStream(aUrl, StreamMode::READ);
+    GDIMetaFile aGDIMetaFile;
+    ReadWindowMetafile(aFileStream, aGDIMetaFile);
+
+    MetafileXmlDump dumper;
+    xmlDocUniquePtr pDoc = dumpAndParse(dumper, aGDIMetaFile);
+    CPPUNIT_ASSERT(pDoc);
+
+    assertXPathContent(pDoc, "/metafile/push[2]/textarray/text", u"AnsiSmall");
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/emfio/qa/cppunit/emf/data/TestSmallTextOut.emf 
b/emfio/qa/cppunit/emf/data/TestSmallTextOut.emf
new file mode 100644
index 000000000000..2505d2d65f2c
Binary files /dev/null and b/emfio/qa/cppunit/emf/data/TestSmallTextOut.emf 
differ
diff --git a/emfio/qa/cppunit/emf/data/TestSmallTextOutAnsi.emf 
b/emfio/qa/cppunit/emf/data/TestSmallTextOutAnsi.emf
new file mode 100644
index 000000000000..cfba2f8e1a4f
Binary files /dev/null and b/emfio/qa/cppunit/emf/data/TestSmallTextOutAnsi.emf 
differ
diff --git a/emfio/source/reader/emfreader.cxx 
b/emfio/source/reader/emfreader.cxx
index 57e715fad4ea..bd5defcd1cc7 100644
--- a/emfio/source/reader/emfreader.cxx
+++ b/emfio/source/reader/emfreader.cxx
@@ -1879,6 +1879,81 @@ namespace emfio
                     }
                     break;
 
+                    case EMR_SMALLTEXTOUT :
+                    {
+                        sal_Int32   ptlReferenceX, ptlReferenceY;
+                        sal_uInt32  nLen, nOptions, nGfxMode;
+                        float       nXScale, nYScale;
+
+                        mpInputStream->ReadInt32( ptlReferenceX ).ReadInt32( 
ptlReferenceY )
+                           .ReadUInt32( nLen ).ReadUInt32( nOptions )
+                           .ReadUInt32( nGfxMode ).ReadFloat( nXScale 
).ReadFloat( nYScale );
+                        SAL_INFO("emfio", "            Reference: (" << 
ptlReferenceX << ", " << ptlReferenceY << ")");
+                        SAL_INFO("emfio", "            cChars: " << nLen);
+                        SAL_INFO("emfio", "            fuOptions: 0x" << 
std::hex << nOptions << std::dec);
+                        SAL_INFO("emfio", "            iGraphicsMode: 0x" << 
std::hex << nGfxMode << std::dec);
+                        SAL_INFO("emfio", "            Scale: " << nXScale << 
" x " << nYScale);
+
+                        // Read optional bounding rectangle (present only if 
ETO_NO_RECT is NOT set)
+                        tools::Rectangle aRect;
+                        if ( !( nOptions & ETO_NO_RECT ) )
+                        {
+                            sal_Int32 nLeftRect, nTopRect, nRightRect, 
nBottomRect;
+                            mpInputStream->ReadInt32( nLeftRect ).ReadInt32( 
nTopRect ).ReadInt32( nRightRect ).ReadInt32( nBottomRect );
+                            aRect = tools::Rectangle( nLeftRect, nTopRect, 
nRightRect, nBottomRect );
+                            SAL_INFO("emfio", "                Bounds: " << 
nLeftRect << ", " << nTopRect << ", " << nRightRect << ", " << nBottomRect);
+                        }
+
+                        if (!mpInputStream->good())
+                        {
+                            bStatus = false;
+                        }
+                        else
+                        {
+                            const BackgroundMode mnBkModeBackup = mnBkMode;
+                            if ( nOptions & ETO_NO_RECT )
+                                mnBkMode = BackgroundMode::Transparent;
+                            else if ( nOptions & ETO_OPAQUE )
+                                DrawRectWithBGColor( aRect );
+
+                            vcl::text::ComplexTextLayoutFlags nTextLayoutMode 
= vcl::text::ComplexTextLayoutFlags::Default;
+                            if ( nOptions & ETO_RTLREADING )
+                                nTextLayoutMode = 
vcl::text::ComplexTextLayoutFlags::BiDiRtl | 
vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+                            SetTextLayoutMode( nTextLayoutMode );
+
+                            Point aPos( ptlReferenceX, ptlReferenceY );
+                            OUString aText;
+                            if ( nOptions & ETO_SMALL_CHARS )
+                            {
+                                if ( nLen <= ( mnEndPos - 
mpInputStream->Tell() ) )
+                                {
+                                    std::vector<char> pBuf( nLen );
+                                    mpInputStream->ReadBytes(pBuf.data(), 
nLen);
+                                    aText = OUString(pBuf.data(), nLen, 
GetCharSet());
+                                }
+                            }
+                            else
+                            {
+                                if ( ( nLen * sizeof(sal_Unicode) ) <= ( 
mnEndPos - mpInputStream->Tell() ) )
+                                {
+                                    aText = 
read_uInt16s_ToOUString(*mpInputStream, nLen);
+                                }
+                            }
+                            SAL_INFO("emfio", "                Text: " << 
aText);
+
+                            if ( nOptions & ETO_CLIPPED )
+                            {
+                                Push();
+                                IntersectClipRect( aRect );
+                            }
+                            DrawText(aPos, aText, nullptr, nullptr, 
mbRecordPath, static_cast<GraphicsMode>(nGfxMode));
+                            if ( nOptions & ETO_CLIPPED )
+                                Pop();
+                            mnBkMode = mnBkModeBackup;
+                        }
+                    }
+                    break;
+
                     case EMR_POLYTEXTOUTA :
                     case EMR_EXTTEXTOUTA :
                         bFlag = true;
@@ -2219,7 +2294,6 @@ namespace emfio
                     case EMR_DRAWESCAPE :
                     case EMR_EXTESCAPE :
                     case EMR_STARTDOC :
-                    case EMR_SMALLTEXTOUT :
                     case EMR_FORCEUFIMAPPING :
                     case EMR_NAMEDESCAPE :
                     case EMR_COLORCORRECTPALETTE :
commit fb3f4a7bd20d85cc38a6980f9f64b77dc9e6ae64
Author:     Andras Timar <[email protected]>
AuthorDate: Sun Feb 8 16:46:30 2026 +0100
Commit:     Xisco Fauli <[email protected]>
CommitDate: Tue Feb 10 14:43:49 2026 +0100

    Revert tdf#159306 EMF: fix PS_COSMETIC pen width regression
    
    Commit be9d6e414e4b forced all PS_COSMETIC pen widths to 1 in both
    EMR_CREATEPEN and EMR_EXTCREATEPEN handlers. Since PS_COSMETIC is
    0x00000000 (same value as PS_SOLID), the check
    "if (nPenStyle == PS_COSMETIC)" matched every simple PS_SOLID pen,
    causing all line widths to be lost. This made all lines render as
    hairlines regardless of the actual width specified in the EMF file.
    
    The style sanitization check for invalid PS_STYLE_MASK values is
    retained, but the width forcing is removed. Pen widths from the
    file are now used as-is: width=0 produces a hairline, width>=1
    produces a scaled line. This effectively reverts to the behavior
    established by tdf#140271 (commit 8932c8906ff3), which recognized
    that real-world EMF producers use EMR_CREATEPEN with widths > 1
    and expect them to be honored.
    
    Change-Id: I9a3acf83ce03b4e646e9b95fe0db54f8ce403a49
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198904
    Reviewed-by: Andras Timar <[email protected]>
    Tested-by: Jenkins
    (cherry picked from commit aa616d930e7078b4226203c7c894e382c6f449b8)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/198946
    Reviewed-by: Xisco Fauli <[email protected]>

diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx 
b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index 2a5aa8ab9968..c11f6b41c7c2 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -1601,7 +1601,7 @@ CPPUNIT_TEST_FIXTURE(Test, testRoundRect)
 CPPUNIT_TEST_FIXTURE(Test, testCreatePen)
 {
     // Check import of EMF image with records: RESTOREDC, SAVEDC, MOVETOEX, 
LINETO, POLYLINE16, EXTTEXTOUTW with DxBuffer
-    // The CREATEPEN record is used with PS_COSMETIC line style, and in this 
case width must be set to 0
+    // The CREATEPEN record is used with PS_COSMETIC line style, which 
sometimes will be displayed as solid hairline
     Primitive2DSequence aSequence = 
parseEmf(u"/emfio/qa/cppunit/emf/data/TestCreatePen.emf");
     CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
     drawinglayer::Primitive2dXmlDump dumper;
@@ -1610,23 +1610,23 @@ CPPUNIT_TEST_FIXTURE(Test, testCreatePen)
 
     assertXPath(pDocument, aXPathPrefix + "mask/polypolygon", "path", u"m0 
0h31250v18192h-31250z");
 
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke", 758);
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke", 748);
     assertXPathContent(pDocument, aXPathPrefix + 
"mask/polygonstroke[1]/polygon",
-                       u"0,0 31225,0 31225,17742 0,17742");
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", 
"color", u"#ffffff");
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", 
"width", u"25");
+                       u"27875,16523 27875,1453");
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", 
"color", u"#ff0000");
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[1]/line", 
"width", u"6");
 
     assertXPathContent(pDocument, aXPathPrefix + 
"mask/polygonstroke[2]/polygon",
-                       u"25,23 31200,23 31200,17719 25,17719");
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", 
"color", u"#ffffff");
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", 
"width", u"25");
+                       u"27975,16453 27875,16453");
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", 
"color", u"#ff0000");
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[2]/line", 
"width", u"6");
 
     assertXPathContent(pDocument, aXPathPrefix + 
"mask/polygonstroke[3]/polygon",
-                       u"27875,16523 27875,1453");
+                       u"27925,16078 27875,16078");
     assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[3]/line", 
"color", u"#ff0000");
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[3]/line", 
"width", u"3");
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonstroke[3]/line", 
"width", u"6");
 
-    assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 0);
+    assertXPath(pDocument, aXPathPrefix + "mask/polygonhairline", 10);
 
     assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion", 69);
     assertXPath(pDocument, aXPathPrefix + "mask/textsimpleportion[1]", 
"width", u"374");
diff --git a/emfio/source/reader/emfreader.cxx 
b/emfio/source/reader/emfreader.cxx
index c90db53b7dbc..57e715fad4ea 100644
--- a/emfio/source/reader/emfreader.cxx
+++ b/emfio/source/reader/emfreader.cxx
@@ -1259,10 +1259,8 @@ namespace emfio
                             SAL_INFO("emfio", "                Index: " << 
nIndex << " Style: 0x" << std::hex
                                                             << nPenStyle << 
std::dec
                                                             << " PenWidth: " 
<< nPenWidth);
-                            // PS_COSMETIC width is always fixed at one 
logical unit
-                            // and is not affected by any geometric 
transformations like scaling
-                            if (nPenStyle == PS_COSMETIC)
-                                nPenWidth = 1;
+                            if ((nPenStyle & PS_STYLE_MASK) > PS_INSIDEFRAME)
+                                nPenStyle = PS_COSMETIC;
                             CreateObjectIndexed(nIndex, 
std::make_unique<WinMtfLineStyle>(ReadColor(), nPenStyle, nPenWidth));
                         }
                     }
@@ -1285,10 +1283,8 @@ namespace emfio
                             else
                             {
                                 SAL_INFO("emfio", "            Style: 0x" << 
std::hex << nPenStyle << std::dec);
-                                // PS_COSMETIC width is always fixed at one 
logical unit
-                                // and is not affected by any geometric 
transformations like scaling
-                                if (nPenStyle == PS_COSMETIC)
-                                    nWidth = 1;
+                                if ((nPenStyle & PS_STYLE_MASK) > 
PS_INSIDEFRAME)
+                                    nPenStyle = PS_COSMETIC;
                                 SAL_INFO("emfio", "            Width: " << 
nWidth);
                                 CreateObjectIndexed(nIndex, 
std::make_unique<WinMtfLineStyle>(aColorRef, nPenStyle, nWidth));
                             }

Reply via email to