[Libreoffice-commits] core.git: include/vcl vcl/inc vcl/Library_vcl.mk vcl/qa vcl/source

Chris Sherlock (via logerrit) logerrit at kemper.freedesktop.org
Mon Sep 6 10:52:53 UTC 2021


 include/vcl/outdev.hxx             |    8 
 include/vcl/vcllayout.hxx          |    6 
 vcl/Library_vcl.mk                 |    1 
 vcl/inc/ImplLayoutArgs.hxx         |   69 +++++++
 vcl/inc/salgdi.hxx                 |    1 
 vcl/inc/sallayout.hxx              |   62 ------
 vcl/qa/cppunit/complextext.cxx     |    5 
 vcl/qa/cppunit/text.cxx            |  142 +++++++++++++++
 vcl/source/gdi/CommonSalLayout.cxx |    7 
 vcl/source/gdi/sallayout.cxx       |  278 ------------------------------
 vcl/source/outdev/font.cxx         |    5 
 vcl/source/outdev/text.cxx         |    9 
 vcl/source/text/ImplLayoutArgs.cxx |  339 +++++++++++++++++++++++++++++++++++++
 13 files changed, 585 insertions(+), 347 deletions(-)

New commits:
commit 8381242c2e0bfda1b37e7e82525926c0497c81d4
Author:     Chris Sherlock <chris.sherlock79 at gmail.com>
AuthorDate: Sun Mar 7 13:59:04 2021 +1100
Commit:     Noel Grandin <noel.grandin at collabora.co.uk>
CommitDate: Mon Sep 6 12:52:14 2021 +0200

    vcl: move ImplLayoutArgs to own header and source files
    
    Add unit tests for ImplLayoutArgs. Also add to the vcl::text namespace.
    
    Change-Id: I9fa0943548be8ca17ea3dac104e9cb609337a70e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121209
    Tested-by: Jenkins
    Reviewed-by: Noel Grandin <noel.grandin at collabora.co.uk>

diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index 26376fedc9e2..6bf789793af6 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -89,7 +89,6 @@ class LineInfo;
 class AlphaMask;
 class FontCharMap;
 class SalLayout;
-class ImplLayoutArgs;
 class VirtualDevice;
 struct SalTwoRect;
 class Printer;
@@ -109,6 +108,7 @@ namespace vcl
     }
 
     namespace text {
+        class ImplLayoutArgs;
         class TextLayoutCache;
     }
 }
@@ -1226,18 +1226,18 @@ public:
                                             const tools::Long* pLogicDXArray=nullptr, SalLayoutFlags flags = SalLayoutFlags::NONE,
                                             vcl::text::TextLayoutCache const* = nullptr,
                                             const SalLayoutGlyphs* pGlyphs = nullptr) const;
-    SAL_DLLPRIVATE ImplLayoutArgs ImplPrepareLayoutArgs( OUString&, const sal_Int32 nIndex, const sal_Int32 nLen,
+    SAL_DLLPRIVATE vcl::text::ImplLayoutArgs ImplPrepareLayoutArgs( OUString&, const sal_Int32 nIndex, const sal_Int32 nLen,
                                                          DeviceCoordinate nPixelWidth, const DeviceCoordinate* pPixelDXArray,
                                                          SalLayoutFlags flags = SalLayoutFlags::NONE,
                                                          vcl::text::TextLayoutCache const* = nullptr) const;
     SAL_DLLPRIVATE std::unique_ptr<SalLayout>
                                 ImplGlyphFallbackLayout( std::unique_ptr<SalLayout>,
-                                                         ImplLayoutArgs&,
+                                                         vcl::text::ImplLayoutArgs&,
                                                          const SalLayoutGlyphs* ) const;
     SAL_DLLPRIVATE std::unique_ptr<SalLayout>
                                 getFallbackLayout(
                                     LogicalFontInstance* pLogicalFont, int nFallbackLevel,
-                                    ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* ) const;
+                                    vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* ) const;
 
 
     // Enabling/disabling RTL only makes sense for OutputDevices that use a mirroring SalGraphicsLayout
diff --git a/include/vcl/vcllayout.hxx b/include/vcl/vcllayout.hxx
index 670e8aaad0c3..2546b5f72c09 100644
--- a/include/vcl/vcllayout.hxx
+++ b/include/vcl/vcllayout.hxx
@@ -28,7 +28,7 @@
 #include <vcl/dllapi.h>
 
 class LogicalFontInstance;
-class ImplLayoutArgs;
+namespace vcl::text { class ImplLayoutArgs; }
 class PhysicalFontFace;
 class SalGraphics;
 class GlyphItem;
@@ -74,8 +74,8 @@ public:
     const Point&    DrawOffset() const                      { return maDrawOffset; }
     Point           GetDrawPosition( const Point& rRelative = Point(0,0) ) const;
 
-    virtual bool    LayoutText( ImplLayoutArgs&, const SalLayoutGlyphsImpl* ) = 0;  // first step of layouting
-    virtual void    AdjustLayout( ImplLayoutArgs& );    // adjusting after fallback etc.
+    virtual bool    LayoutText( vcl::text::ImplLayoutArgs&, const SalLayoutGlyphsImpl* ) = 0;  // first step of layouting
+    virtual void    AdjustLayout( vcl::text::ImplLayoutArgs& );    // adjusting after fallback etc.
     virtual void    InitFont() const {}
     virtual void    DrawText( SalGraphics& ) const = 0;
 
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 05f33b07115d..f9403f25b7e9 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -236,6 +236,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/outdev/vclreferencebase \
     vcl/source/outdev/nativecontrols \
     vcl/source/outdev/map \
+    vcl/source/text/ImplLayoutArgs \
     vcl/source/text/TextLayoutCache \
     vcl/source/treelist/headbar \
     vcl/source/treelist/iconview \
