diff options
author | Jonathan Clark <jonathan@libreoffice.org> | 2024-09-25 16:07:06 -0600 |
---|---|---|
committer | Jonathan Clark <jonathan@libreoffice.org> | 2024-09-26 22:44:03 +0200 |
commit | d8f430e4bef414616fd80bbf4ea16d767991b5b9 (patch) | |
tree | c12ef8a2dbb0cf69c66e1166e5c1bfaf04575d56 /i18nutil | |
parent | 348efa75b992fe863bf85b39e5d0e71eb7ba8aba (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.cxx | 38 | ||||
-rw-r--r-- | i18nutil/source/utility/kashida.cxx | 62 |
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)) |