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)
