sw/qa/core/layout/data/floattable-3pages.docx |binary sw/qa/core/layout/flycnt.cxx | 67 +++ sw/qa/filter/ww8/ww8.cxx | 39 ++ sw/source/core/layout/anchoredobject.cxx | 20 - sw/source/core/layout/flycnt.cxx | 17 sw/source/core/layout/tabfrm.cxx | 8 sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx | 23 - sw/source/core/text/frmform.cxx | 8 sw/source/core/text/itratr.cxx | 15 sw/source/core/text/txtfrm.cxx | 7 sw/source/filter/ww8/docxattributeoutput.cxx | 171 +++++----- writerfilter/source/dmapper/DomainMapperTableHandler.cxx | 28 + 12 files changed, 305 insertions(+), 98 deletions(-)
New commits: commit 58e70891131a99d4eb38b362bb2d4246c7a67cb6 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Feb 22 08:11:00 2023 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Mar 3 08:45:03 2023 +0100 sw floatable: teach the DOCX filter about SwFormatFlySplit - stop creating a grab-bag for floating tables in the DOCX import if split flys are allowed, which gives the exporter an opportunity to actually read the doc model - extract that code that writes a <w:tblpPr> from a ww8::Frame to a new CollectFloatingTableAttributes() - in case a fly frame has a table and the fly has SwFormatFlySplit=true, then call CollectFloatingTableAttributes() even without grab-bags - in the unlikely case that we would have both a split fly and a grab-bag, ignore the grab-bag With this, we get a working DOCX export for multi-page floating tables. The import is still disabled by default. (cherry picked from commit e7be3b821cd42fdc9d8e51772b8202030d76497e) Change-Id: I601833c49f49f94e1ff3cdc994e3027ee0542b94 diff --git a/sw/qa/filter/ww8/ww8.cxx b/sw/qa/filter/ww8/ww8.cxx index 611d63259ae8..8eac94ae36d0 100644 --- a/sw/qa/filter/ww8/ww8.cxx +++ b/sw/qa/filter/ww8/ww8.cxx @@ -15,6 +15,10 @@ #include <docsh.hxx> #include <formatcontentcontrol.hxx> #include <wrtsh.hxx> +#include <itabenum.hxx> +#include <frmmgr.hxx> +#include <frameformats.hxx> +#include <formatflysplit.hxx> namespace { @@ -189,6 +193,41 @@ CPPUNIT_TEST_FIXTURE(Test, testDocxSymbolFontExport) assertXPath(pXmlDoc, "//w:p/w:r/w:sym[1]", "font", "Wingdings"); assertXPath(pXmlDoc, "//w:p/w:r/w:sym[1]", "char", "f0e0"); } + +CPPUNIT_TEST_FIXTURE(Test, testDocxFloatingTableExport) +{ + // Given a document with a floating table: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + // Insert a table: + SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); + pWrtShell->InsertTable(aTableOptions, 1, 1); + pWrtShell->MoveTable(GotoPrevTable, fnTableStart); + // Select it: + pWrtShell->SelAll(); + // Wrap in a fly: + SwFlyFrameAttrMgr aMgr(true, pWrtShell, Frmmgr_Type::TEXT, nullptr); + pWrtShell->StartAllAction(); + aMgr.InsertFlyFrame(RndStdIds::FLY_AT_PARA, aMgr.GetPos(), aMgr.GetSize()); + // Mark it as a floating table: + SwFrameFormats& rFlys = *pDoc->GetSpzFrameFormats(); + SwFrameFormat* pFly = rFlys[0]; + SwAttrSet aSet(pFly->GetAttrSet()); + aSet.Put(SwFormatFlySplit(true)); + pDoc->SetAttr(aSet, *pFly); + pWrtShell->EndAllAction(); + + // When saving to docx: + save("Office Open XML Text"); + + // Then make sure we write a floating table, not a textframe containing a table: + xmlDocUniquePtr pXmlDoc = parseExport("word/document.xml"); + // Without the accompanying fix in place, this test would have failed with: + // - XPath '//w:tbl/w:tblPr/w:tblpPr' number of nodes is incorrect + // i.e. no floating table was exported. + assertXPath(pXmlDoc, "//w:tbl/w:tblPr/w:tblpPr", 1); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 11a35b8bd55e..f1485b5814f4 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -137,6 +137,7 @@ #include <txtatr.hxx> #include <frameformats.hxx> #include <textcontentcontrol.hxx> +#include <formatflysplit.hxx> #include <o3tl/string_view.hxx> #include <o3tl/unit_conversion.hxx> @@ -409,7 +410,14 @@ static void checkAndWriteFloatingTables(DocxAttributeOutput& rDocxAttributeOutpu std::map<OUString, css::uno::Any> aTableGrabBag = pTableGrabBag->GetGrabBag(); // no grabbag? if (aTableGrabBag.find("TablePosition") == aTableGrabBag.end()) + { + if (pFrameFormat->GetFlySplit().GetValue()) + { + ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor()); + rDocxAttributeOutput.WriteFloatingTable(&aFrame); + } continue; + } // write table to docx ww8::Frame aFrame(*pFrameFormat, *rAnchor.GetContentAnchor()); @@ -4554,6 +4562,84 @@ sal_Int32 lcl_getWordCompatibilityMode(const DocxExport& rDocExport) return nWordCompatibilityMode; } +void CollectFloatingTableAttributes(DocxExport& rExport, const ww8::Frame& rFrame, + ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner, + rtl::Reference<FastAttributeList>& pAttributes) +{ + // we export the values of the surrounding Frame + OString sOrientation; + sal_Int32 nValue; + + // If tblpXSpec or tblpYSpec are present, we do not write tblpX or tblpY! + OString sTblpXSpec + = convertToOOXMLHoriOrient(rFrame.GetFrameFormat().GetHoriOrient().GetHoriOrient(), + rFrame.GetFrameFormat().GetHoriOrient().IsPosToggle()); + OString sTblpYSpec + = convertToOOXMLVertOrient(rFrame.GetFrameFormat().GetVertOrient().GetVertOrient()); + + sOrientation + = convertToOOXMLVertOrientRel(rFrame.GetFrameFormat().GetVertOrient().GetRelationOrient()); + pAttributes->add(FSNS(XML_w, XML_vertAnchor), sOrientation); + + if (!sTblpYSpec.isEmpty()) + pAttributes->add(FSNS(XML_w, XML_tblpYSpec), sTblpYSpec); + + sOrientation + = convertToOOXMLHoriOrientRel(rFrame.GetFrameFormat().GetHoriOrient().GetRelationOrient()); + pAttributes->add(FSNS(XML_w, XML_horzAnchor), sOrientation); + + if (!sTblpXSpec.isEmpty()) + pAttributes->add(FSNS(XML_w, XML_tblpXSpec), sTblpXSpec); + + nValue = rFrame.GetFrameFormat().GetULSpace().GetLower(); + if (nValue != 0) + pAttributes->add(FSNS(XML_w, XML_bottomFromText), OString::number(nValue)); + + nValue = rFrame.GetFrameFormat().GetLRSpace().GetLeft(); + if (nValue != 0) + pAttributes->add(FSNS(XML_w, XML_leftFromText), OString::number(nValue)); + + nValue = rFrame.GetFrameFormat().GetLRSpace().GetRight(); + if (nValue != 0) + pAttributes->add(FSNS(XML_w, XML_rightFromText), OString::number(nValue)); + + nValue = rFrame.GetFrameFormat().GetULSpace().GetUpper(); + if (nValue != 0) + pAttributes->add(FSNS(XML_w, XML_topFromText), OString::number(nValue)); + + if (sTblpXSpec.isEmpty()) // do not write tblpX if tblpXSpec is present + { + nValue = rFrame.GetFrameFormat().GetHoriOrient().GetPos(); + // we need to revert the additional shift introduced by + // lcl_DecrementHoriOrientPosition() in writerfilter + // 1st: left distance of the table + const SwTableBox* pTabBox = pTableTextNodeInfoInner->getTableBox(); + const SwFrameFormat* pFrameFormat = pTabBox->GetFrameFormat(); + const SvxBoxItem& rBox = pFrameFormat->GetBox(); + sal_Int32 nMode = lcl_getWordCompatibilityMode(rExport); + if (nMode < 15) + { + sal_uInt16 nLeftDistance = rBox.GetDistance(SvxBoxItemLine::LEFT); + nValue += nLeftDistance; + } + + // 2nd: if a left border is given, revert the shift by half the width + // from lcl_DecrementHoriOrientPosition() in writerfilter + if (const editeng::SvxBorderLine* pLeftBorder = rBox.GetLeft()) + { + tools::Long nWidth = pLeftBorder->GetWidth(); + nValue += (nWidth / 2); + } + + pAttributes->add(FSNS(XML_w, XML_tblpX), OString::number(nValue)); + } + + if (sTblpYSpec.isEmpty()) // do not write tblpY if tblpYSpec is present + { + nValue = rFrame.GetFrameFormat().GetVertOrient().GetPos(); + pAttributes->add(FSNS(XML_w, XML_tblpY), OString::number(nValue)); + } +} } void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t pTableTextNodeInfoInner ) @@ -4660,6 +4746,16 @@ void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t std::map<SvxBoxItemLine, css::table::BorderLine2>& rTableStyleConf = m_aTableStyleConfs.back(); rTableStyleConf.clear(); + bool bFloatingTableWritten = false; + if (pFloatingTableFrame && pFloatingTableFrame->GetFrameFormat().GetFlySplit().GetValue()) + { + rtl::Reference<FastAttributeList> pAttributes = FastSerializerHelper::createAttrList(); + CollectFloatingTableAttributes(m_rExport, *pFloatingTableFrame, pTableTextNodeInfoInner, + pAttributes); + m_pSerializer->singleElementNS(XML_w, XML_tblpPr, pAttributes); + bFloatingTableWritten = true; + } + // Extract properties from grab bag for( const auto & rGrabBagElement : aGrabBag ) { @@ -4719,74 +4815,8 @@ void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t const ww8::Frame* pFrame = m_rExport.GetFloatingTableFrame(); if( pFrame ) { - // we export the values of the surrounding Frame - OString sOrientation; - sal_Int32 nValue; - - // If tblpXSpec or tblpYSpec are present, we do not write tblpX or tblpY! - OString sTblpXSpec = convertToOOXMLHoriOrient( pFrame->GetFrameFormat().GetHoriOrient().GetHoriOrient(), pFrame->GetFrameFormat().GetHoriOrient().IsPosToggle() ); - OString sTblpYSpec = convertToOOXMLVertOrient( pFrame->GetFrameFormat().GetVertOrient().GetVertOrient() ); - - sOrientation = convertToOOXMLVertOrientRel( pFrame->GetFrameFormat().GetVertOrient().GetRelationOrient() ); - attrListTablePos->add(FSNS(XML_w, XML_vertAnchor), sOrientation); - - if( !sTblpYSpec.isEmpty() ) - attrListTablePos->add(FSNS(XML_w, XML_tblpYSpec), sTblpYSpec); - - sOrientation = convertToOOXMLHoriOrientRel( pFrame->GetFrameFormat().GetHoriOrient().GetRelationOrient() ); - attrListTablePos->add(FSNS(XML_w, XML_horzAnchor), sOrientation); - - if( !sTblpXSpec.isEmpty() ) - attrListTablePos->add(FSNS(XML_w, XML_tblpXSpec), sTblpXSpec); - - nValue = pFrame->GetFrameFormat().GetULSpace().GetLower(); - if( nValue != 0 ) - attrListTablePos->add( FSNS( XML_w, XML_bottomFromText ), OString::number( nValue ) ); - - nValue = pFrame->GetFrameFormat().GetLRSpace().GetLeft(); - if( nValue != 0 ) - attrListTablePos->add( FSNS( XML_w, XML_leftFromText ), OString::number( nValue ) ); - - nValue = pFrame->GetFrameFormat().GetLRSpace().GetRight(); - if( nValue != 0 ) - attrListTablePos->add( FSNS( XML_w, XML_rightFromText ), OString::number( nValue ) ); - - nValue = pFrame->GetFrameFormat().GetULSpace().GetUpper(); - if( nValue != 0 ) - attrListTablePos->add( FSNS( XML_w, XML_topFromText ), OString::number( nValue ) ); - - if( sTblpXSpec.isEmpty() ) // do not write tblpX if tblpXSpec is present - { - nValue = pFrame->GetFrameFormat().GetHoriOrient().GetPos(); - // we need to revert the additional shift introduced by - // lcl_DecrementHoriOrientPosition() in writerfilter - // 1st: left distance of the table - const SwTableBox * pTabBox = pTableTextNodeInfoInner->getTableBox(); - const SwFrameFormat * pFrameFormat = pTabBox->GetFrameFormat(); - const SvxBoxItem& rBox = pFrameFormat->GetBox( ); - sal_Int32 nMode = lcl_getWordCompatibilityMode(m_rExport); - if (nMode < 15) - { - sal_uInt16 nLeftDistance = rBox.GetDistance(SvxBoxItemLine::LEFT); - nValue += nLeftDistance; - } - - // 2nd: if a left border is given, revert the shift by half the width - // from lcl_DecrementHoriOrientPosition() in writerfilter - if (const editeng::SvxBorderLine* pLeftBorder = rBox.GetLeft()) - { - tools::Long nWidth = pLeftBorder->GetWidth(); - nValue += (nWidth / 2); - } - - attrListTablePos->add( FSNS( XML_w, XML_tblpX ), OString::number( nValue ) ); - } - - if( sTblpYSpec.isEmpty() ) // do not write tblpY if tblpYSpec is present - { - nValue = pFrame->GetFrameFormat().GetVertOrient().GetPos(); - attrListTablePos->add( FSNS( XML_w, XML_tblpY ), OString::number( nValue ) ); - } + CollectFloatingTableAttributes(m_rExport, *pFrame, pTableTextNodeInfoInner, + attrListTablePos); } else // ( pFrame = 0 ) { @@ -4846,7 +4876,10 @@ void DocxAttributeOutput::TableDefinition( ww8::WW8TableNodeInfoInner::Pointer_t } } - m_pSerializer->singleElementNS( XML_w, XML_tblpPr, attrListTablePos); + if (!bFloatingTableWritten) + { + m_pSerializer->singleElementNS(XML_w, XML_tblpPr, attrListTablePos); + } attrListTablePos = nullptr; } else diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx index df4e3fb5424e..9608509b03e6 100644 --- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx +++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx @@ -59,6 +59,22 @@ #include <utility> #endif +namespace +{ +bool IsFlySplitAllowed() +{ + bool bRet + = officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::get(); + + if (!bRet) + { + bRet = getenv("SW_FORCE_FLY_SPLIT") != nullptr; + } + + return bRet; +} +} + namespace writerfilter::dmapper { using namespace ::com::sun::star; @@ -376,7 +392,10 @@ TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo comphelper::makePropertyValue("vertAnchor", pTablePositions->getVertAnchor()) }; - aGrabBag["TablePosition"] <<= aGrabBagTS; + if (!IsFlySplitAllowed()) + { + aGrabBag["TablePosition"] <<= aGrabBagTS; + } } else if (bConvertToFloatingInFootnote) { @@ -1566,12 +1585,7 @@ void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel, bool bTab comphelper::makePropertyValue("IsFollowingTextFlow", true)); } - bool bSplitAllowed = officecfg::Office::Writer::Filter::Import::DOCX::ImportFloatingTableAsSplitFly::get(); - if (!bSplitAllowed) - { - bSplitAllowed = getenv("SW_FORCE_FLY_SPLIT") != nullptr; - } - if (bSplitAllowed) + if (IsFlySplitAllowed()) { aFrameProperties.push_back(comphelper::makePropertyValue("IsSplitAllowed", true)); } commit 22826c8a9e765a5894828ae1764b72b30284352c Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Tue Feb 21 08:05:33 2023 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Mar 3 08:43:20 2023 +0100 sw floattable: fix locking in SwTextFrame::Prepare() Once a floating table is split across 2 pages, we currently emit this warning: warn:legacy.osl:23427:23427:sw/source/core/text/txtfrm.cxx:2808: SwTextFrame::Prepare: three of a perfect pair Which suggests that we want to invalidate the size of a frame that is already locked. The way normal frame split doesn't hit this warning is that the master has some content, then once SwContentFrame::Paste() on the follow calls SwTextFrame::Prepare() on the master, the !HasPara() condition is not hit, so it's not a problem that the frame is locked. Fix this for the split fly case by assuming that in case our offset and our follow's offset is both 0, then this frame will have some fly content, so don't return early (similar to what happens when the frame has some text content). We may want to revisit this later so that at least an SwParaPortion is generated for non-last anchors for split flys, but this is good enough for now, since the point is to not invalidate the size of a locked frame. (cherry picked from commit b697ee5dc3c38806fc6f096364590e9e60256aeb) Change-Id: I7c78472653bb6818f0d5880d21f468286be547e4 diff --git a/sw/source/core/text/txtfrm.cxx b/sw/source/core/text/txtfrm.cxx index aa371cd3df63..080bf1d89987 100644 --- a/sw/source/core/text/txtfrm.cxx +++ b/sw/source/core/text/txtfrm.cxx @@ -2802,7 +2802,12 @@ bool SwTextFrame::Prepare( const PrepareHint ePrep, const void* pVoid, } } - if( !HasPara() && PrepareHint::MustFit != ePrep ) + // Split fly anchors are technically empty (have no SwParaPortion), but otherwise behave like + // other split text frames, which are non-empty. + bool bSplitFlyAnchor = GetOffset() == TextFrameIndex(0) && HasFollow() + && GetFollow()->GetOffset() == TextFrameIndex(0); + + if( !HasPara() && !bSplitFlyAnchor && PrepareHint::MustFit != ePrep ) { SetInvalidVert( true ); // Test OSL_ENSURE( !IsLocked(), "SwTextFrame::Prepare: three of a perfect pair" ); commit c65c11e0721199729dd10d65cff5bbf8277d0be8 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Mon Feb 20 08:15:41 2023 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Mar 3 08:40:37 2023 +0100 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 (cherry picked from commit 7c9acfe5baef275f07c185c6fedf8b6d62d88637) Change-Id: Ibecaf5fcf0b8c56c5109461a16dcca058231b660 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 b18cb6a21cb5..e05370e46750 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 13ca7f71494c..d52bdccdef18 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 995481ceb97e..feddd1a2347f 100644 --- a/sw/source/core/text/itratr.cxx +++ b/sw/source/core/text/itratr.cxx @@ -1496,8 +1496,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; commit 217e0862da8d5055761aa227221a3002320adb95 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Feb 17 10:14:31 2023 +0100 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Mar 3 08:37:54 2023 +0100 sw floattable: it's fine to recalc the table in SwTabFrame::MakeAll() Avoids this warning: warn:legacy.osl:22791:22791:sw/source/core/layout/tabfrm.cxx:2751: debug assertion: <SwTabFrame::MakeAll()> - format of table lowers suppressed by fix i44910 I assume that the intention is that flys first format their content and then grow as necessary, so there is no risk to call lcl_RecalcTable() in the floattable case. (Parent will grow, then split if necessary.) (cherry picked from commit e11e1d48abedf17db40c069d9f37b4edcbcc09c4) Change-Id: Ib15d2d56066b695010c76f2052114082678c2e9b diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index b95f8ca45b25..177bfa947fb2 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -2736,7 +2736,13 @@ void SwTabFrame::MakeAll(vcl::RenderContext* pRenderContext) // allowed to split. SwTwips nDistToUpperPrtBottom = aRectFnSet.BottomDist( getFrameArea(), aRectFnSet.GetPrtBottom(*GetUpper())); - if ( nDistToUpperPrtBottom >= 0 || bTryToSplit ) + bool bFlySplit = false; + if (GetUpper()->IsFlyFrame()) + { + auto pUpperFly = static_cast<SwFlyFrame*>(GetUpper()); + bFlySplit = pUpperFly->IsFlySplitAllowed(); + } + if ( nDistToUpperPrtBottom >= 0 || bTryToSplit || bFlySplit ) { lcl_RecalcTable( *this, nullptr, aNotify ); m_bLowersFormatted = true;