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.
