drawinglayer/source/processor2d/vclprocessor2d.cxx |   43 +++++++++-----
 include/vcl/outdev.hxx                             |    5 -
 include/vcl/vcllayout.hxx                          |    6 --
 sd/qa/unit/PNGExportTests.cxx                      |   61 +++++++++++++++++++++
 sd/qa/unit/data/svg/tdf162259.svg                  |   15 +++++
 sw/source/uibase/sidebar/QuickFindPanel.cxx        |    2 
 vcl/inc/win/winlayout.hxx                          |    2 
 vcl/source/outdev/text.cxx                         |    1 
 vcl/win/gdi/DWriteTextRenderer.cxx                 |   21 ++-----
 vcl/win/gdi/winlayout.cxx                          |    8 ++
 10 files changed, 123 insertions(+), 41 deletions(-)

New commits:
commit af96d0741afe869e24531c5c31013e196dba6868
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu Aug 1 19:31:41 2024 +0500
Commit:     Michael Stahl <[email protected]>
CommitDate: Wed Oct 16 11:41:35 2024 +0200

    tdf#162259: correctly handle font width on Windows
    
    Unlike other platforms, on Windows, the font width is not relative to
    font height, but to average width of font's glyphs. This is mentioned
    in LogicalFontInstance::GetScale.
    
    1. In VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D,
    when calculating the correction for width / height (introduced in
    commit cc3663bbaed4f65d64154e5f9abb51a5f622f710, 2024-04-20), the
    already applied X scale is now calculated using unscaled font's width.
    
    2. Commit 8557ea84c9336ba8061246f1f46ddb6e02f413a1 (Exclude getHScale
    from DirectWrite font rendering, 2024-04-08) was effectively reverted,
    because I was wrong assuming that the code there was unnecessary.
    
    3. Commit 2092df2a9044f1c2ae4379f48a3201e5867575a8 (tdf#161154: pass
    "scaling is done externally" information down the stack, 2024-05-18)
    was also reverted.
    
    Change-Id: I8cff39b67a6efd380f7807f5655f401bdb62cc3a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171382
    Reviewed-by: Mike Kaganski <[email protected]>
    Tested-by: Jenkins
    Signed-off-by: Xisco Fauli <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174978
    Reviewed-by: Michael Stahl <[email protected]>

diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx 
b/drawinglayer/source/processor2d/vclprocessor2d.cxx
index b402d395a171..0ffdb98a04e6 100644
--- a/drawinglayer/source/processor2d/vclprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx
@@ -428,6 +428,25 @@ void 
VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(
                           / (aResultFontSize.Width() ? aResultFontSize.Width()
                                                      : 
aResultFontSize.Height());
 
+#ifdef _WIN32
+                    if (aResultFontSize.Width()
+                        && aResultFontSize.Width() != aResultFontSize.Height())
+                    {
+                        // See getVclFontFromFontAttribute in 
drawinglayer/source/primitive2d/textlayoutdevice.cxx
+                        vcl::Font aUnscaledTest(aFont);
+                        aUnscaledTest.SetFontSize({ 0, 
aResultFontSize.Height() });
+                        const FontMetric aUnscaledFontMetric(
+                            
Application::GetDefaultDevice()->GetFontMetric(aUnscaledTest));
+                        if (aUnscaledFontMetric.GetAverageFontWidth() > 0)
+                        {
+                            double nExistingXScale = 
static_cast<double>(aResultFontSize.Width())
+                                                     / 
aUnscaledFontMetric.GetAverageFontWidth();
+                            nFontScalingFixX
+                                = aFontScaling.getX() / aFontScaling.getY() / 
nExistingXScale;
+                        }
+                    }
+#endif
+
                     if (!rtl_math_approxEqual(nFontScalingFixY, 1.0)
                         || !rtl_math_approxEqual(nFontScalingFixX, 1.0))
                     {
@@ -454,21 +473,17 @@ void 
VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(
             mpOutputDevice->SetFont(aFont);
             mpOutputDevice->SetTextColor(Color(aRGBFontColor));
 
+            if (!aDXArray.empty())
             {
-                // For D2DWriteTextOutRenderer, we must pass a flag to not use 
font scaling
-                auto guard = mpOutputDevice->ScopedNoFontScaling();
-                if (!aDXArray.empty())
-                {
-                    const SalLayoutGlyphs* pGlyphs = 
SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
-                        mpOutputDevice, aText, nPos, nLen);
-                    mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray,
-                                                  
rTextCandidate.getKashidaArray(), nPos, nLen,
-                                                  SalLayoutFlags::NONE, 
pGlyphs);
-                }
-                else
-                {
-                    mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen);
-                }
+                const SalLayoutGlyphs* pGlyphs = 
SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
+                    mpOutputDevice, aText, nPos, nLen);
+                mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray,
+                                              
rTextCandidate.getKashidaArray(), nPos, nLen,
+                                              SalLayoutFlags::NONE, pGlyphs);
+            }
+            else
+            {
+                mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen);
             }
 
             // Restore previous layout mode
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index 2fddb3c3f24a..e353acd2938e 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -21,7 +21,6 @@
 
 #include <sal/config.h>
 
