diff options
author | Jonathan Clark <jonathan@libreoffice.org> | 2024-05-07 02:43:00 -0600 |
---|---|---|
committer | Jonathan Clark <jonathan@libreoffice.org> | 2024-05-22 19:20:38 +0200 |
commit | ab0a4543cab77ae0c7c0a79feb8aebab71163dd7 (patch) | |
tree | 8b9f0fc175c41eb719865f69674bbfe65a1e0df9 /vcl | |
parent | d1ddd136a1b0e452492464d58715eaec144fd811 (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
Diffstat (limited to 'vcl')
29 files changed, 1675 insertions, 152 deletions
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 Binary files differnew file mode 100644 index 000000000000..e3f3becee3a8 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/tdf124116-hebrew-track-untrack.odt 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="'Liberation Mono'" style:font-family-generic="modern" style:font-pitch="fixed"/> + <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Noto Sans" svg:font-family="'Noto Sans'" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Noto Sans Arabic" svg:font-family="'Noto Sans Arabic'" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Noto Sans Mono" svg:font-family="'Noto Sans Mono'" 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="'Noto Sans'" 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="'Noto Sans Arabic'" 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="'Liberation Sans'" 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="'Liberation Mono'" style:font-family-generic="modern" style:font-pitch="fixed" fo:font-size="10pt" style:font-name-asian="Noto Sans Mono" style:font-family-asian="'Noto Sans Mono'" 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="'Liberation Mono'" 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="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Noto Sans Arabic" svg:font-family="'Noto Sans Arabic'" 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="'Noto Sans Arabic'" 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="'Liberation Sans'" 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 Binary files differnew file mode 100644 index 000000000000..b4ebb2a1b95b --- /dev/null +++ b/vcl/qa/cppunit/svm/data/textarraycontext.svm 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 << "]"; } |