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>
This commit is contained in:
Jonathan Clark
2024-09-26 22:44:03 +02:00
parent 348efa75b9
commit d8f430e4be
13 changed files with 550 additions and 33 deletions
+10 -4
View File
@@ -60,6 +60,7 @@
#include <editeng/forbiddencharacterstable.hxx>
#include <comphelper/configuration.hxx>
#include <comphelper/scopeguard.hxx>
#include <math.h>
#include <vcl/metric.hxx>
@@ -2344,10 +2345,17 @@ void ImpEditEngine::ImpAdjustBlocks(ParaPortion& rParaPortion, EditLine& rLine,
void ImpEditEngine::ImpFindKashidas(ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd,
std::vector<sal_Int32>& rArray, sal_Int32 nRemainingSpace)
{
auto nOldLayout = GetRefDevice()->GetLayoutMode();
comphelper::ScopeGuard stGuard{ [this, nOldLayout]
{ GetRefDevice()->SetLayoutMode(nOldLayout); } };
GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
// Kashida glyph looks suspicious, skip Kashida justification
if (GetRefDevice()->GetMinKashida() <= 0)
return;
std::vector<bool> aValidPositions;
std::vector<sal_Int32> aKashidaArray;
std::vector<sal_Int32> aMinKashidaArray;
sal_Int32 nTotalMinKashida = 0U;
@@ -2367,11 +2375,12 @@ void ImpEditEngine::ImpFindKashidas(ContentNode* pNode, sal_Int32 nStart, sal_In
aWordSel.Max().SetIndex( nEnd );
OUString aWord = GetSelected( aWordSel );
GetRefDevice()->GetWordKashidaPositions(aWord, &aValidPositions);
// restore selection for proper iteration at the end of the function
aWordSel.Max().SetIndex( nSavPos );
auto stKashidaPos = i18nutil::GetWordKashidaPosition(aWord);
auto stKashidaPos = i18nutil::GetWordKashidaPosition(aWord, aValidPositions);
if (stKashidaPos.has_value())
{
@@ -2406,12 +2415,9 @@ void ImpEditEngine::ImpFindKashidas(ContentNode* pNode, sal_Int32 nStart, sal_In
// Validate
std::vector<sal_Int32> aDropped;
auto nOldLayout = GetRefDevice()->GetLayoutMode();
GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart,
/*nPartIdx=*/nStart, /*nPartLen=*/nEnd - nStart, aKashidaArray,
&aDropped);
GetRefDevice()->SetLayoutMode(nOldLayout);
for (auto const& pos : aKashidaArray)
if (std::find(aDropped.begin(), aDropped.end(), pos) == aDropped.end())
+38
View File
@@ -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);
}
+37 -19
View File
@@ -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:
auto fnTryInsertBefore
= [&rWord, &nIdx, &nPrevIdx, &nKashidaPos, &nPriorityLevel, &nWordLen,
&pValidPositions](sal_Int32 nNewPriority, bool bIgnoreFinalYeh = false) {
// Exclusions:
// #i98410#: prevent ZWNJ expansion
if (rWord[nPrevIdx] == 0x200C || rWord[nPrevIdx + 1] == 0x200C)
{
return;
}
// tdf#163105: Do not insert kashida if the position is invalid
if (!pValidPositions.empty() && !pValidPositions[nPrevIdx])
{
return;
}
// tdf#65344: Do not insert kashida before a final Yeh
if (!bIgnoreFinalYeh && nIdx == (nWordLen - 1) && isYehChar(rWord[nIdx]))
{
return;
}
// #i98410#: prevent ZWNJ expansion
if (rWord[nPrevIdx] == 0x200C || rWord[nPrevIdx + 1] == 0x200C)
{
return;
}
nKashidaPos = nPrevIdx;
nPriorityLevel = nNewPriority;
};
// 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))
+3 -1
View File
@@ -10,6 +10,7 @@
#include <i18nutil/i18nutildllapi.h>
#include <rtl/ustring.hxx>
#include <optional>
#include <vector>
namespace i18nutil
{
@@ -18,7 +19,8 @@ struct KashidaPosition
sal_Int32 nIndex;
};
I18NUTIL_DLLPUBLIC std::optional<KashidaPosition> GetWordKashidaPosition(const OUString& rWord);
I18NUTIL_DLLPUBLIC std::optional<KashidaPosition>
GetWordKashidaPosition(const OUString& rWord, const std::vector<bool>& pValidPositions = {});
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
+3
View File
@@ -1171,6 +1171,9 @@ public:
std::vector<sal_Int32>* pKashidaPosDropped // invalid kashida positions (out)
) const;
// tdf#163105: Get map of valid kashida positions for a single word
void GetWordKashidaPositions(const OUString& rText, std::vector<bool>* pOutMap) const;
static void BeginFontSubstitution();
static void EndFontSubstitution();
static void AddFontSubstitute( const OUString& rFontName,
+8
View File
@@ -322,6 +322,14 @@ public:
void GetKashidaPositions(TextFrameIndex nStt, TextFrameIndex nLen,
std::vector<TextFrameIndex>& rKashidaPosition);
/** replaces kashida opportunities for a given text range.
rKashidaPositions: buffer containing char indices of the
kashida opportunities relative to the paragraph
*/
void ReplaceKashidaPositions(TextFrameIndex nStt, TextFrameIndex nEnd,
const std::vector<TextFrameIndex>& rKashidaPositions);
/** Use regular blank justification instead of kashdida justification for the given line of text.
nStt Start char index of the line referring to the paragraph.
nLen Number of characters in the line
+62 -8
View File
@@ -20,6 +20,9 @@
#include <sal/config.h>
#include <o3tl/safeint.hxx>
#include <com/sun/star/i18n/WordType.hpp>
#include <swscanner.hxx>
#include <i18nutil/kashida.hxx>
#include <IDocumentSettingAccess.hxx>
#include <doc.hxx>
@@ -135,13 +138,64 @@ void SwTextAdjuster::FormatBlock( )
GetInfo().GetParaPortion()->GetRepaint().SetOffset(0);
}
static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr,
sal_Int32& rKashidas, TextFrameIndex& nGluePortion)
static bool lcl_CheckKashidaPositions(SwScriptInfo& rSI, SwTextSizeInfo& rInf, SwTextIter& rItr,
sal_Int32& rKashidas, TextFrameIndex& nGluePortion,
bool& rRemovedAllKashida)
{
rRemovedAllKashida = true;
// i60594 validate Kashida justification
TextFrameIndex nIdx = rItr.GetStart();
TextFrameIndex nEnd = rItr.GetEnd();
// Get the initial kashida position set, for invalidation
std::vector<TextFrameIndex> aOldKashidaPositions;
rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aOldKashidaPositions);
std::vector<TextFrameIndex> aNewKashidaPositions;
std::vector<bool> aValidPositions;
// Reparse the text, and reapply the kashida insertion rules
std::function<LanguageType(sal_Int32, sal_Int32, bool)> const pGetLangOfChar(
[&rInf](sal_Int32 const nBegin, sal_uInt16 const nScript, bool const bNoChar)
{ return rInf.GetTextFrame()->GetLangOfChar(TextFrameIndex{ nBegin }, nScript, bNoChar); });
SwScanner aScanner(pGetLangOfChar, rInf.GetText(), nullptr, ModelToViewHelper(),
i18n::WordType::DICTIONARY_WORD, sal_Int32(nIdx), sal_Int32(nEnd));
while (aScanner.NextWord())
{
const OUString& rWord = aScanner.GetWord();
// Fetch the set of valid positions from VCL, where possible
aValidPositions.clear();
if ( SwScriptInfo::IsArabicText( rInf.GetText(), TextFrameIndex{aScanner.GetBegin()}, TextFrameIndex{aScanner.GetLen()} ) )
{
rItr.SeekAndChgAttrIter(TextFrameIndex{ aScanner.GetBegin() }, rInf.GetRefDev());
vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetRefDev()->GetLayoutMode();
rInf.GetRefDev()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
rInf.GetRefDev()->GetWordKashidaPositions(rWord, &aValidPositions);
rInf.GetRefDev()->SetLayoutMode(nOldLayout);
}
auto stKashidaPos = i18nutil::GetWordKashidaPosition(rWord, aValidPositions);
if (stKashidaPos.has_value())
{
TextFrameIndex nNewKashidaPos{ aScanner.GetBegin() + stKashidaPos->nIndex };
aNewKashidaPositions.push_back(nNewKashidaPos);
}
}
if (aOldKashidaPositions != aNewKashidaPositions)
{
// Kashida positions have changed; restart CalcNewBlock
rSI.ReplaceKashidaPositions(nIdx, nEnd, aNewKashidaPositions);
rRemovedAllKashida = aNewKashidaPositions.empty();
return false;
}
// Note on calling KashidaJustify():
// Kashida positions may be marked as invalid. Therefore KashidaJustify may return the clean
// total number of kashida positions, or the number of kashida positions after some positions
@@ -154,12 +208,10 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf,
// kashida positions found in SwScriptInfo are not necessarily valid in every font
// if two characters are replaced by a ligature glyph, there will be no place for a kashida
std::vector<TextFrameIndex> aUncastKashidaPos;
rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aUncastKashidaPos);
assert(aUncastKashidaPos.size() >= o3tl::make_unsigned(rKashidas));
assert(aNewKashidaPositions.size() >= o3tl::make_unsigned(rKashidas));
std::vector<sal_Int32> aKashidaPos;
std::transform(std::cbegin(aUncastKashidaPos), std::cend(aUncastKashidaPos),
std::transform(std::cbegin(aNewKashidaPositions), std::cend(aNewKashidaPositions),
std::back_inserter(aKashidaPos),
[](TextFrameIndex nPos) { return static_cast<sal_Int32>(nPos); });
@@ -404,13 +456,15 @@ void SwTextAdjuster::CalcNewBlock( SwLineLayout *pCurrent,
{
// kashida positions found in SwScriptInfo are not necessarily valid in every font
// if two characters are replaced by a ligature glyph, there will be no place for a kashida
if ( !lcl_CheckKashidaPositions ( rSI, aInf, aItr, nKashidas, nGluePortion ))
bool bRemovedAllKashida = false;
if (!lcl_CheckKashidaPositions(rSI, aInf, aItr, nKashidas, nGluePortion,
bRemovedAllKashida))
{
// all kashida positions are invalid
// do regular blank justification
pCurrent->FinishSpaceAdd();
GetInfo().SetIdx( m_nStart );
CalcNewBlock( pCurrent, pStopAt, nReal, true );
CalcNewBlock(pCurrent, pStopAt, nReal, bRemovedAllKashida);
return;
}
}
+21
View File
@@ -2314,6 +2314,27 @@ void SwScriptInfo::GetKashidaPositions(
}
}
void SwScriptInfo::ReplaceKashidaPositions(TextFrameIndex const nStt, TextFrameIndex const nEnd,
const std::vector<TextFrameIndex>& rKashidaPositions)
{
auto it = m_Kashida.begin();
while (it != m_Kashida.end() && *it < nStt)
{
++it;
}
it = m_Kashida.insert(it, rKashidaPositions.begin(), rKashidaPositions.end());
it += rKashidaPositions.size();
auto jt = it;
while (jt != m_Kashida.end() && *jt < nEnd)
{
++jt;
}
m_Kashida.erase(it, jt);
}
void SwScriptInfo::SetNoKashidaLine(TextFrameIndex const nStt, TextFrameIndex const nLen)
{
m_NoKashidaLine.push_back( nStt );
@@ -0,0 +1,131 @@
<?xml version='1.0' encoding='UTF-8'?>
<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:creation-date>2024-09-25T22:51:39.180592512</meta:creation-date><dc:date>2024-09-25T22:52:41.682098885</dc:date><meta:editing-duration>PT1M3S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="0" meta:word-count="0" meta:character-count="0" meta:non-whitespace-character-count="0"/><meta:generator>LibreOfficeDev/25.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/8493389ffaaa809c9feb77622a5bd695dbb76e43</meta:generator></office:meta>
<office:font-face-decls>
<style:font-face style:name="Amiri" svg:font-family="Amiri" style:font-adornments="Regular" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Noto Sans" svg:font-family="'Noto Sans'" style:font-family-generic="swiss"/>
<style:font-face style:name="Noto Serif CJK SC" svg:font-family="'Noto Serif CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans" style:font-size-complex="12pt" style:language-complex="ar" style:country-complex="SA"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans" style:font-size-complex="12pt" style:language-complex="ar" style:country-complex="SA" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text">
<style:text-properties style:font-name-complex="Amiri" style:font-family-complex="Amiri" style:font-style-name-complex="Regular" style:font-pitch-complex="variable"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="2" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="3" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="4" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="5" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="6" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="7" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="8" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="9" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="10" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
</text:outline-style>
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
<text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
</office:styles>
<office:automatic-styles>
<style:style style:name="P1" style:family="paragraph">
<style:paragraph-properties fo:text-align="justify" style:writing-mode="rl-tb"/>
<style:text-properties style:font-family-complex="Amiri" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="P2" style:family="paragraph">
<loext:graphic-properties draw:fill="none" draw:fill-color="#ffffff"/>
<style:paragraph-properties fo:text-align="end"/>
<style:text-properties style:font-family-complex="Amiri" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="T1" style:family="text">
<style:text-properties style:font-family-complex="Amiri" style:font-pitch-complex="variable"/>
</style:style>
<style:style style:name="gr1" style:family="graphic">
<style:graphic-properties draw:stroke="none" svg:stroke-color="#000000" draw:fill="none" draw:fill-color="#ffffff" fo:min-height="2.1965in" loext:decorative="false" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph"/>
<style:paragraph-properties style:writing-mode="lr-tb"/>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:footnote-max-height="0in" loext:margin-gutter="0in">
<style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style/>
<style:footer-style/>
</style:page-layout>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1"/>
</office:master-styles>
<office:body>
<office:text>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
</text:sequence-decls>
<text:p text:style-name="Standard"><draw:frame text:anchor-type="paragraph" draw:z-index="0" draw:name="Text Frame 1" draw:style-name="gr1" draw:text-style-name="P2" svg:width="6.4843in" svg:height="2.1969in" svg:x="0.0917in" svg:y="0.1091in">
<draw:text-box>
<text:p text:style-name="P1"><text:span text:style-name="T1">متن فارسی</text:span><text:span text:style-name="T1"><text:line-break/></text:span><text:span text:style-name="T1"/></text:p>
</draw:text-box>
</draw:frame></text:p>
</office:text>
</office:body>
</office:document>
@@ -0,0 +1,114 @@
<?xml version='1.0' encoding='UTF-8'?>
<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:creation-date>2024-09-25T22:50:47.128978128</meta:creation-date><dc:date>2024-09-25T22:51:34.458596131</dc:date><meta:editing-duration>PT48S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="1" meta:word-count="2" meta:character-count="10" meta:non-whitespace-character-count="8"/><meta:generator>LibreOfficeDev/25.2.0.0.alpha0$Linux_X86_64 LibreOffice_project/8493389ffaaa809c9feb77622a5bd695dbb76e43</meta:generator></office:meta>
<office:font-face-decls>
<style:font-face style:name="Amiri" svg:font-family="Amiri" style:font-adornments="Regular" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Noto Sans" svg:font-family="'Noto Sans'" style:font-family-generic="swiss"/>
<style:font-face style:name="Noto Serif CJK SC" svg:font-family="'Noto Serif CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0in" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans" style:font-size-complex="12pt" style:language-complex="ar" style:country-complex="SA"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
<style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Noto Sans" style:font-size-complex="12pt" style:language-complex="ar" style:country-complex="SA" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text">
<style:text-properties style:font-name-complex="Amiri" style:font-family-complex="Amiri" style:font-style-name-complex="Regular" style:font-pitch-complex="variable"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="2" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="3" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="4" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="5" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="6" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="7" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="8" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="9" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
<text:outline-level-style text:level="10" style:num-format="">
<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">
<style:list-level-label-alignment text:label-followed-by="listtab"/>
</style:list-level-properties>
</text:outline-level-style>
</text:outline-style>
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
<text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
</office:styles>
<office:automatic-styles>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:text-align="justify" style:justify-single-word="false" style:writing-mode="rl-tb"/>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:footnote-max-height="0in" loext:margin-gutter="0in">
<style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style/>
<style:footer-style/>
</style:page-layout>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1"/>
</office:master-styles>
<office:body>
<office:text>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
<text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
</text:sequence-decls>
<text:p text:style-name="P1">متن فارسی<text:line-break/></text:p>
</office:text>
</office:body>
</office:document>
+94 -1
View File
@@ -5627,7 +5627,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf151748KashidaSpace)
// tdf#163105 - Writer kashida justification should expand spaces
CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf163105SwKashidaSpaceExpansion)
{
saveAsPDF(u"tdf163105.fodt");
saveAsPDF(u"tdf163105-kashida-spaces.fodt");
auto pPdfDocument = parsePDFExport();
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
@@ -5668,6 +5668,99 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf163105SwKashidaSpaceExpansion)
CPPUNIT_ASSERT_GREATER(150.0, aRect.at(2).getWidth());
}
// tdf#163105 - Writer should use font information when choosing kashida positions
CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf163105Writer)
{
saveAsPDF(u"tdf163105-writer.fodt");
auto pPdfDocument = parsePDFExport();
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
CPPUNIT_ASSERT(pPdfPage);
auto pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
// The fix allows kashida justification in this document.
// Without the fix, this will be 1.
CPPUNIT_ASSERT_EQUAL(5, nPageObjectCount);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
int nTextObjectCount = 0;
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
aText.push_back(pPageObject->getText(pTextPage));
aRect.push_back(pPageObject->getBounds());
++nTextObjectCount;
}
}
CPPUNIT_ASSERT_EQUAL(5, nTextObjectCount);
CPPUNIT_ASSERT_EQUAL(u"ارسی"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"تن ف"_ustr, aText.at(2).trim()); // This span is whitespace justified
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"م"_ustr, aText.at(4).trim());
// Without the fix, this will be greater than X
CPPUNIT_ASSERT_LESS(170.0, aRect.at(2).getWidth());
}
// tdf#163105 - Edit Engine should use font information when choosing kashida positions
CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf163105Editeng)
{
saveAsPDF(u"tdf163105-editeng.fodt");
auto pPdfDocument = parsePDFExport();
CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
CPPUNIT_ASSERT(pPdfPage);
auto pTextPage = pPdfPage->getTextPage();
CPPUNIT_ASSERT(pTextPage);
int nPageObjectCount = pPdfPage->getObjectCount();
// The fix allows kashida justification in this document.
// Without the fix, this will be 1.
CPPUNIT_ASSERT_EQUAL(5, nPageObjectCount);
std::vector<OUString> aText;
std::vector<basegfx::B2DRectangle> aRect;
int nTextObjectCount = 0;
for (int i = 0; i < nPageObjectCount; ++i)
{
auto pPageObject = pPdfPage->getObject(i);
CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
{
aText.push_back(pPageObject->getText(pTextPage));
aRect.push_back(pPageObject->getBounds());
++nTextObjectCount;
}
}
CPPUNIT_ASSERT_EQUAL(5, nTextObjectCount);
CPPUNIT_ASSERT_EQUAL(u"ارسی"_ustr, aText.at(0).trim());
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(1).trim());
CPPUNIT_ASSERT_EQUAL(u"تن ف"_ustr, aText.at(2).trim()); // This span is whitespace justified
CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
CPPUNIT_ASSERT_EQUAL(u"م"_ustr, aText.at(4).trim());
CPPUNIT_ASSERT_LESS(170.0, aRect.at(2).getWidth());
}
} // end anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();
+29
View File
@@ -1214,6 +1214,35 @@ sal_Int32 OutputDevice::ValidateKashidas(const OUString& rTxt, sal_Int32 nIdx, s
return nDropped;
}
// tdf#163105: Get map of valid kashida positions for a single word
void OutputDevice::GetWordKashidaPositions(const OUString& rText,
std::vector<bool>* pOutMap) const
{
pOutMap->clear();
auto nEnd = rText.getLength();
// do layout
std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rText, 0, nEnd);
if (!pSalLayout)
return;
pOutMap->resize(nEnd, false);
for (sal_Int32 i = 0; i < nEnd; ++i)
{
auto nNextPos = i + 1;
// Skip combining marks to find the next character after this position.
while (nNextPos < nEnd
&& u_getIntPropertyValue(rText[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT)
{
++nNextPos;
}
pOutMap->at(i) = pSalLayout->IsKashidaPosValid(i, nNextPos);
}
}
bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr,
int nIndex, int nLen, std::vector< tools::Rectangle >& rVector ) const
{