sw/source/core/text/portxt.cxx               |   21 ++++
 sw/source/core/txtnode/fntcache.cxx          |   14 ++-
 vcl/qa/cppunit/pdfexport/data/tdf163105.fodt |  115 +++++++++++++++++++++++++++
 vcl/qa/cppunit/pdfexport/pdfexport2.cxx      |   44 ++++++++++
 4 files changed, 190 insertions(+), 4 deletions(-)

New commits:
commit 41671d732ad933175779b61159b56824ff77b2fe
Author:     Jonathan Clark <[email protected]>
AuthorDate: Tue Sep 24 14:51:56 2024 -0600
Commit:     Jonathan Clark <[email protected]>
CommitDate: Wed Sep 25 03:22:38 2024 +0200

    tdf#163105 sw: Add some whitespace expansion to kashida justification
    
    Tihs change updates Writer kashida justification to include some
    whitespace expansion, mirroring the behavior of Edit Engine and other
    word processor programs.
    
    Each kashida and space character are given 1 unit each of extra space.
    
    Change-Id: I8c9031a0d51844e532b9d1f7e3619c2c9ba23f6d
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173884
    Tested-by: Jenkins
    Reviewed-by: Jonathan Clark <[email protected]>

diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx
index bd64b6336a15..4c71e5da2c29 100644
--- a/sw/source/core/text/portxt.cxx
+++ b/sw/source/core/text/portxt.cxx
@@ -43,6 +43,11 @@ using namespace ::sw::mark;
 using namespace ::com::sun::star;
 using namespace ::com::sun::star::i18n::ScriptType;
 
+static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const 
OUString* pStr,
+                                         const SwLinePortion& rPor, 
TextFrameIndex nPos,
+                                         TextFrameIndex nEnd, const 
SwScriptInfo* pSI,
+                                         sal_uInt8 nScript);
+
 // Returns for how many characters an extra space has to be added
 // (for justified alignment).
 static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo &rInf,
@@ -117,7 +122,11 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo 
&rInf,
             // i60591: need to check result of KashidaJustify
             // determine if kashida justification is applicable
             if (nKashRes != -1)
-                return TextFrameIndex(nKashRes);
+            {
+                // tdf#163105: For kashida justification, also expand 
whitespace.
+                auto nCntLatin = lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, 
nEnd, pSI, nScript);
+                return nCntLatin + TextFrameIndex{ nKashRes };
+            }
         }
     }
 
@@ -144,6 +153,16 @@ static TextFrameIndex lcl_AddSpace(const SwTextSizeInfo 
&rInf,
         }
     }
 
+    return lcl_AddSpace_Latin(rInf, pStr, rPor, nPos, nEnd, pSI, nScript);
+}
+
+static TextFrameIndex lcl_AddSpace_Latin(const SwTextSizeInfo& rInf, const 
OUString* pStr,
+                                         const SwLinePortion& rPor, 
TextFrameIndex nPos,
+                                         TextFrameIndex nEnd, const 
SwScriptInfo* pSI,
+                                         sal_uInt8 nScript)
+{
+    TextFrameIndex nCnt(0);
+
     // Here starts the good old "Look for blanks and add space to them" part.
     // Note: We do not want to add space to an isolated latin blank in front
     // of some complex characters in RTL environment
diff --git a/sw/source/core/txtnode/fntcache.cxx 
b/sw/source/core/txtnode/fntcache.cxx
index 1e1fc24e0a5f..8b68e29b9ec3 100644
--- a/sw/source/core/txtnode/fntcache.cxx
+++ b/sw/source/core/txtnode/fntcache.cxx
@@ -1135,7 +1135,9 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                                                  rInf.GetLen(), nSpaceAdd ) != 
-1 )
                         {
                             bSpecialJust = true;
-                            nSpaceAdd = 0;
+
+                            // Intentionally do not clear nSpaceAdd for 
kashida justification.
+                            // The rest of the space will be handled below.
                         }
                         else
                             aKashidaArray.clear();
@@ -1348,7 +1350,10 @@ void SwFntObj::DrawText( SwDrawTextInfo &rInf )
                     if ( pSI && pSI->CountKashida() &&
                          pSI->KashidaJustify( &aKernArray, 
aKashidaArray.data(), rInf.GetIdx(),
                                               rInf.GetLen(), nSpaceAdd ) != -1 
)
-                        nSpaceAdd = 0;
+                    {
+                        // Intentionally do not clear nSpaceAdd for kashida 
justification.
+                        // The rest of the space will be handled below.
+                    }
                     else
                     {
                         aKashidaArray.clear();
@@ -1838,7 +1843,10 @@ TextFrameIndex 
SwFntObj::GetModelPositionForViewPoint(SwDrawTextInfo &rInf)
                 if ( pSI && pSI->CountKashida() &&
                     pSI->KashidaJustify( &aKernArray, nullptr, rInf.GetIdx(), 
rInf.GetLen(),
                                          nSpaceAdd ) != -1 )
-                    nSpaceAdd = 0;
+                {
+                    // Intentionally do not clear nSpaceAdd for kashida 
justification.
+                    // The rest of the space will be handled below.
+                }
             }
         }
 
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf163105.fodt 
b/vcl/qa/cppunit/pdfexport/data/tdf163105.fodt
new file mode 100644
index 000000000000..eae83caedc1d
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf163105.fodt
@@ -0,0 +1,115 @@
+<?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:creation-date>2024-09-24T15:20:14.511295513</meta:creation-date><dc:date>2024-09-24T15:21:44.061753584</dc:date><meta:editing-duration>PT1M30S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic
 meta:table-count="0" meta:image-count="0" meta:object-count="0" 
