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 e4ec4aa609e5a67ec5e3301822d79d90faec30e4 Author: Armin Le Grand (collabora) <armin.legr...@collabora.com> AuthorDate: Thu Aug 21 13:02:41 2025 +0200 Commit: Adolfo Jayme Barrientos <fit...@ubuntu.com> CommitDate: Thu Aug 28 15:28:02 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 (cherry picked from commit ba462658d955b6d7909f9ffbdfd341f18aff2028) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190236 Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com> 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 d29fa9a18a90..76b9480d90c8 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -254,6 +254,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; @@ -1281,6 +1282,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 43b6650ce4e8..c96457ab2a7d 100644 --- a/vcl/source/outdev/outdev.cxx +++ b/vcl/source/outdev/outdev.cxx @@ -118,6 +118,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 4aac96d13d1a..767e17be4edc 100644 --- a/vcl/source/outdev/text.cxx +++ b/vcl/source/outdev/text.cxx @@ -1292,7 +1292,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 ) )