drawinglayer/source/primitive2d/textlayoutdevice.cxx |   10 +----
 include/vcl/outdev.hxx                               |    7 +++
 include/vcl/vcllayout.hxx                            |    5 ++
 svgio/qa/cppunit/SvgImportTest.cxx                   |   11 +++++
 sw/qa/uitest/writer_tests6/tdf157569.py              |    4 +-
 vcl/skia/gdiimpl.cxx                                 |    2 -
 vcl/source/gdi/sallayout.cxx                         |   33 ++++++++--------
 vcl/source/outdev/map.cxx                            |    8 ++++
 vcl/source/outdev/text.cxx                           |   38 +++++++++++++++----
 vcl/win/gdi/DWriteTextRenderer.cxx                   |   11 ++++-
 10 files changed, 89 insertions(+), 40 deletions(-)

New commits:
commit 2642643ec07cd7f3d28fe558769297578c36de19
Author:     Mike Kaganski <[email protected]>
AuthorDate: Wed Apr 10 12:15:55 2024 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Fri Apr 19 05:31:51 2024 +0200

    tdf#160622: Let SalLayout::GetBoundRect return basegfx::B2DRectangle
    
    This avoids premature rounding in TextLayouterDevice::getTextBoundRect.
    The box in D2DWriteTextOutRenderer::performRender needs to be expanded
    to allow room for the line width (which now will be guaranteed on all
    sides; previously, the rounding could happen to give no room on some
    side, even prior to commit 8962141a12c966b2d891829925e6203bf8d51852).
    Fixes some lines partially cut off in smaller text (or zoomed out).
    
    Change-Id: I07335136021f894cf045363b4d736bfab06c64d4
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166236
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>

diff --git a/drawinglayer/source/primitive2d/textlayoutdevice.cxx 
b/drawinglayer/source/primitive2d/textlayoutdevice.cxx
index 1c551ce01363..5145d84ed2b6 100644
--- a/drawinglayer/source/primitive2d/textlayoutdevice.cxx
+++ b/drawinglayer/source/primitive2d/textlayoutdevice.cxx
@@ -260,15 +260,9 @@ basegfx::B2DRange 
TextLayouterDevice::getTextBoundRect(const OUString& rText, sa
 
     if (nTextLength)
     {
-        ::tools::Rectangle aRect;
-
+        basegfx::B2DRange aRect;
         mrDevice.GetTextBoundRect(aRect, rText, nIndex, nIndex, nLength);
-
-        // #i104432#, #i102556# take empty results into account
-        if (!aRect.IsEmpty())
-        {
-            return vcl::unotools::b2DRectangleFromRectangle(aRect);
-        }
+        return aRect;
     }
 
     return basegfx::B2DRange();
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index e4a0b4827b5b..3211ed368989 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -53,6 +53,7 @@
 #include <vcl/vclenum.hxx>
 #include <vcl/vclreferencebase.hxx>
 
+#include <basegfx/range/b2drectangle.hxx>
 #include <basegfx/numeric/ftools.hxx>
 #include <basegfx/point/b2dpoint.hxx>
 #include <basegfx/vector/b2enums.hxx>
@@ -962,6 +963,11 @@ public:
                                                   sal_uLong nLayoutWidth = 0, 
KernArraySpan aDXArray = KernArraySpan(),
                                                   std::span<const sal_Bool> 
pKashidaArray = {},
                                                   const SalLayoutGlyphs* 
pGlyphs = nullptr ) const;
+    bool                        GetTextBoundRect( basegfx::B2DRectangle& rRect,
+                                                  const OUString& rStr, 
sal_Int32 nBase = 0, sal_Int32 nIndex = 0, sal_Int32 nLen = -1,
+                                                  sal_uLong nLayoutWidth = 0, 
KernArraySpan aDXArray = KernArraySpan(),
+                                                  std::span<const sal_Bool> 
pKashidaArray = {},
+                                                  const SalLayoutGlyphs* 
pGlyphs = nullptr ) const;
 
     tools::Rectangle            ImplGetTextBoundRect( const SalLayout& ) const;
 
@@ -1615,6 +1621,7 @@ public:
     SAL_WARN_UNUSED_RESULT SAL_DLLPRIVATE tools::Polygon PixelToLogic(const 
tools::Polygon& rDevicePoly) const;
     SAL_WARN_UNUSED_RESULT SAL_DLLPRIVATE tools::PolyPolygon 
