sw/source/core/text/itratr.cxx               |   88 +++++++++++++++++++
 sw/source/core/text/itratr.hxx               |    1 
 sw/source/core/text/itrform2.cxx             |    4 
 vcl/qa/cppunit/pdfexport/data/tdf162750.fodt |  119 +++++++++++++++++++++++++++
 vcl/qa/cppunit/pdfexport/pdfexport2.cxx      |   37 ++++++++
 5 files changed, 248 insertions(+), 1 deletion(-)

New commits:
commit 1221b01653240a515fc8a88c70ae2e06fc5ef57b
Author:     Jonathan Clark <[email protected]>
AuthorDate: Wed Dec 4 01:43:37 2024 -0700
Commit:     Adolfo Jayme Barrientos <[email protected]>
CommitDate: Sat Dec 7 20:37:16 2024 +0100

    tdf#162750 sw: Fix layout with small caps inside ligatures
    
    Previously, Writer was not correctly terminating layout contexts at the
    starts of small caps spans. This could cause incorrect character
    placement in certain cases.
    
    Regression since:
    
    Commit 30d376fb7ded4c96c85ad1112a0e44b5929657c9
        "tdf#61444 Correct Writer text layout across formatting changes"
    
    and
    
    Commit ab0a4543cab77ae0c7c0a79feb8aebab71163dd7
        "tdf#124116 Correct Writer text shaping across formatting changes"
    
    Change-Id: I863b9b66356eb0a9efb5bbdc75e80b43d56aaaf0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177839
    Reviewed-by: Jonathan Clark <[email protected]>
    Tested-by: Jenkins
    (cherry picked from commit dfa81bdb3a7956d631c8ccb1e00166289d37993e)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177865
    Reviewed-by: Xisco Fauli <[email protected]>
    Signed-off-by: Xisco Fauli <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/178042
    Reviewed-by: Adolfo Jayme Barrientos <[email protected]>

diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
index ae38bda0bab3..800d6f5781ac 100644
--- a/sw/source/core/text/itratr.cxx
+++ b/sw/source/core/text/itratr.cxx
@@ -23,6 +23,7 @@
 
 #include <hintids.hxx>
 #include <editeng/charscaleitem.hxx>
+#include <editeng/cmapitem.hxx>
 #include <svl/itemiter.hxx>
 #include <svx/svdobj.hxx>
 #include <vcl/svapp.hxx>
@@ -838,6 +839,93 @@ TextFrameIndex SwAttrIter::GetNextAttr() const
     }
 }
 