diff --git a/vcl/inc/ImplLayoutArgs.hxx b/vcl/inc/ImplLayoutArgs.hxx
new file mode 100644
index 000000000000..865470b7897a
--- /dev/null
+++ b/vcl/inc/ImplLayoutArgs.hxx
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "sallayout.hxx"
+
+namespace vcl::text
+{
+class VCL_DLLPUBLIC ImplLayoutArgs
+{
+public:
+    // string related inputs
+    LanguageTag maLanguageTag;
+    SalLayoutFlags mnFlags;
+    const OUString& mrStr;
+    int mnMinCharPos;
+    int mnEndCharPos;
+
+    // performance hack
+    vcl::text::TextLayoutCache const* m_pTextLayoutCache;
+
+    // positioning related inputs
+    const DeviceCoordinate* mpDXArray; // in pixel units
+    DeviceCoordinate mnLayoutWidth; // in pixel units
+    Degree10 mnOrientation; // in 0-3600 system
+
+    // data for bidi and glyph+script fallback
+    ImplLayoutRuns maRuns;
+    ImplLayoutRuns maFallbackRuns;
+
+    ImplLayoutArgs(OUString const& rStr, int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags,
+                   LanguageTag const& rLanguageTag, vcl::text::TextLayoutCache const* pLayoutCache);
+
+    void SetLayoutWidth(DeviceCoordinate nWidth);
+    void SetDXArray(const DeviceCoordinate* pDXArray);
+    void SetOrientation(Degree10 nOrientation);
+
+    void ResetPos();
+    bool GetNextPos(int* nCharPos, bool* bRTL);
+    bool GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL);
+    void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL);
+    // methods used by BiDi and glyph fallback
+    bool HasFallbackRun() const;
+    bool PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl);
+
+private:
+    void AddRun(int nMinCharPos, int nEndCharPos, bool bRTL);
+};
+}
+
+// For nice SAL_INFO logging of ImplLayoutArgs values
+std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/salgdi.hxx b/vcl/inc/salgdi.hxx
index 9c99fcd5fa0b..65e56367d2f2 100644
--- a/vcl/inc/salgdi.hxx
+++ b/vcl/inc/salgdi.hxx
@@ -40,7 +40,6 @@ class FontSelectPattern;
 class FontAttributes;
 class PhysicalFontFace;
 class SalLayout;
-class ImplLayoutArgs;
 namespace tools { class Rectangle; }
 class FontSubsetInfo;
 class OutputDevice;
diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx
index b2bdcc2d4cb0..594202b23239 100644
--- a/vcl/inc/sallayout.hxx
+++ b/vcl/inc/sallayout.hxx
@@ -73,56 +73,6 @@ public:
     bool    PosIsInAnyRun( int nCharPos ) const;
 };
 
-class VCL_DLLPUBLIC ImplLayoutArgs
-{
-public:
-    // string related inputs
-    LanguageTag         maLanguageTag;
-    SalLayoutFlags      mnFlags;
-    const OUString&     mrStr;
-    int                 mnMinCharPos;
-    int                 mnEndCharPos;
-
-    // performance hack
-    vcl::text::TextLayoutCache const* m_pTextLayoutCache;
-
-    // positioning related inputs
-    const DeviceCoordinate* mpDXArray;     // in pixel units
-    DeviceCoordinate    mnLayoutWidth;      // in pixel units
-    Degree10            mnOrientation;      // in 0-3600 system
-
-    // data for bidi and glyph+script fallback
-    ImplLayoutRuns      maRuns;
-    ImplLayoutRuns      maFallbackRuns;
-
-                ImplLayoutArgs( const OUString& rStr,
-                                int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags,
-                                const LanguageTag& rLanguageTag,
-                                vcl::text::TextLayoutCache const* pLayoutCache);
-
-    void        SetLayoutWidth( DeviceCoordinate nWidth )       { mnLayoutWidth = nWidth; }
-    void        SetDXArray( const DeviceCoordinate* pDXArray )  { mpDXArray = pDXArray; }
-    void        SetOrientation( Degree10 nOrientation )  { mnOrientation = nOrientation; }
-
-    void        ResetPos()
-                    { maRuns.ResetPos(); }
-    bool        GetNextPos( int* nCharPos, bool* bRTL )
-                    { return maRuns.GetNextPos( nCharPos, bRTL ); }
-    bool        GetNextRun( int* nMinRunPos, int* nEndRunPos, bool* bRTL );
-    void        AddFallbackRun( int nMinRunPos, int nEndRunPos, bool bRTL )
-                    { maFallbackRuns.AddRun( nMinRunPos, nEndRunPos, bRTL ); }
-    // methods used by BiDi and glyph fallback
-    bool        HasFallbackRun() const
-                    { return !maFallbackRuns.IsEmpty(); }
-    bool        PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl);
-
-private:
-    void        AddRun( int nMinCharPos, int nEndCharPos, bool bRTL );
-};
-
-// For nice SAL_INFO logging of ImplLayoutArgs values
-std::ostream &operator <<(std::ostream& s, ImplLayoutArgs const &rArgs);
-
 class MultiSalLayout final : public SalLayout
 {
 public:
@@ -142,8 +92,8 @@ public:
     void            AddFallback(std::unique_ptr<SalLayout> pFallbackLayout, ImplLayoutRuns const &);
     // give up ownership of the initial pBaseLayout taken by the ctor
     std::unique_ptr<SalLayout>  ReleaseBaseLayout();
-    bool            LayoutText(ImplLayoutArgs&, const SalLayoutGlyphsImpl*) override;
-    void            AdjustLayout(ImplLayoutArgs&) override;
+    bool            LayoutText(vcl::text::ImplLayoutArgs&, const SalLayoutGlyphsImpl*) override;
+    void            AdjustLayout(vcl::text::ImplLayoutArgs&) override;
     void            InitFont() const override;
 
     void SetIncomplete(bool bIncomplete);
@@ -163,14 +113,14 @@ private:
 
 class VCL_DLLPUBLIC GenericSalLayout : public SalLayout
 {
-    friend void MultiSalLayout::AdjustLayout(ImplLayoutArgs&);
+    friend void MultiSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs&);
 
 public:
                     GenericSalLayout(LogicalFontInstance&);
                     ~GenericSalLayout() override;
 
-    void            AdjustLayout(ImplLayoutArgs&) final override;
-    bool            LayoutText(ImplLayoutArgs&, const SalLayoutGlyphsImpl*) final override;
+    void            AdjustLayout(vcl::text::ImplLayoutArgs&) final override;
+    bool            LayoutText(vcl::text::ImplLayoutArgs&, const SalLayoutGlyphsImpl*) final override;
     void            DrawText(SalGraphics&) const final override;
     static std::shared_ptr<vcl::text::TextLayoutCache> CreateTextLayoutCache(OUString const&);
     SalLayoutGlyphs GetGlyphs() const final override;
@@ -208,7 +158,7 @@ private:
 
     void            GetCharWidths(std::vector<DeviceCoordinate>& rCharWidths) const;
 
-    void            SetNeedFallback(ImplLayoutArgs&, sal_Int32, bool);
+    void            SetNeedFallback(vcl::text::ImplLayoutArgs&, sal_Int32, bool);
 
     bool            HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aNextChar);
 