meta:page-count="1" meta:paragraph-count="1" meta:word-count="2" 
meta:character-count="12" 
meta:non-whitespace-character-count="10"/><meta:generator>LibreOffice/24.2.6.2$Linux_X86_64
 
LibreOffice_project/8e9a753d9daaea75c34b417ba1bdf556bf2fc5b3</meta:generator></office:meta>
+ <office:font-face-decls>
+  <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="Mangal" svg:font-family="Mangal"/>
+  <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 CJK SC" svg:font-family="'Noto Sans 
CJK SC'" style:font-family-generic="swiss"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" 
draw:fill-color="#729fcf" fo:wrap-option="no-wrap" 
draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" 
draw:start-line-spacing-horizontal="0.1114in" 
draw:start-line-spacing-vertical="0.1114in" 
draw:end-line-spacing-horizontal="0.1114in" 
draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" 
style:line-break="strict" loext:tab-stop-distance="0in" 
style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="ja" style:country-asian="JP" 
style:font-name-complex="Mangal" 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="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" 
style:language-asian="ja" style:country-asian="JP" 
style:font-name-complex="Mangal" 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="Noto Sans" fo:font-family="'Noto 
Sans'" style:font-style-name="Regular" style:font-family-generic="swiss" 
style:font-pitch="variable" 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:style>
+  <text:outline-style style:name="Outline">
+   <text:outline-level-style text:level="1" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="2" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="3" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="4" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="5" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="6" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="7" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="8" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="9" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+   <text:outline-level-style text:level="10" style:num-format="">
+    <style:list-level-properties 
text:list-level-position-and-space-mode="label-alignment">
+     <style:list-level-label-alignment text:label-followed-by="listtab"/>
+    </style:list-level-properties>
+   </text:outline-level-style>
+  </text:outline-style>
+  <text:notes-configuration text:note-class="footnote" style:num-format="1" 
text:start-value="0" text:footnotes-position="page" 
text:start-numbering-at="document"/>
+  <text:notes-configuration text:note-class="endnote" style:num-format="i" 
text:start-value="0"/>
+  <text:linenumbering-configuration text:number-lines="false" 
text:offset="0.1965in" style:num-format="1" text:number-position="left" 
text:increment="5"/>
+  </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Standard">
+   <style:paragraph-properties fo:text-align="justify" 
style:justify-single-word="false" style:writing-mode="rl-tb"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="8.2681in" 
fo:page-height="11.6929in" style:num-format="1" 
style:print-orientation="portrait" fo:margin-top="0.7874in" 
fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" 
fo:margin-right="0.7874in" style:writing-mode="lr-tb" 
style:footnote-max-height="0in" loext:margin-gutter="0in">
+    <style:footnote-sep style:width="0.0071in" 
style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text>
+   <text:sequence-decls>
+    <text:sequence-decl text:display-outline-level="0" 
text:name="Illustration"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Table"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Text"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
+    <text:sequence-decl text:display-outline-level="0" text:name="Figure"/>
+   </text:sequence-decls>
+   <text:p text:style-name="P1">نویسه کشیده<text:line-break/></text:p>
+  </office:text>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx 
b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
index 974e26dda193..390dbc203438 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -5624,6 +5624,50 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, 
testTdf151748KashidaSpace)
     CPPUNIT_ASSERT_EQUAL(u"توسط"_ustr, aText.at(16).trim());
 }
 
+// tdf#163105 - Writer kashida justification should expand spaces
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf163105SwKashidaSpaceExpansion)
+{
+    saveAsPDF(u"tdf163105.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(5, nPageObjectCount);
+
+    std::vector<OUString> aText;
+    std::vector<basegfx::B2DRectangle> aRect;
+
+    int nTextObjectCount = 0;
+    for (int i = 0; i < nPageObjectCount; ++i)
+    {
+        auto pPageObject = pPdfPage->getObject(i);
+        CPPUNIT_ASSERT_MESSAGE("no object", pPageObject != nullptr);
+        if (pPageObject->getType() == vcl::pdf::PDFPageObjectType::Text)
+        {
+            aText.push_back(pPageObject->getText(pTextPage));
+            aRect.push_back(pPageObject->getBounds());
+            ++nTextObjectCount;
+        }
+    }
+
+    CPPUNIT_ASSERT_EQUAL(5, nTextObjectCount);
+
+    CPPUNIT_ASSERT_EQUAL(u"یده"_ustr, aText.at(0).trim());
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(1).trim());
+    CPPUNIT_ASSERT_EQUAL(u"ه کش"_ustr, aText.at(2).trim()); // This span is 
whitespace justified
+    CPPUNIT_ASSERT_EQUAL(u""_ustr, aText.at(3).trim());
+    CPPUNIT_ASSERT_EQUAL(u"نویس"_ustr, aText.at(4).trim());
+
+    // Without the fix, this will be less than 25
+    CPPUNIT_ASSERT_GREATER(150.0, aRect.at(2).getWidth());
+}
+
 } // end anonymous namespace
 
 CPPUNIT_PLUGIN_IMPLEMENT();

Reply via email to