summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Clark <jonathan@libreoffice.org>2024-05-07 02:43:00 -0600
committerJonathan Clark <jonathan@libreoffice.org>2024-05-22 19:20:38 +0200
commitab0a4543cab77ae0c7c0a79feb8aebab71163dd7 (patch)
tree8b9f0fc175c41eb719865f69674bbfe65a1e0df9
parentd1ddd136a1b0e452492464d58715eaec144fd811 (diff)
tdf#124116 Correct Writer text shaping across formatting changes
Previously, Writer performed shaping for each span of text separately. In certain situations, this caused incorrect glyph use, or incorrect glyph positioning. This change updates Writer so it will also consider neighboring text while performing shaping. This change resolves the outstanding duplicates filed against tdf#61444. As a side effect, this change also fixes tdf#134226. In addition to the shaping fix, this change implements rendering for individually-styled glyphs, which is required to fix tdf#71956. However, this change does not implement diacritic selection, which is also required for that issue. Change-Id: Iab4774ffaab5ad6113778c54d02cb260a70c1010 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/167699 Reviewed-by: Jonathan Clark <jonathan@libreoffice.org> Tested-by: Jenkins
-rw-r--r--editeng/source/editeng/impedit3.cxx5
-rw-r--r--include/vcl/glyphitemcache.hxx5
-rw-r--r--include/vcl/metaact.hxx15
-rw-r--r--include/vcl/outdev.hxx27
-rw-r--r--include/vcl/pdfwriter.hxx8
-rw-r--r--include/vcl/rendercontext/SalLayoutFlags.hxx3
-rw-r--r--include/vcl/vcllayout.hxx2
-rw-r--r--sw/source/core/text/guess.cxx21
-rw-r--r--sw/source/core/text/itradj.cxx39
-rw-r--r--sw/source/core/txtnode/fntcache.cxx4
-rw-r--r--vcl/CppunitTest_vcl_text.mk1
-rw-r--r--vcl/Library_vcl.mk4
-rw-r--r--vcl/inc/ImplLayoutArgs.hxx13
-rw-r--r--vcl/inc/impglyphitem.hxx7
-rw-r--r--vcl/inc/justificationdata.hxx125
-rw-r--r--vcl/inc/pdf/pdfwriter_impl.hxx4
-rw-r--r--vcl/inc/sallayout.hxx17
-rw-r--r--vcl/qa/cppunit/justificationdata.cxx113
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf124116-hebrew-track-untrack.odtbin0 -> 15102 bytes
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf134226-shadda-in-hidden-span.fodt328
-rw-r--r--vcl/qa/cppunit/pdfexport/data/tdf71956-styled-diacritics.fodt320
-rw-r--r--vcl/qa/cppunit/pdfexport/pdfexport2.cxx179
-rw-r--r--vcl/qa/cppunit/svm/data/textarraycontext.svmbin0 -> 288 bytes
-rw-r--r--vcl/qa/cppunit/svm/svmtest.cxx31
-rw-r--r--vcl/source/filter/svm/SvmReader.cxx28
-rw-r--r--vcl/source/filter/svm/SvmWriter.cxx11
-rw-r--r--vcl/source/gdi/CommonSalLayout.cxx212
-rw-r--r--vcl/source/gdi/gdimtf.cxx6
-rw-r--r--vcl/source/gdi/impglyphitem.cxx28
-rw-r--r--vcl/source/gdi/metaact.cxx44
-rw-r--r--vcl/source/gdi/mtfxmldump.cxx8
-rw-r--r--vcl/source/gdi/pdfwriter.cxx16
-rw-r--r--vcl/source/gdi/pdfwriter_impl.cxx27
-rw-r--r--vcl/source/gdi/pdfwriter_impl2.cxx5
-rw-r--r--vcl/source/gdi/sallayout.cxx46
-rw-r--r--vcl/source/outdev/font.cxx45
-rw-r--r--vcl/source/outdev/text.cxx164
-rw-r--r--vcl/source/outdev/transparent.cxx23
-rw-r--r--vcl/source/text/ImplLayoutArgs.cxx22
39 files changed, 1761 insertions, 195 deletions
diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx
index ffd47d95f56a..b28532ae46d0 100644
--- a/editeng/source/editeng/impedit3.cxx
+++ b/editeng/source/editeng/impedit3.cxx
@@ -2596,11 +2596,12 @@ void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_I
}
// Validate
- std::vector<sal_Int32> aDropped(aKashidaArray.size());
+ std::vector<sal_Int32> aDropped;
auto nOldLayout = GetRefDevice()->GetLayoutMode();
GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart,
- aKashidaArray.size(), aKashidaArray.data(), aDropped.data());
+ /*nPartIdx=*/nStart, /*nPartLen=*/nEnd - nStart, aKashidaArray,
+ &aDropped);
GetRefDevice()->SetLayoutMode(nOldLayout);
for (auto const& pos : aKashidaArray)
diff --git a/include/vcl/glyphitemcache.hxx b/include/vcl/glyphitemcache.hxx
index ffe9bb7eb0c4..79f05e606550 100644
--- a/include/vcl/glyphitemcache.hxx
+++ b/include/vcl/glyphitemcache.hxx
@@ -53,6 +53,11 @@ public:
const OUString& text, sal_Int32 nIndex, sal_Int32 nLen,
tools::Long nLogicWidth = 0,
const vcl::text::TextLayoutCache* layoutCache = nullptr);
+ const SalLayoutGlyphs* GetLayoutGlyphs(const VclPtr<const OutputDevice>& outputDevice,
+ const OUString& text, sal_Int32 nIndex, sal_Int32 nLen,
+ sal_Int32 nDrawMinCharPos, sal_Int32 nDrawEndCharPos,
+ tools::Long nLogicWidth = 0,
+ const vcl::text::TextLayoutCache* layoutCache = nullptr);
void clear();
/// Normally, we cannot cache glyphs when doing font fallback, because the font fallbacks
diff --git a/include/vcl/metaact.hxx b/include/vcl/metaact.hxx
index 71869689dc55..79a91a629783 100644
--- a/include/vcl/metaact.hxx
+++ b/include/vcl/metaact.hxx
@@ -510,6 +510,8 @@ private:
std::vector<sal_Bool> maKashidaAry;
sal_Int32 mnIndex;
sal_Int32 mnLen;
+ sal_Int32 mnLayoutContextIndex = -1;
+ sal_Int32 mnLayoutContextLen = -1;
SAL_DLLPRIVATE virtual ~MetaTextArrayAction() override;
@@ -526,6 +528,9 @@ public:
std::span<const sal_Bool> pKashidaAry,
sal_Int32 nIndex,
sal_Int32 nLen );
+ MetaTextArrayAction(const Point& rStartPt, OUString aStr, KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry, sal_Int32 nIndex, sal_Int32 nLen,
+ sal_Int32 nLayoutContextIndex, sal_Int32 nLayoutContextLen);
SAL_DLLPRIVATE virtual void Execute( OutputDevice* pOut ) override;
@@ -538,12 +543,22 @@ public:
const OUString& GetText() const { return maStr; }
sal_Int32 GetIndex() const { return mnIndex; }
sal_Int32 GetLen() const { return mnLen; }
+ sal_Int32 GetLayoutContextIndex() const { return mnLayoutContextIndex; }
+ sal_Int32 GetLayoutContextLen() const { return mnLayoutContextLen; }
const KernArray& GetDXArray() const { return maDXAry; }
const std::vector<sal_Bool> & GetKashidaArray() const { return maKashidaAry; }
void SetPoint(const Point& rPt) { maStartPt = rPt; }
void SetText(const OUString& rStr) { maStr = rStr; }
void SetIndex(sal_Int32 rIndex) { mnIndex = rIndex; }
void SetLen(sal_Int32 rLen) { mnLen = rLen; }
+ void SetLayoutContextIndex(sal_Int32 nLayoutContextIndex)
+ {
+ mnLayoutContextIndex = nLayoutContextIndex;
+ }
+ void SetLayoutContextLen(sal_Int32 nLayoutContextLen)
+ {
+ mnLayoutContextLen = nLayoutContextLen;
+ }
SAL_DLLPRIVATE void SetDXArray(KernArray aArray);
SAL_DLLPRIVATE void SetKashidaArray(std::vector<sal_Bool> aArray);
};
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index 6dbb9acf7f32..347d5bb82226 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -1160,11 +1160,12 @@ public:
// i60594
// validate kashida positions against the current font
// returns count of invalid kashida positions
- sal_Int32 ValidateKashidas( const OUString& rTxt, sal_Int32 nIdx, sal_Int32 nLen,
- sal_Int32 nKashCount, // number of suggested kashida positions (in)
- const sal_Int32* pKashidaPos, // suggested kashida positions (in)
- sal_Int32* pKashidaPosDropped // invalid kashida positions (out)
- ) const;
+ sal_Int32
+ ValidateKashidas(const OUString& rTxt, sal_Int32 nIdx, sal_Int32 nLen, sal_Int32 nPartIdx,
+ sal_Int32 nPartLen,
+ std::span<const sal_Int32> pKashidaPos, // suggested kashida positions (in)
+ std::vector<sal_Int32>* pKashidaPosDropped // invalid kashida positions (out)
+ ) const;
static void BeginFontSubstitution();
static void EndFontSubstitution();
@@ -1235,14 +1236,14 @@ public:
SAL_DLLPRIVATE void ReMirror( vcl::Region &rRegion ) const;
SAL_DLLPRIVATE bool ImplIsRecordLayout() const;
virtual bool HasMirroredGraphics() const;
- std::unique_ptr<SalLayout>
- ImplLayout( const OUString&, sal_Int32 nIndex, sal_Int32 nLen,
- const Point& rLogicPos = Point(0,0), tools::Long nLogicWidth=0,
- KernArraySpan aKernArray = KernArraySpan(),
- std::span<const sal_Bool> pKashidaArray={},
- SalLayoutFlags flags = SalLayoutFlags::NONE,
- vcl::text::TextLayoutCache const* = nullptr,
- const SalLayoutGlyphs* pGlyphs = nullptr) const;
+ std::unique_ptr<SalLayout> ImplLayout(
+ const OUString&, sal_Int32 nIndex, sal_Int32 nLen, const Point& rLogicPos = Point(0, 0),
+ tools::Long nLogicWidth = 0, KernArraySpan aKernArray = KernArraySpan(),
+ std::span<const sal_Bool> pKashidaArray = {}, SalLayoutFlags flags = SalLayoutFlags::NONE,
+ vcl::text::TextLayoutCache const* = nullptr, const SalLayoutGlyphs* pGlyphs = nullptr,
+ std::optional<sal_Int32> nDrawOriginCluster = std::nullopt,
+ std::optional<sal_Int32> nDrawMinCharPos = std::nullopt,
+ std::optional<sal_Int32> nDrawEndCharPos = std::nullopt) const;
SAL_DLLPRIVATE vcl::text::ImplLayoutArgs ImplPrepareLayoutArgs( OUString&, const sal_Int32 nIndex, const sal_Int32 nLen,
double nPixelWidth,
diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx
index f4599154e465..34d44525b4d4 100644
--- a/include/vcl/pdfwriter.hxx
+++ b/include/vcl/pdfwriter.hxx
@@ -790,11 +790,9 @@ The following structure describes the permissions used in PDF security
FontStrikeout eStrikeout,
FontLineStyle eUnderline,
FontLineStyle eOverline );
- void DrawTextArray( const Point& rStartPt, const OUString& rStr,
- KernArraySpan aKernArray,
- std::span<const sal_Bool> pKashidaAry,
- sal_Int32 nIndex,
- sal_Int32 nLen );
+ void DrawTextArray(const Point& rStartPt, const OUString& rStr, KernArraySpan aKernArray,
+ std::span<const sal_Bool> pKashidaAry, sal_Int32 nIndex, sal_Int32 nLen,
+ sal_Int32 nLayoutContextIndex, sal_Int32 nLayoutContextLen);
void DrawStretchText( const Point& rStartPt, sal_Int32 nWidth,
const OUString& rStr,
sal_Int32 nIndex, sal_Int32 nLen );
diff --git a/include/vcl/rendercontext/SalLayoutFlags.hxx b/include/vcl/rendercontext/SalLayoutFlags.hxx
index 576a4abd8fbd..3424fa2a1de4 100644
--- a/include/vcl/rendercontext/SalLayoutFlags.hxx
+++ b/include/vcl/rendercontext/SalLayoutFlags.hxx
@@ -33,10 +33,11 @@ enum class SalLayoutFlags
DisableLigatures = 0x0200,
ForFallback = 0x2000,
GlyphItemsOnly = 0x4000,
+ UnclusteredGlyphs = 0x8000,
};
namespace o3tl
{
-template <> struct typed_flags<SalLayoutFlags> : is_typed_flags<SalLayoutFlags, 0x6277>
+template <> struct typed_flags<SalLayoutFlags> : is_typed_flags<SalLayoutFlags, 0xE277>
{
};
}
diff --git a/include/vcl/vcllayout.hxx b/include/vcl/vcllayout.hxx
index 9370c69ded2e..58ca11b876ef 100644
--- a/include/vcl/vcllayout.hxx
+++ b/include/vcl/vcllayout.hxx
@@ -137,6 +137,8 @@ private:
protected:
int mnMinCharPos;
int mnEndCharPos;
+ int mnDrawMinCharPos = std::numeric_limits<int>::min();
+ int mnDrawEndCharPos = std::numeric_limits<int>::max();
LanguageTag maLanguageTag;
Degree10 mnOrientation;
diff --git a/sw/source/core/text/guess.cxx b/sw/source/core/text/guess.cxx
index af41e0971090..789cae852921 100644
--- a/sw/source/core/text/guess.cxx
+++ b/sw/source/core/text/guess.cxx
@@ -248,7 +248,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
( bUnbreakableNumberings && rPor.IsNumberPortion() ) )
{
// call GetTextSize with maximum compression (for kanas)
- rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rPor.GetLayoutContext(), nMaxComp,
+ rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rInf.GetLayoutContext(), nMaxComp,
m_nBreakWidth, nMaxSizeDiff);
if ( ( m_nBreakWidth <= nLineWidth ) || ( bUnbreakableNumberings && rPor.IsNumberPortion() ) )
@@ -259,7 +259,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
|| maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
m_nBreakWidth, m_nExtraBlankWidth,
nMaxSizeDiff, rInf, rSI, nMaxComp,
- rPor.GetLayoutContext());
+ rInf.GetLayoutContext());
if( nItalic &&
(m_nCutPos >= TextFrameIndex(rInf.GetText().getLength()) ||
// #i48035# Needed for CalcFitToContent
@@ -421,8 +421,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
if ( TextFrameIndex(COMPLETE_STRING) != m_nCutPos )
{
sal_uInt16 nMinSize;
- rInf.GetTextSize(&rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(),
- rPor.GetLayoutContext(), nMaxComp, nMinSize, nMaxSizeDiff);
+ rInf.GetTextSize(&rSI, rInf.GetIdx(), m_nCutPos - rInf.GetIdx(), std::nullopt, nMaxComp,
+ nMinSize, nMaxSizeDiff);
OSL_ENSURE( nMinSize <= nLineWidth, "What a Guess!!!" );
}
#endif
@@ -432,7 +432,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
{
// second check if everything fits to line
m_nCutPos = m_nBreakPos = rInf.GetIdx() + nMaxLen - TextFrameIndex(1);
- rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rPor.GetLayoutContext(), nMaxComp,
+ rInf.GetTextSize(&rSI, rInf.GetIdx(), nMaxLen, rInf.GetLayoutContext(), nMaxComp,
m_nBreakWidth, nMaxSizeDiff);
// The following comparison should always give true, otherwise
@@ -443,7 +443,7 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
|| maybeAdjustPositionsForBlockAdjust(m_nCutPos, m_nBreakPos, m_nBreakStart,
m_nBreakWidth, m_nExtraBlankWidth,
nMaxSizeDiff, rInf, rSI, nMaxComp,
- rPor.GetLayoutContext());
+ rInf.GetLayoutContext());
if (nItalic && (m_nBreakPos + TextFrameIndex(1)) >= TextFrameIndex(rInf.GetText().getLength()))
m_nBreakWidth += nItalic;
@@ -734,8 +734,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
if( nPorLen )
{
- rInf.GetTextSize(&rSI, rInf.GetIdx(), nPorLen, rPor.GetLayoutContext(), nMaxComp,
- m_nBreakWidth, nMaxSizeDiff, rInf.GetCachedVclData().get());
+ rInf.GetTextSize(&rSI, rInf.GetIdx(), nPorLen, std::nullopt, nMaxComp, m_nBreakWidth,
+ nMaxSizeDiff, rInf.GetCachedVclData().get());
// save maximum width for later use
if ( nMaxSizeDiff )
@@ -749,9 +749,8 @@ bool SwTextGuess::Guess( const SwTextPortion& rPor, SwTextFormatInfo &rInf,
if (m_nBreakStart > rInf.GetIdx() + nPorLen + m_nFieldDiff)
{
rInf.GetTextSize(&rSI, rInf.GetIdx() + nPorLen,
- m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff,
- rPor.GetLayoutContext(), nMaxComp, m_nExtraBlankWidth, nMaxSizeDiff,
- rInf.GetCachedVclData().get());
+ m_nBreakStart - rInf.GetIdx() - nPorLen - m_nFieldDiff, std::nullopt,
+ nMaxComp, m_nExtraBlankWidth, nMaxSizeDiff, rInf.GetCachedVclData().get());
}
if( m_pHanging )
diff --git a/sw/source/core/text/itradj.cxx b/sw/source/core/text/itradj.cxx
index 4dcaf03df1f8..c5e24b0913f5 100644
--- a/sw/source/core/text/itradj.cxx
+++ b/sw/source/core/text/itradj.cxx
@@ -129,10 +129,18 @@ 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> aKashidaPos;
- rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aKashidaPos);
- assert(aKashidaPos.size() >= o3tl::make_unsigned(rKashidas));
- std::vector<TextFrameIndex> aKashidaPosDropped(aKashidaPos.size());
+ std::vector<TextFrameIndex> aUncastKashidaPos;
+ rSI.GetKashidaPositions(nIdx, rItr.GetLength(), aUncastKashidaPos);
+ assert(aUncastKashidaPos.size() >= o3tl::make_unsigned(rKashidas));
+
+ std::vector<sal_Int32> aKashidaPos;
+ std::transform(std::cbegin(aUncastKashidaPos), std::cend(aUncastKashidaPos),
+ std::back_inserter(aKashidaPos),
+ [](TextFrameIndex nPos) { return static_cast<sal_Int32>(nPos); });
+
+ std::vector<sal_Int32> aKashidaPosDropped;
+ std::vector<TextFrameIndex> aCastKashidaPosDropped;
+
sal_Int32 nKashidaIdx = 0;
while ( rKashidas && nIdx < nEnd )
{
@@ -147,6 +155,14 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf,
if (nNext == TextFrameIndex(COMPLETE_STRING) || nNext > nEnd)
nNext = nEnd;
+
+ // Use an expanded context to validate kashida insertions between spans
+ TextFrameIndex nWholeNext = nNextScript;
+ if (nWholeNext == TextFrameIndex(COMPLETE_STRING) || nWholeNext > nEnd)
+ {
+ nWholeNext = nEnd;
+ }
+
sal_Int32 nKashidasInAttr = rSI.KashidaJustify(nullptr, nullptr, nIdx, nNext - nIdx);
if (nKashidasInAttr > 0)
{
@@ -167,14 +183,19 @@ static bool lcl_CheckKashidaPositions( SwScriptInfo& rSI, SwTextSizeInfo& rInf,
vcl::text::ComplexTextLayoutFlags nOldLayout = rInf.GetOut()->GetLayoutMode();
rInf.GetOut()->SetLayoutMode ( nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl );
nKashidasDropped = rInf.GetOut()->ValidateKashidas(
- rInf.GetText(), sal_Int32(nIdx), sal_Int32(nNext - nIdx),
- nKashidasInAttr,
- reinterpret_cast<sal_Int32*>(aKashidaPos.data() + nKashidaIdx),
- reinterpret_cast<sal_Int32*>(aKashidaPosDropped.data()));
+ rInf.GetText(), /*nIdx=*/sal_Int32{ nIdx },
+ /*nLen=*/sal_Int32{ nWholeNext - nIdx },
+ /*nPartIdx=*/sal_Int32{ nIdx }, /*nPartLen=*/sal_Int32{ nNext - nIdx },
+ std::span(aKashidaPos).subspan(nKashidaIdx, nKashidasInAttr),
+ &aKashidaPosDropped);
rInf.GetOut()->SetLayoutMode ( nOldLayout );
if ( nKashidasDropped )
{
- rSI.MarkKashidasInvalid(nKashidasDropped, aKashidaPosDropped.data());
+ aCastKashidaPosDropped.clear();
+ std::transform(std::cbegin(aKashidaPosDropped), std::cend(aKashidaPosDropped),
+ std::back_inserter(aCastKashidaPosDropped),
+ [](sal_Int32 nPos) { return TextFrameIndex{ nPos }; });
+ rSI.MarkKashidasInvalid(nKashidasDropped, aCastKashidaPosDropped.data());
rKashidas -= nKashidasDropped;
nGluePortion -= TextFrameIndex(nKashidasDropped);
}
diff --git a/sw/source/core/txtnode/fntcache.cxx b/sw/source/core/txtnode/fntcache.cxx
index b91f73756c58..fd42378b1bdc 100644
--- a/sw/source/core/txtnode/fntcache.cxx
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -751,12 +751,12 @@ static void GetTextArray(const OutputDevice& rDevice, const OUString& rStr, Kern
if (nLayoutContext.has_value())
{
auto nStrEnd = nIndex + nLen;
- auto nContextBegin = std::clamp(nLayoutContext->m_nBegin, sal_Int32{0}, nIndex);
+ auto nContextBegin = std::clamp(nLayoutContext->m_nBegin, sal_Int32{ 0 }, nIndex);
auto nContextEnd = std::clamp(nLayoutContext->m_nEnd, nStrEnd, rStr.getLength());
auto nContextLen = nContextEnd - nContextBegin;
const SalLayoutGlyphs* pLayoutCache = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
- &rDevice, rStr, nContextBegin, nContextLen, 0, layoutCache);
+ &rDevice, rStr, nContextBegin, nContextLen, nIndex, nIndex + nLen, 0, layoutCache);
rDevice.GetPartialTextArray(rStr, &rDXAry, nContextBegin, nContextLen, nIndex, nLen, bCaret,
layoutCache, pLayoutCache);
}
diff --git a/vcl/CppunitTest_vcl_text.mk b/vcl/CppunitTest_vcl_text.mk
index 4ffa3b1990ca..d829c3fc24b5 100644
--- a/vcl/CppunitTest_vcl_text.mk
+++ b/vcl/CppunitTest_vcl_text.mk
@@ -17,6 +17,7 @@ $(eval $(call gb_CppunitTest_set_include,vcl_text,\
$(eval $(call gb_CppunitTest_add_exception_objects,vcl_text, \
vcl/qa/cppunit/canvasbitmaptest \
vcl/qa/cppunit/complextext \
+ vcl/qa/cppunit/justificationdata \
vcl/qa/cppunit/text \
))
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index c832e712e22f..a92d6281e0b0 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -242,6 +242,8 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
vcl/source/outdev/nativecontrols \
vcl/source/outdev/map \
vcl/source/text/ImplLayoutArgs \
+ vcl/source/text/ImplLayoutRuns \
+ vcl/source/text/mnemonic \
vcl/source/text/TextLayoutCache \
vcl/source/text/textlayout \
vcl/source/treelist/headbar \
@@ -261,8 +263,6 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
vcl/source/treelist/svimpbox \
vcl/source/treelist/svlbitm \
vcl/source/treelist/uiobject \
- vcl/source/text/ImplLayoutRuns \
- vcl/source/text/mnemonic \
vcl/source/gdi/formpdfexport \
vcl/source/gdi/configsettings \
vcl/source/gdi/cvtgrf \
diff --git a/vcl/inc/ImplLayoutArgs.hxx b/vcl/inc/ImplLayoutArgs.hxx
index faf170ca72b3..804fe28c3c3f 100644
--- a/vcl/inc/ImplLayoutArgs.hxx
+++ b/vcl/inc/ImplLayoutArgs.hxx
@@ -22,6 +22,9 @@
#include "impglyphitem.hxx"
#include "ImplLayoutRuns.hxx"
+#include "justificationdata.hxx"
+
+#include <span>
namespace vcl::text
{
@@ -34,13 +37,15 @@ public:
const OUString& mrStr;
int mnMinCharPos;
int mnEndCharPos;
+ int mnDrawOriginCluster = std::numeric_limits<int>::min();
+ int mnDrawMinCharPos = std::numeric_limits<int>::min();
+ int mnDrawEndCharPos = std::numeric_limits<int>::max();
// performance hack
vcl::text::TextLayoutCache const* m_pTextLayoutCache;
// positioning related inputs
- const double* mpDXArray; // in floating point pixel units
- const sal_Bool* mpKashidaArray;
+ JustificationData mstJustification;
double mnLayoutWidth; // in pixel units
Degree10 mnOrientation; // in 0-3600 system
@@ -52,15 +57,13 @@ public:
LanguageTag aLanguageTag, vcl::text::TextLayoutCache const* pLayoutCache);
void SetLayoutWidth(double nWidth);
- void SetDXArray(const double* pDXArray);
- void SetKashidaArray(const sal_Bool* pKashidaArray);
+ void SetJustificationData(JustificationData stJustification);
void SetOrientation(Degree10 nOrientation);
void ResetPos();
bool GetNextPos(int* nCharPos, bool* bRTL);
bool GetNextRun(int* nMinRunPos, int* nEndRunPos, bool* bRTL);
void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL);
- bool HasDXArray() const { return mpDXArray; }
// methods used by BiDi and glyph fallback
bool HasFallbackRun() const;
diff --git a/vcl/inc/impglyphitem.hxx b/vcl/inc/impglyphitem.hxx
index bb08031f3ab6..320adc27f667 100644
--- a/vcl/inc/impglyphitem.hxx
+++ b/vcl/inc/impglyphitem.hxx
@@ -53,7 +53,8 @@ class VCL_DLLPUBLIC GlyphItem
{
basegfx::B2DPoint m_aLinearPos; // absolute position of non rotated string
double m_nOrigWidth; // original glyph width
- sal_Int32 m_nCharPos; // index in string
+ sal_Int32 m_nCharPos; // index in string (by grapheme cluster)
+ sal_Int32 m_nOrigCharPos; // original index in string, if available
double m_nXOffset;
double m_nYOffset;
double m_nNewWidth; // width after adjustments
@@ -64,10 +65,11 @@ class VCL_DLLPUBLIC GlyphItem
public:
GlyphItem(int nCharPos, int nCharCount, sal_GlyphId aGlyphId,
const basegfx::B2DPoint& rLinearPos, GlyphItemFlags nFlags, double nOrigWidth,
- double nXOffset, double nYOffset)
+ double nXOffset, double nYOffset, int nOrigCharPos)
: m_aLinearPos(rLinearPos)
, m_nOrigWidth(nOrigWidth)
, m_nCharPos(nCharPos)
+ , m_nOrigCharPos(nOrigCharPos)
, m_nXOffset(nXOffset)
, m_nYOffset(nYOffset)
, m_nNewWidth(nOrigWidth)
@@ -97,6 +99,7 @@ public:
int charCount() const { return m_nCharCount; }
double origWidth() const { return m_nOrigWidth; }
int charPos() const { return m_nCharPos; }
+ int origCharPos() const { return m_nOrigCharPos; }
double xOffset() const { return m_nXOffset; }
double yOffset() const { return m_nYOffset; }
double newWidth() const { return m_nNewWidth; }
diff --git a/vcl/inc/justificationdata.hxx b/vcl/inc/justificationdata.hxx
new file mode 100644
index 000000000000..b8fff6149851
--- /dev/null
+++ b/vcl/inc/justificationdata.hxx
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+#pragma once
+
+#include <sal/types.h>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <optional>
+#include <vector>
+
+enum class ClusterJustificationFlags
+{
+ NONE = 0x0000,
+ KashidaPosition = 0x0001,
+};
+
+namespace o3tl
+{
+template <>
+struct typed_flags<ClusterJustificationFlags> : is_typed_flags<ClusterJustificationFlags, 0x0001>
+{
+};
+}
+
+class ClusterJustification
+{
+public:
+ double m_nTotalAdvance = 0.0;
+ ClusterJustificationFlags m_nFlags = ClusterJustificationFlags::NONE;
+};
+
+class JustificationData
+{
+private:
+ std::vector<ClusterJustification> m_aClusters;
+ sal_Int32 m_nBaseIndex = 0;
+ sal_Int32 m_nEndIndex = 0;
+ bool m_bContainsAdvances = false;
+ bool m_bContainsKashidaPositions = false;
+
+ [[nodiscard]] inline std::optional<size_t> GetIndex(sal_Int32 nClusterId) const
+ {
+ auto nIndex = nClusterId - m_nBaseIndex;
+ if (nIndex >= 0 && nIndex < static_cast<sal_Int32>(m_aClusters.size()))
+ {
+ return static_cast<size_t>(nIndex);
+ }
+
+ return std::nullopt;
+ }
+
+public:
+ JustificationData() = default;
+ JustificationData(sal_Int32 nBaseIndex, sal_Int32 nSize)
+ : m_nBaseIndex(nBaseIndex)
+ , m_nEndIndex(nBaseIndex + nSize)
+ {
+ m_aClusters.resize(nSize);
+ }
+
+ [[nodiscard]] bool empty() const { return m_aClusters.empty(); }
+
+ [[nodiscard]] bool ContainsAdvances() const { return m_bContainsAdvances; }
+ [[nodiscard]] bool ContainsKashidaPositions() const { return m_bContainsKashidaPositions; }
+
+ [[nodiscard]] double GetTotalAdvance(sal_Int32 nClusterId) const
+ {
+ if (nClusterId < m_nBaseIndex || m_aClusters.empty())
+ {
+ return 0.0;
+ }
+
+ if (nClusterId < m_nEndIndex)
+ {
+ return m_aClusters.at(nClusterId - m_nBaseIndex).m_nTotalAdvance;
+ }
+
+ return m_aClusters.back().m_nTotalAdvance;
+ }
+
+ [[nodiscard]] std::optional<bool> GetPositionHasKashida(sal_Int32 nClusterId) const
+ {
+ if (auto nIndex = GetIndex(nClusterId); nIndex.has_value())
+ {
+ return std::optional<bool>{ m_aClusters.at(*nIndex).m_nFlags
+ & ClusterJustificationFlags::KashidaPosition };
+ }
+
+ return std::nullopt;
+ }
+
+ void SetTotalAdvance(sal_Int32 nClusterId, double nValue)
+ {
+ if (auto nIndex = GetIndex(nClusterId); nIndex.has_value())
+ {
+ m_aClusters.at(*nIndex).m_nTotalAdvance = nValue;
+ m_bContainsAdvances = true;
+ }
+ }
+
+ void SetKashidaPosition(sal_Int32 nClusterId, bool bValue)
+ {
+ if (auto nIndex = GetIndex(nClusterId); nIndex.has_value())
+ {
+ if (bValue)
+ {
+ m_aClusters.at(*nIndex).m_nFlags |= ClusterJustificationFlags::KashidaPosition;
+ }
+ else
+ {
+ m_aClusters.at(*nIndex).m_nFlags &= ~ClusterJustificationFlags::KashidaPosition;
+ }
+
+ m_bContainsKashidaPositions = true;
+ }
+ }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx
index 483201f624fd..b0388ecd27a1 100644
--- a/vcl/inc/pdf/pdfwriter_impl.hxx
+++ b/vcl/inc/pdf/pdfwriter_impl.hxx
@@ -1256,7 +1256,9 @@ public:
/* actual drawing functions */
void drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines = true );
- void drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen );
+ void drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen,
+ sal_Int32 nLayoutContextIndex, sal_Int32 nLayoutContextLen);
void drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText,
sal_Int32 nIndex, sal_Int32 nLen );
void drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle );
diff --git a/vcl/inc/sallayout.hxx b/vcl/inc/sallayout.hxx
index 43b59e91a30c..49eb0c4ce93a 100644
--- a/vcl/inc/sallayout.hxx
+++ b/vcl/inc/sallayout.hxx
@@ -29,6 +29,7 @@
#include <vcl/vclenum.hxx> // for typedef sal_UCS4
#include <vcl/vcllayout.hxx>
+#include "justificationdata.hxx"
#include "ImplLayoutRuns.hxx"
#include "impglyphitem.hxx"
@@ -37,6 +38,7 @@
#include <hb.h>
#include <memory>
+#include <span>
#include <vector>
#define MAX_FALLBACK 16
@@ -81,9 +83,9 @@ public:
SAL_DLLPRIVATE void SetIncomplete(bool bIncomplete);
- SAL_DLLPRIVATE void ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
- vcl::text::ImplLayoutArgs& rMultiArgs,
- const double* pMultiDXArray);
+ SAL_DLLPRIVATE void ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const JustificationData& rstJustification);
SAL_DLLPRIVATE ImplLayoutRuns* GetFallbackRuns() { return maFallbackRuns; }
@@ -101,10 +103,9 @@ private:
class VCL_DLLPUBLIC GenericSalLayout : public SalLayout
{
- friend void MultiSalLayout::ImplAdjustMultiLayout(
- vcl::text::ImplLayoutArgs& rArgs,
- vcl::text::ImplLayoutArgs& rMultiArgs,
- const double* pMultiDXArray);
+ friend void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
+ vcl::text::ImplLayoutArgs& rMultiArgs,
+ const JustificationData& rstJustification);
public:
GenericSalLayout(LogicalFontInstance&);
@@ -144,7 +145,7 @@ private:
GenericSalLayout( const GenericSalLayout& ) = delete;
GenericSalLayout& operator=( const GenericSalLayout& ) = delete;
- SAL_DLLPRIVATE void ApplyDXArray(const double*, const sal_Bool*);
+ SAL_DLLPRIVATE void ApplyJustificationData(const JustificationData& rstJustification);
SAL_DLLPRIVATE void Justify(double nNewWidth);
SAL_DLLPRIVATE void ApplyAsianKerning(std::u16string_view rStr);
diff --git a/vcl/qa/cppunit/justificationdata.cxx b/vcl/qa/cppunit/justificationdata.cxx
new file mode 100644
index 000000000000..1c01e3ed00c5
--- /dev/null
+++ b/vcl/qa/cppunit/justificationdata.cxx
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <justificationdata.hxx>
+
+class JustificationDataTest : public CppUnit::TestFixture
+{
+public:
+ void testEmpty()
+ {
+ JustificationData stJust;
+ CPPUNIT_ASSERT(stJust.empty());
+
+ JustificationData stJust2{ /*nBaseIndex*/ 100, /*nSize*/ 5 };
+ CPPUNIT_ASSERT(!stJust2.empty());
+ }
+
+ void testContainsAdvances()
+ {
+ JustificationData stJust{ /*nBaseIndex*/ 100, /*nSize*/ 5 };
+ CPPUNIT_ASSERT(!stJust.ContainsAdvances());
+
+ // Try to set advances out of bounds
+ stJust.SetTotalAdvance(10, 1.0);
+ CPPUNIT_ASSERT(!stJust.ContainsAdvances());
+
+ stJust.SetTotalAdvance(200, 1.0);
+ CPPUNIT_ASSERT(!stJust.ContainsAdvances());
+
+ // Insert in bounds
+ stJust.SetTotalAdvance(102, 1.0);
+ CPPUNIT_ASSERT(stJust.ContainsAdvances());
+ }
+
+ void testAdvances()
+ {
+ JustificationData stJust{ /*nBaseIndex*/ 100, /*nSize*/ 3 };
+ stJust.SetTotalAdvance(99, 1.0);
+ stJust.SetTotalAdvance(100, 2.0);
+ stJust.SetTotalAdvance(101, 3.0);
+ stJust.SetTotalAdvance(102, 4.0);
+ stJust.SetTotalAdvance(103, 5.0);
+
+ // Total advance before the valid range must be 0
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, stJust.GetTotalAdvance(99), 0.05);
+
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(2.0, stJust.GetTotalAdvance(100), 0.05);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(3.0, stJust.GetTotalAdvance(101), 0.05);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, stJust.GetTotalAdvance(102), 0.05);
+
+ // Total advance after the valid range must be the last advance
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(4.0, stJust.GetTotalAdvance(103), 0.05);
+ }
+
+ void testContainsKashidaPositions()
+ {
+ JustificationData stJust{ /*nBaseIndex*/ 100, /*nSize*/ 5 };
+ CPPUNIT_ASSERT(!stJust.ContainsKashidaPositions());
+
+ // Try to set kashida positions out of bounds
+ stJust.SetKashidaPosition(10, true);
+ CPPUNIT_ASSERT(!stJust.ContainsKashidaPositions());
+
+ stJust.SetKashidaPosition(200, true);
+ CPPUNIT_ASSERT(!stJust.ContainsKashidaPositions());
+
+ // Insert in bounds
+ stJust.SetKashidaPosition(102, true);
+ CPPUNIT_ASSERT(stJust.ContainsKashidaPositions());
+ }
+
+ void testKashidaPositions()
+ {
+ JustificationData stJust{ /*nBaseIndex*/ 100, /*nSize*/ 1 };
+
+ CPPUNIT_ASSERT(!stJust.GetPositionHasKashida(99).has_value());
+ CPPUNIT_ASSERT(!stJust.GetPositionHasKashida(100).value_or(true));
+ CPPUNIT_ASSERT(!stJust.GetPositionHasKashida(101).has_value());
+
+ stJust.SetKashidaPosition(99, true);
+ stJust.SetKashidaPosition(100, true);
+ stJust.SetKashidaPosition(101, true);
+
+ CPPUNIT_ASSERT(!stJust.GetPositionHasKashida(99).has_value());
+ CPPUNIT_ASSERT(stJust.GetPositionHasKashida(100).value_or(false));
+ CPPUNIT_ASSERT(!stJust.GetPositionHasKashida(101).has_value());
+
+ stJust.SetKashidaPosition(100, false);
+
+ CPPUNIT_ASSERT(!stJust.GetPositionHasKashida(100).value_or(true));
+ }
+
+ CPPUNIT_TEST_SUITE(JustificationDataTest);
+ CPPUNIT_TEST(testEmpty);
+ CPPUNIT_TEST(testContainsAdvances);
+ CPPUNIT_TEST(testAdvances);
+ CPPUNIT_TEST(testContainsKashidaPositions);
+ CPPUNIT_TEST(testKashidaPositions);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(JustificationDataTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf124116-hebrew-track-untrack.odt b/vcl/qa/cppunit/pdfexport/data/tdf124116-hebrew-track-untrack.odt
new file mode 100644
index 000000000000..e3f3becee3a8
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf124116-hebrew-track-untrack.odt
Binary files differ
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf134226-shadda-in-hidden-span.fodt b/vcl/qa/cppunit/pdfexport/data/tdf134226-shadda-in-hidden-span.fodt
new file mode 100644
index 000000000000..635ffc86883c
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf134226-shadda-in-hidden-span.fodt
@@ -0,0 +1,328 @@
+<?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-05-17T12:26:58.973389885</meta:creation-date><dc:date>2024-05-17T13:33:16.344029125</dc:date><meta:editing-duration>PT12M29S</meta:editing-duration><meta:editing-cycles>16</meta:editing-cycles><meta:generator>LibreOffice/24.2.3.2$Linux_X86_64 LibreOffice_project/4f88f79086d18691a72ac668802d5bc5b5a88122</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="2" meta:character-count="6" meta:non-whitespace-character-count="6"/></office:meta>
+ <office:settings>
+ <config:config-item-set config:name="ooo:view-settings">
+ <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaWidth" config:type="long">46651</config:config-item>
+ <config:config-item config:name="ViewAreaHeight" config:type="long">29386</config:config-item>
+ <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
+ <config:config-item-map-indexed config:name="Views">
+ <config:config-item-map-entry>
+ <config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
+ <config:config-item config:name="ViewLeft" config:type="long">26141</config:config-item>
+ <config:config-item config:name="ViewTop" config:type="long">9654</config:config-item>
+ <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleRight" config:type="long">46650</config:config-item>
+ <config:config-item config:name="VisibleBottom" config:type="long">29385</config:config-item>
+ <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
+ <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item>
+ <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ZoomFactor" config:type="short">160</config:config-item>
+ <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="LegacySingleLineFontwork" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ConnectorUseSnapRect" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreBreakAfterMultilineField" config:type="boolean">false</config:config-item>
+ </config:config-item-map-entry>
+ </config:config-item-map-indexed>
+ </config:config-item-set>
+ <config:config-item-set config:name="ooo:configuration-settings">
+ <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintFaxName" config:type="string"/>
+ <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
+ <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="NoNumberingShowFollowBy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HyphenateURLs" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ImagePreferredDPI" config:type="int">0</config:config-item>
+ <config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AutoFirstLineIndentDisregardLineSpace" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
+ <config:config-item config:name="JustifyLinesWithShrinking" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RsidRoot" config:type="int">200646</config:config-item>
+ <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
+ <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
+ <config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
+ <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
+ <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterName" config:type="string"/>
+ <config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
+ <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
+ <config:config-item config:name="UseVariableWidthNBSP" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
+ <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="NoGapAfterNoteNumber" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DropCapPunctuation" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
+ <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="Rsid" config:type="int">754914</config:config-item>
+ <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item>
+ </config:config-item-set>
+ </office:settings>
+ <office:scripts>
+ <office:script script:language="ooo:Basic">
+ <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <ooo:library-embedded ooo:name="Standard"/>
+ </ooo:libraries>
+ </office:script>
+ </office:scripts>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Mono" svg:font-family="&apos;Liberation Mono&apos;" style:font-family-generic="modern" style:font-pitch="fixed"/>
+ <style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans" svg:font-family="&apos;Noto Sans&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans Arabic" svg:font-family="&apos;Noto Sans Arabic&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans Mono" svg:font-family="&apos;Noto Sans Mono&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Tahoma" svg:font-family="Tahoma" style:font-family-generic="swiss"/>
+ <style:font-face style:name="Tahoma1" svg:font-family="Tahoma" 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.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" 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 Sans" style:font-size-asian="10.5pt" style:font-name-complex="Tahoma1" 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" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" 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 Sans" style:font-size-asian="10.5pt" style:font-name-complex="Tahoma1" 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-asian="Noto Sans" style:font-family-asian="&apos;Noto Sans&apos;" style:font-style-name-asian="Regular" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Noto Sans Arabic" style:font-family-complex="&apos;Noto Sans Arabic&apos;" style:font-style-name-complex="Regular" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="96pt" style:language-complex="ar" style:country-complex="SA"/>
+ </style:style>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-complex="Tahoma1" style:font-family-complex="Tahoma" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+ <style:text-properties style:font-name-complex="Tahoma" style:font-family-complex="Tahoma" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+ </style:style>
+ <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+ <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+ <style:text-properties style:font-name-complex="Tahoma" style:font-family-complex="Tahoma" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <style:style style:name="Addressee" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.106cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ </style:style>
+ <style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
+ <style:paragraph-properties fo:margin-left="1cm" fo:margin-right="1cm" fo:margin-top="0cm" fo:margin-bottom="0.499cm" style:contextual-spacing="false" fo:text-indent="0cm" style:auto-text-indent="false"/>
+ </style:style>
+ <style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:orphans="0" fo:widows="0" text:number-lines="false" text:line-number="0"/>
+ </style:style>
+ <style:style style:name="Signature" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+ </style:style>
+ <style:style style:name="Sender" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.106cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ </style:style>
+ <style:style style:name="Preformatted_20_Text" style:display-name="Preformatted Text" style:family="paragraph" style:parent-style-name="Standard" style:class="html">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false"/>
+ <style:text-properties style:font-name="Liberation Mono" fo:font-family="&apos;Liberation Mono&apos;" style:font-family-generic="modern" style:font-pitch="fixed" fo:font-size="10pt" style:font-name-asian="Noto Sans Mono" style:font-family-asian="&apos;Noto Sans Mono&apos;" style:font-style-name-asian="Regular" style:font-family-generic-asian="swiss" style:font-pitch-asian="variable" style:font-size-asian="10pt" style:font-name-complex="Liberation Mono" style:font-family-complex="&apos;Liberation Mono&apos;" style:font-family-generic-complex="modern" style:font-pitch-complex="fixed" style:font-size-complex="10pt"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office">
+ <loext:theme-colors loext:name="LibreOffice">
+ <loext:color loext:name="dark1" loext:color="#000000"/>
+ <loext:color loext:name="light1" loext:color="#ffffff"/>
+ <loext:color loext:name="dark2" loext:color="#000000"/>
+ <loext:color loext:name="light2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+ <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+ </loext:theme-colors>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:paragraph-properties fo:text-align="end" style:justify-single-word="false" style:writing-mode="rl-tb"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties fo:font-weight="normal" style:font-weight-complex="normal"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" 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>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </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:p>
+ <text:p text:style-name="P1">شق<text:span text:style-name="T1">ّ</text:span>ة</text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf71956-styled-diacritics.fodt b/vcl/qa/cppunit/pdfexport/data/tdf71956-styled-diacritics.fodt
new file mode 100644
index 000000000000..408744885c2d
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf71956-styled-diacritics.fodt
@@ -0,0 +1,320 @@
+<?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-05-17T12:26:58.973389885</meta:creation-date><dc:date>2024-05-17T12:40:53.077636652</dc:date><meta:editing-duration>PT9M18S</meta:editing-duration><meta:editing-cycles>6</meta:editing-cycles><meta:generator>LibreOffice/24.2.3.2$Linux_X86_64 LibreOffice_project/4f88f79086d18691a72ac668802d5bc5b5a88122</meta:generator><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="2" meta:character-count="6" meta:non-whitespace-character-count="6"/></office:meta>
+ <office:settings>
+ <config:config-item-set config:name="ooo:view-settings">
+ <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="ViewAreaWidth" config:type="long">46701</config:config-item>
+ <config:config-item config:name="ViewAreaHeight" config:type="long">29438</config:config-item>
+ <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
+ <config:config-item-map-indexed config:name="Views">
+ <config:config-item-map-entry>
+ <config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
+ <config:config-item config:name="ViewLeft" config:type="long">32143</config:config-item>
+ <config:config-item config:name="ViewTop" config:type="long">2501</config:config-item>
+ <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
+ <config:config-item config:name="VisibleRight" config:type="long">46699</config:config-item>
+ <config:config-item config:name="VisibleBottom" config:type="long">29436</config:config-item>
+ <config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
+ <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item>
+ <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ZoomFactor" config:type="short">160</config:config-item>
+ <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="LegacySingleLineFontwork" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ConnectorUseSnapRect" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreBreakAfterMultilineField" config:type="boolean">false</config:config-item>
+ </config:config-item-map-entry>
+ </config:config-item-map-indexed>
+ </config:config-item-set>
+ <config:config-item-set config:name="ooo:configuration-settings">
+ <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintFaxName" config:type="string"/>
+ <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
+ <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="NoNumberingShowFollowBy" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HyphenateURLs" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ImagePreferredDPI" config:type="int">0</config:config-item>
+ <config:config-item config:name="FootnoteInColumnToPageEnd" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="GutterAtTop" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ContinuousEndnotes" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="FrameAutowidthWithMorePara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AutoFirstLineIndentDisregardLineSpace" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedComplexScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmbedAsianScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectBookmarks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
+ <config:config-item config:name="JustifyLinesWithShrinking" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RsidRoot" config:type="int">200646</config:config-item>
+ <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
+ <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
+ <config:config-item config:name="SaveThumbnail" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
+ <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
+ <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="HeaderSpacingBelowLastPara" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="PrinterName" config:type="string"/>
+ <config:config-item config:name="AddParaLineSpacingToTableCells" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
+ <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedLatinScriptFonts" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
+ <config:config-item config:name="UseVariableWidthNBSP" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmbedOnlyUsedFonts" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ProtectFields" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
+ <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="NoGapAfterNoteNumber" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="EmptyDbFieldHidesPara" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="DropCapPunctuation" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
+ <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="Rsid" config:type="int">313543</config:config-item>
+ <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
+ <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="MsWordCompMinLineHeightByFly" config:type="boolean">false</config:config-item>
+ <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item>
+ </config:config-item-set>
+ </office:settings>
+ <office:scripts>
+ <office:script script:language="ooo:Basic">
+ <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
+ </office:script>
+ </office:scripts>
+ <office:font-face-decls>
+ <style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
+ <style:font-face style:name="Noto Sans Arabic" svg:font-family="&apos;Noto Sans Arabic&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
+ <style:font-face style:name="Tahoma" svg:font-family="Tahoma" style:font-family-generic="swiss"/>
+ <style:font-face style:name="Tahoma1" svg:font-family="Tahoma" 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.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" style:flow-with-text="false"/>
+ <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" 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-complex="Tahoma1" style:font-size-complex="12pt" style:language-complex="fa" style:country-complex="IR"/>
+ </style:default-style>
+ <style:default-style style:family="paragraph">
+ <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" 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-complex="Tahoma1" style:font-size-complex="12pt" style:language-complex="fa" style:country-complex="IR" 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="Noto Sans Arabic" style:font-family-complex="&apos;Noto Sans Arabic&apos;" style:font-style-name-complex="Regular" style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" style:font-size-complex="96pt" style:language-complex="ar" style:country-complex="SA"/>
+ </style:style>
+ <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
+ <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/>
+ <style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-complex="Tahoma1" style:font-family-complex="Tahoma" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
+ </style:style>
+ <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+ <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/>
+ </style:style>
+ <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
+ <style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
+ <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/>
+ </style:style>
+ <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
+ <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+ <style:text-properties style:font-size-asian="12pt" style:font-name-complex="Tahoma" style:font-family-complex="Tahoma" style:font-family-generic-complex="swiss"/>
+ </style:style>
+ <text:outline-style style:name="Outline">
+ <text:outline-level-style text:level="1" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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" loext:num-list-format="%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.499cm" style:num-format="1" text:number-position="left" text:increment="5"/>
+ <loext:theme loext:name="Office">
+ <loext:theme-colors loext:name="LibreOffice">
+ <loext:color loext:name="dark1" loext:color="#000000"/>
+ <loext:color loext:name="light1" loext:color="#ffffff"/>
+ <loext:color loext:name="dark2" loext:color="#000000"/>
+ <loext:color loext:name="light2" loext:color="#ffffff"/>
+ <loext:color loext:name="accent1" loext:color="#18a303"/>
+ <loext:color loext:name="accent2" loext:color="#0369a3"/>
+ <loext:color loext:name="accent3" loext:color="#a33e03"/>
+ <loext:color loext:name="accent4" loext:color="#8e03a3"/>
+ <loext:color loext:name="accent5" loext:color="#c99c00"/>
+ <loext:color loext:name="accent6" loext:color="#c9211e"/>
+ <loext:color loext:name="hyperlink" loext:color="#0000ee"/>
+ <loext:color loext:name="followed-hyperlink" loext:color="#551a8b"/>
+ </loext:theme-colors>
+ </loext:theme>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
+ <style:paragraph-properties fo:text-align="end" style:justify-single-word="false" style:writing-mode="rl-tb"/>
+ <style:text-properties officeooo:paragraph-rsid="00040e0a"/>
+ </style:style>
+ <style:style style:name="T1" style:family="text">
+ <style:text-properties fo:color="#ff0000" loext:opacity="100%"/>
+ </style:style>
+ <style:style style:name="T2" style:family="text">
+ <style:text-properties fo:color="#ff8000" loext:opacity="100%"/>
+ </style:style>
+ <style:style style:name="T3" style:family="text">
+ <style:text-properties fo:color="#ffbf00" loext:opacity="100%"/>
+ </style:style>
+ <style:style style:name="T4" style:family="text">
+ <style:text-properties fo:color="#81d41a" loext:opacity="100%"/>
+ </style:style>
+ <style:style style:name="T5" style:family="text">
+ <style:text-properties fo:color="#00a933" loext:opacity="100%"/>
+ </style:style>
+ <style:style style:name="T6" style:family="text">
+ <style:text-properties fo:color="#2a6099" loext:opacity="100%"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="21.59cm" fo:page-height="27.94cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+ <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" 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>
+ <style:style style:name="dp1" style:family="drawing-page">
+ <style:drawing-page-properties draw:background-size="full"/>
+ </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/>
+ </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:p>
+ <text:p text:style-name="P1"><text:span text:style-name="T1">ل</text:span><text:span text:style-name="T2">ل</text:span><text:span text:style-name="T3">َ</text:span><text:span text:style-name="T4">ّ</text:span><text:span text:style-name="T5">ٰ</text:span><text:span text:style-name="T6">ه</text:span></text:p>
+ </office:text>
+ </office:body>
+</office:document>
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
index a0b0e96d32ab..8502a6b5593a 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -5048,6 +5048,185 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf61444)
CPPUNIT_ASSERT_DOUBLES_EQUAL(solid_extent, color_extent, /*delta*/ 0.15);
}
+// tdf#124116 - Tests that track-changes inside a grapheme cluster does not break positioning
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf124116TrackUntrack)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf124116-hebrew-track-untrack.odt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Get the first page
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
+ CPPUNIT_ASSERT(pPdfPage);
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pTextPage);
+
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ CPPUNIT_ASSERT_EQUAL(15, 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;
+ }
+ }
+
+ // The underlying document has 4 lines:
+ // - שמחַ plain
+ // - שמחַ tracked
+ // - שמחַ with patah tracked
+ // - שמחַ with everything except patah tracked
+ // ---
+ // However, due to the way text items are inserted for Hebrew, there will be 10:
+ // - het with an improperly spaced patah, then שמ for the first 2 lines
+ // - as above, followed by a blank for the next 2 representing the actual diacritic
+ // ---
+ // This test will likely need to be rewritten if tdf#158329 is fixed.
+ CPPUNIT_ASSERT_EQUAL(10, nTextObjectCount);
+
+ // All that matters for this test is that the patah is positioned well under the het
+ auto het_x0 = aRect.at(4).getMinX();
+ auto patah_x0 = aRect.at(6).getMinX();
+ CPPUNIT_ASSERT_GREATER(10.0, patah_x0 - het_x0);
+
+ auto het_x1 = aRect.at(7).getMinX();
+ auto patah_x1 = aRect.at(9).getMinX();
+ CPPUNIT_ASSERT_GREATER(10.0, patah_x1 - het_x1);
+}
+
+// tdf#134226 - Tests that shaping is not broken by invisible spans
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf134226)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf134226-shadda-in-hidden-span.fodt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Get the first page
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
+ CPPUNIT_ASSERT(pPdfPage);
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pTextPage);
+
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ CPPUNIT_ASSERT_EQUAL(8, 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(8, nTextObjectCount);
+
+ CPPUNIT_ASSERT_EQUAL(u"ة"_ustr, aText[0].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[1].trim());
+ CPPUNIT_ASSERT_EQUAL(u"\u0651ق"_ustr, aText[2].trim());
+ CPPUNIT_ASSERT_EQUAL(u"ش"_ustr, aText[3].trim());
+ CPPUNIT_ASSERT_EQUAL(u"\u0651ق"_ustr, aText[4].trim());
+ CPPUNIT_ASSERT_EQUAL(u"ش"_ustr, aText[5].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[6].trim());
+ CPPUNIT_ASSERT_EQUAL(u"ة"_ustr, aText[7].trim());
+
+ // Verify that the corresponding text segments are positioned roughly equally
+ auto fnEqualPos
+ = [](const basegfx::B2DRectangle& stExpected, const basegfx::B2DRectangle& stFound) {
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMinX(), stFound.getMinX(), /*delta*/ 0.15);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMaxX(), stFound.getMaxX(), /*delta*/ 0.15);
+ };
+
+ fnEqualPos(aRect[0], aRect[7]);
+ fnEqualPos(aRect[1], aRect[6]);
+ fnEqualPos(aRect[2], aRect[4]);
+ fnEqualPos(aRect[3], aRect[5]);
+}
+
+// tdf#71956 - Tests that glyphs can be individually styled
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf71956)
+{
+ aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export");
+ saveAsPDF(u"tdf71956-styled-diacritics.fodt");
+ std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+
+ CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+ // Get the first page
+ std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
+ CPPUNIT_ASSERT(pPdfPage);
+ std::unique_ptr<vcl::pdf::PDFiumTextPage> pTextPage = pPdfPage->getTextPage();
+ CPPUNIT_ASSERT(pTextPage);
+
+ int nPageObjectCount = pPdfPage->getObjectCount();
+ CPPUNIT_ASSERT_EQUAL(12, 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(12, nTextObjectCount);
+
+ CPPUNIT_ASSERT_EQUAL(u"ه"_ustr, aText[0].trim());
+ CPPUNIT_ASSERT_EQUAL(u"\u064e\u0651\u0670ل"_ustr, aText[1].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[2].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[3].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[4].trim());
+ CPPUNIT_ASSERT_EQUAL(u"ل"_ustr, aText[5].trim());
+ CPPUNIT_ASSERT_EQUAL(u"ل"_ustr, aText[6].trim());
+ CPPUNIT_ASSERT_EQUAL(u"\u064e\u0651\u0670ل"_ustr, aText[7].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[8].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[9].trim());
+ CPPUNIT_ASSERT_EQUAL(u""_ustr, aText[10].trim());
+ CPPUNIT_ASSERT_EQUAL(u"ه"_ustr, aText[11].trim());
+
+ // Verify that the corresponding text segments are positioned roughly equally
+ auto fnEqualPos
+ = [](const basegfx::B2DRectangle& stExpected, const basegfx::B2DRectangle& stFound) {
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMinX(), stFound.getMinX(), /*delta*/ 0.15);
+ CPPUNIT_ASSERT_DOUBLES_EQUAL(stExpected.getMaxX(), stFound.getMaxX(), /*delta*/ 0.15);
+ };
+
+ fnEqualPos(aRect[0], aRect[11]);
+ fnEqualPos(aRect[1], aRect[10]);
+ fnEqualPos(aRect[2], aRect[8]);
+ fnEqualPos(aRect[3], aRect[9]);
+ fnEqualPos(aRect[4], aRect[7]);
+ fnEqualPos(aRect[5], aRect[6]);
+}
+
} // end anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/vcl/qa/cppunit/svm/data/textarraycontext.svm b/vcl/qa/cppunit/svm/data/textarraycontext.svm
new file mode 100644
index 000000000000..b4ebb2a1b95b
--- /dev/null
+++ b/vcl/qa/cppunit/svm/data/textarraycontext.svm
Binary files differ
diff --git a/vcl/qa/cppunit/svm/svmtest.cxx b/vcl/qa/cppunit/svm/svmtest.cxx
index b3b58b3f4783..f3773424ea65 100644
--- a/vcl/qa/cppunit/svm/svmtest.cxx
+++ b/vcl/qa/cppunit/svm/svmtest.cxx
@@ -106,6 +106,9 @@ class SvmTest : public test::BootstrapFixture, public XmlTestTools
void checkTextArray(const GDIMetaFile& rMetaFile);
void testTextArray();
+ void checkTextArrayWithContext(const GDIMetaFile& rMetaFile);
+ void testTextArrayWithContext();
+
void checkstretchText(const GDIMetaFile& rMetaFile);
void teststretchText();
@@ -225,6 +228,7 @@ public:
CPPUNIT_TEST(testPolyPolygon);
CPPUNIT_TEST(testText);
CPPUNIT_TEST(testTextArray);
+ CPPUNIT_TEST(testTextArrayWithContext);
CPPUNIT_TEST(teststretchText);
CPPUNIT_TEST(testTextRect);
CPPUNIT_TEST(testTextLine);
@@ -868,6 +872,33 @@ void SvmTest::testTextArray()
checkTextArray(readFile(u"textarray.svm"));
}
+void SvmTest::checkTextArrayWithContext(const GDIMetaFile& rMetaFile)
+{
+ xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
+
+ assertXPathAttrs(pDoc, "/metafile/textarray[1]"_ostr,
+ { { "x", "4" },
+ { "y", "6" },
+ { "index", "1" },
+ { "length", "4" },
+ { "layoutcontextindex", "0" },
+ { "layoutcontextlength", "5" } });
+ assertXPathContent(pDoc, "/metafile/textarray[1]/dxarray"_ostr, "15 20 25 ");
+ assertXPathContent(pDoc, "/metafile/textarray[1]/text"_ostr, "123456");
+}
+
+void SvmTest::testTextArrayWithContext()
+{
+ GDIMetaFile aGDIMetaFile;
+ ScopedVclPtrInstance<VirtualDevice> pVirtualDev;
+ setupBaseVirtualDevice(*pVirtualDev, aGDIMetaFile);
+ sal_Int32 const aDX[] = { 10, 15, 20, 25, 30, 35 };
+ pVirtualDev->DrawPartialTextArray(Point(4, 6), "123456", KernArraySpan(aDX), {}, 0, 5, 1, 4);
+
+ checkTextArrayWithContext(writeAndReadStream(aGDIMetaFile));
+ checkTextArrayWithContext(readFile(u"textarraycontext.svm"));
+}
+
void SvmTest::checkstretchText(const GDIMetaFile& rMetaFile)
{
xmlDocUniquePtr pDoc = dumpMeta(rMetaFile);
diff --git a/vcl/source/filter/svm/SvmReader.cxx b/vcl/source/filter/svm/SvmReader.cxx
index c04ebbec2222..aa2468c11d1d 100644
--- a/vcl/source/filter/svm/SvmReader.cxx
+++ b/vcl/source/filter/svm/SvmReader.cxx
@@ -729,6 +729,34 @@ rtl::Reference<MetaAction> SvmReader::TextArrayHandler(const ImplMetaReadData* p
}
}
+ if (aCompat.GetVersion() >= 4) // Version 4
+ {
+ bool bTmpHasContext = false;
+ mrStream.ReadCharAsBool(bTmpHasContext);
+
+ if (bTmpHasContext)
+ {
+ sal_uInt16 nTmpContextIndex = 0;
+ mrStream.ReadUInt16(nTmpContextIndex);
+
+ sal_uInt16 nTmpContextLen = 0;
+ mrStream.ReadUInt16(nTmpContextLen);
+
+ sal_uInt16 nTmpEnd = nTmpIndex + nTmpLen;
+ sal_uInt16 nTmpContextEnd = nTmpContextIndex + nTmpContextLen;
+ if ((nTmpContextEnd <= aStr.getLength()) && (nTmpContextIndex <= nTmpIndex)
+ && (nTmpContextEnd >= nTmpEnd))
+ {
+ pAction->SetLayoutContextIndex(nTmpContextIndex);
+ pAction->SetLayoutContextLen(nTmpContextLen);
+ }
+ else
+ {
+ SAL_WARN("vcl.gdi", "inconsistent layout context offset and len");
+ }
+ }
+ }
+
return pAction;
}
diff --git a/vcl/source/filter/svm/SvmWriter.cxx b/vcl/source/filter/svm/SvmWriter.cxx
index 09c7cce21e87..ca0832dc2d63 100644
--- a/vcl/source/filter/svm/SvmWriter.cxx
+++ b/vcl/source/filter/svm/SvmWriter.cxx
@@ -987,7 +987,7 @@ void SvmWriter::TextArrayHandler(const MetaTextArrayAction* pAction, const ImplM
const sal_Int32 nAryLen = !rDXArray.empty() ? pAction->GetLen() : 0;
- VersionCompatWrite aCompat(mrStream, 3);
+ VersionCompatWrite aCompat(mrStream, 4);
TypeSerializer aSerializer(mrStream);
aSerializer.writePoint(pAction->GetPoint());
mrStream.WriteUniOrByteString(pAction->GetText(), pData->meActualCharSet);
@@ -1005,6 +1005,15 @@ void SvmWriter::TextArrayHandler(const MetaTextArrayAction* pAction, const ImplM
mrStream.WriteUInt32(rKashidaArray.size());
for (const auto& val : rKashidaArray)
mrStream.WriteUChar(val);
+
+ // Version 4
+ bool bHasLayoutContext = (pAction->GetLayoutContextIndex() >= 0);
+ mrStream.WriteBool(bHasLayoutContext);
+ if (bHasLayoutContext)
+ {
+ mrStream.WriteUInt16(pAction->GetLayoutContextIndex());
+ mrStream.WriteUInt16(pAction->GetLayoutContextLen());
+ }
}
void SvmWriter::StretchTextHandler(const MetaStretchTextAction* pAction,
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
index 8211b9ea6aa9..a3646b69edc2 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -43,6 +43,7 @@
#include <map>
#include <memory>
+#include <set>
GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
: m_GlyphItems(rFont)
@@ -81,6 +82,135 @@ struct SubRun
hb_direction_t maDirection;
};
+struct UnclusteredGlyphData
+{
+ sal_Int32 m_nGlyphId;
+ bool m_bUsed = false;
+
+ explicit UnclusteredGlyphData(sal_Int32 nGlyphId)
+ : m_nGlyphId(nGlyphId)
+ {
+ }
+};
+
+// This is a helper class to enable correct styling and glyph placement when a grapheme cluster is
+// split across multiple adjoining layouts.
+//
+// In order to justify text, we need glyphs grouped into grapheme clusters so diacritics will stay
+// attached to characters under adjustment. However, in order to correctly position and style
+// grapheme clusters that span multiple layouts, we need best-effort character-level position data.
+//
+// At time of writing, HarfBuzz cannot provide both types of information simultaneously. As a work-
+// around, this helper class runs HarfBuzz a second time to get the missing information. Should a
+// future version of HarfBuzz support this use case directly, this helper code should be deleted.
+//
+// See tdf#61444, tdf#71956, tdf#124116
+class UnclusteredGlyphMapper
+{
+private:
+ hb_buffer_t* m_pHbBuffer = nullptr;
+ std::multimap<sal_Int32, UnclusteredGlyphData> m_aGlyphs;
+ bool m_bEnable = false;
+
+public:
+ UnclusteredGlyphMapper(bool bEnable, int nGlyphCapacity)
+ : m_bEnable(bEnable)
+ {
+ if (!m_bEnable)
+ {
+ return;
+ }
+
+ m_pHbBuffer = hb_buffer_create();
+ hb_buffer_pre_allocate(m_pHbBuffer, nGlyphCapacity);
+ }
+
+ ~UnclusteredGlyphMapper()
+ {
+ if (m_bEnable)
+ {
+ hb_buffer_destroy(m_pHbBuffer);
+ }
+ }
+
+ [[nodiscard]] sal_Int32 RemapGlyph(sal_Int32 nClusterId, sal_Int32 nGlyphId)
+ {
+ if (auto it = m_aGlyphs.lower_bound(nClusterId); it != m_aGlyphs.end())
+ {
+ for (; it != m_aGlyphs.end(); ++it)
+ {
+ if (it->second.m_nGlyphId == nGlyphId && !it->second.m_bUsed)
+ {
+ it->second.m_bUsed = true;
+ return it->first;
+ }
+ }
+ }
+
+ return nClusterId;
+ }
+
+ void ShapeSubRun(const sal_Unicode* pStr, const int nLength, const SubRun& aSubRun,
+ hb_font_t* pHbFont, const std::vector<hb_feature_t>& maFeatures,
+ hb_language_t oHbLanguage)
+ {
+ if (!m_bEnable)
+ {
+ return;
+ }
+
+ m_aGlyphs.clear();
+
+ hb_buffer_clear_contents(m_pHbBuffer);
+
+ const int nMinRunPos = aSubRun.mnMin;
+ const int nEndRunPos = aSubRun.mnEnd;
+ const int nRunLen = nEndRunPos - nMinRunPos;
+
+ int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
+ nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
+
+ if (nMinRunPos == 0)
+ {
+ nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
+ }
+
+ if (nEndRunPos == nLength)
+ {
+ nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
+ }
+
+ hb_buffer_set_flags(m_pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
+
+ hb_buffer_set_cluster_level(m_pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS);
+
+ hb_buffer_set_direction(m_pHbBuffer, aSubRun.maDirection);
+ hb_buffer_set_script(m_pHbBuffer, aSubRun.maScript);
+ hb_buffer_set_language(m_pHbBuffer, oHbLanguage);
+
+ hb_buffer_add_utf16(m_pHbBuffer, reinterpret_cast<uint16_t const*>(pStr), nLength,
+ nMinRunPos, nRunLen);
+
+ // The shapers that we want HarfBuzz to use, in the order of
+ // preference.
+ const char* const pHbShapers[] = { "graphite2", "ot", "fallback", nullptr };
+ bool ok
+ = hb_shape_full(pHbFont, m_pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
+ assert(ok);
+ (void)ok;
+
+ int nRunGlyphCount = hb_buffer_get_length(m_pHbBuffer);
+ hb_glyph_info_t* pHbGlyphInfos = hb_buffer_get_glyph_infos(m_pHbBuffer, nullptr);
+
+ for (int i = 0; i < nRunGlyphCount; ++i)
+ {
+ int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
+ int32_t nCharPos = pHbGlyphInfos[i].cluster;
+
+ m_aGlyphs.emplace(nCharPos, UnclusteredGlyphData{ nGlyphIndex });
+ }
+ }
+};
}
namespace {
@@ -155,14 +285,20 @@ void GenericSalLayout::AdjustLayout(vcl::text::ImplLayoutArgs& rArgs)
{
SalLayout::AdjustLayout(rArgs);
- if (rArgs.mpDXArray)
- ApplyDXArray(rArgs.mpDXArray, rArgs.mpKashidaArray);
+ if (!rArgs.mstJustification.empty())
+ {
+ ApplyJustificationData(rArgs.mstJustification);
+ }
else if (rArgs.mnLayoutWidth)
+ {
Justify(rArgs.mnLayoutWidth);
- // apply asian kerning if the glyphs are not already formatted
+ }
else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
&& !(rArgs.mnFlags & SalLayoutFlags::Vertical))
+ {
+ // apply asian kerning if the glyphs are not already formatted
ApplyAsianKerning(rArgs.mrStr);
+ }
}
void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
@@ -263,6 +399,10 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
nBaseOffset = ( extents.ascender + extents.descender ) / 2.0;
}
+ UnclusteredGlyphMapper stClusterMapper{
+ bool{ rArgs.mnFlags & SalLayoutFlags::UnclusteredGlyphs }, nGlyphCapacity
+ };
+
hb_buffer_t* pHbBuffer = hb_buffer_create();
hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
@@ -405,15 +545,21 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
+
+ hb_language_t oHbLanguage = nullptr;
if (!msLanguage.isEmpty())
{
- hb_buffer_set_language(pHbBuffer, hb_language_from_string(msLanguage.getStr(), msLanguage.getLength()));
+ oHbLanguage = hb_language_from_string(msLanguage.getStr(), msLanguage.getLength());
}
else
{
- OString sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
- hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()));
+ OString sLanguage
+ = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
+ oHbLanguage = hb_language_from_string(sLanguage.getStr(), sLanguage.getLength());
}
+
+ hb_buffer_set_language(pHbBuffer, oHbLanguage);
+
hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
hb_buffer_add_utf16(
pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
@@ -426,6 +572,9 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
assert(ok);
(void) ok;
+ // Populate glyph cluster remapping data
+ stClusterMapper.ShapeSubRun(pStr, nLength, aSubRun, pHbFont, maFeatures, oHbLanguage);
+
int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
@@ -494,9 +643,13 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
// if needed request glyph fallback by updating LayoutArgs
if (!nGlyphIndex)
{
- SetNeedFallback(rArgs, nCharPos, bRightToLeft);
- if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
- continue;
+ // Only request fallback for grapheme clusters that are drawn
+ if (nCharPos >= rArgs.mnDrawMinCharPos && nCharPos < rArgs.mnDrawEndCharPos)
+ {
+ SetNeedFallback(rArgs, nCharPos, bRightToLeft);
+ if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
+ continue;
+ }
}
GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
@@ -562,10 +715,20 @@ bool GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
basegfx::B2DPoint aNewPos(aCurrPos.getX() + nXOffset, aCurrPos.getY() + nYOffset);
const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
- nAdvance, nXOffset, nYOffset);
- m_GlyphItems.push_back(aGI);
+ nAdvance, nXOffset, nYOffset,
+ stClusterMapper.RemapGlyph(nCharPos, nGlyphIndex));
- aCurrPos.adjustX(nAdvance);
+ if (aGI.origCharPos() >= rArgs.mnDrawMinCharPos
+ && aGI.origCharPos() < rArgs.mnDrawEndCharPos)
+ {
+ m_GlyphItems.push_back(aGI);
+ }
+
+ if (aGI.origCharPos() >= rArgs.mnDrawOriginCluster
+ && aGI.origCharPos() < rArgs.mnDrawEndCharPos)
+ {
+ aCurrPos.adjustX(nAdvance);
+ }
}
}
}
@@ -677,11 +840,11 @@ void GenericSalLayout::GetCharWidths(std::vector<double>& rCharWidths, const OUS
}
}
-// - pDXArray: is the adjustments to glyph advances (usually due to
-// justification).
-// - pKashidaArray: is the places where kashidas are inserted (for Arabic
-// justification). The number of kashidas is calculated from the pDXArray.
-void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKashidaArray)
+// - stJustification:
+// - contains adjustments to glyph advances (usually due to justification).
+// - contains kashida insertion positions, for Arabic script justification.
+// - The number of kashidas is calculated from the adjusted advances.
+void GenericSalLayout::ApplyJustificationData(const JustificationData& rstJustification)
{
int nCharCount = mnEndCharPos - mnMinCharPos;
std::vector<double> aOldCharWidths;
@@ -694,9 +857,14 @@ void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKas
for (int i = 0; i < nCharCount; ++i)
{
if (i == 0)
- pNewCharWidths[i] = pDXArray[i];
+ {
+ pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i);
+ }
else
- pNewCharWidths[i] = pDXArray[i] - pDXArray[i - 1];
+ {
+ pNewCharWidths[i] = rstJustification.GetTotalAdvance(mnMinCharPos + i)
+ - rstJustification.GetTotalAdvance(mnMinCharPos + i - 1);
+ }
}
// Map of Kashida insertion points (in the glyph items vector) and the
@@ -753,8 +921,10 @@ void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKas
// This is a Kashida insertion position, mark it. Kashida glyphs
// will be inserted below.
- if (pKashidaArray && pKashidaArray[nCharPos])
+ if (rstJustification.GetPositionHasKashida(mnMinCharPos + nCharPos).value_or(false))
+ {
pKashidas[i] = { nDiff, pNewCharWidths[nCharPos] };
+ }
i++;
}
@@ -815,7 +985,7 @@ void GenericSalLayout::ApplyDXArray(const double* pDXArray, const sal_Bool* pKas
aPos.adjustX(-nClusterWidth + pGlyphIter->origWidth());
while (nCopies--)
{
- GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0);
+ GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0, nCharPos);
pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
aPos.adjustX(nKashidaWidth - nOverlap);
++pGlyphIter;
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx
index 31ca14215561..3e8ebc0380b8 100644
--- a/vcl/source/gdi/gdimtf.cxx
+++ b/vcl/source/gdi/gdimtf.cxx
@@ -986,8 +986,10 @@ void GDIMetaFile::Rotate( Degree10 nAngle10 )
case MetaActionType::TEXTARRAY:
{
MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction);
- aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ),
- pAct->GetText(), pAct->GetDXArray(), pAct->GetKashidaArray(), pAct->GetIndex(), pAct->GetLen() ) );
+ aMtf.AddAction(new MetaTextArrayAction(
+ ImplGetRotatedPoint(pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos),
+ pAct->GetText(), pAct->GetDXArray(), pAct->GetKashidaArray(), pAct->GetIndex(),
+ pAct->GetLen(), pAct->GetLayoutContextIndex(), pAct->GetLayoutContextLen()));
}
break;
diff --git a/vcl/source/gdi/impglyphitem.cxx b/vcl/source/gdi/impglyphitem.cxx
index 301b2b9db824..a08a3b043626 100644
--- a/vcl/source/gdi/impglyphitem.cxx
+++ b/vcl/source/gdi/impglyphitem.cxx
@@ -466,6 +466,34 @@ const SalLayoutGlyphs* SalLayoutGlyphsCache::GetLayoutGlyphs(
return nullptr;
}
+const SalLayoutGlyphs* SalLayoutGlyphsCache::GetLayoutGlyphs(
+ const VclPtr<const OutputDevice>& outputDevice, const OUString& text, sal_Int32 nIndex,
+ sal_Int32 nLen, sal_Int32 nDrawMinCharPos, sal_Int32 nDrawEndCharPos, tools::Long nLogicWidth,
+ const vcl::text::TextLayoutCache* layoutCache)
+{
+ // This version is used by callers that need to draw a subset of a layout. In all ordinary uses
+ // this function will be called for successive glyph subsets, so should optimize for that case.
+ auto* pWholeGlyphs
+ = GetLayoutGlyphs(outputDevice, text, nIndex, nLen, nLogicWidth, layoutCache);
+ if (nDrawMinCharPos <= nIndex && nDrawEndCharPos >= (nIndex + nLen))
+ {
+ return pWholeGlyphs;
+ }
+
+ if (pWholeGlyphs && pWholeGlyphs->IsValid())
+ {
+ mLastTemporaryKey.reset();
+ mLastTemporaryGlyphs = makeGlyphsSubset(*pWholeGlyphs, outputDevice, text, nDrawMinCharPos,
+ nDrawEndCharPos - nDrawMinCharPos);
+ if (mLastTemporaryGlyphs.IsValid())
+ {
+ return &mLastTemporaryGlyphs;
+ }
+ }
+
+ return nullptr;
+}
+
void SalLayoutGlyphsCache::SetCacheGlyphsWhenDoingFallbackFonts(bool bOK)
{
mbCacheGlyphsWhenDoingFallbackFonts = bOK;
diff --git a/vcl/source/gdi/metaact.cxx b/vcl/source/gdi/metaact.cxx
index becd359ac610..19ab84ff6c23 100644
--- a/vcl/source/gdi/metaact.cxx
+++ b/vcl/source/gdi/metaact.cxx
@@ -632,14 +632,16 @@ MetaTextArrayAction::MetaTextArrayAction() :
mnLen ( 0 )
{}
-MetaTextArrayAction::MetaTextArrayAction( const MetaTextArrayAction& rAction ) :
- MetaAction ( MetaActionType::TEXTARRAY ),
- maStartPt ( rAction.maStartPt ),
- maStr ( rAction.maStr ),
- maDXAry ( rAction.maDXAry ),
- maKashidaAry( rAction.maKashidaAry ),
- mnIndex ( rAction.mnIndex ),
- mnLen ( rAction.mnLen )
+MetaTextArrayAction::MetaTextArrayAction(const MetaTextArrayAction& rAction)
+ : MetaAction(MetaActionType::TEXTARRAY)
+ , maStartPt(rAction.maStartPt)
+ , maStr(rAction.maStr)
+ , maDXAry(rAction.maDXAry)
+ , maKashidaAry(rAction.maKashidaAry)
+ , mnIndex(rAction.mnIndex)
+ , mnLen(rAction.mnLen)
+ , mnLayoutContextIndex(rAction.mnLayoutContextIndex)
+ , mnLayoutContextLen(rAction.mnLayoutContextLen)
{
}
@@ -675,6 +677,22 @@ MetaTextArrayAction::MetaTextArrayAction( const Point& rStartPt,
maDXAry.assign(pDXAry);
}
+MetaTextArrayAction::MetaTextArrayAction(const Point& rStartPt, OUString aStr, KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry, sal_Int32 nIndex,
+ sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
+ sal_Int32 nLayoutContextLen)
+ : MetaAction(MetaActionType::TEXTARRAY)
+ , maStartPt(rStartPt)
+ , maStr(std::move(aStr))
+ , maKashidaAry(pKashidaAry.begin(), pKashidaAry.end())
+ , mnIndex(nIndex)
+ , mnLen(nLen)
+ , mnLayoutContextIndex(nLayoutContextIndex)
+ , mnLayoutContextLen(nLayoutContextLen)
+{
+ maDXAry.assign(pDXAry);
+}
+
MetaTextArrayAction::~MetaTextArrayAction()
{
}
@@ -684,7 +702,15 @@ void MetaTextArrayAction::Execute( OutputDevice* pOut )
if (!AllowPoint(pOut->LogicToPixel(maStartPt)))
return;
- pOut->DrawTextArray( maStartPt, maStr, maDXAry, maKashidaAry, mnIndex, mnLen );
+ if (mnLayoutContextIndex >= 0)
+ {
+ pOut->DrawPartialTextArray(maStartPt, maStr, maDXAry, maKashidaAry, mnLayoutContextIndex,
+ mnLayoutContextLen, mnIndex, mnLen);
+ }
+ else
+ {
+ pOut->DrawTextArray(maStartPt, maStr, maDXAry, maKashidaAry, mnIndex, mnLen);
+ }
}
rtl::Reference<MetaAction> MetaTextArrayAction::Clone() const
diff --git a/vcl/source/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx
index 0f251f535d18..fda2db24ecb9 100644
--- a/vcl/source/gdi/mtfxmldump.cxx
+++ b/vcl/source/gdi/mtfxmldump.cxx
@@ -874,6 +874,14 @@ void MetafileXmlDump::writeXml(const GDIMetaFile& rMetaFile, tools::XmlWriter& r
rWriter.attribute("index", aIndex);
rWriter.attribute("length", aLength);
+ if (pMetaTextArrayAction->GetLayoutContextIndex() >= 0)
+ {
+ rWriter.attribute("layoutcontextindex",
+ pMetaTextArrayAction->GetLayoutContextIndex());
+ rWriter.attribute("layoutcontextlength",
+ pMetaTextArrayAction->GetLayoutContextLen());
+ }
+
if (!pMetaTextArrayAction->GetDXArray().empty())
{
auto & rArray = pMetaTextArrayAction->GetDXArray();
diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx
index 60437f55fe34..3fb8b6015570 100644
--- a/vcl/source/gdi/pdfwriter.cxx
+++ b/vcl/source/gdi/pdfwriter.cxx
@@ -78,15 +78,13 @@ void PDFWriter::DrawTextLine(
xImplementation->drawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, false/*bUnderlineAbove*/ );
}
-void PDFWriter::DrawTextArray(
- const Point& rStartPt,
- const OUString& rStr,
- KernArraySpan pDXAry,
- std::span<const sal_Bool> pKashidaAry,
- sal_Int32 nIndex,
- sal_Int32 nLen )
-{
- xImplementation->drawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen );
+void PDFWriter::DrawTextArray(const Point& rStartPt, const OUString& rStr, KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry, sal_Int32 nIndex,
+ sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
+ sal_Int32 nLayoutContextLen)
+{
+ xImplementation->drawTextArray(rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen,
+ nLayoutContextIndex, nLayoutContextLen);
}
void PDFWriter::DrawStretchText(
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 9d4cd6c51bf0..17e7c2744a23 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -7183,7 +7183,10 @@ void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int3
}
}
-void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen )
+void PDFWriterImpl::drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
+ sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
+ sal_Int32 nLayoutContextLen)
{
MARK( "drawText with array" );
@@ -7191,10 +7194,24 @@ void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, Ker
// get a layout from the OutputDevice's SalGraphics
// this also enforces font substitution and sets the font on SalGraphics
- const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
- GetLayoutGlyphs( this, rText, nIndex, nLen );
- std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
- SalLayoutFlags::NONE, nullptr, layoutGlyphs );
+ std::unique_ptr<SalLayout> pLayout;
+ if (nLayoutContextIndex >= 0)
+ {
+ const auto* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
+ this, rText, nLayoutContextIndex, nLayoutContextLen, nIndex, nIndex + nLen);
+ pLayout = ImplLayout(rText, nLayoutContextIndex, nLayoutContextLen, rPos, 0, pDXArray,
+ pKashidaArray, SalLayoutFlags::UnclusteredGlyphs, nullptr,
+ layoutGlyphs, /*nDrawOriginCluster=*/nIndex,
+ /*nDrawMinCharPos=*/nIndex, /*nDrawEndCharPos=*/nIndex + nLen);
+ }
+ else
+ {
+ const auto* layoutGlyphs
+ = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(this, rText, nIndex, nLen);
+ pLayout = ImplLayout(rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
+ SalLayoutFlags::NONE, nullptr, layoutGlyphs);
+ }
+
if( pLayout )
{
drawLayout( *pLayout, rText, true );
diff --git a/vcl/source/gdi/pdfwriter_impl2.cxx b/vcl/source/gdi/pdfwriter_impl2.cxx
index 0820e80c07d1..9447ffcbf2bb 100644
--- a/vcl/source/gdi/pdfwriter_impl2.cxx
+++ b/vcl/source/gdi/pdfwriter_impl2.cxx
@@ -870,7 +870,10 @@ void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevDa
case MetaActionType::TEXTARRAY:
{
const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
- m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetKashidaArray(), pA->GetIndex(), pA->GetLen() );
+ m_rOuterFace.DrawTextArray(pA->GetPoint(), pA->GetText(), pA->GetDXArray(),
+ pA->GetKashidaArray(), pA->GetIndex(), pA->GetLen(),
+ pA->GetLayoutContextIndex(),
+ pA->GetLayoutContextLen());
}
break;
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
index 206715fc6b1e..8233f599ada3 100644
--- a/vcl/source/gdi/sallayout.cxx
+++ b/vcl/source/gdi/sallayout.cxx
@@ -137,6 +137,8 @@ void SalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
{
mnMinCharPos = rArgs.mnMinCharPos;
mnEndCharPos = rArgs.mnEndCharPos;
+ mnDrawMinCharPos = rArgs.mnDrawMinCharPos;
+ mnDrawEndCharPos = rArgs.mnDrawEndCharPos;
mnOrientation = rArgs.mnOrientation;
maLanguageTag = rArgs.maLanguageTag;
}
@@ -711,7 +713,7 @@ void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
vcl::text::ImplLayoutArgs aMultiArgs = rArgs;
std::vector<double> aJustificationArray;
- if( !rArgs.HasDXArray() && rArgs.mnLayoutWidth )
+ if (!rArgs.mstJustification.empty() && rArgs.mnLayoutWidth)
{
// for stretched text in a MultiSalLayout the target width needs to be
// distributed by individually adjusting its virtual character widths
@@ -759,16 +761,22 @@ void MultiSalLayout::AdjustLayout( vcl::text::ImplLayoutArgs& rArgs )
aJustificationArray[ nCharCount-1 ] = nTargetWidth;
// change the DXArray temporarily (just for the justification)
- aMultiArgs.mpDXArray = aJustificationArray.data();
+ JustificationData stJustData{ rArgs.mnMinCharPos, nCharCount };
+ for (sal_Int32 i = 0; i < nCharCount; ++i)
+ {
+ stJustData.SetTotalAdvance(rArgs.mnMinCharPos + i, aJustificationArray[i]);
+ }
+
+ aMultiArgs.SetJustificationData(std::move(stJustData));
}
}
- ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mpDXArray);
+ ImplAdjustMultiLayout(rArgs, aMultiArgs, aMultiArgs.mstJustification);
}
void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
vcl::text::ImplLayoutArgs& rMultiArgs,
- const double* pMultiDXArray)
+ const JustificationData& rstJustification)
{
// Compute rtl flags, since in some scripts glyphs/char order can be
// reversed for a few character sequences e.g. Myanmar
@@ -949,7 +957,7 @@ void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
bKeepNotDef = bNeedFallback;
}
// check for reordered glyphs
- if (pMultiDXArray &&
+ if (!rstJustification.empty() &&
nRunVisibleEndChar < mnEndCharPos &&
nRunVisibleEndChar >= mnMinCharPos &&
pGlyphs[n]->charPos() < mnEndCharPos &&
@@ -957,14 +965,14 @@ void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
{
if (vRtl[nActiveCharPos - mnMinCharPos])
{
- if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos]
- >= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ if (rstJustification.GetTotalAdvance(nRunVisibleEndChar)
+ >= rstJustification.GetTotalAdvance(pGlyphs[n]->charPos()))
{
nRunVisibleEndChar = pGlyphs[n]->charPos();
}
}
- else if (pMultiDXArray[nRunVisibleEndChar-mnMinCharPos]
- <= pMultiDXArray[pGlyphs[n]->charPos() - mnMinCharPos])
+ else if (rstJustification.GetTotalAdvance(nRunVisibleEndChar)
+ <= rstJustification.GetTotalAdvance(pGlyphs[n]->charPos()))
{
nRunVisibleEndChar = pGlyphs[n]->charPos();
}
@@ -973,7 +981,7 @@ void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
// if a justification array is available
// => use it directly to calculate the corresponding run width
- if (pMultiDXArray)
+ if (!rstJustification.empty())
{
// the run advance is the width from the first char
// in the run to the first char in the next run
@@ -982,16 +990,26 @@ void MultiSalLayout::ImplAdjustMultiLayout(vcl::text::ImplLayoutArgs& rArgs,
if (nActiveCharIndex >= 0 && vRtl[nActiveCharIndex])
{
if (nRunVisibleEndChar > mnMinCharPos && nRunVisibleEndChar <= mnEndCharPos)
- nRunAdvance -= pMultiDXArray[nRunVisibleEndChar - 1 - mnMinCharPos];
+ {
+ nRunAdvance -= rstJustification.GetTotalAdvance(nRunVisibleEndChar - 1);
+ }
+
if (nLastRunEndChar > mnMinCharPos && nLastRunEndChar <= mnEndCharPos)
- nRunAdvance += pMultiDXArray[nLastRunEndChar - 1 - mnMinCharPos];
+ {
+ nRunAdvance += rstJustification.GetTotalAdvance(nLastRunEndChar - 1);
+ }
}
else
{
if (nRunVisibleEndChar >= mnMinCharPos)
- nRunAdvance += pMultiDXArray[nRunVisibleEndChar - mnMinCharPos];
+ {
+ nRunAdvance += rstJustification.GetTotalAdvance(nRunVisibleEndChar);
+ }
+
if (nLastRunEndChar >= mnMinCharPos)
- nRunAdvance -= pMultiDXArray[nLastRunEndChar - mnMinCharPos];
+ {
+ nRunAdvance -= rstJustification.GetTotalAdvance(nLastRunEndChar);
+ }
}
nLastRunEndChar = nRunVisibleEndChar;
nRunVisibleEndChar = pGlyphs[nFirstValid]->charPos();
diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx
index ae38e3d1c159..9462bb23acd1 100644
--- a/vcl/source/outdev/font.cxx
+++ b/vcl/source/outdev/font.cxx
@@ -1168,38 +1168,49 @@ tools::Long OutputDevice::GetMinKashida() const
return ImplDevicePixelToLogicWidth(nKashidaWidth);
}
-sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt,
- sal_Int32 nIdx, sal_Int32 nLen,
- sal_Int32 nKashCount,
- const sal_Int32* pKashidaPos,
- sal_Int32* pKashidaPosDropped ) const
+sal_Int32 OutputDevice::ValidateKashidas(const OUString& rTxt, sal_Int32 nIdx, sal_Int32 nLen,
+ sal_Int32 nPartIdx, sal_Int32 nPartLen,
+ std::span<const sal_Int32> pKashidaPos,
+ std::vector<sal_Int32>* pKashidaPosDropped) const
{
+ pKashidaPosDropped->clear();
+
// do layout
std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rTxt, nIdx, nLen );
if( !pSalLayout )
return 0;
- auto nEnd = nIdx + nLen - 1;
+ auto nEnd = nIdx + nLen;
+ auto nPartEnd = nPartIdx + nPartLen;
sal_Int32 nDropped = 0;
- for( int i = 0; i < nKashCount; ++i )
+ for (auto nPos : pKashidaPos)
{
- auto nPos = pKashidaPos[i];
auto nNextPos = nPos + 1;
// Skip combining marks to find the next character after this position.
- while (nNextPos <= nEnd &&
- u_getIntPropertyValue(rTxt[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT)
+ while (nNextPos < nEnd
+ && u_getIntPropertyValue(rTxt[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT)
+ {
nNextPos++;
+ }
- // The next position is past end of the layout, it would happen if we
- // changed the text styling in the middle of a word. Since we don’t do
- // apply OpenType features across different layouts, this can’t be an
- // invalid place to insert Kashida.
- if (nNextPos > nEnd)
- continue;
+ // tdf#124116: We now apply OpenType features across different layouts. Positions past the
+ // end of the layout must be validated.
+ // Currently, kashidas cannot be inserted if the grapheme cluster indicated by nPos is
+ // split across multiple layouts. Reject any such position.
+ if (nNextPos > nPartEnd)
+ {
+ pKashidaPosDropped->push_back(nPos);
+ ++nDropped;
+ }
+
+ // Check the glyph flags from HarfBuzz in all other situations.
if (!pSalLayout->IsKashidaPosValid(nPos, nNextPos))
- pKashidaPosDropped[nDropped++] = nPos;
+ {
+ pKashidaPosDropped->push_back(nPos);
+ ++nDropped;
+ }
}
return nDropped;
}
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
index 7d29256953fd..5c5edd3b0612 100644
--- a/vcl/source/outdev/text.cxx
+++ b/vcl/source/outdev/text.cxx
@@ -691,23 +691,59 @@ float OutputDevice::approximate_digit_width() const
void OutputDevice::DrawPartialTextArray(const Point& rStartPt, const OUString& rStr,
KernArraySpan pDXArray,
- std::span<const sal_Bool> pKashidaArray,
- sal_Int32 /*nIndex*/, sal_Int32 /*nLen*/,
- sal_Int32 nPartIndex, sal_Int32 nPartLen,
+ std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
+ sal_Int32 nLen, sal_Int32 nPartIndex, sal_Int32 nPartLen,
SalLayoutFlags flags, const SalLayoutGlyphs* pLayoutCache)
{
- // Currently, this is just a wrapper for DrawTextArray().
- //
- // In certain documents, DrawTextArray/DrawPartialTextArray can be called such that combining
- // characters straddle multiple draw calls. This can happen if, for example, a user attempts to
- // use different text colors for a character and its diacritical marks.
- //
- // In order to fix this issue, this implementation should be replaced with one that performs
- // correct glyph substitutions across text portion boundaries, using the extra layout context.
- //
- // See tdf#124116
- DrawTextArray(rStartPt, rStr, pDXArray, pKashidaArray, nPartIndex, nPartLen, flags,
- pLayoutCache);
+ assert(!is_double_buffered_window());
+
+ if (nLen < 0 || nIndex + nLen >= rStr.getLength())
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if (nPartLen < 0 || nPartIndex + nPartLen >= rStr.getLength())
+ {
+ nPartLen = rStr.getLength() - nPartIndex;
+ }
+
+ if (mpMetaFile)
+ {
+ mpMetaFile->AddAction(new MetaTextArrayAction(rStartPt, rStr, pDXArray, pKashidaArray,
+ nPartIndex, nPartLen, nIndex, nLen));
+ }
+
+ if (!IsDeviceOutputNecessary())
+ return;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+
+ assert(mpGraphics);
+ if (mbInitClipRegion)
+ InitClipRegion();
+
+ if (mbOutputClipped)
+ return;
+
+ // Adding the UnclusteredGlyphs flag during layout enables per-glyph styling.
+ std::unique_ptr<SalLayout> pSalLayout
+ = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXArray, pKashidaArray,
+ flags | SalLayoutFlags::UnclusteredGlyphs, nullptr, pLayoutCache,
+ /*pivot cluster*/ nPartIndex,
+ /*min cluster*/ nPartIndex,
+ /*end cluster*/ nPartIndex + nPartLen);
+
+ if (pSalLayout)
+ {
+ ImplDrawText(*pSalLayout);
+ }
+
+ if (mpAlphaVDev)
+ {
+ mpAlphaVDev->DrawPartialTextArray(rStartPt, rStr, pDXArray, pKashidaArray, nIndex, nLen,
+ nPartIndex, nPartLen, flags, pLayoutCache);
+ }
}
void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
@@ -780,8 +816,21 @@ double OutputDevice::GetPartialTextArray(const OUString &rStr,
std::vector<sal_Int32>* pDXAry = pKernArray ? &pKernArray->get_subunit_array() : nullptr;
// do layout
- std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
- Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache);
+ std::unique_ptr<SalLayout> pSalLayout;
+ if (nIndex == nPartIndex && nLen == nPartLen)
+ {
+ pSalLayout = ImplLayout(rStr, nIndex, nLen, Point{ 0, 0 }, 0, {}, {}, eDefaultLayout,
+ pLayoutCache, pSalLayoutCache);
+ }
+ else
+ {
+ pSalLayout = ImplLayout(rStr, nIndex, nLen, Point{ 0, 0 }, 0, {}, {}, eDefaultLayout,
+ pLayoutCache, pSalLayoutCache,
+ /*pivot cluster*/ nPartIndex,
+ /*min cluster*/ nPartIndex,
+ /*end cluster*/ nPartIndex + nPartLen);
+ }
+
if( !pSalLayout )
{
// The caller expects this to init the elements of pDXAry.
@@ -1110,14 +1159,12 @@ OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse()
return ret;
}
-std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
- sal_Int32 nMinIndex, sal_Int32 nLen,
- const Point& rLogicalPos, tools::Long nLogicalWidth,
- KernArraySpan pDXArray,
- std::span<const sal_Bool> pKashidaArray,
- SalLayoutFlags flags,
- vcl::text::TextLayoutCache const* pLayoutCache,
- const SalLayoutGlyphs* pGlyphs) const
+std::unique_ptr<SalLayout> OutputDevice::ImplLayout(
+ const OUString& rOrigStr, sal_Int32 nMinIndex, sal_Int32 nLen, const Point& rLogicalPos,
+ tools::Long nLogicalWidth, KernArraySpan pDXArray, std::span<const sal_Bool> pKashidaArray,
+ SalLayoutFlags flags, vcl::text::TextLayoutCache const* pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs, std::optional<sal_Int32> nDrawOriginCluster,
+ std::optional<sal_Int32> nDrawMinCharPos, std::optional<sal_Int32> nDrawEndCharPos) const
{
if (pGlyphs && !pGlyphs->IsValid())
{
@@ -1173,34 +1220,71 @@ std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
nPixelWidth, flags, pLayoutCache);
+ if (nDrawOriginCluster.has_value())
+ {
+ aLayoutArgs.mnDrawOriginCluster = *nDrawOriginCluster;
+ }
+
+ if (nDrawMinCharPos.has_value())
+ {
+ aLayoutArgs.mnDrawMinCharPos = *nDrawMinCharPos;
+ }
+
+ if (nDrawEndCharPos.has_value())
+ {
+ aLayoutArgs.mnDrawEndCharPos = *nDrawEndCharPos;
+ }
+
double nEndGlyphCoord(0);
- std::unique_ptr<double[]> xDXPixelArray;
- if( !pDXArray.empty() )
+ if (!pDXArray.empty() || !pKashidaArray.empty())
{
- xDXPixelArray.reset(new double[nLen]);
+ // The provided advance and kashida arrays are indexed relative to the first visible cluster
+ auto nJustMinCluster = nDrawMinCharPos.value_or(nMinIndex);
+ auto nJustLen = nLen;
+ if (nDrawEndCharPos.has_value())
+ {
+ nJustLen = *nDrawEndCharPos - nJustMinCluster;
+ }
- if (mbMap)
+ JustificationData stJustification{ nJustMinCluster, nJustLen };
+
+ if (!pDXArray.empty() && mbMap)
{
// convert from logical units to font units without rounding,
// keeping accuracy for lower levels
int nSubPixels = pDXArray.get_factor();
- for (int i = 0; i < nLen; ++i)
- xDXPixelArray[i] = ImplLogicWidthToDeviceSubPixel(pDXArray.get_subunit(i)) / nSubPixels;
- nEndGlyphCoord = xDXPixelArray[nLen - 1];
+ for (int i = 0; i < nJustLen; ++i)
+ {
+ stJustification.SetTotalAdvance(
+ nJustMinCluster + i,
+ ImplLogicWidthToDeviceSubPixel(pDXArray.get_subunit(i)) / nSubPixels);
+ }
+
+ nEndGlyphCoord = stJustification.GetTotalAdvance(nJustMinCluster + nJustLen - 1);
}
- else
+ else if (!pDXArray.empty())
+ {
+ for (int i = 0; i < nJustLen; ++i)
+ {
+ stJustification.SetTotalAdvance(nJustMinCluster + i, pDXArray.get(i));
+ }
+
+ nEndGlyphCoord
+ = std::round(stJustification.GetTotalAdvance(nJustMinCluster + nJustLen - 1));
+ }
+
+ if (!pKashidaArray.empty())
{
- for(int i = 0; i < nLen; ++i)
- xDXPixelArray[i] = pDXArray.get(i);
- nEndGlyphCoord = std::round(xDXPixelArray[nLen - 1]);
+ for (sal_Int32 i = 0; i < static_cast<sal_Int32>(pKashidaArray.size()); ++i)
+ {
+ stJustification.SetKashidaPosition(nJustMinCluster + i,
+ static_cast<bool>(pKashidaArray[i]));
+ }
}
- aLayoutArgs.SetDXArray(xDXPixelArray.get());
+ aLayoutArgs.SetJustificationData(std::move(stJustification));
}
- if (!pKashidaArray.empty())
- aLayoutArgs.SetKashidaArray(pKashidaArray.data());
-
// get matching layout object for base font
std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
index cba0d16e3537..1c8b116408b5 100644
--- a/vcl/source/outdev/transparent.cxx
+++ b/vcl/source/outdev/transparent.cxx
@@ -1212,9 +1212,26 @@ tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevic
if( !aString.isEmpty() )
{
// #105987# ImplLayout takes everything in logical coordinates
- std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
- rTextAct.GetLen(), rTextAct.GetPoint(),
- 0, rTextAct.GetDXArray(), rTextAct.GetKashidaArray() );
+ std::unique_ptr<SalLayout> pSalLayout;
+ if (rTextAct.GetLayoutContextIndex() >= 0)
+ {
+ pSalLayout = rOut.ImplLayout(
+ rTextAct.GetText(), rTextAct.GetLayoutContextIndex(),
+ rTextAct.GetLayoutContextLen(), rTextAct.GetPoint(), 0,
+ rTextAct.GetDXArray(), rTextAct.GetKashidaArray(), SalLayoutFlags::NONE,
+ /*pTextLayoutCache=*/nullptr,
+ /*pGlyphs=*/nullptr,
+ /*nDrawOriginCluster=*/rTextAct.GetIndex(),
+ /*nDrawMinCharPos=*/rTextAct.GetIndex(),
+ /*nDrawEndCharPos=*/rTextAct.GetIndex() + rTextAct.GetLen());
+ }
+ else
+ {
+ pSalLayout = rOut.ImplLayout(rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(), 0,
+ rTextAct.GetDXArray(), rTextAct.GetKashidaArray());
+ }
+
if( pSalLayout )
{
tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
diff --git a/vcl/source/text/ImplLayoutArgs.cxx b/vcl/source/text/ImplLayoutArgs.cxx
index 826d288bba6f..dbe5f7fd5135 100644
--- a/vcl/source/text/ImplLayoutArgs.cxx
+++ b/vcl/source/text/ImplLayoutArgs.cxx
@@ -37,8 +37,6 @@ ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCh
, mnMinCharPos(nMinCharPos)
, mnEndCharPos(nEndCharPos)
, m_pTextLayoutCache(pLayoutCache)
- , mpDXArray(nullptr)
- , mpKashidaArray(nullptr)
, mnLayoutWidth(0)
, mnOrientation(0)
{
@@ -90,11 +88,9 @@ ImplLayoutArgs::ImplLayoutArgs(const OUString& rStr, int nMinCharPos, int nEndCh
void ImplLayoutArgs::SetLayoutWidth(double nWidth) { mnLayoutWidth = nWidth; }
-void ImplLayoutArgs::SetDXArray(double const* pDXArray) { mpDXArray = pDXArray; }
-
-void ImplLayoutArgs::SetKashidaArray(sal_Bool const* pKashidaArray)
+void ImplLayoutArgs::SetJustificationData(JustificationData stJustification)
{
- mpKashidaArray = pKashidaArray;
+ mstJustification = std::move(stJustification);
}
void ImplLayoutArgs::SetOrientation(Degree10 nOrientation) { mnOrientation = nOrientation; }
@@ -303,7 +299,7 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
s << "\"";
s << ",DXArray=";
- if (rArgs.mpDXArray)
+ if (!rArgs.mstJustification.empty())
{
s << "[";
int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
@@ -312,7 +308,7 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
lim = 7;
for (int i = 0; i < lim; i++)
{
- s << rArgs.mpDXArray[i];
+ s << rArgs.mstJustification.GetTotalAdvance(rArgs.mnMinCharPos + i);
if (i < lim - 1)
s << ",";
}
@@ -320,7 +316,7 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
{
if (count > lim + 1)
s << "...";
- s << rArgs.mpDXArray[count - 1];
+ s << rArgs.mstJustification.GetTotalAdvance(rArgs.mnMinCharPos + count - 1);
}
s << "]";
}
@@ -328,7 +324,7 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
s << "NULL";
s << ",KashidaArray=";
- if (rArgs.mpKashidaArray)
+ if (!rArgs.mstJustification.empty() && rArgs.mstJustification.ContainsKashidaPositions())
{
s << "[";
int count = rArgs.mnEndCharPos - rArgs.mnMinCharPos;
@@ -337,7 +333,8 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
lim = 7;
for (int i = 0; i < lim; i++)
{
- s << rArgs.mpKashidaArray[i];
+ s << rArgs.mstJustification.GetPositionHasKashida(rArgs.mnMinCharPos + i)
+ .value_or(false);
if (i < lim - 1)
s << ",";
}
@@ -345,7 +342,8 @@ std::ostream& operator<<(std::ostream& s, vcl::text::ImplLayoutArgs const& rArgs
{
if (count > lim + 1)
s << "...";
- s << rArgs.mpKashidaArray[count - 1];
+ s << rArgs.mstJustification.GetPositionHasKashida(rArgs.mnMinCharPos + count - 1)
+ .value_or(false);
}
s << "]";
}