diff --git a/vcl/qa/cppunit/complextext.cxx b/vcl/qa/cppunit/complextext.cxx
index 77a98aa7afdb..5b16b4d06e74 100644
--- a/vcl/qa/cppunit/complextext.cxx
+++ b/vcl/qa/cppunit/complextext.cxx
@@ -25,6 +25,9 @@ static std::ostream& operator<<(std::ostream& rStream, const std::vector<tools::
 #include <sallayout.hxx>
 #include <salgdi.hxx>
 
+
+#include <ImplLayoutArgs.hxx>
+
 #if HAVE_MORE_FONTS
 static std::ostream& operator<<(std::ostream& rStream, const std::vector<tools::Long>& rVec)
 {
@@ -129,7 +132,7 @@ void VclComplexTextTest::testKashida()
     CPPUNIT_ASSERT(aGlyphs.Impl(0) != nullptr);
 
     // Now lay it out using the cached glyph list.
-    ImplLayoutArgs aLayoutArgs(aText, 0, aText.getLength(), SalLayoutFlags::NONE,
+    vcl::text::ImplLayoutArgs aLayoutArgs(aText, 0, aText.getLength(), SalLayoutFlags::NONE,
                                pOutputDevice->GetFont().GetLanguageTag(), nullptr);
     pLayout = pOutputDevice->GetGraphics()->GetTextLayout(0);
     CPPUNIT_ASSERT(pLayout->LayoutText(aLayoutArgs, aGlyphs.Impl(0)));
diff --git a/vcl/qa/cppunit/text.cxx b/vcl/qa/cppunit/text.cxx
index 548690674f47..c9f0d36c50c3 100644
--- a/vcl/qa/cppunit/text.cxx
+++ b/vcl/qa/cppunit/text.cxx
@@ -10,6 +10,7 @@
 #include <test/bootstrapfixture.hxx>
 #include <sal/log.hxx>
 #include <tools/stream.hxx>
+#include <i18nlangtag/languagetag.hxx>
 
 #include <vcl/BitmapReadAccess.hxx>
 #include <vcl/graphicfilter.hxx>
@@ -17,6 +18,7 @@
 #include <vcl/svapp.hxx>
 #include <vcl/virdev.hxx>
 
+#include <ImplLayoutArgs.hxx>
 #include <TextLayoutCache.hxx>
 #include <salgdi.hxx>
 
@@ -45,11 +47,19 @@ public:
     void testSimpleText();
     void testVerticalText();
     void testTextLayoutCache();
+    void testImplLayoutArgsBiDiStrong();
+    void testImplLayoutArgsBiDiRtl();
+    void testImplLayoutArgsRightAlign();
+    void testImplLayoutArgs_PrepareFallback_precalculatedglyphs();
 
     CPPUNIT_TEST_SUITE(VclTextTest);
     CPPUNIT_TEST(testSimpleText);
     CPPUNIT_TEST(testVerticalText);
     CPPUNIT_TEST(testTextLayoutCache);
+    CPPUNIT_TEST(testImplLayoutArgsBiDiStrong);
+    CPPUNIT_TEST(testImplLayoutArgsBiDiRtl);
+    CPPUNIT_TEST(testImplLayoutArgsRightAlign);
+    CPPUNIT_TEST(testImplLayoutArgs_PrepareFallback_precalculatedglyphs);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -397,6 +407,138 @@ void VclTextTest::testTextLayoutCache()
     CPPUNIT_ASSERT_EQUAL(51, run2.nEnd);
 }
 
+void VclTextTest::testImplLayoutArgsBiDiStrong()
+{
+    OUString sTestString = u"The quick brown fox\n jumped over the lazy dog"
+                           "العاشر";
+    vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+                                    SalLayoutFlags::BiDiStrong, LanguageTag(LANGUAGE_NONE),
+                                    nullptr);
+
+    int* nMinRunPos = new int(0);
+    int* nEndRunPos = new int(0);
+    bool* pRTL = new bool(false);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(0, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(19, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(20, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(51, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(20, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(51, *nEndRunPos);
+}
+
+void VclTextTest::testImplLayoutArgsBiDiRtl()
+{
+    OUString sTestString = u"The quick brown fox\n jumped over the lazy dog"
+                           "العاشر";
+    vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+                                    SalLayoutFlags::BiDiRtl, LanguageTag(LANGUAGE_NONE), nullptr);
+
+    int* nMinRunPos = new int(0);
+    int* nEndRunPos = new int(0);
+    bool* pRTL = new bool(false);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(45, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(51, *nEndRunPos);
+    CPPUNIT_ASSERT(*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(21, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(45, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(20, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(21, *nEndRunPos);
+    CPPUNIT_ASSERT(*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(0, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(19, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+}
+
+void VclTextTest::testImplLayoutArgsRightAlign()
+{
+    OUString sTestString = u"The quick brown fox\n jumped over the lazy dog"
+                           "العاشر";
+    vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+                                    SalLayoutFlags::RightAlign, LanguageTag(LANGUAGE_NONE),
+                                    nullptr);
+
+    int* nMinRunPos = new int(0);
+    int* nEndRunPos = new int(0);
+    bool* pRTL = new bool(false);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(0, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(19, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(20, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(45, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(45, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(51, *nEndRunPos);
+    CPPUNIT_ASSERT(*pRTL);
+}
+
+void VclTextTest::testImplLayoutArgs_PrepareFallback_precalculatedglyphs()
+{
+    // this font has no latin characters and thus needs fallback
+    const vcl::Font aFont("KacstBook", Size(0, 36));
+
+    ScopedVclPtrInstance<VirtualDevice> pVirDev;
+    pVirDev->SetFont(aFont);
+
+    const OUString sTestString = "The quick\n jumped over";
+    std::unique_ptr<SalLayout> pLayout
+        = pVirDev->ImplLayout(sTestString, 0, sTestString.getLength(), Point(0, 0), 0, nullptr,
+                              SalLayoutFlags::GlyphItemsOnly);
+    SalLayoutGlyphs aGlyphs = pLayout->GetGlyphs();
+    SalLayoutGlyphsImpl* pGlyphsImpl = aGlyphs.Impl(1);
+
+    vcl::text::ImplLayoutArgs aArgs(sTestString, 0, sTestString.getLength(),
+                                    SalLayoutFlags::BiDiRtl, LanguageTag(LANGUAGE_LATIN), nullptr);
+
+    aArgs.PrepareFallback(pGlyphsImpl);
+
+    int* nMinRunPos = new int(0);
+    int* nEndRunPos = new int(0);
+    bool* pRTL = new bool(false);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(0, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(3, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(4, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(9, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(11, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(17, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+
+    aArgs.GetNextRun(nMinRunPos, nEndRunPos, pRTL);
+    CPPUNIT_ASSERT_EQUAL(18, *nMinRunPos);
+    CPPUNIT_ASSERT_EQUAL(22, *nEndRunPos);
+    CPPUNIT_ASSERT(!*pRTL);
+}
+
 CPPUNIT_TEST_SUITE_REGISTRATION(VclTextTest);
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
index b6a489a88a9a..fbfacdee6b02 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -25,6 +25,7 @@
 #include <vcl/font/Feature.hxx>
 #include <vcl/font/FeatureParser.hxx>
 
+#include <ImplLayoutArgs.hxx>
 #include <TextLayoutCache.hxx>
 #include <fontselect.hxx>
 #include <salgdi.hxx>
@@ -162,7 +163,7 @@ SalLayoutGlyphs GenericSalLayout::GetGlyphs() const
     return glyphs;
 }
 
-void GenericSalLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
+void GenericSalLayout::SetNeedFallback(vcl::text::ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
 {
     if (nCharPos < 0 || mbFuzzing)
         return;
@@ -190,7 +191,7 @@ void GenericSalLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos
     rArgs.AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
 }
 
-void GenericSalLayout::AdjustLayout(ImplLayoutArgs& rArgs)
+void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
 {
     SalLayout::AdjustLayout(rArgs);
 
@@ -251,7 +252,7 @@ bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationS
     return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
 }
 
-bool GenericSalLayout::LayoutText(ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs)
+bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* pGlyphs)
 {
     // No need to touch m_GlyphItems at all for an empty string.
     if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
index e731d6782cca..debe3017a92a 100644
--- a/vcl/source/gdi/sallayout.cxx
+++ b/vcl/source/gdi/sallayout.cxx
@@ -27,6 +27,7 @@
 
 #include <math.h>
 
+#include <ImplLayoutArgs.hxx>
 #include <salgdi.hxx>
 #include <sallayout.hxx>
 #include <basegfx/polygon/b2dpolypolygon.hxx>
@@ -49,85 +50,6 @@
 #define GF_FONTSHIFT 28
 
 
-std::ostream &operator <<(std::ostream& s, ImplLayoutArgs const &rArgs)
-{
-#ifndef SAL_LOG_INFO
-    (void) rArgs;
-#else
-    s << "ImplLayoutArgs{";
-
-    s << "Flags=";
-    if (rArgs.mnFlags == SalLayoutFlags::NONE)
-        s << 0;
-    else {
-        bool need_or = false;
-        s << "{";
-#define TEST(x) if (rArgs.mnFlags & SalLayoutFlags::x) { if (need_or) s << "|"; s << #x; need_or = true; }
-        TEST(BiDiRtl);
-        TEST(BiDiStrong);
-        TEST(RightAlign);
-        TEST(DisableKerning);
-        TEST(KerningAsian);
-        TEST(Vertical);
-        TEST(KashidaJustification);
-        TEST(ForFallback);
-#undef TEST
-        s << "}";
-    }
-
-    const int nLength = rArgs.mrStr.getLength();
-
-    s << ",Length=" << nLength;
-    s << ",MinCharPos=" << rArgs.mnMinCharPos;
-    s << ",EndCharPos=" << rArgs.mnEndCharPos;
-
-    s << ",Str=\"";
-    int lim = nLength;
-    if (lim > 10)
-        lim = 7;
-    for (int i = 0; i < lim; i++) {
-        if (rArgs.mrStr[i] == '\n')
-            s << "\\n";
-        else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
-            s << "\\0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
-        else if (rArgs.mrStr[i] < 0x7F)
-            s << static_cast<char>(rArgs.mrStr[i]);
-        else
-            s << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
-    }
-    if (nLength > lim)
-        s << "...";
-    s << "\"";
-
-    s << ",DXArray=";
-    if (rArgs.mpDXArray) {
-        s << "[";
-        int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
-        lim = count;
-        if (lim > 10)
-            lim = 7;
-        for (int i = 0; i < lim; i++) {
-            s << rArgs.mpDXArray[i];
-            if (i < lim-1)
-                s << ",";
-        }
-        if (count > lim) {
-            if (count > lim + 1)
-                s << "...";
-            s << rArgs.mpDXArray[count-1];
-        }
-        s << "]";
-    } else
-        s << "NULL";
-
-    s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
-
-    s << "}";
-
-#endif
-    return s;
-}
-
 sal_UCS4 GetMirroredChar( sal_UCS4 nChar )
 {
     nChar = u_charMirror( nChar );
@@ -207,27 +129,6 @@ sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang )
     return nChar;
 }
 
-static bool IsControlChar( sal_UCS4 cChar )
-{
-    // C0 control characters
-    if( (0x0001 <= cChar) && (cChar <= 0x001F) )
-        return true;
-    // formatting characters
-    if( (0x200E <= cChar) && (cChar <= 0x200F) )
-        return true;
-    if( (0x2028 <= cChar) && (cChar <= 0x202E) )
-        return true;
-    // deprecated formatting characters
-    if( (0x206A <= cChar) && (cChar <= 0x206F) )
-        return true;
-    if( 0x2060 == cChar )
-        return true;
-    // byte order markers and invalid unicode
-    if( (cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF) )
-        return true;
-    return false;
-}
-
 void ImplLayoutRuns::AddPos( int nCharPos, bool bRTL )
 {
     // check if charpos could extend current run
@@ -385,175 +286,6 @@ bool ImplLayoutRuns::GetRun( int* nMinRunPos, int* nEndRunPos, bool* bRightToLef
     return true;
 }
 
-ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr,
-    int nMinCharPos, int nEndCharPos, SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
-    vcl::text::TextLayoutCache const*const pLayoutCache)
-:
-    maLanguageTag( rLanguageTag ),
-    mnFlags( nFlags ),
-    mrStr( rStr ),
-    mnMinCharPos( nMinCharPos ),
-    mnEndCharPos( nEndCharPos ),
-    m_pTextLayoutCache(pLayoutCache),
-    mpDXArray( nullptr ),
-    mnLayoutWidth( 0 ),
-    mnOrientation( 0 )
-{
-    if( mnFlags & SalLayoutFlags::BiDiStrong )
-    {
-        // handle strong BiDi mode
-
-        // do not bother to BiDi analyze strong LTR/RTL
-        // TODO: can we assume these strings do not have unicode control chars?
-        //       if not remove the control characters from the runs
-        bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl);
-        AddRun( mnMinCharPos, mnEndCharPos, bRTL );
-    }
-    else
-    {
-        // handle weak BiDi mode
-        UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl)? 1 : 0;
-
-        // prepare substring for BiDi analysis
-        // TODO: reuse allocated pParaBidi
-        UErrorCode rcI18n = U_ZERO_ERROR;
-        const int nLength = mrStr.getLength();
-        UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
-        if( !pParaBidi )
-            return;
-        ubidi_setPara(pParaBidi, reinterpret_cast<const UChar *>(mrStr.getStr()), nLength, nLevel, nullptr, &rcI18n);
-
-        UBiDi* pLineBidi = pParaBidi;
-        int nSubLength = mnEndCharPos - mnMinCharPos;
-        if (nSubLength != nLength)
-        {
-            pLineBidi = ubidi_openSized( nSubLength, 0, &rcI18n );
-            ubidi_setLine( pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n );
-        }
-
-        // run BiDi algorithm
-        const int nRunCount = ubidi_countRuns( pLineBidi, &rcI18n );
-        //maRuns.resize( 2 * nRunCount );
-        for( int i = 0; i < nRunCount; ++i )
-        {
-            int32_t nMinPos, nRunLength;
-            const UBiDiDirection nDir = ubidi_getVisualRun( pLineBidi, i, &nMinPos, &nRunLength );
-            const int nPos0 = nMinPos + mnMinCharPos;
-            const int nPos1 = nPos0 + nRunLength;
-
-            const bool bRTL = (nDir == UBIDI_RTL);
-            AddRun( nPos0, nPos1, bRTL );
-        }
-
-        // cleanup BiDi engine
-        if( pLineBidi != pParaBidi )
-            ubidi_close( pLineBidi );
-        ubidi_close( pParaBidi );
-    }
-
-    // prepare calls to GetNextPos/GetNextRun
-    maRuns.ResetPos();
-}
-
-// add a run after splitting it up to get rid of control chars
-void ImplLayoutArgs::AddRun( int nCharPos0, int nCharPos1, bool bRTL )
-{
-    SAL_WARN_IF( nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1" );
-
-    // remove control characters from runs by splitting them up
-    if( !bRTL )
-    {
-        for( int i = nCharPos0; i < nCharPos1; ++i )
-            if( IsControlChar( mrStr[i] ) )
-            {
-                // add run until control char
-                maRuns.AddRun( nCharPos0, i, bRTL );
-                nCharPos0 = i + 1;
-            }
-    }
-    else
-    {
-        for( int i = nCharPos1; --i >= nCharPos0; )
-            if( IsControlChar( mrStr[i] ) )
-            {
-                // add run until control char
-                maRuns.AddRun( i+1, nCharPos1, bRTL );
-                nCharPos1 = i;
-            }
-    }
-
-    // add remainder of run
-    maRuns.AddRun( nCharPos0, nCharPos1, bRTL );
-}
-
-bool ImplLayoutArgs::PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl)
-{
-    // Generate runs with pre-calculated glyph items instead maFallbackRuns.
-    if( pGlyphsImpl != nullptr )
-    {
-        maRuns.Clear();
-        maFallbackRuns.Clear();
-
-        for (auto const& aGlyphItem : *pGlyphsImpl)
-        {
-            for(int i = aGlyphItem.charPos(); i < aGlyphItem.charPos() + aGlyphItem.charCount(); ++i)
-                maRuns.AddPos(i, aGlyphItem.IsRTLGlyph());
-        }
-
-        return !maRuns.IsEmpty();
-    }
-
-    // short circuit if no fallback is needed
-    if( maFallbackRuns.IsEmpty() )
-    {
-        maRuns.Clear();
-        return false;
-    }
-
-    // convert the fallback requests to layout requests
-    bool bRTL;
-    int nMin, nEnd;
-
-    // get the individual fallback requests
-    std::vector<int> aPosVector;
-    aPosVector.reserve(mrStr.getLength());
-    maFallbackRuns.ResetPos();
-    for(; maFallbackRuns.GetRun( &nMin, &nEnd, &bRTL ); maFallbackRuns.NextRun() )
-        for( int i = nMin; i < nEnd; ++i )
-            aPosVector.push_back( i );
-    maFallbackRuns.Clear();
-
-    // sort the individual fallback requests
-    std::sort( aPosVector.begin(), aPosVector.end() );
-
-    // adjust fallback runs to have the same order and limits of the original runs
-    ImplLayoutRuns aNewRuns;
-    maRuns.ResetPos();
-    for(; maRuns.GetRun( &nMin, &nEnd, &bRTL ); maRuns.NextRun() )
-    {
-        if( !bRTL) {
-            auto it = std::lower_bound( aPosVector.begin(), aPosVector.end(), nMin );
-            for(; (it != aPosVector.end()) && (*it < nEnd); ++it )
-                aNewRuns.AddPos( *it, bRTL );
-        } else {
-            auto it = std::upper_bound( aPosVector.begin(), aPosVector.end(), nEnd );
-            while( (it != aPosVector.begin()) && (*--it >= nMin) )
-                aNewRuns.AddPos( *it, bRTL );
-        }
-    }
-
-    maRuns = aNewRuns;  // TODO: use vector<>::swap()
-    maRuns.ResetPos();
-    return true;
-}
-
-bool ImplLayoutArgs::GetNextRun( int* nMinRunPos, int* nEndRunPos, bool* bRTL )
-{
-    bool bValid = maRuns.GetRun( nMinRunPos, nEndRunPos, bRTL );
-    maRuns.NextRun();
-    return bValid;
-}
-
 SalLayout::SalLayout()
 :   mnMinCharPos( -1 ),
     mnEndCharPos( -1 ),
@@ -565,7 +297,7 @@ SalLayout::SalLayout()
 SalLayout::~SalLayout()
 {}
 
-void SalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
 {
     mnMinCharPos  = rArgs.mnMinCharPos;
     mnEndCharPos  = rArgs.mnEndCharPos;
@@ -1047,7 +779,7 @@ void MultiSalLayout::AddFallback( std::unique_ptr<SalLayout> pFallback,
     ++mnLevel;
 }
 
-bool MultiSalLayout::LayoutText( ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* )
+bool MultiSalLayout::LayoutText( vcl::text::ImplLayoutArgs& rArgs, const SalLayoutGlyphsImpl* )
 {
     if( mnLevel <= 1 )
         return false;
@@ -1056,10 +788,10 @@ bool MultiSalLayout::LayoutText( ImplLayoutArgs& rArgs, const SalLayoutGlyphsImp
     return true;
 }
 
-void MultiSalLayout::AdjustLayout( ImplLayoutArgs& rArgs )
+void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
 {
     SalLayout::AdjustLayout( rArgs );
-    ImplLayoutArgs aMultiArgs = rArgs;
+    vcl::text::ImplLayoutArgs aMultiArgs = rArgs;
     std::vector<DeviceCoordinate> aJustificationArray;
 
     if( !rArgs.mpDXArray && rArgs.mnLayoutWidth )
diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx
index 1875394eaf93..06c339dd795c 100644
--- a/vcl/source/outdev/font.cxx
+++ b/vcl/source/outdev/font.cxx
@@ -35,6 +35,7 @@
 #include <outdev.h>
 #include <window.h>
 
+#include <ImplLayoutArgs.hxx>
 #include <PhysicalFontCollection.hxx>
 #include <drawmode.hxx>
 #include <font/FeatureCollector.hxx>
@@ -1253,7 +1254,7 @@ void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout )
 
 std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout(
     LogicalFontInstance* pLogicalFont, int nFallbackLevel,
-    ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs) const
+    vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs) const
 {
     // we need a graphics
     if (!mpGraphics && !AcquireGraphics())
@@ -1278,7 +1279,7 @@ std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout(
 }
 
 std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout,
-    ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs ) const
+    vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs ) const
 {
     // This function relies on a valid mpFontInstance, if it doesn't exist bail out
     // - we'd have crashed later on anyway. At least here we can catch the error in debug
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
index 7d5fe5256353..801615401fcd 100644
--- a/vcl/source/outdev/text.cxx
+++ b/vcl/source/outdev/text.cxx
@@ -41,6 +41,7 @@
 
 #include <config_fuzzers.h>
 #include <outdev.h>
+#include <ImplLayoutArgs.hxx>
 #include <drawmode.hxx>
 #include <salgdi.hxx>
 #include <svdata.hxx>
@@ -1095,7 +1096,7 @@ void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong nWidth,
         mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
 }
 
-ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
+vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
                                                     const sal_Int32 nMinIndex, const sal_Int32 nLen,
                                                     DeviceCoordinate nPixelWidth, const DeviceCoordinate* pDXArray,
                                                     SalLayoutFlags nLayoutFlags,
@@ -1184,7 +1185,7 @@ ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
         nLayoutFlags |= SalLayoutFlags::RightAlign;
 
     // set layout options
-    ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
+    vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
 
     Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10;
     aLayoutArgs.SetOrientation( nOrientation );
@@ -1267,7 +1268,7 @@ std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
         }
     }
 
-    ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
+    vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
             nPixelWidth, pDXPixelArray, flags, pLayoutCache);
 
     // get matching layout object for base font
@@ -1320,7 +1321,7 @@ std::shared_ptr<vcl::text::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
 bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
 {
     OUString aStr( rString );
-    ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, nullptr );
+    vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, nullptr );
     bool bRTL = false;
     int nCharPos = -1;
     if (!aArgs.GetNextPos(&nCharPos, &bRTL))
diff --git a/vcl/source/text/ImplLayoutArgs.cxx b/vcl/source/text/ImplLayoutArgs.cxx
new file mode 100644
index 000000000000..7957d1ace824
--- /dev/null
+++ b/vcl/source/text/ImplLayoutArgs.cxx
@@ -0,0 +1,339 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <ImplLayoutArgs.hxx>
+
+#include <unicode/ubidi.h>
+#include <unicode/uchar.h>
+
+#include <algorithm>
+#include <memory>
+
+namespace vcl::text
+{
+ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCharPos,
+                               SalLayoutFlags nFlags, const LanguageTag& rLanguageTag,
+                               vcl::text::TextLayoutCache const* const pLayoutCache)
+    : maLanguageTag(rLanguageTag)
+    , mnFlags(nFlags)
+    , mrStr(rStr)
+    , mnMinCharPos(nMinCharPos)
+    , mnEndCharPos(nEndCharPos)
+    , m_pTextLayoutCache(pLayoutCache)
+    , mpDXArray(nullptr)
+    , mnLayoutWidth(0)
+    , mnOrientation(0)
+{
+    if (mnFlags & SalLayoutFlags::BiDiStrong)
+    {
+        // handle strong BiDi mode
+
+        // do not bother to BiDi analyze strong LTR/RTL
+        // TODO: can we assume these strings do not have unicode control chars?
+        //       if not remove the control characters from the runs
+        bool bRTL(mnFlags & SalLayoutFlags::BiDiRtl);
+        AddRun(mnMinCharPos, mnEndCharPos, bRTL);
+    }
+    else
+    {
+        // handle weak BiDi mode
+        UBiDiLevel nLevel = (mnFlags & SalLayoutFlags::BiDiRtl) ? 1 : 0;
+
+        // prepare substring for BiDi analysis
+        // TODO: reuse allocated pParaBidi
+        UErrorCode rcI18n = U_ZERO_ERROR;
+        const int nLength = mrStr.getLength();
+        UBiDi* pParaBidi = ubidi_openSized(nLength, 0, &rcI18n);
+        if (!pParaBidi)
+            return;
+        ubidi_setPara(pParaBidi, reinterpret_cast<const UChar*>(mrStr.getStr()), nLength, nLevel,
+                      nullptr, &rcI18n);
+
+        UBiDi* pLineBidi = pParaBidi;
+        int nSubLength = mnEndCharPos - mnMinCharPos;
+        if (nSubLength != nLength)
+        {
+            pLineBidi = ubidi_openSized(nSubLength, 0, &rcI18n);
+            ubidi_setLine(pParaBidi, mnMinCharPos, mnEndCharPos, pLineBidi, &rcI18n);
+        }
+
+        // run BiDi algorithm
+        const int nRunCount = ubidi_countRuns(pLineBidi, &rcI18n);
+        //maRuns.resize( 2 * nRunCount );
+        for (int i = 0; i < nRunCount; ++i)
+        {
+            int32_t nMinPos, nRunLength;
+            const UBiDiDirection nDir = ubidi_getVisualRun(pLineBidi, i, &nMinPos, &nRunLength);
+            const int nPos0 = nMinPos + mnMinCharPos;
+            const int nPos1 = nPos0 + nRunLength;
+
+            const bool bRTL = (nDir == UBIDI_RTL);
+            AddRun(nPos0, nPos1, bRTL);
+        }
+
+        // cleanup BiDi engine
+        if (pLineBidi != pParaBidi)
+            ubidi_close(pLineBidi);
+        ubidi_close(pParaBidi);
+    }
+
+    // prepare calls to GetNextPos/GetNextRun
+    maRuns.ResetPos();
+}
+
+void ImplLayoutArgs::SetLayoutWidth(DeviceCoordinate nWidth) { mnLayoutWidth = nWidth; }
+
+void ImplLayoutArgs::SetDXArray(DeviceCoordinate const* pDXArray) { mpDXArray = pDXArray; }
+
+void ImplLayoutArgs::SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }
+
+void ImplLayoutArgs::ResetPos() { maRuns.ResetPos(); }
+
+bool ImplLayoutArgs::GetNextPos(int* nCharPos, bool* bRTL)
+{
+    return maRuns.GetNextPos(nCharPos, bRTL);
+}
+
+void ImplLayoutArgs::AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
+{
+    maFallbackRuns.AddRun(nMinRunPos, nEndRunPos, bRTL);
+}
+
+bool ImplLayoutArgs::HasFallbackRun() const { return !maFallbackRuns.IsEmpty(); }
+
+static bool IsControlChar(sal_UCS4 cChar)
+{
+    // C0 control characters
+    if ((0x0001 <= cChar) && (cChar <= 0x001F))
+        return true;
+    // formatting characters
+    if ((0x200E <= cChar) && (cChar <= 0x200F))
+        return true;
+    if ((0x2028 <= cChar) && (cChar <= 0x202E))
+        return true;
+    // deprecated formatting characters
+    if ((0x206A <= cChar) && (cChar <= 0x206F))
+        return true;
+    if (0x2060 == cChar)
+        return true;
+    // byte order markers and invalid unicode
+    if ((cChar == 0xFEFF) || (cChar == 0xFFFE) || (cChar == 0xFFFF))
+        return true;
+    return false;
+}
+
+// add a run after splitting it up to get rid of control chars
+void ImplLayoutArgs::AddRun(int nCharPos0, int nCharPos1, bool bRTL)
+{
+    SAL_WARN_IF(nCharPos0 > nCharPos1, "vcl", "ImplLayoutArgs::AddRun() nCharPos0>=nCharPos1");
+
+    // remove control characters from runs by splitting them up
+    if (!bRTL)
+    {
+        for (int i = nCharPos0; i < nCharPos1; ++i)
+            if (IsControlChar(mrStr[i]))
+            {
+                // add run until control char
+                maRuns.AddRun(nCharPos0, i, bRTL);
+                nCharPos0 = i + 1;
+            }
+    }
+    else
+    {
+        for (int i = nCharPos1; --i >= nCharPos0;)
+            if (IsControlChar(mrStr[i]))
+            {
+                // add run until control char
+                maRuns.AddRun(i + 1, nCharPos1, bRTL);
+                nCharPos1 = i;
+            }
+    }
+
+    // add remainder of run
+    maRuns.AddRun(nCharPos0, nCharPos1, bRTL);
+}
+
+bool ImplLayoutArgs::PrepareFallback(const SalLayoutGlyphsImpl* pGlyphsImpl)
+{
+    // Generate runs with pre-calculated glyph items instead maFallbackRuns.
+    if (pGlyphsImpl != nullptr)
+    {
+        maRuns.Clear();
+        maFallbackRuns.Clear();
+
+        for (auto const& aGlyphItem : *pGlyphsImpl)
+        {
+            for (int i = aGlyphItem.charPos(); i < aGlyphItem.charPos() + aGlyphItem.charCount();
+                 ++i)
+                maRuns.AddPos(i, aGlyphItem.IsRTLGlyph());
+        }
+
+        return !maRuns.IsEmpty();
+    }
+
+    // short circuit if no fallback is needed
+    if (maFallbackRuns.IsEmpty())
+    {
+        maRuns.Clear();
+        return false;
+    }
+
+    // convert the fallback requests to layout requests
+    bool bRTL;
+    int nMin, nEnd;
+
+    // get the individual fallback requests
+    std::vector<int> aPosVector;
+    aPosVector.reserve(mrStr.getLength());
+    maFallbackRuns.ResetPos();
+    for (; maFallbackRuns.GetRun(&nMin, &nEnd, &bRTL); maFallbackRuns.NextRun())
+        for (int i = nMin; i < nEnd; ++i)
+            aPosVector.push_back(i);
+    maFallbackRuns.Clear();
+
+    // sort the individual fallback requests
+    std::sort(aPosVector.begin(), aPosVector.end());
+
+    // adjust fallback runs to have the same order and limits of the original runs
+    ImplLayoutRuns aNewRuns;
+    maRuns.ResetPos();
+    for (; maRuns.GetRun(&nMin, &nEnd, &bRTL); maRuns.NextRun())
+    {
+        if (!bRTL)
+        {
+            auto it = std::lower_bound(aPosVector.begin(), aPosVector.end(), nMin);
+            for (; (it != aPosVector.end()) && (*it < nEnd); ++it)
+                aNewRuns.AddPos(*it, bRTL);
+        }
+        else
+        {
+            auto it = std::upper_bound(aPosVector.begin(), aPosVector.end(), nEnd);
+            while ((it != aPosVector.begin()) && (*--it >= nMin))
+                aNewRuns.AddPos(*it, bRTL);
+        }
+    }
+
+    maRuns = aNewRuns; // TODO: use vector<>::swap()
+    maRuns.ResetPos();
+    return true;
+}
+
+bool ImplLayoutArgs::GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL)
+{
+    bool bValid = maRuns.GetRun(nMinRunPos, nEndRunPos, bRTL);
+    maRuns.NextRun();
+    return bValid;
+}
+}
+
+std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs)
+{
+#ifndef SAL_LOG_INFO
+    (void)rArgs;
+#else
+    s << "ImplLayoutArgs{";
+
+    s << "Flags=";
+    if (rArgs.mnFlags == SalLayoutFlags::NONE)
+        s << 0;
+    else
+    {
+        bool need_or = false;
+        s << "{";
+#define TEST(x)                                                                                    \
+    if (rArgs.mnFlags & SalLayoutFlags::x)                                                         \
+    {                                                                                              \
+        if (need_or)                                                                               \
+            s << "|";                                                                              \
+        s << #x;                                                                                   \
+        need_or = true;                                                                            \
+    }
+        TEST(BiDiRtl);
+        TEST(BiDiStrong);
+        TEST(RightAlign);
+        TEST(DisableKerning);
+        TEST(KerningAsian);
+        TEST(Vertical);
+        TEST(KashidaJustification);
+        TEST(ForFallback);
+#undef TEST
+        s << "}";
+    }
+
+    const int nLength = rArgs.mrStr.getLength();
+
+    s << ",Length=" << nLength;
+    s << ",MinCharPos=" << rArgs.mnMinCharPos;
+    s << ",EndCharPos=" << rArgs.mnEndCharPos;
+
+    s << ",Str=\"";
+    int lim = nLength;
+    if (lim > 10)
+        lim = 7;
+    for (int i = 0; i < lim; i++)
+    {
+        if (rArgs.mrStr[i] == '\n')
+            s << "\\n";
+        else if (rArgs.mrStr[i] < ' ' || (rArgs.mrStr[i] >= 0x7F && rArgs.mrStr[i] <= 0xFF))
+            s << "\\0x" << std::hex << std::setw(2) << std::setfill('0')
+              << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+        else if (rArgs.mrStr[i] < 0x7F)
+            s << static_cast<char>(rArgs.mrStr[i]);
+        else
+            s << "\\u" << std::hex << std::setw(4) << std::setfill('0')
+              << static_cast<int>(rArgs.mrStr[i]) << std::setfill(' ') << std::setw(1) << std::dec;
+    }
+    if (nLength > lim)
+        s << "...";
+    s << "\"";
+
+    s << ",DXArray=";
+    if (rArgs.mpDXArray)
+    {
+        s << "[";
+        int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
+        lim = count;
+        if (lim > 10)
+            lim = 7;
+        for (int i = 0; i < lim; i++)
+        {
+            s << rArgs.mpDXArray[i];
+            if (i < lim - 1)
+                s << ",";
+        }
+        if (count > lim)
+        {
+            if (count > lim + 1)
+                s << "...";
+            s << rArgs.mpDXArray[count - 1];
+        }
+        s << "]";
+    }
+    else
+        s << "NULL";
+
+    s << ",LayoutWidth=" << rArgs.mnLayoutWidth;
+
+    s << "}";
+
+#endif
+    return s;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list