-#include <comphelper/flagguard.hxx>
 #include <tools/gen.hxx>
 #include <tools/ref.hxx>
 #include <tools/solar.h>
@@ -267,8 +266,6 @@ private:
     mutable bool                    mbRefPoint : 1;
     mutable bool                    mbEnableRTL : 1;
 
-    bool mbNoFontScaling = false; // Used only by D2DWriteTextOutRenderer
-
 protected:
     mutable std::shared_ptr<vcl::font::PhysicalFontCollection> 
mxFontCollection;
     mutable std::shared_ptr<ImplFontCache> mxFontCache;
@@ -351,8 +348,6 @@ public:
     /// request XSpriteCanvas render interface
     css::uno::Reference< css::rendering::XSpriteCanvas > GetSpriteCanvas() 
const;
 
-    auto ScopedNoFontScaling() { return 
comphelper::FlagRestorationGuard(mbNoFontScaling, true); }
-
 protected:
 
     /** Acquire a graphics device that the output device uses to draw on.
diff --git a/include/vcl/vcllayout.hxx b/include/vcl/vcllayout.hxx
index 58ca11b876ef..abae888bedeb 100644
--- a/include/vcl/vcllayout.hxx
+++ b/include/vcl/vcllayout.hxx
@@ -22,7 +22,6 @@
 #include <basegfx/point/b2dpoint.hxx>
 #include <basegfx/polygon/b2dpolypolygon.hxx>
 #include <basegfx/range/b2drectangle.hxx>
-#include <comphelper/flagguard.hxx>
 #include <i18nlangtag/languagetag.hxx>
 #include <tools/gen.hxx>
 #include <tools/degree.hxx>
@@ -121,9 +120,6 @@ public:
 
     virtual SalLayoutGlyphs GetGlyphs() const;
 
-    auto ScopedFontScaling(bool v) { return 
comphelper::FlagRestorationGuard(mbScaleFont, v); }
-    bool ScaleFont() const { return mbScaleFont; }
-
 protected:
     // used by layout engines
     SalLayout();
@@ -132,8 +128,6 @@ private:
     SalLayout(const SalLayout&) = delete;
     SalLayout& operator=(const SalLayout&) = delete;
 
-    bool mbScaleFont = true; // Used only by D2DWriteTextOutRenderer
-
 protected:
     int             mnMinCharPos;
     int             mnEndCharPos;
diff --git a/sd/qa/unit/PNGExportTests.cxx b/sd/qa/unit/PNGExportTests.cxx
index c2af95329ca2..78135e3a5eba 100644
--- a/sd/qa/unit/PNGExportTests.cxx
+++ b/sd/qa/unit/PNGExportTests.cxx
@@ -941,4 +941,65 @@ CPPUNIT_TEST_FIXTURE(SdPNGExportTest, testTdf155048)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(SdPNGExportTest, testTdf162259)
+{
+    // The top X in the SVG, having no skew, used a fast rendering path, and 
was output much wider
+    // than the bottom one, which has a skew. Test the rendered pixels inside 
the known boundaries.
+
+    loadFromFile(u"svg/tdf162259.svg");
+
+    auto xGraphicExporter = 
drawing::GraphicExportFilter::create(getComponentContext());
+    CPPUNIT_ASSERT(xGraphicExporter);
+
+    auto xSupplier = 
mxComponent.queryThrow<css::drawing::XDrawPagesSupplier>();
+    auto xPage = 
xSupplier->getDrawPages()->getByIndex(0).queryThrow<css::lang::XComponent>();
+    xGraphicExporter->setSourceDocument(xPage);
+
+    // 101 x 151 is current width x height ratio of the loaded SVG. FIXME: it 
should be 100 x 150.
+    css::uno::Sequence<css::beans::PropertyValue> aFilterData{
+        comphelper::makePropertyValue(u"PixelWidth"_ustr, sal_Int32(101)),
+        comphelper::makePropertyValue(u"PixelHeight"_ustr, sal_Int32(151)),
+    };
+
+    css::uno::Sequence<css::beans::PropertyValue> aDescriptor{
+        comphelper::makePropertyValue(u"URL"_ustr, maTempFile.GetURL()),
+        comphelper::makePropertyValue(u"FilterName"_ustr, u"PNG"_ustr),
+        comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData)
+    };
+
+    xGraphicExporter->filter(aDescriptor);
+    BitmapEx bmp = 
vcl::PngImageReader(*maTempFile.GetStream(StreamMode::READ)).read();
+
+    tools::Rectangle topX(12, 21, 37, 60);
+    int topNonWhites = 0;
+    tools::Rectangle bottomX(13, 83, 37, 126);
+    int bottomNonWhites = 0;
+
+    // Check that there is nothing outside the X recrangles
+    for (tools::Long x = 0; x < bmp.GetSizePixel().Width(); ++x)
+    {
+        for (tools::Long y = 0; y < bmp.GetSizePixel().Height(); ++y)
+        {
+            if (topX.Contains(Point{ x, y }))
+            {
+                if (bmp.GetPixelColor(x, y) != COL_WHITE)
+                    ++topNonWhites;
+            }
+            else if (bottomX.Contains(Point{ x, y }))
+            {
+                if (bmp.GetPixelColor(x, y) != COL_WHITE)
+                    ++bottomNonWhites;
+            }
+            else
+            {
+                OString msg("Pixel: "_ostr + OString::number(x) + "," + 
OString::number(y));
+                CPPUNIT_ASSERT_EQUAL_MESSAGE(msg.getStr(), COL_WHITE, 
bmp.GetPixelColor(x, y));
+            }
+        }
+    }
+
+    CPPUNIT_ASSERT_GREATER(350, topNonWhites); // 399 in my testing
+    CPPUNIT_ASSERT_GREATER(350, bottomNonWhites); // 362 in my testing
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sd/qa/unit/data/svg/tdf162259.svg 
b/sd/qa/unit/data/svg/tdf162259.svg
new file mode 100644
index 000000000000..96e7bd930c8d
--- /dev/null
+++ b/sd/qa/unit/data/svg/tdf162259.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"; version="1.1" width="100" height="150" 
viewBox="0 0 100 150">
+ <g>
+  <text style="font-size:5.9px;font-family:Liberation Serif"
+        transform="scale(6,10)"
+        x="2" y="6">
+   <tspan>X</tspan>
+  </text>
+  <text style="font-size:5.9px;font-family:Liberation Serif"
+        transform="scale(6,10) skewY(5)"
+        x="2" y="12">
+   <tspan>X</tspan>
+  </text>
+ </g>
+</svg>
diff --git a/sw/source/uibase/sidebar/QuickFindPanel.cxx 
b/sw/source/uibase/sidebar/QuickFindPanel.cxx
index 753888702587..e7633926fe01 100644
--- a/sw/source/uibase/sidebar/QuickFindPanel.cxx
+++ b/sw/source/uibase/sidebar/QuickFindPanel.cxx
@@ -10,6 +10,8 @@
 
 #include "QuickFindPanel.hxx"
 #include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+#include <comphelper/scopeguard.hxx>
 #include <svl/srchitem.hxx>
 #include <view.hxx>
 #include <comphelper/dispatchcommand.hxx>
diff --git a/vcl/inc/win/winlayout.hxx b/vcl/inc/win/winlayout.hxx
index 31066a7db28a..cfb36e825b54 100644
--- a/vcl/inc/win/winlayout.hxx
+++ b/vcl/inc/win/winlayout.hxx
@@ -36,6 +36,8 @@ class WinFontInstance : public LogicalFontInstance
 public:
     ~WinFontInstance() override;
 
+    float getHScale() const;
+
     void SetGraphics(WinSalGraphics*);
     WinSalGraphics* GetGraphics() const { return m_pGraphics; }
 
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
index d0bdd7ccf68f..534eb0d8f453 100644
--- a/vcl/source/outdev/text.cxx
+++ b/vcl/source/outdev/text.cxx
@@ -449,7 +449,6 @@ void OutputDevice::ImplDrawSpecialText( SalLayout& 
rSalLayout )
 
 void OutputDevice::ImplDrawText( SalLayout& rSalLayout )
 {
-    auto guard = rSalLayout.ScopedFontScaling(!mbNoFontScaling);
     if( mbInitClipRegion )
         InitClipRegion();
     if( mbOutputClipped )
diff --git a/vcl/win/gdi/DWriteTextRenderer.cxx 
b/vcl/win/gdi/DWriteTextRenderer.cxx
index 1731a1e4c379..f25fe80cd79d 100644
--- a/vcl/win/gdi/DWriteTextRenderer.cxx
+++ b/vcl/win/gdi/DWriteTextRenderer.cxx
@@ -99,7 +99,7 @@ HRESULT checkResult(HRESULT hr, const char* location)
 class WinFontTransformGuard
 {
 public:
-    WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget,
+    WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float hscale,
                           const GenericSalLayout& rLayout, const 
D2D1_POINT_2F& rBaseline,
                           bool bIsVertical);
     ~WinFontTransformGuard();
@@ -247,17 +247,18 @@ bool 
D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, Sa
     {
         mpRT->BeginDraw();
 
+        const float hscale = rWinFont.getHScale();
         int nStart = 0;
         basegfx::B2DPoint aPos;
         const GlyphItem* pGlyph;
         while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
         {
             UINT16 glyphIndices[] = { static_cast<UINT16>(pGlyph->glyphId()) };
-            FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->newWidth()) };
+            FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->newWidth()) / 
hscale };
             DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, };
-            D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.getX() - 
bounds.Left()),
+            D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.getX() - 
bounds.Left()) / hscale,
                                        static_cast<FLOAT>(aPos.getY() - 
bounds.Top()) };
-            WinFontTransformGuard aTransformGuard(mpRT, rLayout, baseline, 
pGlyph->IsVertical());
+            WinFontTransformGuard aTransformGuard(mpRT, hscale, rLayout, 
baseline, pGlyph->IsVertical());
             DWRITE_GLYPH_RUN glyphs = {
                 pFontFace,
                 lfEmHeight,
@@ -305,22 +306,12 @@ IDWriteFontFace* 
D2DWriteTextOutRenderer::GetDWriteFace(const WinFontInstance& r
     return pFontFace;
 }
 
-WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget,
+WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, 
float hscale,
                                              const GenericSalLayout& rLayout,
                                              const D2D1_POINT_2F& rBaseline,
                                              bool bIsVertical)
     : mpRenderTarget(pRenderTarget)
 {
-    const float hscale = [&rLayout]
-    {
-        if (!rLayout.ScaleFont())
-            return 1.0;
-        const auto& rPattern = rLayout.GetFont().GetFontSelectPattern();
-        if (!rPattern.mnHeight || !rPattern.mnWidth)
-            return 1.0;
-        return rPattern.mnWidth * rLayout.GetFont().GetAverageWidthFactor() / 
rPattern.mnHeight;
-    }();
-
     Degree10 angle = rLayout.GetOrientation();
     if (bIsVertical)
         angle += 900_deg10;
diff --git a/vcl/win/gdi/winlayout.cxx b/vcl/win/gdi/winlayout.cxx
index 0c64759e1ab8..19eaae2ecee7 100644
--- a/vcl/win/gdi/winlayout.cxx
+++ b/vcl/win/gdi/winlayout.cxx
@@ -146,6 +146,14 @@ WinFontInstance::~WinFontInstance()
         ::DeleteFont(m_hFont);
 }
 
+float WinFontInstance::getHScale() const
+{
+    const vcl::font::FontSelectPattern& rPattern = GetFontSelectPattern();
+    if (!rPattern.mnHeight || !rPattern.mnWidth)
+        return 1.0;
+    return rPattern.mnWidth * GetAverageWidthFactor() / rPattern.mnHeight;
+}
+
 void WinFontInstance::ImplInitHbFont(hb_font_t* /*pHbFont*/)
 {
     assert(m_pGraphics);

Reply via email to