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 a12ac9f4da5efd114c84720834ec11fabd01d0fb Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Sun May 18 18:26:50 2025 +0500 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Sun May 18 17:45:45 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> 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 17091665be20..5e8ff7b5a112 100644 --- a/sw/qa/extras/rtfexport/rtfexport8.cxx +++ b/sw/qa/extras/rtfexport/rtfexport8.cxx @@ -648,6 +648,62 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf165564) 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 8c52b33ee73d..cc290716e95d 100644 --- a/sw/qa/extras/ww8export/ww8export4.cxx +++ b/sw/qa/extras/ww8export/ww8export4.cxx @@ -720,6 +720,62 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf88908) } } +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 1819d395ca9c..f6a886e1057e 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 083943375f20..f59a749b3ca0 100644 --- a/sw/source/filter/ww8/wrtww8.cxx +++ b/sw/source/filter/ww8/wrtww8.cxx @@ -1832,7 +1832,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 111c711b8fbe..9708826f9773 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 5885684b29ba..8ee662a8b3d7 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -4520,20 +4520,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 81ab58298560..a903bbaa0a86 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -4173,7 +4173,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;