editeng/source/editeng/impedit.hxx           |    3 
 editeng/source/editeng/impedit3.cxx          |   40 +++++-
 vcl/qa/cppunit/pdfexport/data/tdf151748.fodt |  160 +++++++++++++++++++++++++++
 vcl/qa/cppunit/pdfexport/pdfexport2.cxx      |   60 ++++++++++
 4 files changed, 254 insertions(+), 9 deletions(-)

New commits:
commit 937023bca427f803a9e7085d5090d5d2b17623ed
Author:     Jonathan Clark <[email protected]>
AuthorDate: Fri Aug 30 00:21:12 2024 -0600
Commit:     Jonathan Clark <[email protected]>
CommitDate: Fri Aug 30 16:17:21 2024 +0200

    tdf#151748 editeng: Improve kashida position validation
    
    Previously, editeng did not validate whether kashida insertion positions
    had enough room for at least a single kashida glyph. This caused kashida
    glyphs to overlap other characters in some situations.
    
    Editeng will now drop candidate kashida insertion positions from the
    beginning of the line, until there is enough room to safely justify the
    remaining text. This approximates Writer's behavior.
    
    Change-Id: I804cae72503332bea8dc9e60cdfe08bd3429dc52
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172641
    Reviewed-by: Jonathan Clark <[email protected]>
    Tested-by: Jenkins

diff --git a/editeng/source/editeng/impedit.hxx 
b/editeng/source/editeng/impedit.hxx
index 5ea71794912d..d62edc53419b 100644
--- a/editeng/source/editeng/impedit.hxx
+++ b/editeng/source/editeng/impedit.hxx
@@ -719,7 +719,8 @@ private:
 
     bool                ImplHasText() const;
 
-    void                ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, 
sal_Int32 nEnd, std::vector<sal_Int32>& rArray );
+    void ImpFindKashidas(ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd,
+                         std::vector<sal_Int32>& rArray, sal_Int32 
nRemainingSpace);
 
     void                InsertContent(std::unique_ptr<ContentNode> pNode, 
sal_Int32 nPos);
     EditPaM             SplitContent( sal_Int32 nNode, sal_Int32 nSepPos );
diff --git a/editeng/source/editeng/impedit3.cxx 
b/editeng/source/editeng/impedit3.cxx
index d8a89139cd59..3cd8ef12eb4d 100644
--- a/editeng/source/editeng/impedit3.cxx
+++ b/editeng/source/editeng/impedit3.cxx
@@ -2306,17 +2306,16 @@ void ImpEditEngine::ImpAdjustBlocks(ParaPortion& 
rParaPortion, EditLine& rLine,
     std::vector<sal_Int32> aPositions;
 
     // Kashidas ?
-    ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions );
+    ImpFindKashidas(pNode, nFirstChar, nLastChar, aPositions, nRemainingSpace);
     auto nKashidas = aPositions.size();
 
     sal_uInt16 nLastScript = i18n::ScriptType::LATIN;
     for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ )
     {
         EditPaM aPaM( pNode, nChar+1 );
-        LanguageType eLang = GetLanguage(aPaM).nLang;
         sal_uInt16 nScript = GetI18NScriptType(aPaM);
         // Arabic script is handled above, but if no Kashida positions are 
found, use blanks.
-        if (MsLangId::getPrimaryLanguage(eLang) == 
LANGUAGE_ARABIC_PRIMARY_ONLY && nKashidas)
+        if (nKashidas)
             continue;
 
         if ( pNode->GetChar(nChar) == ' ' )
@@ -2348,8 +2347,7 @@ void ImpEditEngine::ImpAdjustBlocks(ParaPortion& 
rParaPortion, EditLine& rLine,
     // If the last character is a blank, it is rejected!
     // The width must be distributed to the blockers in front...
     // But not if it is the only one.
-    if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) &&
-         ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, 
nLastChar ) ).nLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) )
+    if ((pNode->GetChar(nLastChar) == ' ') && (aPositions.size() > 1) && 
(!nKashidas))
     {
         aPositions.pop_back();
         sal_Int32 nPortionStart, nPortion;
@@ -2427,13 +2425,16 @@ void ImpEditEngine::ImpAdjustBlocks(ParaPortion& 
rParaPortion, EditLine& rLine,
 }
 
 // For Kashidas from sw/source/core/text/porlay.cxx
-void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, 
sal_Int32 nEnd, std::vector<sal_Int32>& rArray )
+void ImpEditEngine::ImpFindKashidas(ContentNode* pNode, sal_Int32 nStart, 
sal_Int32 nEnd,
+                                    std::vector<sal_Int32>& rArray, sal_Int32 
nRemainingSpace)
 {
     // Kashida glyph looks suspicious, skip Kashida justification
     if (GetRefDevice()->GetMinKashida() <= 0)
         return;
 
     std::vector<sal_Int32> aKashidaArray;
+    std::vector<sal_Int32> aMinKashidaArray;
+    sal_Int32 nTotalMinKashida = 0U;
 
     // the search has to be performed on a per word base
 
@@ -2442,6 +2443,7 @@ void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, 
sal_Int32 nStart, sal_I
     if ( aWordSel.Min().GetIndex() < nStart )
        aWordSel.Min().SetIndex( nStart );
 
+    SvxFont aTmpFont(pNode->GetCharAttribs().GetDefFont());
     while ( ( aWordSel.Min().GetNode() == pNode ) && ( 
aWordSel.Min().GetIndex() < nEnd ) )
     {
         const sal_Int32 nSavPos = aWordSel.Max().GetIndex();
@@ -2599,13 +2601,35 @@ void ImpEditEngine::ImpFindKashidas( ContentNode* 
pNode, sal_Int32 nStart, sal_I
             ++nIdx;
         } // end of current word
 
-        if ( nKashidaPos>=0 )
-            aKashidaArray.push_back( nKashidaPos );
+        if (nKashidaPos >= 0)
+        {
+            SeekCursor(pNode, nKashidaPos + 1, aTmpFont);
+            aTmpFont.SetPhysFont(*GetRefDevice());
+
+            auto nMinKashidaWidth = GetRefDevice()->GetMinKashida();
+            nTotalMinKashida += nMinKashidaWidth;
+            aMinKashidaArray.push_back(nMinKashidaWidth);
+
+            aKashidaArray.push_back(nKashidaPos);
+        }
 
         aWordSel = WordRight( aWordSel.Max(), 
css::i18n::WordType::DICTIONARY_WORD );
         aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD 
);
     }
 