+namespace
+{
+class FormatBreakTracker
+{
+private:
+    std::optional<SvxCaseMap> m_nCaseMap;
+
+    bool m_bNeedsBreak = false;
+
+    void SetCaseMap(SvxCaseMap nValue)
+    {
+        if (m_nCaseMap != nValue)
+            m_bNeedsBreak = true;
+
+        m_nCaseMap = nValue;
+    }
+
+public:
+    void HandleItemSet(const SfxItemSet& rSet)
+    {
+        if (const SvxCaseMapItem* pItem = rSet.GetItem(RES_CHRATR_CASEMAP))
+            SetCaseMap(pItem->GetCaseMap());
+    }
+
+    void Reset() { m_bNeedsBreak = false; }
+
+    bool NeedsBreak() const { return m_bNeedsBreak; }
+};
+
+bool HasFormatBreakAttribute(FormatBreakTracker* pTracker, const SwTextAttr* 
pAttr)
+{
+    pTracker->Reset();
+
+    switch (pAttr->Which())
+    {
+        case RES_TXTATR_AUTOFMT:
+        case RES_TXTATR_CHARFMT:
+        {
+            const SfxItemSet& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
+                                       ? static_cast<SfxItemSet const&>(
+                                             
pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
+                                       : 
*pAttr->GetAutoFormat().GetStyleHandle());
+
+            pTracker->HandleItemSet(rSet);
+        }
+        break;
+    }
+
+    if (pAttr->IsFormatIgnoreStart() || pAttr->IsFormatIgnoreEnd())
+        pTracker->Reset();
+
+    return pTracker->NeedsBreak();
+}
+}
+
+TextFrameIndex SwAttrIter::GetNextLayoutBreakAttr() const
+{
+    size_t nStartIndex(m_nStartIndex);
+    SwTextNode const* pTextNode(m_pTextNode);
+
+    sal_Int32 nNext = std::numeric_limits<sal_Int32>::max();
+
+    auto* pHints = pTextNode->GetpSwpHints();
+    if (!pHints)
+    {
+        return TextFrameIndex{ nNext };
+    }
+
+    FormatBreakTracker stTracker;
+    stTracker.HandleItemSet(pTextNode->GetSwAttrSet());
+
+    for (size_t i = 0; i < pHints->Count(); ++i)
+    {
+        SwTextAttr* const pAttr(pHints->Get(i));
+        if (HasFormatBreakAttribute(&stTracker, pAttr))
+        {
+            if (i >= nStartIndex)
+            {
+                nNext = pAttr->GetStart();
+                break;
+            }
+        }
+    }
+
+    return TextFrameIndex{ nNext };
+}
+
 namespace {
 
 class SwMinMaxArgs
diff --git a/sw/source/core/text/itratr.hxx b/sw/source/core/text/itratr.hxx
index 91b3d4f9200c..d61d112404eb 100644
--- a/sw/source/core/text/itratr.hxx
+++ b/sw/source/core/text/itratr.hxx
@@ -87,6 +87,7 @@ public:
     // The parameter returns the position of the next change before or at the
     // char position.
     TextFrameIndex GetNextAttr() const;
+    TextFrameIndex GetNextLayoutBreakAttr() const;
     /// Enables the attributes used at char pos nPos in the logical font
     bool Seek(TextFrameIndex nPos);
     // Creates the font at the specified position via Seek() and checks
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index 06b7361550cf..16fe77e5f12b 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -1385,6 +1385,8 @@ SwTextPortion *SwTextFormatter::NewTextPortion( 
SwTextFormatInfo &rInf )
 
     // until next attribute change:
     const TextFrameIndex nNextAttr = GetNextAttr();
+    // until next layout-breaking attribute change:
+    const TextFrameIndex nNextLayoutBreakAttr = GetNextLayoutBreakAttr();
     // end of script type:
     const TextFrameIndex nNextScript = 
m_pScriptInfo->NextScriptChg(rInf.GetIdx());
     // end of direction:
@@ -1394,7 +1396,7 @@ SwTextPortion *SwTextFormatter::NewTextPortion( 
SwTextFormatInfo &rInf )
     // bookmarks
     const TextFrameIndex nNextBookmark = 
m_pScriptInfo->NextBookmark(rInf.GetIdx());
 
-    auto nNextContext = std::min({ nNextChg, nNextScript, nNextDir });
+    auto nNextContext = std::min({ nNextChg, nNextLayoutBreakAttr, 
nNextScript, nNextDir });
     nNextChg = std::min({ nNextChg, nNextAttr, nNextScript, nNextDir, 
nNextHidden, nNextBookmark });
 
     // Turbo boost:
diff --git a/vcl/qa/cppunit/pdfexport/data/tdf162750.fodt 
b/vcl/qa/cppunit/pdfexport/data/tdf162750.fodt
new file mode 100644
index 000000000000..be345551e91c
--- /dev/null
+++ b/vcl/qa/cppunit/pdfexport/data/tdf162750.fodt
@@ -0,0 +1,119 @@
+<?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.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ 
<office:meta><meta:creation-date>2024-12-04T21:34:47.433434835</meta:creation-date><dc:date>2024-12-04T21:37:36.661647530</dc:date><meta:editing-duration>PT2M49S</meta:editing-duration><meta:editing-cycles>2</meta:editing-cycles><meta:generator>LibreOfficeDev/25.2.0.0.alpha1$Linux_X86_64
 
LibreOffice_project/277d26808adc6812e17b910a6300006fe03f4614</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: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="Noto Serif" svg:font-family="'Noto Serif'" 
style:font-adornments="Regular" style:font-family-generic="roman" 
style:font-pitch="variable"/>
+  <style:font-face style:name="Noto Serif CJK JP" svg:font-family="'Noto Serif 
CJK JP'" style:font-family-generic="system" style:font-pitch="variable"/>
+  <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.1181in" draw:shadow-offset-y="0.1181in" 
draw:start-line-spacing-horizontal="0.1114in" 
draw:start-line-spacing-vertical="0.1114in" 
draw:end-line-spacing-horizontal="0.1114in" 
draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" 
style:line-break="strict" loext:tab-stop-distance="0in" 
style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK JP" style:font-size-asian="10.5pt" 
style:language-asian="ja" style:country-asian="JP" 
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" fo:hyphenation-keep="auto" 
loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" 
style:punctuation-wrap="hanging" style:line-break="strict" 
style:tab-stop-distance="0.4925in" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
style:font-name-asian="Noto Serif CJK JP" style:font-size-asian="10.5pt" 
style:language-asian="ja" style:country-asian="JP" 
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="Noto Serif" fo:font-family="'Noto 
Serif'" style:font-style-name="Regular" style:font-family-generic="roman" 
style:font-pitch="variable" fo:font-size="60pt"/>
+  </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:text-properties/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties fo:font-variant="small-caps"/>
+  </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">ffi</text:p>
+   <text:p text:style-name="P1">f<text:span 
text:style-name="T1">fi</text:span></text:p>
+   <text:p text:style-name="P1"/>
+  </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 c8527d71dd9a..541bb5f8009e 100644
--- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
+++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx
@@ -5682,6 +5682,43 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, 
testTdf151748KashidaSpace)
     CPPUNIT_ASSERT_EQUAL(u"توسط"_ustr, aText.at(16).trim());
 }
 
+CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf162750SmallCapsLigature)
+{
+    aMediaDescriptor[u"FilterName"_ustr] <<= u"writer_pdf_Export"_ustr;
+    saveAsPDF(u"tdf162750.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(3, nPageObjectCount);
+
+    std::vector<OUString> aText;
+    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));
+        }
+    }
+
+    CPPUNIT_ASSERT_EQUAL(size_t(3), aText.size());
+    CPPUNIT_ASSERT_EQUAL(u"ffi"_ustr, aText.at(0).trim());
+
+    // Without the fix, this will be "ffi"
+    CPPUNIT_ASSERT_EQUAL(u"f"_ustr, aText.at(1).trim());
+
+    CPPUNIT_ASSERT_EQUAL(u"FI"_ustr, aText.at(2).trim());
+}
+
 } // end anonymous namespace
 
 CPPUNIT_PLUGIN_IMPLEMENT();

Reply via email to