PixelToLogic(const tools::PolyPolygon& rDevicePolyPoly) const;
     SAL_WARN_UNUSED_RESULT SAL_DLLPRIVATE basegfx::B2DPolyPolygon 
PixelToLogic(const basegfx::B2DPolyPolygon& rDevicePolyPoly) const;
+    SAL_WARN_UNUSED_RESULT SAL_DLLPRIVATE basegfx::B2DRectangle 
PixelToLogic(const basegfx::B2DRectangle& rDeviceRect) const;
     SAL_WARN_UNUSED_RESULT vcl::Region PixelToLogic(const vcl::Region& 
rDeviceRegion) const;
     SAL_WARN_UNUSED_RESULT Point PixelToLogic(const Point& rDevicePt, const 
MapMode& rMapMode) const;
     SAL_WARN_UNUSED_RESULT Size PixelToLogic(const Size& rDeviceSize, const 
MapMode& rMapMode) const;
diff --git a/include/vcl/vcllayout.hxx b/include/vcl/vcllayout.hxx
index 7c7a179b9b05..c946f6f67884 100644
--- a/include/vcl/vcllayout.hxx
+++ b/include/vcl/vcllayout.hxx
@@ -21,6 +21,7 @@
 
 #include <basegfx/point/b2dpoint.hxx>
 #include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/range/b2drectangle.hxx>
 #include <i18nlangtag/languagetag.hxx>
 #include <tools/gen.hxx>
 #include <tools/degree.hxx>
@@ -103,7 +104,9 @@ public:
     virtual bool    GetNextGlyph(const GlyphItem** pGlyph, basegfx::B2DPoint& 
rPos, int& nStart,
                                  const LogicalFontInstance** ppGlyphFont = 
nullptr) const = 0;
     virtual bool GetOutline(basegfx::B2DPolyPolygonVector&) const;
-    bool GetBoundRect(tools::Rectangle&) const;
+    bool GetBoundRect(basegfx::B2DRectangle&) const;
+
+    static tools::Rectangle BoundRect2Rectangle(basegfx::B2DRectangle&);
 
     virtual SalLayoutGlyphs GetGlyphs() const;
 
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx 
b/svgio/qa/cppunit/SvgImportTest.cxx
index fe7c51d11324..3d6ef34d5857 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -40,6 +40,8 @@ protected:
 
     Primitive2DSequence parseSvg(std::u16string_view aSource);
     xmlDocUniquePtr dumpAndParseSvg(std::u16string_view aSource);
+    void assertXPathDouble(const xmlDocUniquePtr& pXmlDoc, const OString& 
rXPath,
+                           const OString& rAttribute, double nExpectedValue, 
double delta);
 };
 
 Primitive2DSequence Test::parseSvg(std::u16string_view aSource)
@@ -90,6 +92,13 @@ void Test::checkRectPrimitive(Primitive2DSequence const & 
rPrimitive)
     assertXPath(pDocument, 
"/primitive2D/transform/polypolygonstroke/line"_ostr, "width"_ostr, "3"); // 
rect stroke width
 }
 
+void Test::assertXPathDouble(const xmlDocUniquePtr& pXmlDoc, const OString& 
rXPath,
+                             const OString& rAttribute, double nExpectedValue, 
double delta)
+{
+    auto sVal = getXPath(pXmlDoc, rXPath, rAttribute);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(nExpectedValue, sVal.toDouble(), delta);
+}
+
 namespace
 {
 bool arePrimitive2DSequencesEqual(const Primitive2DSequence& rA, const 
Primitive2DSequence& rB)
@@ -725,7 +734,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf156834)
 
     assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, 
"text"_ostr, "Hanging");
     assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, 
"x"_ostr, "30");
-    assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]"_ostr, 
"y"_ostr, "94");
+    assertXPathDouble(pDocument, 
"/primitive2D/transform/textsimpleportion[3]"_ostr, "y"_ostr, 93.5, 0.5);
 
     assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, 
"text"_ostr, "Central");
     assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]"_ostr, 
"x"_ostr, "30");
diff --git a/sw/qa/uitest/writer_tests6/tdf157569.py 
b/sw/qa/uitest/writer_tests6/tdf157569.py
index 493760ed2fc7..9177047cec51 100644
--- a/sw/qa/uitest/writer_tests6/tdf157569.py
+++ b/sw/qa/uitest/writer_tests6/tdf157569.py
@@ -24,9 +24,9 @@ class tdf157569(UITestCase):
             # AssertionError: 1663 != 944
             self.assertEqual(1663, nHeight)
             if platform.system() == "Windows":
