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();
