drawinglayer/source/primitive2d/textprimitive2d.cxx |   12 +++++-
 drawinglayer/source/tools/primitive2dxmldump.cxx    |    5 ++
 svgio/inc/svgstyleattributes.hxx                    |   12 ++++++
 svgio/qa/cppunit/SvgImportTest.cxx                  |   21 ++++++++++++
 svgio/qa/cppunit/data/BiDitext.svg                  |    9 +++++
 svgio/source/svgreader/svgcharacternode.cxx         |    3 +
 svgio/source/svgreader/svgstyleattributes.cxx       |   35 +++++++++++++++++++-
 7 files changed, 92 insertions(+), 5 deletions(-)

New commits:
commit e5f0dad2b38d2a3902a9cd6b9d434abc206590a4
Author:     Xisco Fauli <[email protected]>
AuthorDate: Thu Sep 18 14:28:13 2025 +0200
Commit:     Adolfo Jayme Barrientos <[email protected]>
CommitDate: Sat Sep 20 10:07:48 2025 +0200

    tdf#168452: support unicode-bidi in svg
    
    Before this change, getBiDiStrong() was never used
    to manipulate the text
    
    Change-Id: I9d6a2e8b79d27126902f2be84174c641ec71c28c
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191123
    Tested-by: Jenkins
    Reviewed-by: Xisco Fauli <[email protected]>
    (cherry picked from commit f127f708f6ccc941beabaacc7be18daf478a79cd)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191133
    Reviewed-by: Adolfo Jayme Barrientos <[email protected]>