+    // Greedily reject kashida positions from start-to-end until there is 
enough room.
+    // This will push kashida justification away from the start of the line.
+    std::reverse(aKashidaArray.begin(), aKashidaArray.end());
+    std::reverse(aMinKashidaArray.begin(), aMinKashidaArray.end());
+    while (!aKashidaArray.empty() && nTotalMinKashida > nRemainingSpace)
+    {
+        nTotalMinKashida -= aMinKashidaArray.back();
+        aMinKashidaArray.pop_back();
+        aKashidaArray.pop_back();
+    }
+
+    std::reverse(aKashidaArray.begin(), aKashidaArray.end());
+
     // Validate
     std::vector<sal_Int32> aDropped;
     auto nOldLayout = GetRefDevice()->GetLayoutMode();
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf151748.fodt 
b/vcl/qa/cppunit/pdfexport/data/tdf151748.fodt
new file mode 100644
index 000000000000..93c12a655fff
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf151748.fodt
@@ -0,0 +1,160 @@
+<?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:c
 alcext="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:initial-creator>Eyal 
Rozenberg</meta:initial-creator><meta:creation-date>2024-08-16T21:34:46.833527525</meta:creation-date><dc:date>2024-08-30T01:36:34.697764958</dc:date><meta:editing-duration>PT9M57S</meta:editing-duration><meta:editing-cycles>6</meta:editing-cycles><meta:generator>LibreOfficeDev/25.2.0.0.alpha0$Linux_X86_64
 
LibreOffice_project/87ae339de7d1f83235a180dc50293f2857ef37a8</meta:generator><meta:document-statistic
 meta:table-count="0" meta:image-count="0" meta:object-count="0" 
