tdf#63929 sw: Show bidi control characters as formatting marks
Updates Writer to visually indicate the following Unicode bidi control characters when "Show Formatting Marks" is enabled: - U+200E LEFT-TO-RIGHT MARK - U+200F RIGHT-TO-LEFT MARK - U+202A LEFT-TO-RIGHT EMBEDDING - U+202B RIGHT-TO-LEFT EMBEDDING - U+202C POP DIRECTIONAL FORMATTING - U+202D LEFT-TO-RIGHT OVERRIDE - U+202E RIGHT-TO-LEFT OVERRIDE Change-Id: I1989eb793f8f89633c067a4bcbf0f1245dc0b9f0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/203132 Reviewed-by: Jonathan Clark <jonathan@libreoffice.org> Tested-by: Christian Lohmaier <lohmaier+LibreOffice@googlemail.com>
This commit is contained in:
@@ -53,6 +53,10 @@ public:
|
||||
void testHintAsian();
|
||||
void testHintComplex();
|
||||
void testHintMultipleRuns();
|
||||
void testBidiMarkRemovesZeroLengthRun();
|
||||
void testBidiMarkPartOfPreviousRun();
|
||||
void testBidiEmbeddingPartOfPreviousRun();
|
||||
void testBidiOverridePartOfPreviousRun();
|
||||
|
||||
CPPUNIT_TEST_SUITE(ScriptChangeScannerTest);
|
||||
CPPUNIT_TEST(testEmpty);
|
||||
@@ -85,6 +89,10 @@ public:
|
||||
CPPUNIT_TEST(testHintAsian);
|
||||
CPPUNIT_TEST(testHintComplex);
|
||||
CPPUNIT_TEST(testHintMultipleRuns);
|
||||
CPPUNIT_TEST(testBidiMarkRemovesZeroLengthRun);
|
||||
CPPUNIT_TEST(testBidiMarkPartOfPreviousRun);
|
||||
CPPUNIT_TEST(testBidiEmbeddingPartOfPreviousRun);
|
||||
CPPUNIT_TEST(testBidiOverridePartOfPreviousRun);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
};
|
||||
|
||||
@@ -659,14 +667,14 @@ void ScriptChangeScannerTest::testRtlRunOverrideCJKAsian()
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(1), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(6), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
@@ -1060,6 +1068,122 @@ void ScriptChangeScannerTest::testHintMultipleRuns()
|
||||
CPPUNIT_ASSERT(pScanner->AtEnd());
|
||||
}
|
||||
|
||||
void ScriptChangeScannerTest::testBidiMarkRemovesZeroLengthRun()
|
||||
{
|
||||
auto aText = u"Before\u200FAfter"_ustr;
|
||||
ScriptHintProvider stHints;
|
||||
auto pDirScanner = MakeDirectionChangeScanner(aText, 0);
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(12), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(pDirScanner->AtEnd());
|
||||
}
|
||||
|
||||
void ScriptChangeScannerTest::testBidiMarkPartOfPreviousRun()
|
||||
{
|
||||
auto aText = u"Before..شز..\u200FAfter"_ustr;
|
||||
ScriptHintProvider stHints;
|
||||
auto pDirScanner = MakeDirectionChangeScanner(aText, 0);
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(8), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(1), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(8), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(13), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(13), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(18), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(pDirScanner->AtEnd());
|
||||
}
|
||||
|
||||
void ScriptChangeScannerTest::testBidiEmbeddingPartOfPreviousRun()
|
||||
{
|
||||
auto aText = u"Before\u202B..ش..\u202CAfter"_ustr;
|
||||
ScriptHintProvider stHints;
|
||||
auto pDirScanner = MakeDirectionChangeScanner(aText, 0);
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(1), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(12), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(12), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(18), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(pDirScanner->AtEnd());
|
||||
}
|
||||
|
||||
void ScriptChangeScannerTest::testBidiOverridePartOfPreviousRun()
|
||||
{
|
||||
auto aText = u"Before\u202EAfter"_ustr;
|
||||
ScriptHintProvider stHints;
|
||||
auto pDirScanner = MakeDirectionChangeScanner(aText, 0);
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(0), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(!pDirScanner->AtEnd());
|
||||
CPPUNIT_ASSERT_EQUAL(sal_uInt8(1), pDirScanner->Peek().m_nLevel);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(7), pDirScanner->Peek().m_nStartIndex);
|
||||
CPPUNIT_ASSERT_EQUAL(sal_Int32(12), pDirScanner->Peek().m_nEndIndex);
|
||||
CPPUNIT_ASSERT(!pDirScanner->Peek().m_bHasEmbeddedStrongLTR);
|
||||
|
||||
pDirScanner->Advance();
|
||||
|
||||
CPPUNIT_ASSERT(pDirScanner->AtEnd());
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(ScriptChangeScannerTest);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ i18nutil::ScriptHintType ScriptHintProvider::Peek() const { return m_eCurrHint;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr sal_uInt32 CHAR_LRM = 0x200e;
|
||||
constexpr sal_uInt32 CHAR_RLM = 0x200f;
|
||||
constexpr sal_uInt32 CHAR_NNBSP = 0x202f;
|
||||
|
||||
class IcuDirectionChangeScanner : public DirectionChangeScanner
|
||||
@@ -102,6 +104,42 @@ private:
|
||||
bHasEmbeddedStrongLTR = RangeHasStrongLTR(m_nCurrIndex, nEndIndex);
|
||||
}
|
||||
|
||||
// UBA treats certain bidi control characters as strongly-directional, but
|
||||
// LO assumes portion-terminal control characters are always at the end of
|
||||
// the terminated portion. Advance the end of the range to include them.
|
||||
if (nEndIndex < m_rText.getLength())
|
||||
{
|
||||
auto nCh = m_rText[nEndIndex];
|
||||
bool bAdvanceOne = (nCh == CHAR_LRM || nCh == CHAR_RLM);
|
||||
switch (u_charDirection(nCh))
|
||||
{
|
||||
case U_LEFT_TO_RIGHT_EMBEDDING:
|
||||
case U_RIGHT_TO_LEFT_EMBEDDING:
|
||||
case U_LEFT_TO_RIGHT_OVERRIDE:
|
||||
case U_RIGHT_TO_LEFT_OVERRIDE:
|
||||
bAdvanceOne = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bAdvanceOne)
|
||||
{
|
||||
// LRM/RLM can sometimes form a 1-character run. If this happens, the
|
||||
// next run needs to be skipped entirely.
|
||||
int32_t nControlEndIndex = 0;
|
||||
UBiDiLevel nControlCurrLevel = 0;
|
||||
ubidi_getLogicalRun(m_pBidi, nEndIndex, &nControlEndIndex, &nControlCurrLevel);
|
||||
|
||||
++nEndIndex;
|
||||
if (nEndIndex == nControlEndIndex)
|
||||
{
|
||||
++m_nCurr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_stCurr = { m_nCurrIndex, nEndIndex, nCurrLevel, bHasEmbeddedStrongLTR };
|
||||
|
||||
m_nCurrIndex = nEndIndex;
|
||||
|
||||
+7
-2
@@ -178,8 +178,13 @@ constexpr bool SW_ISPRINTABLE(sal_Unicode c) { return c >= ' ' && 127 != c; }
|
||||
#define CHAR_HARDBLANK u'\x00A0'
|
||||
#define CHAR_HARDHYPHEN u'\x2011'
|
||||
#define CHAR_SOFTHYPHEN u'\x00AD'
|
||||
#define CHAR_RLM u'\x200F'
|
||||
#define CHAR_LRM u'\x200E'
|
||||
#define CHAR_LRM u'\x200E' // LEFT-TO-RIGHT MARK
|
||||
#define CHAR_RLM u'\x200F' // RIGHT-TO-LEFT MARK
|
||||
#define CHAR_LRE u'\x202A' // LEFT-TO-RIGHT EMBEDDING
|
||||
#define CHAR_RLE u'\x202B' // RIGHT-TO-LEFT EMBEDDING
|
||||
#define CHAR_PDF u'\x202C' // POP DIRECTIONAL FORMATTING
|
||||
#define CHAR_LRO u'\x202D' // LEFT-TO-RIGHT OVERRIDE
|
||||
#define CHAR_RLO u'\x202E' // RIGHT-TO-LEFT OVERRIDE
|
||||
#define CHAR_ZWSP u'\x200B'
|
||||
#define CHAR_WJ u'\x2060'
|
||||
#define CHAR_NNBSP u'\x202F' //NARROW NO-BREAK SPACE
|
||||
|
||||
@@ -2027,6 +2027,13 @@ TextFrameIndex SwTextFormatInfo::ScanPortionEnd(TextFrameIndex const nStart,
|
||||
case CH_BREAK:
|
||||
case CHAR_ZWSP :
|
||||
case CHAR_WJ :
|
||||
case CHAR_LRM:
|
||||
case CHAR_RLM:
|
||||
case CHAR_LRE:
|
||||
case CHAR_RLE:
|
||||
case CHAR_PDF:
|
||||
case CHAR_LRO:
|
||||
case CHAR_RLO:
|
||||
SetHookChar( cPos );
|
||||
return i;
|
||||
|
||||
|
||||
@@ -1878,6 +1878,13 @@ SwLinePortion *SwTextFormatter::NewPortion(SwTextFormatInfo &rInf,
|
||||
|
||||
case CHAR_ZWSP: // zero width space
|
||||
case CHAR_WJ : // word joiner
|
||||
case CHAR_LRM: // left-to-right mark
|
||||
case CHAR_RLM: // right-to-left mark
|
||||
case CHAR_LRE: // left-to-right-embedding
|
||||
case CHAR_RLE: // right-to-left embedding
|
||||
case CHAR_PDF: // pop directional formatting
|
||||
case CHAR_LRO: // left-to-right override
|
||||
case CHAR_RLO: // right-to-left override
|
||||
pPor = new SwControlCharPortion( cChar ); break;
|
||||
|
||||
case CH_TXTATR_BREAKWORD:
|
||||
|
||||
@@ -626,11 +626,21 @@ bool SwControlCharPortion::DoPaint(SwTextPaintInfo const& rTextPaintInfo,
|
||||
switch (mcChar)
|
||||
{
|
||||
case CHAR_ZWSP:
|
||||
rOutString = "/"; break;
|
||||
// case CHAR_LRM :
|
||||
// rText = sal_Unicode(0x2514); break;
|
||||
// case CHAR_RLM :
|
||||
// rText = sal_Unicode(0x2518); break;
|
||||
rOutString = u"/"_ustr;
|
||||
break;
|
||||
case CHAR_LRM:
|
||||
case CHAR_LRE:
|
||||
case CHAR_LRO:
|
||||
rOutString = u"\u21B1"_ustr;
|
||||
break;
|
||||
case CHAR_RLM:
|
||||
case CHAR_RLE:
|
||||
case CHAR_RLO:
|
||||
rOutString = u"\u21B0"_ustr;
|
||||
break;
|
||||
case CHAR_PDF:
|
||||
rOutString = u"\u21A5"_ustr;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user