sw/qa/extras/ooxmlexport/ooxmlexport25.cxx | 57 +++++++++++++++++++ sw/qa/extras/rtfexport/rtfexport5.cxx | 2 sw/qa/extras/rtfexport/rtfexport8.cxx | 56 ++++++++++++++++++ sw/qa/extras/ww8export/ww8export4.cxx | 56 ++++++++++++++++++ sw/qa/extras/ww8import/ww8import.cxx | 2 sw/source/filter/ww8/wrtw8nds.cxx | 5 + sw/source/filter/ww8/wrtww8.cxx | 3 - sw/source/filter/ww8/ww8par2.cxx | 15 ----- sw/source/writerfilter/dmapper/DomainMapper.cxx | 14 ---- sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 1 sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx | 2 11 files changed, 176 insertions(+), 37 deletions(-)
New commits: commit 494c9db821b0aa6d7ee4fa93627f37cff4586ae8 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sun May 18 18:26:50 2025 +0500 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Mon May 19 16:54:11 2025 +0200 tdf#166620: don't strip leading tab from footnotes/endnotes anymore Before the fix for tdf#159382, the layout of footnote/endnote number was incompatible between Writer and Word; in Writer, a spacing was always created between them, while in Word, there was none. This meant, that to create a Word file format with a footnote/endnote that looked similar to what Writer showed, we needed to put a tab in the beginning of the exported note text; and on import, we needed to strip it away. The fix for tdf#159382 introduced NoGapAfterNoteNumber compatibility flag, which avoids the extra space between the note's number and its text. This flag it active for all Word file formats; so we need to keep the tabs in the beginning of the note text, or else we show it wrong. This change drops the code that stripped the tabs, reverting part of commit fcbc2fc4ae4ea9a499616bfeaa6a2cbece93f01a (INTEGRATION: CWS tuamfilterteam23 (1.95.10); FILE MERGED, 2004-02-26), and also commits b38629ae210b204a6d24d6e9c5c62eaaf563d494 (cp#1000017 DOCX/RTF import: avoid fake tab char in footnotes, 2013-12-05), 946fee3ef1e319ad63a599b72dbd55ef52cbc640 (tdf#106062 ooxmlimport: skip fake tab only on hanging indent, 2018-04-24), and a3783c0af4bd21eb9c001aadc60c660c06a47779 (tdf#106062 ww8import: skip fake tab only on hanging indent, 2018-04-28). Also, it makes sure to avoid outputting a tab from a document with the compatibility flag set, even when the note doesn't start with a tab. Change-Id: I8fa085d3ba45367476853723c6ccba1b047eb092 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185471 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/185499 diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx index 2b2976aefac5..3f920a0cf4dc 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx @@ -20,6 +20,7 @@ #include <unotxdoc.hxx> #include <docsh.hxx> #include <IDocumentSettingAccess.hxx> +#include <wrtsh.hxx> namespace { @@ -54,6 +55,62 @@ DECLARE_OOXMLEXPORT_TEST(testTdf166510_sectPr_bottomSpacing, "tdf166510_sectPr_b CPPUNIT_ASSERT_EQUAL(sal_Int32(4253), nHeight); } +CPPUNIT_TEST_FIXTURE(Test, testTdf166620) +{ + createSwDoc(); + { + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Insert(u"Body text"_ustr); + pWrtShell->InsertFootnote({}, /*bEndNote=*/true, /*bEdit=*/true); + pWrtShell->Insert(u"Endnote text"_ustr); + } + + // Exporting to a Word format, a tab is prepended to the endnote text. When imported, the + // NoGapAfterNoteNumber compatibility flag is enabled; and the exported tab is the only thing + // that separates the number and the text. The tab must not be stripped away on import. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u" Endnote text"_ustr, xEndnoteText->getString()); + } + // Do a second round-trip. It must not duplicate the tab. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u" Endnote text"_ustr, xEndnoteText->getString()); + + // Remove the tab + xEndnoteText->setString(u"Endnote text"_ustr); + } + // Do a third round-trip. It must not introduce the tab, because of the compatibility flag. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u"Endnote text"_ustr, xEndnoteText->getString()); + } +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/rtfexport/rtfexport5.cxx b/sw/qa/extras/rtfexport/rtfexport5.cxx index 534526f09513..0c0cc21823d6 100644 --- a/sw/qa/extras/rtfexport/rtfexport5.cxx +++ b/sw/qa/extras/rtfexport/rtfexport5.cxx @@ -565,7 +565,7 @@ CPPUNIT_TEST_FIXTURE(Test, testCp1000018) OUString const aActual = xTextRange->getString(); - CPPUNIT_ASSERT_EQUAL(u"Footnote first line." SAL_NEWLINE_STRING ""_ustr, aActual); + CPPUNIT_ASSERT_EQUAL(u" Footnote first line." SAL_NEWLINE_STRING ""_ustr, aActual); }; createSwDoc("cp1000018.rtf"); verify(); diff --git a/sw/qa/extras/rtfexport/rtfexport8.cxx b/sw/qa/extras/rtfexport/rtfexport8.cxx index 7184b7cc5b89..ef0a32c374e1 100644 --- a/sw/qa/extras/rtfexport/rtfexport8.cxx +++ b/sw/qa/extras/rtfexport/rtfexport8.cxx @@ -622,6 +622,62 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf161878) verify(); } +CPPUNIT_TEST_FIXTURE(Test, testTdf166620) +{ + createSwDoc(); + { + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Insert(u"Body text"_ustr); + pWrtShell->InsertFootnote({}, /*bEndNote=*/true, /*bEdit=*/true); + pWrtShell->Insert(u"Endnote text"_ustr); + } + + // Exporting to a Word format, a tab is prepended to the endnote text. When imported, the + // NoGapAfterNoteNumber compatibility flag is enabled; and the exported tab is the only thing + // that separates the number and the text. The tab must not be stripped away on import. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u" Endnote text"_ustr, xEndnoteText->getString()); + } + // Do a second round-trip. It must not duplicate the tab. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u" Endnote text"_ustr, xEndnoteText->getString()); + + // Remove the tab + xEndnoteText->setString(u"Endnote text"_ustr); + } + // Do a third round-trip. It must not introduce the tab, because of the compatibility flag. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u"Endnote text"_ustr, xEndnoteText->getString()); + } +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/ww8export/ww8export4.cxx b/sw/qa/extras/ww8export/ww8export4.cxx index 7bd30ab653c0..858515a312f8 100644 --- a/sw/qa/extras/ww8export/ww8export4.cxx +++ b/sw/qa/extras/ww8export/ww8export4.cxx @@ -645,6 +645,62 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf54862) verify(); } +CPPUNIT_TEST_FIXTURE(Test, testTdf166620) +{ + createSwDoc(); + { + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Insert(u"Body text"_ustr); + pWrtShell->InsertFootnote({}, /*bEndNote=*/true, /*bEdit=*/true); + pWrtShell->Insert(u"Endnote text"_ustr); + } + + // Exporting to a Word format, a tab is prepended to the endnote text. When imported, the + // NoGapAfterNoteNumber compatibility flag is enabled; and the exported tab is the only thing + // that separates the number and the text. The tab must not be stripped away on import. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u" Endnote text"_ustr, xEndnoteText->getString()); + } + // Do a second round-trip. It must not duplicate the tab. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u" Endnote text"_ustr, xEndnoteText->getString()); + + // Remove the tab + xEndnoteText->setString(u"Endnote text"_ustr); + } + // Do a third round-trip. It must not introduce the tab, because of the compatibility flag. + saveAndReload(mpFilter); + { + auto xFactory = mxComponent.queryThrow<lang::XMultiServiceFactory>(); + auto xSettings = xFactory->createInstance(u"com.sun.star.document.Settings"_ustr); + CPPUNIT_ASSERT(getProperty<bool>(xSettings, u"NoGapAfterNoteNumber"_ustr)); + + auto xSupplier = mxComponent.queryThrow<text::XEndnotesSupplier>(); + auto xEndnotes = xSupplier->getEndnotes(); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xEndnotes->getCount()); + auto xEndnoteText = xEndnotes->getByIndex(0).queryThrow<text::XText>(); + CPPUNIT_ASSERT_EQUAL(u"Endnote text"_ustr, xEndnoteText->getString()); + } +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/qa/extras/ww8import/ww8import.cxx b/sw/qa/extras/ww8import/ww8import.cxx index 68e67fb83f18..78548022d44a 100644 --- a/sw/qa/extras/ww8import/ww8import.cxx +++ b/sw/qa/extras/ww8import/ww8import.cxx @@ -265,7 +265,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf142003) uno::Reference<container::XIndexAccess> xFootnotes = xFootnotesSupplier->getFootnotes(); uno::Reference<text::XTextRange> xParagraph(xFootnotes->getByIndex(0), uno::UNO_QUERY); //before change was incorrect, Loren ipsum , doconsectetur ... - CPPUNIT_ASSERT(xParagraph->getString().startsWith("Lorem ipsum , consectetur adipiscing elit.")); + CPPUNIT_ASSERT(xParagraph->getString().startsWith(" Lorem ipsum , consectetur adipiscing elit.")); } CPPUNIT_TEST_FIXTURE(Test, testTdf160301) diff --git a/sw/source/filter/ww8/wrtw8nds.cxx b/sw/source/filter/ww8/wrtw8nds.cxx index 622ad906b992..5097c8f4a48d 100644 --- a/sw/source/filter/ww8/wrtw8nds.cxx +++ b/sw/source/filter/ww8/wrtw8nds.cxx @@ -2747,7 +2747,8 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) OUString aSnippet( aAttrIter.GetSnippet( aStr, nCurrentPos + ofs, nLen ) ); const SwTextNode* pTextNode( rNode.GetTextNode() ); - if ( ( m_nTextTyp == TXT_EDN || m_nTextTyp == TXT_FTN ) && nCurrentPos == 0 && nLen > 0 ) + if (m_bAddFootnoteTab && (m_nTextTyp == TXT_EDN || m_nTextTyp == TXT_FTN) + && nCurrentPos == 0 && nLen > 0 && aSnippet[0] != 0x09) { // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent sal_Int32 nFirstLineIndent=0; @@ -2761,7 +2762,7 @@ void MSWordExportBase::OutputTextNode( SwTextNode& rNode ) } // Insert tab for aesthetic purposes #i24762# - if ( m_bAddFootnoteTab && nFirstLineIndent < 0 && aSnippet[0] != 0x09 ) + if (nFirstLineIndent < 0) aSnippet = "\x09" + aSnippet; m_bAddFootnoteTab = false; } diff --git a/sw/source/filter/ww8/wrtww8.cxx b/sw/source/filter/ww8/wrtww8.cxx index cad075e97d8b..9a9a2ad7e040 100644 --- a/sw/source/filter/ww8/wrtww8.cxx +++ b/sw/source/filter/ww8/wrtww8.cxx @@ -1830,7 +1830,8 @@ void MSWordExportBase::WriteSpecialText( SwNodeOffset nStart, SwNodeOffset nEnd, SwPaM* pOldEnd = m_pOrigPam; bool bOldPageDescs = m_bOutPageDescs; m_bOutPageDescs = false; - if ( nTTyp == TXT_FTN || nTTyp == TXT_EDN ) + if ((nTTyp == TXT_FTN || nTTyp == TXT_EDN) + && !m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::NO_GAP_AFTER_NOTE_NUMBER)) m_bAddFootnoteTab = true; // enable one aesthetic tab for this footnote SetCurPam(nStart, nEnd); diff --git a/sw/source/filter/ww8/ww8par2.cxx b/sw/source/filter/ww8/ww8par2.cxx index 86ac1f91a202..3647c832e6ee 100644 --- a/sw/source/filter/ww8/ww8par2.cxx +++ b/sw/source/filter/ww8/ww8par2.cxx @@ -284,23 +284,8 @@ sal_uInt16 SwWW8ImplReader::End_Footnote() const OUString &rText = pTNd->GetText(); if (rText[0] == sChar[0]) { - // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent - sal_Int32 nFirstLineIndent=0; - SfxItemSetFixed<RES_MARGIN_FIRSTLINE, RES_MARGIN_FIRSTLINE> aSet(m_rDoc.GetAttrPool()); - if ( pTNd->GetAttr(aSet) ) - { - const SvxFirstLineIndentItem *const pFirstLine(aSet.GetItem<SvxFirstLineIndentItem>(RES_MARGIN_FIRSTLINE)); - if (pFirstLine) - { - nFirstLineIndent = pFirstLine->ResolveTextFirstLineOffset({}); - } - } - rPaMPointPos.SetContent(0); m_pPaM->SetMark(); - // Strip out aesthetic tabs we may have inserted on export #i24762# - if (nFirstLineIndent < 0 && rText.getLength() > 1 && rText[1] == 0x09) - m_pPaM->GetMark()->AdjustContent(1); m_pPaM->GetMark()->AdjustContent(1); m_xReffingStck->Delete(*m_pPaM); m_rDoc.getIDocumentContentOperations().DeleteRange( *m_pPaM ); diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index 01415e209783..76de71c9c17c 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -4481,20 +4481,6 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) m_pImpl->HandleLineBreak(m_pImpl->GetTopContext()); } } - else if (len == 1 && sText[0] == ' ' ) - { - if (m_pImpl->m_StreamStateStack.top().bCheckFirstFootnoteTab && m_pImpl->IsInFootOrEndnote()) - { - // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent - m_pImpl->m_StreamStateStack.top().bCheckFirstFootnoteTab = false; - sal_Int32 nFirstLineIndent = 0; - m_pImpl->GetAnyProperty(PROP_PARA_FIRST_LINE_INDENT, m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH)) >>= nFirstLineIndent; - if ( nFirstLineIndent < 0 ) - { - return; - } - } - } if (!m_pImpl->hasTableManager()) return; diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index 0eba41c7c43b..fa00afddc8ee 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -4092,7 +4092,6 @@ void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote ) { SAL_WARN_IF(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote"); m_StreamStateStack.top().eSubstreamType = bIsFootnote ? SubstreamType::Footnote : SubstreamType::Endnote; - m_StreamStateStack.top().bCheckFirstFootnoteTab = true; try { // Redlines outside the footnote should not affect footnote content diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx index 96a840b1a414..e1a9931ef0fd 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx @@ -251,8 +251,6 @@ struct SubstreamContext bool bParaAutoBefore = false; /// Raw table cell depth. sal_Int32 nTableCellDepth = 0; - /// If the next tab should be ignored, used for footnotes. - bool bCheckFirstFootnoteTab = false; std::optional<sal_Int16> oLineBreakClear; bool bIsInTextBox = false; rtl::Reference<SwXTextEmbeddedObject> xEmbedded;