diff --git a/drawinglayer/source/primitive2d/textprimitive2d.cxx 
b/drawinglayer/source/primitive2d/textprimitive2d.cxx
index fae5866c6517..fa2c2086b53c 100644
--- a/drawinglayer/source/primitive2d/textprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/textprimitive2d.cxx
@@ -334,10 +334,13 @@ void 
TextSimplePortionPrimitive2D::createTextLayouter(TextLayouterDevice& rTextL
 
     if (getFontAttribute().getRTL())
     {
-        vcl::text::ComplexTextLayoutFlags nRTLLayoutMode(
-            rTextLayouter.getLayoutMode() & 
~vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+        vcl::text::ComplexTextLayoutFlags 
nRTLLayoutMode(rTextLayouter.getLayoutMode());
         nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl
                           | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+        if (getFontAttribute().getBiDiStrong())
+            nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+        else
+            nRTLLayoutMode = nRTLLayoutMode & 
~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
         rTextLayouter.setLayoutMode(nRTLLayoutMode);
     }
     else
@@ -345,7 +348,10 @@ void 
TextSimplePortionPrimitive2D::createTextLayouter(TextLayouterDevice& rTextL
         // tdf#101686: This is LTR text, but the output device may have RTL 
state.
         vcl::text::ComplexTextLayoutFlags 
nLTRLayoutMode(rTextLayouter.getLayoutMode());
         nLTRLayoutMode = nLTRLayoutMode & 
~vcl::text::ComplexTextLayoutFlags::BiDiRtl;
-        nLTRLayoutMode = nLTRLayoutMode & 
~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+        if (getFontAttribute().getBiDiStrong())
+            nLTRLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+        else
+            nLTRLayoutMode = nLTRLayoutMode & 
~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
         rTextLayouter.setLayoutMode(nLTRLayoutMode);
     }
 }
diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx 
b/drawinglayer/source/tools/primitive2dxmldump.cxx
index fb0d78a66be4..bf1cc519d770 100644
--- a/drawinglayer/source/tools/primitive2dxmldump.cxx
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -946,6 +946,11 @@ void Primitive2dXmlDump::decomposeAndWrite(
                     rWriter.attribute("rtl", std::u16string_view{ u"true" });
                 }
 
+                if (aFontAttribute.getBiDiStrong())
+                {
+                    rWriter.attribute("bidi", std::u16string_view{ u"true" });
+                }
+
                 const std::vector<double> aDx = 
rTextSimplePortionPrimitive2D.getDXArray();
                 if (aDx.size())
                 {
diff --git a/svgio/inc/svgstyleattributes.hxx b/svgio/inc/svgstyleattributes.hxx
index 52c08d2ad4c6..cf8e9182f108 100644
--- a/svgio/inc/svgstyleattributes.hxx
+++ b/svgio/inc/svgstyleattributes.hxx
@@ -122,6 +122,13 @@ namespace svgio::svgreader
             RTL,
         };
 
+        enum class UnicodeBidi
+        {
+            notset,
+            normal,
+            bidi_override,
+        };
+
         FontWeight getBolder(FontWeight aSource);
         FontWeight getLighter(FontWeight aSource);
         ::FontWeight getVclFontWeight(FontWeight aSource);
@@ -217,6 +224,7 @@ namespace svgio::svgreader
             FontStyle                   maFontStyle;
             FontWeight                  maFontWeight;
             FontDirection               maFontDirection;
+            UnicodeBidi                 maUnicodeBidi;
             TextAlign                   maTextAlign;
             TextDecoration              maTextDecoration;
             TextAnchor                  maTextAnchor;
@@ -428,6 +436,10 @@ namespace svgio::svgreader
             FontDirection getFontDirection() const;
             void setFontDirection(const FontDirection aFontDirection) { 
maFontDirection = aFontDirection; }
 
+            /// UnicodeBidi content
+            UnicodeBidi getUnicodeBidi() const;
+            void setUnicodeBidi(const UnicodeBidi aUnicodeBidi) { 
maUnicodeBidi = aUnicodeBidi; }
+
             /// TextAlign content
             TextAlign getTextAlign() const;
             void setTextAlign(const TextAlign aTextAlign) { maTextAlign = 
aTextAlign; }
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx 
b/svgio/qa/cppunit/SvgImportTest.cxx
index 1387ec6543c8..20db3664d879 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -2146,6 +2146,27 @@ CPPUNIT_TEST_FIXTURE(Test, testRTLtext)
     assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[3]", "y", 
u"150");
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testBiDitext)
+{
+    xmlDocUniquePtr pDocument = 
dumpAndParseSvg(u"/svgio/qa/cppunit/data/BiDitext.svg");
+
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[1]", 
"text", u"Text אני יכול לאכול זכוכית וזה לא מזיק לי is in Hebrew");
+    assertXPathNoAttribute(pDocument, 
"/primitive2D/transform/mask/textsimpleportion[1]", "rtl");
+    assertXPathNoAttribute(pDocument, 
"/primitive2D/transform/mask/textsimpleportion[1]", "bidi");
+
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]", 
"text", u"Text אני יכול לאכול זכוכית וזה לא מזיק לי is in Hebrew");
+    assertXPathNoAttribute(pDocument, 
"/primitive2D/transform/mask/textsimpleportion[2]", "rtl");
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[2]", 
"bidi", u"true");
+
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]", 
"text", u"Text אני יכול לאכול זכוכית וזה לא מזיק לי is in Hebrew");
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]", 
"rtl", u"true");
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[3]", 
"bidi", u"true");
+
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]", 
"text", u"Text אני יכול לאכול זכוכית וזה לא מזיק לי is in Hebrew");
+    assertXPath(pDocument, "/primitive2D/transform/mask/textsimpleportion[4]", 
"rtl", u"true");
+    assertXPathNoAttribute(pDocument, 
"/primitive2D/transform/mask/textsimpleportion[4]", "bidi");
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testCssClassRedefinition)
 {
     // Tests for svg css class redefinition behavior
diff --git a/svgio/qa/cppunit/data/BiDitext.svg 
b/svgio/qa/cppunit/data/BiDitext.svg
new file mode 100644
index 000000000000..36f948d97d1f
--- /dev/null
+++ b/svgio/qa/cppunit/data/BiDitext.svg
@@ -0,0 +1,9 @@
+<svg
+  width="600 px" height="600 px"
+  xmlns="http://www.w3.org/2000/svg";
+  font-family="DejaVu Sans">
+  <text x="0" y="50">Text אני יכול לאכול זכוכית וזה לא מזיק לי is in 
Hebrew</text>
+  <text x="0" y="100" unicode-bidi="bidi-override">Text אני יכול לאכול זכוכית 
וזה לא מזיק לי is in Hebrew</text>
+  <text x="0" y="150" unicode-bidi="bidi-override" direction="rtl" 
text-anchor="end">Text אני יכול לאכול זכוכית וזה לא מזיק לי is in Hebrew</text>
+  <text x="0" y="200" direction="rtl" text-anchor="end">Text אני יכול לאכול 
זכוכית וזה לא מזיק לי is in Hebrew</text>
+</svg>
diff --git a/svgio/source/svgreader/svgcharacternode.cxx 
b/svgio/source/svgreader/svgcharacternode.cxx
index 4e7c8e4794c5..ccaefc14dd34 100644
--- a/svgio/source/svgreader/svgcharacternode.cxx
+++ b/svgio/source/svgreader/svgcharacternode.cxx
@@ -119,6 +119,7 @@ namespace svgio::svgreader
             const ::FontWeight 
nFontWeight(getVclFontWeight(rSvgStyleAttributes.getFontWeight()));
             bool bItalic(FontStyle::italic == 
rSvgStyleAttributes.getFontStyle() || FontStyle::oblique == 
rSvgStyleAttributes.getFontStyle());
             bool bRTL(FontDirection::RTL == 
rSvgStyleAttributes.getFontDirection());
+            bool bUnicodeBidi(UnicodeBidi::bidi_override == 
rSvgStyleAttributes.getUnicodeBidi());
 
             return drawinglayer::attribute::FontAttribute(
                 aFontFamily,
@@ -130,7 +131,7 @@ namespace svgio::svgreader
                 false/*bMonospaced*/,
                 false/*bOutline*/,
                 bRTL,
-                false/*bBiDiStrong*/);
+                bUnicodeBidi);
         }
 
         rtl::Reference<BasePrimitive2D> 
