sw/inc/ToxTabStopTokenHandler.hxx | 14 ++++- sw/inc/ToxTextGenerator.hxx | 3 - sw/qa/core/test_ToxTextGenerator.cxx | 9 ++- sw/qa/extras/uiwriter/uiwriter6.cxx | 2 sw/source/core/doc/doctxm.cxx | 45 ++++++++++++++---- sw/source/core/tox/ToxTabStopTokenHandler.cxx | 64 +++++++++++++++++++------- sw/source/core/tox/ToxTextGenerator.cxx | 14 ++--- 7 files changed, 111 insertions(+), 40 deletions(-)
New commits: commit 0633189fabe85f73062ff2ce67b5f40af7d3f504 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Aug 7 11:35:42 2024 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Aug 7 15:28:59 2024 +0200 tdf#162121 sw: fix tab stop position in columned ToX There is a SvxTabAdjust::End but this only exists in the definition of index entry templates, it is not possible to set this on a tab stop on a text node. So the ToxTabStopTokenHandler converts this into a SvxTabAdjust::Right tab stop with a fixed position. DefaultToxTabStopTokenHandler::CanUseLayoutRectangle() has inverted condition but that doesn't matter because there are no layout frames at that point. (regression from commit 3aca57fb9c7c979d971cae6bb3ad73c6dc412685) The main problem is that getting the with from the layout rectangle never works because there's no layout frame, and the alternative only takes into account the page dimensions and not any margins of the section, columns, or gap between columns, not to mention that the ToX could also be in a table etc. Refactor this so that the tab stops are set only after all the layout frames are created. An unfixable problem remains in case the ToX has columns of different width. The wrong tab positions are problematic if TabOverMargin is enabled since commit 10d753b8aadb50ec4309551b97d4cf2163ea3e3d Change-Id: Ia712c9bf42b2518e396f1b9e7efd65869ebc5ab4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171585 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> Tested-by: Jenkins diff --git a/sw/inc/ToxTabStopTokenHandler.hxx b/sw/inc/ToxTabStopTokenHandler.hxx index 89a74fe6e65b..29d634c67914 100644 --- a/sw/inc/ToxTabStopTokenHandler.hxx +++ b/sw/inc/ToxTabStopTokenHandler.hxx @@ -45,8 +45,11 @@ public: * and to provide the returned SvxTabStop to the attributes of the node. */ virtual HandledTabStopToken - HandleTabStopToken(const SwFormToken& aToken, const SwTextNode& targetNode, - const SwRootFrame *currentLayout) const = 0; + HandleTabStopToken(const SwFormToken& aToken, const SwTextNode& targetNode) + const = 0; + + virtual auto CalcEndStop(SwTextNode const& rNode, + SwRootFrame const* pLayout) const -> tools::Long = 0; }; /** The default implementation of ToxTabStopTokenHandler */ @@ -83,8 +86,11 @@ public: * method behaves differently when deriving the tab stop position. */ ToxTabStopTokenHandler::HandledTabStopToken - HandleTabStopToken(const SwFormToken& aToken, const SwTextNode& targetNode, - const SwRootFrame *currentLayout) const override; + HandleTabStopToken(const SwFormToken& aToken, const SwTextNode& targetNode) + const override; + + auto CalcEndStop(SwTextNode const& rNode, + SwRootFrame const* pLayout) const -> tools::Long override; private: /** Test whether the page layout can be obtained by a layout rectangle. diff --git a/sw/inc/ToxTextGenerator.hxx b/sw/inc/ToxTextGenerator.hxx index 35eefd0fb7ab..105aa9eb0660 100644 --- a/sw/inc/ToxTextGenerator.hxx +++ b/sw/inc/ToxTextGenerator.hxx @@ -26,6 +26,7 @@ #include "fmtautofmt.hxx" #include <memory> +#include <optional> #include <vector> #include <unordered_map> @@ -67,7 +68,7 @@ public: * This method will process the entries in @p entries, starting at @p indexOfEntryToProcess and * process @p numberOfEntriesToProcess entries. */ - void + std::optional<std::pair<SwTextNode *, SvxTabStopItem>> GenerateText(SwDoc *doc, std::unordered_map<OUString, int> & rMarkURLs, const std::vector<std::unique_ptr<SwTOXSortTabBase>>& entries, diff --git a/sw/qa/core/test_ToxTextGenerator.cxx b/sw/qa/core/test_ToxTextGenerator.cxx index cbfffff4ca00..d64830a770ac 100644 --- a/sw/qa/core/test_ToxTextGenerator.cxx +++ b/sw/qa/core/test_ToxTextGenerator.cxx @@ -113,10 +113,15 @@ namespace { class MockedToxTabStopTokenHandler : public ToxTabStopTokenHandler { public: virtual HandledTabStopToken - HandleTabStopToken(const SwFormToken&, const SwTextNode&, - const SwRootFrame *) const override { + HandleTabStopToken(const SwFormToken&, const SwTextNode&) const override + { return HandledTabStopToken(); } + + auto CalcEndStop(SwTextNode const&, SwRootFrame const*) const -> tools::Long override + { + return 0; + } }; class ToxTextGeneratorWithMockedChapterField : public ToxTextGenerator { diff --git a/sw/qa/extras/uiwriter/uiwriter6.cxx b/sw/qa/extras/uiwriter/uiwriter6.cxx index 723cccdfa954..dce10ce25f99 100644 --- a/sw/qa/extras/uiwriter/uiwriter6.cxx +++ b/sw/qa/extras/uiwriter/uiwriter6.cxx @@ -2201,7 +2201,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest6, testTdf116403) CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), aTabs.getLength()); // This was still 17000, refreshing ToX didn't take borders spacings and widths into account CPPUNIT_ASSERT_EQUAL_MESSAGE("Page borders must be considered for right-aligned tabstop", - static_cast<sal_Int32>(17000 - 2 * 500 - 2 * 1), + static_cast<sal_Int32>(17000 - 2 * 500 - 2 * 1 - 1), aTabs[0].Position); } diff --git a/sw/source/core/doc/doctxm.cxx b/sw/source/core/doc/doctxm.cxx index 53cf6cf02681..e90b289e4fba 100644 --- a/sw/source/core/doc/doctxm.cxx +++ b/sw/source/core/doc/doctxm.cxx @@ -1067,6 +1067,13 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr, // Sort the List of all TOC Marks and TOC Sections std::vector<SwTextFormatColl*> aCollArr( GetTOXForm().GetFormMax(), nullptr ); std::unordered_map<OUString, int> markURLs; + std::vector<std::pair<SwTextNode *, SvxTabStopItem>> tabStops; + std::shared_ptr<sw::ToxTabStopTokenHandler> const pTabStopTokenHandler = + std::make_shared<sw::DefaultToxTabStopTokenHandler>( + pSectNd->GetIndex(), *pDefaultPageDesc, GetTOXForm().IsRelTabPos(), + rDoc.GetDocumentSettingManager().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) + ? sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_INDENT + : sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_PAGE); SwNodeIndex aInsPos( *pFirstEmptyNd, 1 ); for( size_t nCnt = 0; nCnt < m_aSortArr.size(); ++nCnt ) { @@ -1081,7 +1088,6 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr, aCollArr[ nLvl ] = pColl; } - // Generate: Set dynamic TabStops SwTextNode* pTOXNd = rDoc.GetNodes().MakeTextNode( aInsPos.GetNode() , pColl ); m_aSortArr[ nCnt ]->pTOXNd = pTOXNd; @@ -1112,14 +1118,13 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr, // to method <GenerateText(..)>. ::SetProgressState( 0, rDoc.GetDocShell() ); - std::shared_ptr<sw::ToxTabStopTokenHandler> tabStopTokenHandler = - std::make_shared<sw::DefaultToxTabStopTokenHandler>( - pSectNd->GetIndex(), *pDefaultPageDesc, GetTOXForm().IsRelTabPos(), - rDoc.GetDocumentSettingManager().get(DocumentSettingId::TABS_RELATIVE_TO_INDENT) ? - sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_INDENT : - sw::DefaultToxTabStopTokenHandler::TABSTOPS_RELATIVE_TO_PAGE); - sw::ToxTextGenerator ttgn(GetTOXForm(), std::move(tabStopTokenHandler)); - ttgn.GenerateText(GetFormat()->GetDoc(), markURLs, m_aSortArr, nCnt, nRange, pLayout); + sw::ToxTextGenerator ttgn(GetTOXForm(), pTabStopTokenHandler); + std::optional<std::pair<SwTextNode *, SvxTabStopItem>> const oTabStops = + ttgn.GenerateText(GetFormat()->GetDoc(), markURLs, m_aSortArr, nCnt, nRange, pLayout); + if (oTabStops) + { + tabStops.emplace_back(*oTabStops); + } nCnt += nRange - 1; } @@ -1157,6 +1162,28 @@ void SwTOXBaseSection::Update(const SfxItemSet* pAttr, { SwFrame::CheckPageDescs( static_cast<SwPageFrame*>(rpLayout->Lower()) ); } + // delay setting tab stops until the layout frames exist, in case the ToX + // is in columns or other non-body environment; best way is to check uppers + // (what if columns have different widths? no idea what to do about that...) + for (auto & it : tabStops) + { + std::vector<SvxTabStop> tabs; + for (size_t i = 0; i < it.second.Count(); ++i) + { + tabs.emplace_back(it.second.At(i)); + } + it.second.Remove(0, it.second.Count()); + for (SvxTabStop & rTab : tabs) + { + if (rTab.GetAdjustment() == SvxTabAdjust::Right) + { + assert(rTab.GetTabPos() == 0); + rTab.GetTabPos() = pTabStopTokenHandler->CalcEndStop(*it.first, pLayout); + } + it.second.Insert(rTab); + } + it.first->SetAttr(it.second); + } SetProtect( SwTOXBase::IsProtected() ); } diff --git a/sw/source/core/tox/ToxTabStopTokenHandler.cxx b/sw/source/core/tox/ToxTabStopTokenHandler.cxx index b3943d6e5393..2cdcc358c971 100644 --- a/sw/source/core/tox/ToxTabStopTokenHandler.cxx +++ b/sw/source/core/tox/ToxTabStopTokenHandler.cxx @@ -18,6 +18,7 @@ #include <fmtpdsc.hxx> #include <frmfmt.hxx> #include <frmatr.hxx> +#include <frmtool.hxx> #include <ndtxt.hxx> #include <pagedesc.hxx> #include <pagefrm.hxx> @@ -40,7 +41,7 @@ DefaultToxTabStopTokenHandler::DefaultToxTabStopTokenHandler(SwNodeOffset indexO ToxTabStopTokenHandler::HandledTabStopToken DefaultToxTabStopTokenHandler::HandleTabStopToken( - const SwFormToken& aToken, const SwTextNode& targetNode, const SwRootFrame *currentLayout) const + const SwFormToken& aToken, const SwTextNode& targetNode) const { HandledTabStopToken result; @@ -61,29 +62,62 @@ DefaultToxTabStopTokenHandler::HandleTabStopToken( return result; } - SwRect aNdRect; - if (CanUseLayoutRectangle(targetNode, currentLayout)) { - aNdRect = targetNode.FindLayoutRect(true); - } + // note: this will be filled later by CalcEndStop() + result.tabStop = SvxTabStop(0, SvxTabAdjust::Right, cDfltDecimalChar, aToken.cTabFillChar); + return result; +} + +auto DefaultToxTabStopTokenHandler::CalcEndStop(SwTextNode const& rNode, + SwRootFrame const*const pLayout) const -> tools::Long +{ tools::Long nRightMargin; - if (aNdRect.IsEmpty()) { - nRightMargin = CalculatePageMarginFromPageDescription(targetNode); - } else { - nRightMargin = aNdRect.Width(); + if (CanUseLayoutRectangle(rNode, pLayout)) + { + // in case it's in a header, any frame should do + SwContentFrame const*const pFrame(rNode.getLayoutFrame(pLayout)); + assert(pFrame); // created in SwTOXBaseSection::Update() + SwRectFnSet const fnRect(pFrame->GetUpper()); + SwRect rect = pFrame->getFramePrintArea(); + if (fnRect.GetWidth(rect) == 0) // typically it's newly created + { + if (pFrame->GetUpper()->IsSctFrame()) + { // this is set in SwSectionFrame::Init() + rect = pFrame->GetUpper()->GetUpper()->getFramePrintArea(); + assert(fnRect.GetWidth(rect) != 0); + } + else if (pFrame->GetUpper()->IsColBodyFrame()) + { + SwFrame const*const pColFrame(pFrame->GetUpper()->GetUpper()); + assert(pColFrame->IsColumnFrame()); + rect = pColFrame->getFrameArea(); + // getFramePrintArea() is not valid yet, manually subtract... + // (it can have a border too!) + SwBorderAttrAccess access(SwFrame::GetCache(), pColFrame); + SwBorderAttrs const& rAttrs(*access.Get()); + auto const nLeft(rAttrs.CalcLeft(pColFrame)); + auto const nRight(rAttrs.CalcRight(pColFrame)); + fnRect.SetWidth(rect, fnRect.GetWidth(rect) - nLeft - nRight); + assert(fnRect.GetWidth(rect) != 0); + } + else assert(false); + } + nRightMargin = fnRect.GetWidth(rect); + } + else + { + nRightMargin = CalculatePageMarginFromPageDescription(rNode); } //#i24363# tab stops relative to indent if (mTabStopReferencePolicy == TABSTOPS_RELATIVE_TO_INDENT) { // left margin of paragraph style SvxFirstLineIndentItem const& rFirstLine( - targetNode.GetTextColl()->GetFirstLineIndent()); + rNode.GetTextColl()->GetFirstLineIndent()); SvxTextLeftMarginItem const& rTextLeftMargin( - targetNode.GetTextColl()->GetTextLeftMargin()); + rNode.GetTextColl()->GetTextLeftMargin()); nRightMargin -= rTextLeftMargin.GetLeft(rFirstLine); nRightMargin -= rFirstLine.GetTextFirstLineOffset(); } - - result.tabStop = SvxTabStop(nRightMargin, SvxTabAdjust::Right, cDfltDecimalChar, aToken.cTabFillChar); - return result; + return nRightMargin - 1; // subtract 1 twip to avoid equal for TabOverMargin } tools::Long @@ -113,7 +147,7 @@ DefaultToxTabStopTokenHandler::CanUseLayoutRectangle(const SwTextNode& targetNod targetNode.SwContentNode::GetAttr(RES_PAGEDESC).GetPageDesc(); if (!pageDescription) { - return false; + return true; } const SwFrame* pFrame = targetNode.getLayoutFrame(currentLayout); if (!pFrame) { diff --git a/sw/source/core/tox/ToxTextGenerator.cxx b/sw/source/core/tox/ToxTextGenerator.cxx index 158cc0c72bc7..32d18d1c8087 100644 --- a/sw/source/core/tox/ToxTextGenerator.cxx +++ b/sw/source/core/tox/ToxTextGenerator.cxx @@ -165,15 +165,14 @@ ToxTextGenerator::GenerateTextForChapterToken(const SwFormToken& chapterToken, c return retval; } -// Add parameter <_TOXSectNdIdx> and <_pDefaultPageDesc> in order to control, -// which page description is used, no appropriate one is found. -void +std::optional<std::pair<SwTextNode *, SvxTabStopItem>> ToxTextGenerator::GenerateText(SwDoc* pDoc, std::unordered_map<OUString, int> & rMarkURLs, const std::vector<std::unique_ptr<SwTOXSortTabBase>> &entries, sal_uInt16 indexOfEntryToProcess, sal_uInt16 numberOfEntriesToProcess, SwRootFrame const*const pLayout) { + std::optional<std::pair<SwTextNode *, SvxTabStopItem>> oRet; // pTOXNd is only set at the first mark SwTextNode* pTOXNd = const_cast<SwTextNode*>(entries.at(indexOfEntryToProcess)->pTOXNd); // FIXME this operates directly on the node text @@ -188,7 +187,7 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, sal_uInt16 nLvl = rBase.GetLevel(); OSL_ENSURE( nLvl < mToxForm.GetFormMax(), "invalid FORM_LEVEL"); - SvxTabStopItem aTStops( 0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP ); + oRet.emplace(pTOXNd, SvxTabStopItem(0, 0, SvxTabAdjust::Default, RES_PARATR_TABSTOP)); // create an enumerator // #i21237# SwFormTokens aPattern = mToxForm.GetPattern(nLvl); @@ -228,9 +227,9 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, case TOKEN_TAB_STOP: { ToxTabStopTokenHandler::HandledTabStopToken htst = - mTabStopTokenHandler->HandleTabStopToken(aToken, *pTOXNd, pDoc->getIDocumentLayoutAccess().GetCurrentLayout()); + mTabStopTokenHandler->HandleTabStopToken(aToken, *pTOXNd); rText += htst.text; - aTStops.Insert(htst.tabStop); + oRet->second.Insert(htst.tabStop); break; } @@ -304,10 +303,9 @@ ToxTextGenerator::GenerateText(SwDoc* pDoc, } } } - - pTOXNd->SetAttr( aTStops ); } mLinkProcessor->InsertLinkAttributes(*pTOXNd); + return oRet; } /*static*/ std::shared_ptr<SfxItemSet>