-                self.assertEqual(2145, nWidth) # no idea why
+                self.assertEqual(2155, nWidth) # no idea why it's different on 
Windows
             else:
-                self.assertEqual(2111, nWidth)
+                self.assertEqual(2118, nWidth)
 
             xDoc = self.xUITest.getTopFocusWindow()
             xEditWin = xDoc.getChild("writer_edit")
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index fd03e4fd2e68..f90fbace9d69 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -2179,7 +2179,7 @@ void SkiaSalGraphicsImpl::drawGenericLayout(const 
GenericSalLayout& layout, Colo
 
     preDraw();
     auto getBoundRect = [&layout]() {
-        tools::Rectangle rect;
+        basegfx::B2DRectangle rect;
         layout.GetBoundRect(rect);
         return rect;
     };
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
index 28138c3f61fd..3b1279dd1f66 100644
--- a/vcl/source/gdi/sallayout.cxx
+++ b/vcl/source/gdi/sallayout.cxx
@@ -220,11 +220,10 @@ static double trimInsignificant(double n)
     return std::abs(n) >= 0x1p53 ? n : std::round(n * 1e5) / 1e5;
 }
 
-bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const
+bool SalLayout::GetBoundRect(basegfx::B2DRectangle& rRect) const
 {
     bool bRet = false;
-
-    basegfx::B2DRectangle aUnion;
+    rRect.reset();
     basegfx::B2DRectangle aRectangle;
 
     basegfx::B2DPoint aPos;
@@ -240,28 +239,28 @@ bool SalLayout::GetBoundRect(tools::Rectangle& rRect) 
const
             {
                 
aRectangle.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos));
                 // merge rectangle
-                aUnion.expand(aRectangle);
+                rRect.expand(aRectangle);
             }
             bRet = true;
         }
     }
-    if (aUnion.isEmpty())
-    {
-        rRect = {};
-    }
-    else
-    {
-        double l = rtl::math::approxFloor(trimInsignificant(aUnion.getMinX())),
-               t = rtl::math::approxFloor(trimInsignificant(aUnion.getMinY())),
-               r = rtl::math::approxCeil(trimInsignificant(aUnion.getMaxX())),
-               b = rtl::math::approxCeil(trimInsignificant(aUnion.getMaxY()));
-        assert(std::isfinite(l) && std::isfinite(t) && std::isfinite(r) && 
std::isfinite(b));
-        rRect = tools::Rectangle(l, t, r, b);
-    }
 
     return bRet;
 }
 
