summaryrefslogtreecommitdiff
path: root/i18nutil
diff options
context:
space:
mode:
authorJonathan Clark <jonathan@libreoffice.org>2024-09-25 16:07:06 -0600
committerJonathan Clark <jonathan@libreoffice.org>2024-09-26 22:44:03 +0200
commitd8f430e4bef414616fd80bbf4ea16d767991b5b9 (patch)
treec12ef8a2dbb0cf69c66e1166e5c1bfaf04575d56 /i18nutil
parent348efa75b992fe863bf85b39e5d0e71eb7ba8aba (diff)
tdf#163105 Use HB data while selecting kashida insertion positions
Previously, Writer and Edit Engine would skip inserting kashida in words if the highest-priority candidate position is marked as invalid by HarfBuzz. This would happen even if the word contained multiple lower-ranked valid candidate positions. This change updates Writer and Edit Engine to pass HarfBuzz kashida position data to the selection algorithm. The algorithm has been updated to return the highest-priority valid position, if any. The algorithm has also been updated to use raw positions marked as valid by HarfBuzz as a fallback, if no better positions could be found. Change-Id: I40c6432c4607aee197e8767e5667db504469956a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173963 Tested-by: Jenkins Reviewed-by: Jonathan Clark <jonathan@libreoffice.org>
Diffstat (limited to 'i18nutil')
-rw-r--r--i18nutil/qa/cppunit/test_kashida.cxx38
-rw-r--r--i18nutil/source/utility/kashida.cxx62
2 files changed, 78 insertions, 22 deletions
diff --git a/i18nutil/qa/cppunit/test_kashida.cxx b/i18nutil/qa/cppunit/test_kashida.cxx
index 1ab2729cb06a..46b40c2a5b7a 100644
--- a/i18nutil/qa/cppunit/test_kashida.cxx
+++ b/i18nutil/qa/cppunit/test_kashida.cxx
@@ -22,13 +22,17 @@ class KashidaTest : public CppUnit::TestFixture
{
public:
void testCharacteristic();
+ void testManualKashida();
void testFinalYeh();
void testNoZwnjExpansion();
+ void testExcludeInvalid();
CPPUNIT_TEST_SUITE(KashidaTest);
CPPUNIT_TEST(testCharacteristic);
+ CPPUNIT_TEST(testManualKashida);
CPPUNIT_TEST(testFinalYeh);
CPPUNIT_TEST(testNoZwnjExpansion);
+ CPPUNIT_TEST(testExcludeInvalid);
CPPUNIT_TEST_SUITE_END();
};
@@ -54,6 +58,14 @@ void KashidaTest::testCharacteristic()
CPPUNIT_ASSERT_EQUAL(sal_Int32(3), GetWordKashidaPosition(u"تمثیل"_ustr).value().nIndex);
}
+void KashidaTest::testManualKashida()
+{
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), GetWordKashidaPosition(u"برـای"_ustr).value().nIndex);
+
+ // Normally, a kashida would not be inserted after a final Yeh.
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(4), GetWordKashidaPosition(u"نیمِـي"_ustr).value().nIndex);
+}
+
// tdf#65344: Do not insert kashida before a final Yeh
void KashidaTest::testFinalYeh()
{
@@ -73,6 +85,32 @@ void KashidaTest::testNoZwnjExpansion()
CPPUNIT_ASSERT(!GetWordKashidaPosition(u"مت\u200Cن"_ustr).has_value());
}
+// tdf#163105: Do not insert kashida if the position is invalid
+void KashidaTest::testExcludeInvalid()
+{
+ std::vector<bool> aValid;
+ aValid.resize(5, true);
+
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(3),
+ GetWordKashidaPosition(u"نویسه"_ustr, aValid).value().nIndex);
+
+ aValid[3] = false;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(0),
+ GetWordKashidaPosition(u"نویسه"_ustr, aValid).value().nIndex);
+
+ // Calls after this use the last resort (positions in aValid from end to start)
+ aValid[0] = false;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2),
+ GetWordKashidaPosition(u"نویسه"_ustr, aValid).value().nIndex);
+
+ aValid[2] = false;
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(1),
+ GetWordKashidaPosition(u"نویسه"_ustr, aValid).value().nIndex);
+
+ aValid[1] = false;
+ CPPUNIT_ASSERT(!GetWordKashidaPosition(u"نویسه"_ustr, aValid).has_value());
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION(KashidaTest);
}
diff --git a/i18nutil/source/utility/kashida.cxx b/i18nutil/source/utility/kashida.cxx
index d016e96294fb..6a6c7adde690 100644
--- a/i18nutil/source/utility/kashida.cxx
+++ b/i18nutil/source/utility/kashida.cxx
@@ -46,6 +46,7 @@ namespace
- tdf#65344: Kashida must not be inserted before the final form of Yeh, unless
preceded by an initial or medial Seen.
+ - tdf#163105: As a last resort, use the last valid insertion position from VCL.
*/
#define IS_JOINING_GROUP(c, g) (u_getIntPropertyValue((c), UCHAR_JOINING_GROUP) == U_JG_##g)
@@ -134,7 +135,8 @@ bool CanConnectToPrev(sal_Unicode cCh, sal_Unicode cPrevCh)
}
}
-std::optional<i18nutil::KashidaPosition> i18nutil::GetWordKashidaPosition(const OUString& rWord)
+std::optional<i18nutil::KashidaPosition>
+i18nutil::GetWordKashidaPosition(const OUString& rWord, const std::vector<bool>& pValidPositions)
{
sal_Int32 nIdx = 0;
sal_Int32 nPrevIdx = 0;
@@ -142,35 +144,45 @@ std::optional<i18nutil::KashidaPosition> i18nutil::GetWordKashidaPosition(const
sal_Unicode cCh = 0;
sal_Unicode cPrevCh = 0;
- int nPriorityLevel = 7; // 0..6 = level found, 7 not found
+ int nPriorityLevel = 8; // 0..7 = level found, 8 not found
sal_Int32 nWordLen = rWord.getLength();
+ SAL_WARN_IF(!pValidPositions.empty() && pValidPositions.size() != static_cast<size_t>(nWordLen),
+ "i18n", "Kashida valid position array wrong size");
+
// ignore trailing vowel chars
while (nWordLen && isTransparentChar(rWord[nWordLen - 1]))
{
--nWordLen;
}
- auto fnTryInsertBefore = [&rWord, &nIdx, &nPrevIdx, &nKashidaPos, &nPriorityLevel,
- &nWordLen](sal_Int32 nNewPriority, bool bIgnoreFinalYeh = false) {
- // Exclusions:
-
- // #i98410#: prevent ZWNJ expansion
- if (rWord[nPrevIdx] == 0x200C || rWord[nPrevIdx + 1] == 0x200C)
- {
- return;
- }
-
- // tdf#65344: Do not insert kashida before a final Yeh
- if (!bIgnoreFinalYeh && nIdx == (nWordLen - 1) && isYehChar(rWord[nIdx]))
- {
- return;
- }
-
- nKashidaPos = nPrevIdx;
- nPriorityLevel = nNewPriority;
- };
+ auto fnTryInsertBefore
+ = [&rWord, &nIdx, &nPrevIdx, &nKashidaPos, &nPriorityLevel, &nWordLen,
+ &pValidPositions](sal_Int32 nNewPriority, bool bIgnoreFinalYeh = false) {
+ // Exclusions:
+
+ // tdf#163105: Do not insert kashida if the position is invalid
+ if (!pValidPositions.empty() && !pValidPositions[nPrevIdx])
+ {
+ return;
+ }
+
+ // #i98410#: prevent ZWNJ expansion
+ if (rWord[nPrevIdx] == 0x200C || rWord[nPrevIdx + 1] == 0x200C)
+ {
+ return;
+ }
+
+ // tdf#65344: Do not insert kashida before a final Yeh
+ if (!bIgnoreFinalYeh && nIdx == (nWordLen - 1) && isYehChar(rWord[nIdx]))
+ {
+ return;
+ }
+
+ nKashidaPos = nPrevIdx;
+ nPriorityLevel = nNewPriority;
+ };
while (nIdx < nWordLen)
{
@@ -270,7 +282,7 @@ std::optional<i18nutil::KashidaPosition> i18nutil::GetWordKashidaPosition(const
}
}
- // other connecting possibilities
+ // 7. Other connecting possibilities
if (nPriorityLevel >= 6 && nIdx > 0)
{
// Reh, Zain (right joining) final form may appear in the middle of word
@@ -286,6 +298,12 @@ std::optional<i18nutil::KashidaPosition> i18nutil::GetWordKashidaPosition(const
}
}
+ // 8. If valid position data exists, use the last legal position
+ if (nPriorityLevel >= 7 && nIdx > 0 && !pValidPositions.empty())
+ {
+ fnTryInsertBefore(7);
+ }
+
// Do not consider vowel marks when checking if a character
// can be connected to previous character.
if (!isTransparentChar(cCh))