drawinglayer/source/primitive2d/textlayoutdevice.cxx | 2 include/vcl/outdev.hxx | 5 ++ svgio/qa/cppunit/SvgImportTest.cxx | 42 +++++++++---------- vcl/source/outdev/outdev.cxx | 1 vcl/source/outdev/text.cxx | 27 +++++++++++- 5 files changed, 55 insertions(+), 22 deletions(-)
New commits: commit ba462658d955b6d7909f9ffbdfd341f18aff2028 Author: Armin Le Grand (collabora) <armin.legr...@collabora.com> AuthorDate: Thu Aug 21 13:02:41 2025 +0200 Commit: Armin Le Grand <armin.le.gr...@me.com> CommitDate: Tue Aug 26 10:45:00 2025 +0200 tdf#168002 CairoSDPR breaks SVG text layout SubpixelPositioning was until now activated when *any* MapMode was set, but there is another case this is needed: When a TextSimplePortionPrimitive2D is rendered by a SDPR. In that case a TextLayouterDevice is used (to isolate all Text-related stuff that should not be at OutputDevice) combined with a 'empty' OutDev -> no MapMode. The DXArray for Primitives (see that TextPrimitive) is defined in the Unit-Text_Coordinate-System, thus in (0..1) ranges. That allows to have the DXArray transformation-independent and thus re-usable and is used since the TextPrimitive was created. If there is a DXArray missing at the text Primitive (as is the case with SVG imported ones, but allowed in general) one gets automatically created during the SalLayout creation for rendering. Unfortunately there (see GenericSalLayout::LayoutText, usages of GetSubpixelPositioning) the coordinates get std::round'ed, so all up to that point correctly calculated metric information in that double-precision dependent coordinate space gets *shredded*. To avoid that, SubpixelPositioning has to be activated. While this might be done in the future for all cases (SubpixelPositioning == true), for now add this case for all usages of TextLayouterDevice to not break old stuff. NOTE: Due to higher precision I had to adapt some values for SVGIO tests: That is expected since higher precision leads to less-wide results internally. I checked testdocs that deviations are minimal and have better quality with SubpixelPositioning then before. Change-Id: Iab5aa6336cdb18224fd06472bf6badc9eb0fce3a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189993 Reviewed-by: Armin Le Grand <armin.le.gr...@me.com> Tested-by: Jenkins diff --git a/drawinglayer/source/primitive2d/textlayoutdevice.cxx b/drawinglayer/source/primitive2d/textlayoutdevice.cxx index 5c0b31254c63..e92732fd1751 100644 --- a/drawinglayer/source/primitive2d/textlayoutdevice.cxx +++ b/drawinglayer/source/primitive2d/textlayoutdevice.cxx @@ -162,6 +162,8 @@ void releaseGlobalVirtualDevice() TextLayouterDevice::TextLayouterDevice() : mrDevice(acquireGlobalVirtualDevice()) { + // tdf#168002 activate SubpixelPositioning for al TextLayouterDevice-calls + mrDevice.setSubpixelPositioning(true); } TextLayouterDevice::~TextLayouterDevice() COVERITY_NOEXCEPT_FALSE { releaseGlobalVirtualDevice(); } diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx index ce8a5efc28c3..0538198be64f 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -246,6 +246,7 @@ private: mutable bool mbTextSpecial : 1; mutable bool mbRefPoint : 1; mutable bool mbEnableRTL : 1; + mutable bool mbSubpixelPositioning : 1; protected: mutable std::shared_ptr<vcl::font::PhysicalFontCollection> mxFontCollection; @@ -1279,6 +1280,10 @@ public: virtual void EnableRTL( bool bEnable = true); bool IsRTLEnabled() const { return mbEnableRTL; } + // tdf#168002 allow SubpixelPositioning for this device (default: false) + bool isSubpixelPositioning() const { return mbSubpixelPositioning; } + void setSubpixelPositioning(bool bNew) { mbSubpixelPositioning = bNew; } + bool GetTextIsRTL( const OUString&, sal_Int32 nIndex, sal_Int32 nLen ) const; ///@} diff --git a/svgio/qa/cppunit/SvgImportTest.cxx b/svgio/qa/cppunit/SvgImportTest.cxx index d87ce85e03c8..529ac1e9381e 100644 --- a/svgio/qa/cppunit/SvgImportTest.cxx +++ b/svgio/qa/cppunit/SvgImportTest.cxx @@ -790,7 +790,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf156777) { xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf156777.svg"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion", 23); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion", 22); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", u"Quick brown fox jumps over the lazy dog."); // Without the fix in place, this test would have failed with @@ -872,17 +872,17 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf93583) xmlDocUniquePtr pDocument = dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf93583.svg"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", u"This is the"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "x", u"58"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "x", u"56"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "y", u"303"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "width", u"16"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "height", u"16"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", u" first "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "x", u"125"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "x", u"122"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "y", u"303"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "width", u"32"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "height", u"32"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", u"line"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", u"192"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", u"190"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "y", u"303"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "width", u"16"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "height", u"16"); @@ -902,22 +902,22 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf156616) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", u"114"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "y", u"122"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "text", u"First "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "x", u"85"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "x", u"83"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "y", u"153"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "text", u"line "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "x", u"118"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "x", u"117"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "y", u"153"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "text", u"Second line"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "x", u"77"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "x", u"76"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "y", u"172"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "text", u"First "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "x", u"55"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "x", u"53"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "y", u"203"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "text", u"line "); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "x", u"88"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "x", u"86"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "y", u"203"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "text", u"Second line"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "x", u"40"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "x", u"39"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "y", u"222"); } @@ -1486,11 +1486,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTextAnchor) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "y", u"40"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[1]", "text", u"ABC"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "x", u"43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "x", u"44"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "y", u"50"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]", "text", u"ABC"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", u"26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "x", u"27"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "y", u"60"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "text", u"ABC"); @@ -1498,11 +1498,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTextAnchor) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "y", u"40"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[4]", "text", u"ABC"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "x", u"43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "x", u"44"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "y", u"50"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[5]", "text", u"ABC"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "x", u"26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "x", u"27"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "y", u"60"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[6]", "text", u"ABC"); @@ -1510,11 +1510,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTextAnchor) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "y", u"40"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[7]", "text", u"ABC"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "x", u"43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "x", u"44"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "y", u"50"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[8]", "text", u"ABC"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "x", u"26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "x", u"27"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "y", u"60"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[9]", "text", u"ABC"); @@ -1533,7 +1533,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTextAnchor) // Without the fix in place, this test would have failed with // - Expected: 43 // - Actual : 54 - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "x", u"43"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "x", u"44"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "y", u"50"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[13]", "text", u"A"); @@ -1541,19 +1541,19 @@ CPPUNIT_TEST_FIXTURE(Test, testTextAnchor) assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]", "y", u"50"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[14]", "text", u"B"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "x", u"65"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "x", u"66"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "y", u"50"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[15]", "text", u"C"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "x", u"26"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "x", u"27"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "y", u"60"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[16]", "text", u"A"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "x", u"38"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "x", u"39"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "y", u"60"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[17]", "text", u"B"); - assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "x", u"48"); + assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "x", u"49"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "y", u"60"); assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[18]", "text", u"C"); } diff --git a/vcl/source/outdev/outdev.cxx b/vcl/source/outdev/outdev.cxx index 21fcae0543c7..cd5fa7b76393 100644 --- a/vcl/source/outdev/outdev.cxx +++ b/vcl/source/outdev/outdev.cxx @@ -117,6 +117,7 @@ OutputDevice::OutputDevice(OutDevType eOutDevType) : mbTextSpecial = false; mbRefPoint = false; mbEnableRTL = false; // mirroring must be explicitly allowed (typically for windows only) + mbSubpixelPositioning = false; // tdf#168002 allow SubpixelPositioning (default: false) // struct ImplMapRes maMapRes.mnMapOfsX = 0; diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx index febf5f433810..4cd9cdbe5b8c 100644 --- a/vcl/source/outdev/text.cxx +++ b/vcl/source/outdev/text.cxx @@ -1253,7 +1253,32 @@ std::unique_ptr<SalLayout> OutputDevice::ImplLayout( std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0); if (pSalLayout) - pSalLayout->SetSubpixelPositioning(mbMap); + { + const bool bActivateSubpixelPositioning(IsMapModeEnabled() || isSubpixelPositioning()); + // tdf#168002 + // SubpixelPositioning was until now activated when *any* MapMode was set, but + // there is another case this is needed: When a TextSimplePortionPrimitive2D + // is rendered by a SDPR. + // In that case a TextLayouterDevice is used (to isolate all Text-related stuff + // that should not be at OutputDevice) combined with a 'empty' OutDev -> no + // MapMode used. It now gets SubpixelPositioning at it's OutDev to allow + // checking/usage here. + // The DXArray for Primitives (see that TextPrimitive) is defined in the + // Unit-Text_Coordinate-System, thus in (0..1) ranges. That allows to + // have the DXArray transformation-independent and thus re-usable and + // is used since the TextPrimitive was created. + // If there is a DXArray missing at the text Primitive (as is the case + // with SVG imported ones, but allowed in general) one gets automatically + // created during the SalLayout creation for rendering. Unfortunately there + // (see GenericSalLayout::LayoutText, usages of GetSubpixelPositioning) the + // coordinates get std::round'ed, so all up to that point correctly + // calculated metric information in that double-precision dependent coordinate + // space gets *shredded*. + // To avoid that, SubpixelPositioning has to be activated. While this might + // be done in the future for all cases (SubpixelPositioning == true) for now + // just add this case with the Primitives to not break stuff. + pSalLayout->SetSubpixelPositioning(bActivateSubpixelPositioning); + } // layout text if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )