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 ) )

Reply via email to