+tools::Rectangle SalLayout::BoundRect2Rectangle(basegfx::B2DRectangle& rRect)
+{
+    if (rRect.isEmpty())
+        return {};
+
+    double l = rtl::math::approxFloor(trimInsignificant(rRect.getMinX())),
+           t = rtl::math::approxFloor(trimInsignificant(rRect.getMinY())),
+           r = rtl::math::approxCeil(trimInsignificant(rRect.getMaxX())),
+           b = rtl::math::approxCeil(trimInsignificant(rRect.getMaxY()));
+    assert(std::isfinite(l) && std::isfinite(t) && std::isfinite(r) && 
std::isfinite(b));
+    return tools::Rectangle(l, t, r, b);
+}
+
 SalLayoutGlyphs SalLayout::GetGlyphs() const
 {
     return SalLayoutGlyphs(); // invalid
diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx
index 23c68a238551..8707805eb1c2 100644
--- a/vcl/source/outdev/map.cxx
+++ b/vcl/source/outdev/map.cxx
@@ -1217,6 +1217,14 @@ basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( 
const basegfx::B2DPolyPolygo
     return aTransformedPoly;
 }
 
+basegfx::B2DRectangle OutputDevice::PixelToLogic(const basegfx::B2DRectangle& 
rDeviceRect) const
+{
+    basegfx::B2DRectangle aTransformedRect = rDeviceRect;
+    const basegfx::B2DHomMatrix& rTransformationMatrix = 
GetInverseViewTransformation();
+    aTransformedRect.transform(rTransformationMatrix);
+    return aTransformedRect;
+}
+
 vcl::Region OutputDevice::PixelToLogic( const vcl::Region& rDeviceRegion ) 
const
 {
 
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
index 1eb9bd82425f..961c095e01f9 100644
--- a/vcl/source/outdev/text.cxx
+++ b/vcl/source/outdev/text.cxx
@@ -21,6 +21,7 @@
 
 #include <sal/log.hxx>
 #include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
 #include <tools/lineend.hxx>
 #include <tools/debug.hxx>
 #include <comphelper/configuration.hxx>
@@ -214,7 +215,11 @@ bool OutputDevice::ImplDrawRotateText( SalLayout& 
rSalLayout )
     tools::Rectangle aBoundRect;
     rSalLayout.DrawBase() = basegfx::B2DPoint( 0, 0 );
     rSalLayout.DrawOffset() = Point( 0, 0 );
-    if (!rSalLayout.GetBoundRect(aBoundRect))
+    if (basegfx::B2DRectangle r; rSalLayout.GetBoundRect(r))
+    {
+        aBoundRect = SalLayout::BoundRect2Rectangle(r);
+    }
+    else
     {
         // guess vertical text extents if GetBoundRect failed
         double nRight = rSalLayout.GetTextWidth();
@@ -1902,9 +1907,22 @@ bool OutputDevice::GetTextBoundRect( tools::Rectangle& 
rRect,
                                          sal_uLong nLayoutWidth, KernArraySpan 
pDXAry,
                                          std::span<const sal_Bool> pKashidaAry,
                                          const SalLayoutGlyphs* pGlyphs ) const
+{
+    basegfx::B2DRectangle aRect;
+    bool bRet = GetTextBoundRect(aRect, rStr, nBase, nIndex, nLen, 
nLayoutWidth, pDXAry,
+                                 pKashidaAry, pGlyphs);
+    rRect = SalLayout::BoundRect2Rectangle(aRect);
+    return bRet;
+}
+
+bool OutputDevice::GetTextBoundRect(basegfx::B2DRectangle& rRect, const 
OUString& rStr,
+                                    sal_Int32 nBase, sal_Int32 nIndex, 
sal_Int32 nLen,
+                                    sal_uLong nLayoutWidth, KernArraySpan 
pDXAry,
+                                    std::span<const sal_Bool> pKashidaAry,
+                                    const SalLayoutGlyphs* pGlyphs) const
 {
     bool bRet = false;
-    rRect.SetEmpty();
+    rRect.reset();
 
     std::unique_ptr<SalLayout> pSalLayout;
     const Point aPoint;
@@ -1928,18 +1946,22 @@ bool OutputDevice::GetTextBoundRect( tools::Rectangle& 
rRect,
                             nullptr, pGlyphs);
     if( pSalLayout )
     {
-        tools::Rectangle aPixelRect;
+        basegfx::B2DRectangle aPixelRect;
         bRet = pSalLayout->GetBoundRect(aPixelRect);
 
         if( bRet )
         {
-            Point aRotatedOfs( mnTextOffX, mnTextOffY );
             basegfx::B2DPoint aPos = 
pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
-            aRotatedOfs -= Point(aPos.getX(), aPos.getY());
-            aPixelRect += aRotatedOfs;
+            auto m = basegfx::utils::createTranslateB2DHomMatrix(mnTextOffX - 
aPos.getX(),
+                                                                 mnTextOffY - 
aPos.getY());
+            aPixelRect.transform(m);
             rRect = PixelToLogic( aPixelRect );
-            if( mbMap )
-                rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY );
+            if (mbMap)
+            {
+                m = 
basegfx::utils::createTranslateB2DHomMatrix(maMapRes.mnMapOfsX,
+                                                                
maMapRes.mnMapOfsY);
+                rRect.transform(m);
+            }
         }
     }
 
diff --git a/vcl/win/gdi/DWriteTextRenderer.cxx 
b/vcl/win/gdi/DWriteTextRenderer.cxx
index e0a50c4ed981..37c8f2aa217b 100644
--- a/vcl/win/gdi/DWriteTextRenderer.cxx
+++ b/vcl/win/gdi/DWriteTextRenderer.cxx
@@ -220,8 +220,15 @@ bool 
D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, Sa
     if (!pFontFace)
         return false;
 
-    tools::Rectangle bounds;
-    bool succeeded = rLayout.GetBoundRect(bounds);
+    auto [succeeded, bounds] = [&rLayout]()
+    {
+        basegfx::B2DRectangle r;
+        bool result = rLayout.GetBoundRect(r);
+        if (result)
+            r.grow(1); // plus 1 pixel to the tight range
+        return std::make_pair(result, SalLayout::BoundRect2Rectangle(r));
+    }();
+
     if (succeeded)
     {
         hr = BindDC(hDC, bounds);   // Update the bounding rect.

Reply via email to