sw/qa/core/layout/data/floattable-3pages.docx |binary sw/qa/core/layout/flycnt.cxx | 67 +++++++++- sw/source/core/layout/anchoredobject.cxx | 20 ++ sw/source/core/layout/flycnt.cxx | 17 +- sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx | 23 ++- sw/source/core/text/frmform.cxx | 8 + sw/source/core/text/itratr.cxx | 15 ++ 7 files changed, 130 insertions(+), 20 deletions(-)
New commits: commit 7c9acfe5baef275f07c185c6fedf8b6d62d88637 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Mon Feb 20 08:15:41 2023 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Mon Feb 20 08:09:20 2023 +0000 sw floattable: fix handling of multiple splits, i.e. table over 3 or more pages - fix SwAnchoredObject::FindAnchorCharFrame() and SwToContentAnchoredObjectPosition::CalcPosition() to not assume that the precede of a follow fly is the master fly, which is not true when the table is split to 3 pages. In this case we have a master, then a normal follow and finally a last follow - fix SwFrame::GetNextFlyLeaf() to split the right anchor, which may be a follow text frame now, and register the new fly in master text frame, not at the split position - improve SwTextFrame::HasNonLastSplitFlyDrawObj() to look at flys in the master, but only consider the ones which will be positioned relative to the current anchor. Previously this was easier, as a "non-last split fly" happened to be always a master text frame, but now we han have split flys which are neither a master nor a last follow - finally improve SwTextFrame::MakePos(), because the anchor on the 2nd page is calculated after the 2nd fly is positioned, so it was only positioned inside the 2nd page, but not at the correct position Change-Id: Ibecaf5fcf0b8c56c5109461a16dcca058231b660 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/147308 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/qa/core/layout/data/floattable-3pages.docx b/sw/qa/core/layout/data/floattable-3pages.docx new file mode 100644 index 000000000000..d6cbdafc1945 Binary files /dev/null and b/sw/qa/core/layout/data/floattable-3pages.docx differ diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx index a41fb17ef54d..4b083914b74d 100644 --- a/sw/qa/core/layout/flycnt.cxx +++ b/sw/qa/core/layout/flycnt.cxx @@ -93,7 +93,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWithTable) CPPUNIT_ASSERT(!pPage1Anchor->HasPara()); } -CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertoffset) +CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertOffset) { // Given a document with a floattable, split on 2 pages and a positive vertical offset: std::shared_ptr<comphelper::ConfigurationChanges> pChanges( @@ -145,6 +145,71 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertoffset) // i.e. the fly frame on the 2nd page was also shifted down in Writer, but not in Word. CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(0), nPage2FlyTop - nPage2AnchorTop); } + +CPPUNIT_TEST_FIXTURE(Test, testSplitFly3Pages) +{ + // Given a document with a floattable, split on 3 pages: + std::shared_ptr<comphelper::ConfigurationChanges> pChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::set(true, + pChanges); + pChanges->commit(); + comphelper::ScopeGuard g([pChanges] { + officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::set( + false, pChanges); + pChanges->commit(); + }); + createSwDoc("floattable-3pages.docx"); + + // When laying out that document: + calcLayout(); + + // Then make sure that row 1, 2 & 3 go to page 1, 2 & 3, while the table is floating: + SwDoc* pDoc = getSwDoc(); + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower()); + CPPUNIT_ASSERT(pPage1); + const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs.size()); + auto pPage1Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage1Objs[0]); + CPPUNIT_ASSERT(pPage1Fly); + auto pPage1Anchor = dynamic_cast<SwTextFrame*>(pPage1->FindLastBodyContent()); + CPPUNIT_ASSERT(pPage1Anchor); + SwTwips nPage1AnchorTop = pPage1Anchor->getFrameArea().Top(); + SwTwips nPage1FlyTop = pPage1Fly->getFrameArea().Top(); + // The vert offset should be there on the first page: + CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(1135), nPage1FlyTop - nPage1AnchorTop); + // Second page: + auto pPage2 = dynamic_cast<SwPageFrame*>(pPage1->GetNext()); + CPPUNIT_ASSERT(pPage2); + const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 2 + // i.e. both the 2nd and 3rd fly was anchored on page 2. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs.size()); + auto pPage2Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage2Objs[0]); + CPPUNIT_ASSERT(pPage2Fly); + auto pPage2Anchor = dynamic_cast<SwTextFrame*>(pPage2->FindLastBodyContent()); + CPPUNIT_ASSERT(pPage2Anchor); + SwTwips nPage2AnchorTop = pPage2Anchor->getFrameArea().Top(); + SwTwips nPage2FlyTop = pPage2Fly->getFrameArea().Top(); + // No vert offset on the second page: + CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(0), nPage2FlyTop - nPage2AnchorTop); + // 3rd page: + auto pPage3 = dynamic_cast<SwPageFrame*>(pPage1->GetNext()); + CPPUNIT_ASSERT(pPage3); + const SwSortedObjs& rPage3Objs = *pPage3->GetSortedObjs(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage3Objs.size()); + auto pPage3Fly = dynamic_cast<SwFlyAtContentFrame*>(rPage3Objs[0]); + CPPUNIT_ASSERT(pPage3Fly); + auto pPage3Anchor = dynamic_cast<SwTextFrame*>(pPage3->FindLastBodyContent()); + CPPUNIT_ASSERT(pPage3Anchor); + SwTwips nPage3AnchorTop = pPage3Anchor->getFrameArea().Top(); + SwTwips nPage3FlyTop = pPage3Fly->getFrameArea().Top(); + // No vert offset on the 3rd page: + CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips>(0), nPage3FlyTop - nPage3AnchorTop); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/layout/anchoredobject.cxx b/sw/source/core/layout/anchoredobject.cxx index 4272437f9e25..5f8a307867bf 100644 --- a/sw/source/core/layout/anchoredobject.cxx +++ b/sw/source/core/layout/anchoredobject.cxx @@ -724,14 +724,24 @@ SwTextFrame* SwAnchoredObject::FindAnchorCharFrame() if (pFlyFrame->IsFlySplitAllowed()) { auto pFlyAtContentFrame = static_cast<SwFlyAtContentFrame*>(pFlyFrame); - if (pFlyAtContentFrame->GetPrecede()) + SwFlyAtContentFrame* pFly = pFlyAtContentFrame; + SwTextFrame* pAnchor = static_cast<SwTextFrame*>(AnchorFrame()); + // If we have to jump back N frames to find the master fly, then we have to step N + // frames from the master anchor to reach the correct follow anchor. + while (pFly->GetPrecede()) { - SwTextFrame* pFrame(static_cast<SwTextFrame*>(AnchorFrame())); - const SwTextFrame* pFollow = pFrame->GetFollow(); - if (pFollow) + pFly = pFly->GetPrecede(); + if (!pAnchor) { - pAnchorCharFrame = const_cast<SwTextFrame*>(pFollow); + SAL_WARN("sw.layout", "SwAnchoredObject::FindAnchorCharFrame: fly chain " + "length is longer then anchor chain length"); + break; } + pAnchor = pAnchor->GetFollow(); + } + if (pAnchor) + { + pAnchorCharFrame = pAnchor; } } } diff --git a/sw/source/core/layout/flycnt.cxx b/sw/source/core/layout/flycnt.cxx index 9e8d1a3ec13b..f96669c537d0 100644 --- a/sw/source/core/layout/flycnt.cxx +++ b/sw/source/core/layout/flycnt.cxx @@ -1600,19 +1600,22 @@ SwLayoutFrame *SwFrame::GetNextFlyLeaf( MakePageType eMakePage ) if( pLayLeaf ) { SwFlyAtContentFrame* pNew = nullptr; - SwFrame* pFlyAnchor = const_cast<SwFrame*>(pFly->GetAnchorFrame()); - if (pFlyAnchor && pFlyAnchor->IsTextFrame()) + // Find the anchor frame to split. + SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame(); + if (pFlyAnchor) { // Split the anchor at char 0: all the content goes to the follow of the anchor. - auto pFlyAnchorTextFrame = static_cast<SwTextFrame*>(pFlyAnchor); - pFlyAnchorTextFrame->SplitFrame(TextFrameIndex(0)); + pFlyAnchor->SplitFrame(TextFrameIndex(0)); auto pNext = static_cast<SwTextFrame*>(pFlyAnchor->GetNext()); pNext->MoveSubTree(pLayLeaf); - // Now create the follow of the fly and anchor it in the just created follow of the - // anchor. + // Now create the follow of the fly and anchor it in the master of the anchor. pNew = new SwFlyAtContentFrame(*pFly); - pFlyAnchorTextFrame->AppendFly(pNew); + while (pFlyAnchor->IsFollow()) + { + pFlyAnchor = pFlyAnchor->FindMaster(); + } + pFlyAnchor->AppendFly(pNew); } pLayLeaf = pNew; } diff --git a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx index 1a58d3a650dd..8756da6a325c 100644 --- a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx +++ b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx @@ -236,15 +236,26 @@ void SwToContentAnchoredObjectPosition::CalcPosition() if (pFlyFrame->IsFlySplitAllowed()) { auto pFlyAtContentFrame = static_cast<SwFlyAtContentFrame*>(pFlyFrame); - if (pFlyAtContentFrame->GetPrecede()) + // Decrement pFly to point to the master; increment pAnchor to point to the correct + // follow anchor. + SwFlyAtContentFrame* pFly = pFlyAtContentFrame; + SwTextFrame* pAnchor = const_cast<SwTextFrame*>(&rAnchorTextFrame); + while (pFly->GetPrecede()) { - const SwTextFrame* pFollow = rAnchorTextFrame.GetFollow(); - if (pFollow) + pFly = pFly->GetPrecede(); + if (!pAnchor) { - pOrientFrame = pFollow; - // Anchored object has a precede, so it's a follow. - bFollowSplitFly = true; + SAL_WARN("sw.core", "SwToContentAnchoredObjectPosition::CalcPosition: fly " + "chain length is longer then anchor chain length"); + break; } + pAnchor = pAnchor->GetFollow(); + } + if (pAnchor && pAnchor->GetPrecede()) + { + pOrientFrame = pAnchor; + // Anchored object has a precede, so it's a follow. + bFollowSplitFly = true; } } } diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx index 5b3db006a2ee..74d1a7cec04e 100644 --- a/sw/source/core/text/frmform.cxx +++ b/sw/source/core/text/frmform.cxx @@ -341,6 +341,14 @@ bool SwTextFrame::CalcFollow(TextFrameIndex const nTextOfst) void SwTextFrame::MakePos() { SwFrame::MakePos(); + + for (const auto& pFly : GetSplitFlyDrawObjs()) + { + // Possibly this fly was positioned relative to us, invalidate its position now that our + // position is changed. + pFly->InvalidatePos(); + } + // Inform LOK clients about change in position of redlines (if any) if(!comphelper::LibreOfficeKit::isActive()) return; diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx index 4a0cdc5e6807..613a2aa44265 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -1495,8 +1495,21 @@ bool SwTextFrame::HasNonLastSplitFlyDrawObj() const // At this point we know what we're part of a chain that is an anchor for split fly frames, but // we're not the last one. See if we have a matching fly. - for (const auto& pFly : GetSplitFlyDrawObjs()) + // Look up the master of the anchor. + const SwTextFrame* pAnchor = this; + while (pAnchor->IsFollow()) { + pAnchor = pAnchor->FindMaster(); + } + for (const auto& pFly : pAnchor->GetSplitFlyDrawObjs()) + { + // Nominally all flys are anchored in the master; see if this fly is effectively anchored in + // us. + SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame(); + if (pFlyAnchor != pAnchor) + { + continue; + } if (pFly->GetFollow()) { return true;