i18nutil/source/utility/kashida.cxx          |    7 ++-
 include/vcl/rendercontext/SalLayoutFlags.hxx |    3 +
 include/vcl/vcllayout.hxx                    |    2 +
 vcl/inc/sallayout.hxx                        |    3 +
 vcl/qa/cppunit/complextext.cxx               |   51 +++++++++++++++++++++++++++
 vcl/source/gdi/CommonSalLayout.cxx           |   15 +++++++
 vcl/source/gdi/sallayout.cxx                 |   15 +++++++
 vcl/source/outdev/font.cxx                   |    4 ++
 8 files changed, 96 insertions(+), 4 deletions(-)

New commits:
commit 224fae69b224d28a1664c48117e77265ed67a136
Author:     Jonathan Clark <jonat...@libreoffice.org>
AuthorDate: Fri Oct 18 06:12:39 2024 -0600
Commit:     Jonathan Clark <jonat...@libreoffice.org>
CommitDate: Fri Oct 18 21:49:03 2024 +0200

    tdf#163215: Enable kashida justification for AAT fonts
    
    Currently, we use HarfBuzz-provided kashida insertion position
    information to decide on positions to insert kashida. This data is used
    both while ranking kashida insertion positions, and to avoid inserting
    kashida in positions that would break shaping on a per-font basis.
    
    Unfortunately, HarfBuzz cannot validate kashida insertion positions for
    AAT fonts. As a result, kashida were previously not inserted for text
    using AAT fonts.
    
    This change updates kashida justification to skip validation against
    HarfBuzz when AAT fonts are used.
    
    Change-Id: If0d31512b1db0f1f8155963f9b1031eb01bacc45
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175165
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <jonat...@libreoffice.org>

diff --git a/i18nutil/source/utility/kashida.cxx 
b/i18nutil/source/utility/kashida.cxx
index aff667033a11..5181f099d92c 100644
--- a/i18nutil/source/utility/kashida.cxx
+++ b/i18nutil/source/utility/kashida.cxx
@@ -136,10 +136,13 @@ bool CanConnectToPrev(sal_Unicode cCh, sal_Unicode 
cPrevCh)
 
 bool isSyriacChar(sal_Unicode cCh)
 {
-    return (cCh >= 0x700 && cCh <= 0x74F) || (cCh >= 0x860 && cCh <= 0x86A);
+    return u_getIntPropertyValue(cCh, UCHAR_SCRIPT) == USCRIPT_SYRIAC;
 }
 
-bool isArabicChar(sal_Unicode cCh) { return cCh >= 0x60C && cCh <= 0x6FE; }
+bool isArabicChar(sal_Unicode cCh)
+{
+    return u_getIntPropertyValue(cCh, UCHAR_SCRIPT) == USCRIPT_ARABIC;
+}
 
 std::optional<i18nutil::KashidaPosition>
 GetWordKashidaPositionArabic(const OUString& rWord, const std::vector<bool>& 
pValidPositions)
diff --git a/include/vcl/rendercontext/SalLayoutFlags.hxx 
b/include/vcl/rendercontext/SalLayoutFlags.hxx
index 3424fa2a1de4..be7e493e49b8 100644
--- a/include/vcl/rendercontext/SalLayoutFlags.hxx
+++ b/include/vcl/rendercontext/SalLayoutFlags.hxx
@@ -31,13 +31,14 @@ enum class SalLayoutFlags
     KerningAsian = 0x0020,
     Vertical = 0x0040,
     DisableLigatures = 0x0200,
+    DisableKashidaValidation = 0x0400,
     ForFallback = 0x2000,
     GlyphItemsOnly = 0x4000,
     UnclusteredGlyphs = 0x8000,
 };
 namespace o3tl
 {
-template <> struct typed_flags<SalLayoutFlags> : 
is_typed_flags<SalLayoutFlags, 0xE277>
+template <> struct typed_flags<SalLayoutFlags> : 
is_typed_flags<SalLayoutFlags, 0xE677>
 {
 };
 }