meta:page-count="1" meta:paragraph-count="4" meta:word-count="0" 
meta:character-count="0" meta:non-whitespace-character-count="0"/></office:meta>
+ <office:font-face-decls>
+  <style:font-face style:name="David CLM1" svg:font-family="'David CLM'" 
style:font-family-generic="system" style:font-pitch="variable"/>
+  <style:font-face style:name="DejaVu Sans" svg:font-family="'DejaVu Sans'" 
style:font-adornments="Book" 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="Noto Serif CJK SC" svg:font-family="'Noto Serif 
CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" 
draw:fill-color="#729fcf" fo:wrap-option="no-wrap" 
draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" 
draw:start-line-spacing-horizontal="0.1114in" 
draw:start-line-spacing-vertical="0.1114in" 
draw:end-line-spacing-horizontal="0.1114in" 
draw:end-line-spacing-vertical="0.1114in" style:writing-mode="lr-tb" 
style:flow-with-text="false"/>
+   <style:paragraph-properties fo:text-align="end" 
style:text-autospace="ideograph-alpha" style:line-break="strict" 
loext:tab-stop-distance="0in" style:writing-mode="lr-tb" 
style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="IL" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="David CLM1" style:font-size-complex="12pt" 
style:language-complex="he" style:country-complex="IL"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:text-align="end" 
style:justify-single-word="false" fo:orphans="2" fo:widows="2" 
fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" 
loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" 
style:punctuation-wrap="hanging" style:line-break="strict" 
style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="IL" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="zh" style:country-asian="CN" 
style:font-name-complex="David CLM1" style:font-size-complex="12pt" 
style:language-complex="he" style:country-complex="IL" 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:paragraph-properties fo:text-align="end" 
style:justify-single-word="false"/>
+   <style:text-properties style:font-name-complex="DejaVu Sans" 
style:font-family-complex="'DejaVu Sans'" style:font-style-name-complex="Book" 
style:font-family-generic-complex="swiss" style:font-pitch-complex="variable" 
style:font-style-complex="normal" style:font-weight-complex="normal"/>
+  </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.1965in" style:num-format="1" text:number-position="left" 
text:increment="5"/>
+  </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Standard">
+   <style:text-properties/>
+  </style:style>
+  <style:style style:name="P2" style:family="paragraph">
+   <style:paragraph-properties fo:text-align="justify" 
style:writing-mode="rl-tb"/>
+  </style:style>
+  <style:style style:name="P3" style:family="paragraph">
+   <loext:graphic-properties draw:fill="none" draw:fill-color="#ffffff"/>
+   <style:paragraph-properties fo:text-align="justify"/>
+   <style:text-properties style:font-name-complex="Noto Sans Arabic" 
style:language-complex="ar" style:country-complex="SA" 
style:font-style-complex="normal" style:font-weight-complex="normal"/>
+  </style:style>
+  <style:style style:name="P4" style:family="paragraph" 
style:parent-style-name="Standard">
+   <style:text-properties/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties style:font-name-complex="Noto Sans Arabic" 
style:language-complex="ar" style:country-complex="SA" 
style:font-style-complex="normal" style:font-weight-complex="normal"/>
+  </style:style>
+  <style:style style:name="T2" style:family="text">
+   <style:text-properties fo:font-size="26pt" style:font-size-asian="26pt" 
style:font-name-complex="Noto Sans Arabic" style:font-size-complex="26pt" 
style:language-complex="ar" style:country-complex="SA" 
style:font-style-complex="normal" style:font-weight-complex="normal"/>
+  </style:style>
+  <style:style style:name="gr1" style:family="graphic">
+   <style:graphic-properties draw:stroke="solid" svg:stroke-color="#000000" 
draw:fill="none" draw:fill-color="#ffffff" fo:min-height="0.7047in" 
loext:decorative="false" style:run-through="foreground" 
style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" 
style:vertical-pos="middle" style:vertical-rel="baseline" 
style:horizontal-pos="from-left" style:horizontal-rel="paragraph" 
draw:wrap-influence-on-position="once-concurrent" loext:allow-overlap="true" 
style:flow-with-text="false"/>
+   <style:paragraph-properties style:writing-mode="lr-tb"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.2681in" 
fo:page-height="11.6929in" style:num-format="1" 
style:print-orientation="portrait" fo:margin-top="0.7874in" 
fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" 
fo:margin-right="0.7874in" style:writing-mode="rl-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" 
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="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" 
style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" 
style:line-style="solid" style:adjustment="right" 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"><draw:frame text:anchor-type="as-char" 
draw:z-index="0" draw:name="Text Frame 1" draw:style-name="gr1" 
draw:text-style-name="P3" svg:width="0.9606in" svg:height="0.7051in">
+     <draw:text-box>
+      <text:p text:style-name="P2"><text:span text:style-name="T1">خط تخوردگی 
و توسط</text:span></text:p>
+     </draw:text-box>
+    </draw:frame></text:p>
+   <text:p text:style-name="P4"><draw:frame text:anchor-type="as-char" 
draw:z-index="1" draw:name="Text Frame 2" draw:style-name="gr1" 
draw:text-style-name="P3" svg:width="1.0394in" svg:height="0.7051in">
+     <draw:text-box>
+      <text:p text:style-name="P2"><text:span text:style-name="T1">خط تخوردگی 
و توسط</text:span></text:p>
+     </draw:text-box>
+    </draw:frame></text:p>
+   <text:p text:style-name="P4"><draw:frame text:anchor-type="as-char" 
draw:z-index="2" draw:name="Text Frame 3" draw:style-name="gr1" 
draw:text-style-name="P3" svg:width="1.2949in" svg:height="0.7051in">
+     <draw:text-box>
+      <text:p text:style-name="P2"><text:span text:style-name="T1">خط تخوردگی 
و توسط</text:span></text:p>
+     </draw:text-box>
+    </draw:frame></text:p>
+   <text:p text:style-name="P4"><draw:frame text:anchor-type="as-char" 
draw:z-index="3" draw:name="Text Frame 4" draw:style-name="gr1" 
draw:text-style-name="P3" svg:width="1.2949in" svg:height="1.1161in">
+     <draw:text-box>
+      <text:p text:style-name="P2"><text:span 
text:style-name="T2">خط</text:span><text:span text:style-name="T1"> تخوردگی و 
توسط</text:span></text:p>
+     </draw:text-box>
+    </draw:frame></text:p>
+   <text:p text:style-name="P4"/>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx 
b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
index 43e3e16ec034..974e26dda193 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -5564,6 +5564,66 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf160786)
     CPPUNIT_ASSERT_LESS(aRect.at(3).getMaxX() + aRect.at(3).getWidth(), 
aRect.at(4).getMinX());
 }
 
