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 | 36 ++++++++ 5 files changed, 247 insertions(+), 1 deletion(-)
New commits: commit e267882ffedd074d6b0308517b88d988e84b82c3 Author: Jonathan Clark <jonat...@libreoffice.org> AuthorDate: Wed Dec 4 01:43:37 2024 -0700 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Fri Dec 6 14:04:39 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 <jonat...@libreoffice.org> Tested-by: Jenkins (cherry picked from commit dfa81bdb3a7956d631c8ccb1e00166289d37993e) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177865 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index fe94ad999ef5..bccc7377072d 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 dd068364292a..8fc8764470b5 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1392,6 +1392,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: @@ -1401,7 +1403,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 f1b8d1bb2219..3e36171f4ba0 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx @@ -6041,6 +6041,42 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFormRoundtrip) } } +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf162750SmallCapsLigature) +{ + 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();