diff --git a/include/vcl/vcllayout.hxx b/include/vcl/vcllayout.hxx
index b36341ccc7a7..e6b743f626d5 100644
--- a/include/vcl/vcllayout.hxx
+++ b/include/vcl/vcllayout.hxx
@@ -107,6 +107,8 @@ public:
     }
 
     virtual void    GetCaretPositions( std::vector<double>& rCaretPositions, 
const OUString& rStr ) const = 0;
+
+    virtual bool HasFontKashidaPositions() const = 0;
     virtual bool    IsKashidaPosValid ( int /*nCharPos*/, int /*nNextCharPos*/ 
) const = 0; // i60594
 
     // methods using glyph indexing
diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx
index ed539f058402..a8c0de8438c2 100644
--- a/vcl/inc/sallayout.hxx
+++ b/vcl/inc/sallayout.hxx
@@ -69,6 +69,7 @@ public:
     SAL_DLLPRIVATE bool            GetNextGlyph(const GlyphItem** pGlyph, 
basegfx::B2DPoint& rPos, int& nStart,
                                  const LogicalFontInstance** ppGlyphFont = 
nullptr) const override;
     SAL_DLLPRIVATE bool            GetOutline(basegfx::B2DPolyPolygonVector&) 
const override;
+    SAL_DLLPRIVATE bool HasFontKashidaPositions() const override;
     SAL_DLLPRIVATE bool            IsKashidaPosValid(int nCharPos, int 
nNextCharPos) const override;
     SAL_DLLPRIVATE SalLayoutGlyphs GetGlyphs() const final override;
 
@@ -117,6 +118,7 @@ public:
     void            DrawText(SalGraphics&) const final override;
     SalLayoutGlyphs GetGlyphs() const final override;
 
+    bool HasFontKashidaPositions() const final override;
     bool            IsKashidaPosValid(int nCharPos, int nNextCharPos) const 
final override;
 
     // used by upper layers
@@ -171,6 +173,7 @@ private:
 
     hb_set_t*       mpVertGlyphs;
     const bool      mbFuzzing;
+    bool m_bHasFontKashidaPositions = false;
 };
 
 #endif // INCLUDED_VCL_INC_SALLAYOUT_HXX
diff --git a/vcl/qa/cppunit/complextext.cxx b/vcl/qa/cppunit/complextext.cxx
index 62b8ac2777dc..a3d033e7adba 100644
--- a/vcl/qa/cppunit/complextext.cxx
+++ b/vcl/qa/cppunit/complextext.cxx
@@ -697,4 +697,55 @@ CPPUNIT_TEST_FIXTURE(VclComplexTextTest, 
testPartialArabicComposition)
     CPPUNIT_ASSERT_DOUBLES_EQUAL(nCompleteWidth, nPartialWidth, /*delta*/ 
0.01);
 }
 
