sw/qa/extras/ooxmlexport/data/tdf167721_chUnits.docx |binary sw/qa/extras/ooxmlexport/data/tdf167721_chUnits2.docx |binary sw/qa/extras/ooxmlexport/data/tdf167721_chUnits3.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport22.cxx | 152 ++++++++++++++++++ sw/source/writerfilter/dmapper/DomainMapper.cxx | 52 ++++++ sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 5 6 files changed, 208 insertions(+), 1 deletion(-)
New commits: commit bdf6b7cfb5b58ffe734e92fc73b9e91f58c1a40e Author: Justin Luth <jl...@mail.com> AuthorDate: Fri Aug 8 18:11:09 2025 -0400 Commit: Justin Luth <jl...@mail.com> CommitDate: Wed Aug 13 03:06:51 2025 +0200 tdf#167721 writerfilter: don't adjust leftChars=0 by hangingChars Second problem - a zero value is a special case which "turns off" a previously defined w:leftChars and uses the w:left value instead (usually). Since it doesn't make sense to add a variable-length ic unit to a static-lengh unit (like cm), just ignore that situation. [NOTE: sometimes they do seem to need to be added together, the likely formula is 1 Ch * 100 == TWIP value.] Thought experiment: what if the hangingChars adjusts a leftChars so that the leftChars BECOMES a zero? It doesn't matter - we are only changing LO internals now. This patch prevents hangingChars from adjusting the left margin from the special zero-case. make CppunitTest_sw_ooxmlexport22 \ CPPUNIT_TEST_NAME=testTdf167721_chUnits Change-Id: I3f2a379c6145c10eccbce1ce59969c4a59acc330 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189233 Reviewed-by: Justin Luth <jl...@mail.com> Tested-by: Jenkins diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx index 03019400db98..d24688775c8a 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx @@ -298,7 +298,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits) = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaFirstLineIndentUnit"_ustr); CPPUNIT_ASSERT_EQUAL(double(-4), aFirstCh.First); - // This first patchset fixes this line. It instead matched ParaLeftMargin 5001. auto aLeftCh = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaLeftMarginUnit"_ustr); CPPUNIT_ASSERT_EQUAL(double(3), aLeftCh.First); @@ -319,10 +318,9 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits) // temporary test // a "zero" leftChars disables a chars margin. // This was being adjusted by ParaFirstLineIndentUnit (0 - -1) to become "1 ic" - // aLeftCh = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaLeftMarginUnit"_ustr); - // CPPUNIT_ASSERT_EQUAL(double(0), aLeftCh.First); + aLeftCh = getProperty<css::beans::Pair<double, sal_Int16>>(xPara, u"ParaLeftMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(0), aLeftCh.First); - // This first patchset also fixes this inheritance. This was zero'd out... aRightCh = getProperty<css::beans::Pair<double, sal_Int16>>(xPara, u"ParaRightMarginUnit"_ustr); CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); } @@ -351,7 +349,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits2) // 5 cm - 1 Ch (1Ch == 100 TWIP == 0.176 cm), so 4825??? It was 5080 // CPPUNIT_ASSERT_EQUAL(sal_Int32(5001-176), getProperty<sal_Int32>(xStyle, u"ParaLeftMargin"_ustr)); - // This first patchset also fixes this inheritance. This was zero'd out... auto aRightCh = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaRightMarginUnit"_ustr); CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); @@ -366,7 +363,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits2) // CPPUNIT_ASSERT_EQUAL(sal_Int32(-2540), getProperty<sal_Int32>(xStyle, u"ParaLeftMargin"_ustr)); - // This first patchset also fixes this inheritance. This was ParaRightMargin 2000... aRightCh = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaRightMarginUnit"_ustr); CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index 262d9f41b53c..ba6f6619eecb 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -3105,24 +3105,27 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con { pParaContext->getProperty(PROP_PARA_LEFT_MARGIN_UNIT)->second >>= stLeftCh; + bLeftChSet = stLeftCh != stZero; } if (bRightChSet) { pParaContext->getProperty(PROP_PARA_RIGHT_MARGIN_UNIT)->second >>= stRightCh; + bRightChSet = stRightCh != stZero; } if (bFirstChSet) { pParaContext->getProperty(PROP_PARA_FIRST_LINE_INDENT_UNIT)->second >>= stFirstCh; + bFirstChSet = stFirstCh != stZero; } // tdf#83844: DOCX stores left and leftChars differently with hanging // indentation. Character-based hanging indentation must be pre-added // to the left margin here. - if (stFirstCh.First < 0.0) + if (bLeftChSet && stFirstCh.First < 0.0) { stLeftCh.First -= stFirstCh.First; } commit 13c15652cb7b14aa4cb40d5379493ec65c5ae117 Author: Justin Luth <jl...@mail.com> AuthorDate: Tue Aug 5 19:39:50 2025 -0400 Commit: Justin Luth <jl...@mail.com> CommitDate: Wed Aug 13 03:06:39 2025 +0200 tdf#167721 writerfilter: ic needs to inherit from parent para-style First problem - all 3 settings were set to 0 if no direct property, so there was no inheritance from parent styles. Note that it is important to record the zero case, otherwise you can't "stop" inheritance. (My first instinct was to not write any property when !nIntValue.) make CppunitTest_sw_ooxmlexport22 \ CPPUNIT_TEST_NAME=testTdf167721_chUnits make CppunitTest_sw_ooxmlexport22 \ CPPUNIT_TEST_NAME=testTdf167721_chUnits2 Change-Id: I5f3dec002271f3401a126b1e55b78191dd622453 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/189232 Reviewed-by: Justin Luth <jl...@mail.com> Tested-by: Jenkins diff --git a/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits.docx b/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits.docx new file mode 100644 index 000000000000..15bd7a7b8f3b Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits2.docx b/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits2.docx new file mode 100644 index 000000000000..293ba645f134 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits2.docx differ diff --git a/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits3.docx b/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits3.docx new file mode 100644 index 000000000000..e658eafa1490 Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf167721_chUnits3.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx index f4f1c89decc2..03019400db98 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport22.cxx @@ -10,6 +10,7 @@ #include <swmodeltestbase.hxx> #include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/beans/Pair.hpp> #include <com/sun/star/beans/XPropertyState.hpp> #include <comphelper/configuration.hxx> @@ -273,6 +274,161 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf166553_paraStyleAfterBreak) CPPUNIT_ASSERT_EQUAL(awt::FontWeight::BOLD, getProperty<float>(xPara, u"CharWeight"_ustr)); } +CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits) +{ + // given a document that specifies some margins using Ch-based Left/Right indentation + // where w:rightChars is inherited from the parent styles - so it overrides w:right + // where w:firstLineChars is specified as a direct property + // and w:leftChars is disabled (0), so it inherits the style's w:left + + // direct formatting (of the paragraph) in document.xml + // <w:ind w:right="1134"(2 cm) w:hangingChars="100" (1 ic) w:leftChars="0"/> + // inherited formatting from the style chain in styles.xml + // <w:ind w:rightChars="200" (2 ic) w:hangingChars=400 (4 ic) + // w:leftChars="300" (3 ic) w:left="2834"/> (5 cm) + createSwDoc("tdf167721_chUnits.docx"); + // saveAndReload(mpFilter); + + // Test the style ############################################################################# + uno::Reference<beans::XPropertySet> xStyle( + getStyles(u"ParagraphStyles"_ustr)->getByName(u"Inherited List Paragraph"_ustr), + uno::UNO_QUERY); + + auto aFirstCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaFirstLineIndentUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(-4), aFirstCh.First); + + // This first patchset fixes this line. It instead matched ParaLeftMargin 5001. + auto aLeftCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaLeftMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(3), aLeftCh.First); + + auto aRightCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaRightMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); + + // Test the paragraph ######################################################################### + uno::Reference<text::XTextRange> xPara(getParagraph(1)); + + aFirstCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xPara, u"ParaFirstLineIndentUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(-1), aFirstCh.First); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(5001), getProperty<sal_Int32>(xPara, u"ParaLeftMargin"_ustr)); + + // temporary test + // a "zero" leftChars disables a chars margin. + // This was being adjusted by ParaFirstLineIndentUnit (0 - -1) to become "1 ic" + // aLeftCh = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaLeftMarginUnit"_ustr); + // CPPUNIT_ASSERT_EQUAL(double(0), aLeftCh.First); + + // This first patchset also fixes this inheritance. This was zero'd out... + aRightCh = getProperty<css::beans::Pair<double, sal_Int16>>(xPara, u"ParaRightMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits2) +{ + // given a nasty edge-case document + // Style "List Paragraph": left = (2 inch minus 1 Ch), right = 2 Ch, hanging indent = +1 Ch + // <w:ind w:left="2880" w:rightChars="200" w:hangingChars="100" w:firstLineChars="200"/> + // Style "Inherited List Paragraph": left = -1 inch, right = 2 Ch, hanging indent = -2 Ch + // <w:ind w:leftChars="0" w:right="1134" w:firstLineChars="200" w:hanging="1440" w:firstLine="2880"/> + // Paragraph: left = -1 inch, right = 2 Ch, hanging indent = +1 inch + // <w:ind w:firstLineChars="0"/> + + createSwDoc("tdf167721_chUnits2.docx"); + saveAndReload(mpFilter); + + // Test the parent style ###################################################################### + uno::Reference<beans::XPropertySet> xStyle( + getStyles(u"ParagraphStyles"_ustr)->getByName(u"List Paragraph"_ustr), uno::UNO_QUERY); + + // auto aFirstCh + // = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaFirstLineIndentUnit"_ustr); + // CPPUNIT_ASSERT_EQUAL(double(-1), aFirstCh.First); + + // 5 cm - 1 Ch (1Ch == 100 TWIP == 0.176 cm), so 4825??? It was 5080 + // CPPUNIT_ASSERT_EQUAL(sal_Int32(5001-176), getProperty<sal_Int32>(xStyle, u"ParaLeftMargin"_ustr)); + + // This first patchset also fixes this inheritance. This was zero'd out... + auto aRightCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaRightMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); + + // Test the style ############################################################################# + xStyle.set(getStyles(u"ParagraphStyles"_ustr)->getByName(u"Inherited List Paragraph"_ustr), + uno::UNO_QUERY); + + auto aFirstCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaFirstLineIndentUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(2), aFirstCh.First); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(-2540), getProperty<sal_Int32>(xStyle, u"ParaLeftMargin"_ustr)); + + // This first patchset also fixes this inheritance. This was ParaRightMargin 2000... + aRightCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaRightMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); + + // Test the paragraph ######################################################################### + // uno::Reference<text::XTextRange> xPara(getParagraph(1)); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(2540), getProperty<sal_Int32>(xPara, u"ParaFirstLineIndent"_ustr)); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(-2540), getProperty<sal_Int32>(xPara, u"ParaLeftMargin"_ustr)); + + // aRightCh = getProperty<css::beans::Pair<double, sal_Int16>>(xPara, u"ParaRightMarginUnit"_ustr); + // CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf167721_chUnits3) +{ + // given a nasty edge-case document + // Style "List Paragraph": left = 2 inch, right = 2 cm, first line = none + // <w:ind w:left="2880" w:right="1134" w:hangingChars="0" w:firstLine="2880"/> + // Style "Inherited List Paragraph": left = -1 inch, right = 2 Ch, hanging indent = +1 inch + // <w:ind w:leftChars="0" left="2880" w:rightChars="200" w:hangingChars="0" w:hanging="1440"/> + // Paragraph: left = 0, right = 0.14 inch, first line = +2 inch + // <w:ind w:rightChars="0" w:hangingChars="0" w:firstLine="2880" /> + + createSwDoc("tdf167721_chUnits3.docx"); + saveAndReload(mpFilter); + + // Test the parent style ###################################################################### + uno::Reference<beans::XPropertySet> xStyle( + getStyles(u"ParagraphStyles"_ustr)->getByName(u"List Paragraph"_ustr), uno::UNO_QUERY); + + auto aFirstCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaFirstLineIndentUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(0), aFirstCh.First); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(5080), getProperty<sal_Int32>(xStyle, u"ParaLeftMargin"_ustr)); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(2000), getProperty<sal_Int32>(xStyle, u"ParaRightMargin"_ustr)); + + // Test the style ############################################################################# + xStyle.set(getStyles(u"ParagraphStyles"_ustr)->getByName(u"Inherited List Paragraph"_ustr), + uno::UNO_QUERY); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(-2540), getProperty<sal_Int32>(xStyle, u"ParaFirstLineIndent"_ustr)); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(-2540), getProperty<sal_Int32>(xStyle, u"ParaLeftMargin"_ustr)); + + auto aRightCh + = getProperty<css::beans::Pair<double, sal_Int16>>(xStyle, u"ParaRightMarginUnit"_ustr); + CPPUNIT_ASSERT_EQUAL(double(2), aRightCh.First); + + // Test the paragraph ######################################################################### + // uno::Reference<text::XTextRange> xPara(getParagraph(1)); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(5080), getProperty<sal_Int32>(xPara, u"ParaFirstLineIndent"_ustr)); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(0), getProperty<sal_Int32>(xPara, u"ParaLeftMargin"_ustr)); + + // CPPUNIT_ASSERT_EQUAL(sal_Int32(353), getProperty<sal_Int32>(xPara, u"ParaRightMargin"_ustr)); +} + CPPUNIT_TEST_FIXTURE(Test, testTdf83844) { createSwDoc("tdf83844.fodt"); diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index 38bcbaca760d..12a799e85e3b 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -555,6 +555,20 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) m_pImpl->GetTopContext()->Insert(PROP_PARA_LEFT_MARGIN, uno::Any(nParaLeftMargin)); + + // w:left is overridden by w:leftChars - even if leftChars is only inherited. + PropertyIds eId = PROP_PARA_LEFT_MARGIN_UNIT; + const css::beans::Pair<double, sal_Int16> stZero{ + 0.0, css::util::MeasureUnit::FONT_CJK_ADVANCE + }; + auto stCh = stZero; + + m_pImpl->GetAnyProperty(eId, m_pImpl->GetTopContext()) >>= stCh; + if (stCh != stZero) // zero means leftChars is disabled + { + // Insert inherited leftChars in case none is provided by this style/paragraph + m_pImpl->GetTopContext()->Insert(eId, uno::Any(stCh), /*Overwrite*/ false); + } } break; case NS_ooxml::LN_CT_Ind_startChars: @@ -583,6 +597,17 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) m_pImpl->GetTopContext()->Insert( PROP_PARA_RIGHT_MARGIN, uno::Any( ConversionHelper::convertTwipToMm100_Limited(nIntValue ) )); + + // Insert inherited rightChars in case none is provided by this style/paragraph + PropertyIds eId = PROP_PARA_RIGHT_MARGIN_UNIT; + const css::beans::Pair<double, sal_Int16> stZero{ + 0.0, css::util::MeasureUnit::FONT_CJK_ADVANCE + }; + auto stCh = stZero; + + m_pImpl->GetAnyProperty(eId, m_pImpl->GetTopContext()) >>= stCh; + if (stCh != stZero) + m_pImpl->GetTopContext()->Insert(eId, uno::Any(stCh), /*Overwrite*/ false); } m_pImpl->appendGrabBag(m_pImpl->m_aSubInteropGrabBag, u"right"_ustr, OUString::number(nIntValue)); break; @@ -605,6 +630,17 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) m_pImpl->GetTopContext()->Insert( PROP_PARA_FIRST_LINE_INDENT, uno::Any( - nValue )); + // Insert inherited hangingChars in case none is provided by this style/paragraph + PropertyIds eId = PROP_PARA_FIRST_LINE_INDENT_UNIT; + const css::beans::Pair<double, sal_Int16> stZero{ + 0.0, css::util::MeasureUnit::FONT_CJK_ADVANCE + }; + auto stCh = stZero; + + m_pImpl->GetAnyProperty(eId, m_pImpl->GetTopContext()) >>= stCh; + if (stCh != stZero) + m_pImpl->GetTopContext()->Insert(eId, uno::Any(stCh), /*Overwrite*/ false); + // See above, need to inherit left margin from list style when first is set. sal_Int32 nParaLeftMargin = m_pImpl->getCurrentNumberingProperty(u"IndentAt"_ustr); if (nParaLeftMargin != 0) @@ -625,6 +661,22 @@ void DomainMapper::lcl_attribute(Id nName, const Value & val) case NS_ooxml::LN_CT_Ind_firstLine: if (m_pImpl->GetTopContext()) { + // TODO: firstLine is supposed to be overridden by hanging - if both are defined. + + // w:firstLineChars overrides w:firstLine - even if firstLineChars is inherited. + // Insert inherited firstLineChars in case none is provided by this style/paragraph + { + PropertyIds eId = PROP_PARA_FIRST_LINE_INDENT_UNIT; + const css::beans::Pair<double, sal_Int16> stZero{ + 0.0, css::util::MeasureUnit::FONT_CJK_ADVANCE + }; + auto stCh = stZero; + + m_pImpl->GetAnyProperty(eId, m_pImpl->GetTopContext()) >>= stCh; + if (stCh != stZero) + m_pImpl->GetTopContext()->Insert(eId, uno::Any(stCh), /*Overwrite*/ false); + } + sal_Int32 nFirstLineIndent = m_pImpl->getCurrentNumberingProperty(u"FirstLineIndent"_ustr); sal_Int32 nParaFirstLineIndent = ConversionHelper::convertTwipToMm100_Limited(nIntValue);