+// tdf#151748 - Textboxes should validate kashida positions
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf151748KashidaSpace)
+{
+    saveAsPDF(u"tdf151748.fodt");
+
+    auto pPdfDocument = parsePDFExport();
+    CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+    auto pPdfPage = pPdfDocument->openPage(/*nIndex*/ 0);
+    CPPUNIT_ASSERT(pPdfPage);
+    auto pTextPage = pPdfPage->getTextPage();
+    CPPUNIT_ASSERT(pTextPage);
+
+    int nPageObjectCount = pPdfPage->getObjectCount();
+    CPPUNIT_ASSERT_EQUAL(21, 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(17, nTextObjectCount);
+
+    // Box 1: Not enough room for kashida
+    CPPUNIT_ASSERT_EQUAL(u"خط تخوردگی و"_ustr, aText.at(0).trim());
+    CPPUNIT_ASSERT_EQUAL(u"توسط"_ustr, aText.at(1).trim());
+
+    // Box 2: One kashida toward end
+    CPPUNIT_ASSERT_EQUAL(u"وردگی و"_ustr, aText.at(2).trim());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim()); // Kashida
+    CPPUNIT_ASSERT_EQUAL(u"خط تخ"_ustr, aText.at(4).trim());
+    CPPUNIT_ASSERT_EQUAL(u"توسط"_ustr, aText.at(5).trim());
+
+    // Box 3: Two kashida
+    CPPUNIT_ASSERT_EQUAL(u"وردگی و"_ustr, aText.at(6).trim());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(7).trim()); // Kashida
+    CPPUNIT_ASSERT_EQUAL(u"ط تخ"_ustr, aText.at(8).trim());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(9).trim()); // Kashida
+    CPPUNIT_ASSERT_EQUAL(u"خ"_ustr, aText.at(10).trim());
+    CPPUNIT_ASSERT_EQUAL(u"توسط"_ustr, aText.at(11).trim());
+
+    // Box 4: One kashida (text size change)
+    CPPUNIT_ASSERT_EQUAL(u"خط"_ustr, aText.at(12).trim());
+    CPPUNIT_ASSERT_EQUAL(u"وردگی و"_ustr, aText.at(13).trim());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(14).trim()); // Kashida
+    CPPUNIT_ASSERT_EQUAL(u"تخ"_ustr, aText.at(15).trim());
+    CPPUNIT_ASSERT_EQUAL(u"توسط"_ustr, aText.at(16).trim());
+}
+
 } // end anonymous namespace
 
 CPPUNIT_PLUGIN_IMPLEMENT();

Reply via email to