+CPPUNIT_TEST_FIXTURE(VclComplexTextTest, testTdf163215)
+{
+    OUString aStr = u"ببببب"_ustr;
+    vcl::Font aFont(u"DejaVu Sans"_ustr, u"Book"_ustr, Size(0, 2048));
+
+    ScopedVclPtrInstance<VirtualDevice> pOutDev;
+    pOutDev->SetFont(aFont);
+
+    // Characteristic case with kashida position validation
+    auto pLayout1 = pOutDev->ImplLayout(aStr, 0, aStr.getLength(), Point(), 0, 
{}, {},
+                                        SalLayoutFlags::GlyphItemsOnly);
+    CPPUNIT_ASSERT(pLayout1->HasFontKashidaPositions());
+
+    SalLayoutGlyphs aGlyphs1 = pLayout1->GetGlyphs();
+
+    std::vector<bool> aFoundPositions1;
+    for (const auto& stGlyph : *aGlyphs1.Impl(0))
+    {
+        aFoundPositions1.push_back(stGlyph.IsSafeToInsertKashida());
+    }
+
+    CPPUNIT_ASSERT_EQUAL(size_t(5), aFoundPositions1.size());
+    CPPUNIT_ASSERT(aFoundPositions1.at(0));
+    CPPUNIT_ASSERT(aFoundPositions1.at(1));
+    CPPUNIT_ASSERT(aFoundPositions1.at(2));
+    CPPUNIT_ASSERT(aFoundPositions1.at(3));
+    CPPUNIT_ASSERT(!aFoundPositions1.at(4));
+
+    // Case with kashida position validation disabled
+    auto pLayout2 = pOutDev->ImplLayout(aStr, 0, aStr.getLength(), Point(), 0, 
{}, {},
+                                        SalLayoutFlags::GlyphItemsOnly
+                                            | 
SalLayoutFlags::DisableKashidaValidation);
+    CPPUNIT_ASSERT(!pLayout2->HasFontKashidaPositions());
+
+    SalLayoutGlyphs aGlyphs2 = pLayout2->GetGlyphs();
+
+    std::vector<bool> aFoundPositions2;
+    for (const auto& stGlyph : *aGlyphs2.Impl(0))
+    {
+        aFoundPositions2.push_back(stGlyph.IsSafeToInsertKashida());
+    }
+
+    // With position validation disabled, all positions must be marked as valid
+    CPPUNIT_ASSERT_EQUAL(size_t(5), aFoundPositions2.size());
+    CPPUNIT_ASSERT(aFoundPositions2.at(0));
+    CPPUNIT_ASSERT(aFoundPositions2.at(1));
+    CPPUNIT_ASSERT(aFoundPositions2.at(2));
+    CPPUNIT_ASSERT(aFoundPositions2.at(3));
+    CPPUNIT_ASSERT(aFoundPositions2.at(4));
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/CommonSalLayout.cxx 
b/vcl/source/gdi/CommonSalLayout.cxx
index a932dd54b16f..26347233d65f 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -40,6 +40,7 @@
 #include <hb-ot.h>
 #include <hb-graphite2.h>
 #include <hb-icu.h>
+#include <hb-aat.h>
 
 #include <map>
 #include <memory>
@@ -376,6 +377,14 @@ bool 
GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
     hb_font_t *pHbFont = GetFont().GetHbFont();
     bool isGraphite = GetFont().IsGraphiteFont();
 
+    // tdf#163215: Identify layouts that don't have strict kashida position 
validation.
+    m_bHasFontKashidaPositions = false;
+    if (!(rArgs.mnFlags & SalLayoutFlags::DisableKashidaValidation))
+    {
+        hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+        m_bHasFontKashidaPositions = !hb_aat_layout_has_substitution(pHbFace);
+    }
+
     int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
     m_GlyphItems.reserve(nGlyphCapacity);
 
@@ -683,7 +692,9 @@ bool 
GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
                 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & 
HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
                     nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
 
-                if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & 
HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
+                if (!m_bHasFontKashidaPositions
+                    || (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i])
+                        & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL))
                     nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
 
                 double nAdvance, nXOffset, nYOffset;
@@ -1010,6 +1021,8 @@ void GenericSalLayout::ApplyJustificationData(const 
JustificationData& rstJustif
     }
 }
 
+bool GenericSalLayout::HasFontKashidaPositions() const { return 
m_bHasFontKashidaPositions; }
+
 // Kashida will be inserted between nCharPos and nNextCharPos.
 bool GenericSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
 {
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
index 4d2d4fe68475..e8dc3999e216 100644
--- a/vcl/source/gdi/sallayout.cxx
+++ b/vcl/source/gdi/sallayout.cxx
@@ -1243,6 +1243,21 @@ bool 
MultiSalLayout::GetOutline(basegfx::B2DPolyPolygonVector& rPPV) const
     return bRet;
 }
 
+bool MultiSalLayout::HasFontKashidaPositions() const
+{
+    // tdf#163215: VCL cannot suggest valid kashida positions for certain 
fonts (e.g. AAT).
+    // In order to strictly validate kashida positions, all fallback fonts 
must allow it.
+    for (int n = 0; n < mnLevel; ++n)
+    {
+        if (!mpLayouts[n]->HasFontKashidaPositions())
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 bool MultiSalLayout::IsKashidaPosValid(int nCharPos, int nNextCharPos) const
 {
     // Check the base layout
diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx
index a2f32327f72c..334f6a311eb0 100644
--- a/vcl/source/outdev/font.cxx
+++ b/vcl/source/outdev/font.cxx
@@ -1227,6 +1227,10 @@ void OutputDevice::GetWordKashidaPositions(const 
OUString& rText,
     if (!pSalLayout)
         return;
 
+    // tdf#163215: VCL cannot suggest valid kashida positions for certain 
fonts (e.g. AAT).
+    if (!pSalLayout->HasFontKashidaPositions())
+        return;
+
     pOutMap->resize(nEnd, false);
     for (sal_Int32 i = 0; i < nEnd; ++i)
     {

Reply via email to