SvgCharacterNode::createSimpleTextPrimitive(
diff --git a/svgio/source/svgreader/svgstyleattributes.cxx 
b/svgio/source/svgreader/svgstyleattributes.cxx
index 6a9a20b56386..7206cd3458aa 100644
--- a/svgio/source/svgreader/svgstyleattributes.cxx
+++ b/svgio/source/svgreader/svgstyleattributes.cxx
@@ -1392,6 +1392,7 @@ namespace svgio::svgreader
             maFontStyle(FontStyle::notset),
             maFontWeight(FontWeight::notset),
             maFontDirection(FontDirection::notset),
+            maUnicodeBidi(UnicodeBidi::notset),
             maTextAlign(TextAlign::notset),
             maTextDecoration(TextDecoration::notset),
             maTextAnchor(TextAnchor::notset),
@@ -1402,7 +1403,7 @@ namespace svgio::svgreader
             maBaselineShift(BaselineShift::Baseline),
             maBaselineShiftNumber(0),
             maDominantBaseline(DominantBaseline::Auto),
-            maResolvingParent(35, 0),
+            maResolvingParent(36, 0),
             mbStrokeDasharraySet(false),
             mbUseFillFromContextFill(false),
             mbUseFillFromContextStroke(false),
@@ -1887,6 +1888,17 @@ namespace svgio::svgreader
                 }
                 case SVGToken::UnicodeBidi:
                 {
+                    if(!aContent.isEmpty())
+                    {
+                        if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), 
u"normal"))
+                        {
+                            setUnicodeBidi(UnicodeBidi::normal);
+                        }
+                        else 
if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), u"bidi-override"))
+                        {
+                            setUnicodeBidi(UnicodeBidi::bidi_override);
+                        }
+                    }
                     break;
                 }
                 case SVGToken::WordSpacing:
@@ -2969,6 +2981,27 @@ namespace svgio::svgreader
             return FontDirection::LTR;
         }
 
+        UnicodeBidi SvgStyleAttributes::getUnicodeBidi() const
+        {
+            if(maUnicodeBidi != UnicodeBidi::notset)
+            {
+                return maUnicodeBidi;
+            }
+
+            const SvgStyleAttributes* pSvgStyleAttributes = 
getCssStyleOrParentStyle();
+            if (pSvgStyleAttributes && maResolvingParent[35] < 
nStyleDepthLimit)
+            {
+                ++maResolvingParent[35];
+                auto ret = pSvgStyleAttributes->getUnicodeBidi();
+                --maResolvingParent[35];
+
+                return ret;
+            }
+
+            // default is normal
+            return UnicodeBidi::normal;
+        }
+
         TextAlign SvgStyleAttributes::getTextAlign() const
         {
             if(maTextAlign != TextAlign::notset)

Reply via email to