/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { /// Covers sw/source/core/layout/flycnt.cxx fixes, i.e. mostly SwFlyAtContentFrame. class Test : public SwModelTestBase { public: Test() : SwModelTestBase("/sw/qa/core/layout/data/") { } /// Creates a document with a multi-page floating table: 1 columns and 2 rows. void Create1x2SplitFly(); }; void Test::Create1x2SplitFly() { createSwDoc(); SwDoc* pDoc = getSwDoc(); SwPageDesc aStandard(pDoc->GetPageDesc(0)); SwFormatFrameSize aPageSize(aStandard.GetMaster().GetFrameSize()); // 10 cm for the page height, 2cm are the top and bottom margins, so 6cm remains for the body // frame: aPageSize.SetHeight(5669); aStandard.GetMaster().SetFormatAttr(aPageSize); pDoc->ChgPageDesc(0, aStandard); // Insert a table: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); SwInsertTableOptions aTableOptions(SwInsertTableFlags::DefaultBorder, 0); pWrtShell->InsertTable(aTableOptions, /*nRows=*/2, /*nCols=*/1); pWrtShell->MoveTable(GotoPrevTable, fnTableStart); pWrtShell->GoPrevCell(); pWrtShell->Insert("A1"); SwFormatFrameSize aRowSize(SwFrameSize::Minimum); // 4 cm, so 2 rows don't fit 1 page. aRowSize.SetHeight(2267); pWrtShell->SetRowHeight(aRowSize); pWrtShell->GoNextCell(); pWrtShell->Insert("A2"); pWrtShell->SetRowHeight(aRowSize); // Select cell: pWrtShell->SelAll(); // Select table: pWrtShell->SelAll(); // Wrap the table in a text frame: SwFlyFrameAttrMgr aMgr(true, pWrtShell, Frmmgr_Type::TEXT, nullptr); pWrtShell->StartAllAction(); aMgr.InsertFlyFrame(RndStdIds::FLY_AT_PARA, aMgr.GetPos(), aMgr.GetSize()); pWrtShell->EndAllAction(); // Allow the text frame to split: pWrtShell->StartAllAction(); auto& rFlys = *pDoc->GetSpzFrameFormats(); auto pFly = rFlys[0]; SwAttrSet aSet(pFly->GetAttrSet()); aSet.Put(SwFormatFlySplit(true)); pDoc->SetAttr(aSet, *pFly); pWrtShell->EndAllAction(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); // We have 2 pages: CPPUNIT_ASSERT(pPage1->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWithTable) { // Given a document with a multi-page floating table: createSwDoc("floattable.docx"); // When laying out that document: calcLayout(); // Then make sure that the first row goes to page 1 and the second row goes to page 2, while the // table is floating: SwDoc* pDoc = getSwDoc(); // Without the accompanying fix in place, this test would have failed with a stack overflow // because the follow frame of the anchor was moved into the follow frame of the fly, so the fly // was anchored in itself. SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); // Page 1 has a master fly, which contains a master table: auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); CPPUNIT_ASSERT(!pPage1Fly->GetPrecede()); CPPUNIT_ASSERT(pPage1Fly->GetFollow()); auto pPage1Table = dynamic_cast(pPage1Fly->GetLower()); CPPUNIT_ASSERT(pPage1Table); CPPUNIT_ASSERT(!pPage1Table->GetPrecede()); CPPUNIT_ASSERT(pPage1Table->GetFollow()); // Page 2 has a follow fly, which contains a follow table: auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); CPPUNIT_ASSERT(pPage2Fly->GetPrecede()); CPPUNIT_ASSERT(!pPage2Fly->GetFollow()); auto pPage2Table = dynamic_cast(pPage2Fly->GetLower()); CPPUNIT_ASSERT(pPage2Table); CPPUNIT_ASSERT(pPage2Table->GetPrecede()); CPPUNIT_ASSERT(!pPage2Table->GetFollow()); // Page 1 anchor has no text: auto pPage1Anchor = dynamic_cast(pPage1->FindLastBodyContent()); CPPUNIT_ASSERT(pPage1Anchor); // This failed, page 1 anchor had unexpected, leftover text. CPPUNIT_ASSERT(!pPage1Anchor->HasPara()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyVertOffset) { // Given a document with a floattable, split on 2 pages and a positive vertical offset: createSwDoc("floattable-vertoffset.docx"); // When laying out that document: calcLayout(); // Then make sure that the vert offset has an effect on the master fly, but not on follow flys: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); auto pPage1Anchor = dynamic_cast(pPage1->FindLastBodyContent()); CPPUNIT_ASSERT(pPage1Anchor); SwTwips nPage1AnchorTop = pPage1Anchor->getFrameArea().Top(); SwTwips nPage1FlyTop = pPage1Fly->getFrameArea().Top(); // First page, the vert offset should be there. This comes from word/document.xml: // CPPUNIT_ASSERT_EQUAL(static_cast(1135), nPage1FlyTop - nPage1AnchorTop); // Also verify that the 2nd page has no such offset: auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); auto pPage2Anchor = dynamic_cast(pPage2->FindLastBodyContent()); CPPUNIT_ASSERT(pPage2Anchor); SwTwips nPage2AnchorTop = pPage2Anchor->getFrameArea().Top(); SwTwips nPage2FlyTop = pPage2Fly->getFrameArea().Top(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 0 // - Actual : 1135 // i.e. the fly frame on the 2nd page was also shifted down in Writer, but not in Word. CPPUNIT_ASSERT_EQUAL(static_cast(0), nPage2FlyTop - nPage2AnchorTop); } CPPUNIT_TEST_FIXTURE(Test, testSplitFly3Pages) { // Given a document with a floattable, split on 3 pages: 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(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); auto pPage1Anchor = dynamic_cast(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(1135), nPage1FlyTop - nPage1AnchorTop); // Second page: auto pPage2 = dynamic_cast(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(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); auto pPage2Anchor = dynamic_cast(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(0), nPage2FlyTop - nPage2AnchorTop); // 3rd page: auto pPage3 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage3); const SwSortedObjs& rPage3Objs = *pPage3->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage3Objs.size()); auto pPage3Fly = dynamic_cast(rPage3Objs[0]); CPPUNIT_ASSERT(pPage3Fly); auto pPage3Anchor = dynamic_cast(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(0), nPage3FlyTop - nPage3AnchorTop); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyRow) { // Given a document with a floattable, single row split on 2 pages: createSwDoc("floattable-rowsplit.docx"); // When laying out that document: calcLayout(); // Then make sure that the single row is split to 2 pages, and the fly frames have the correct // coordinates: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); auto pPage1Anchor = dynamic_cast(pPage1->FindLastBodyContent()); CPPUNIT_ASSERT(pPage1Anchor); // ~No offset between the fly and its anchor: SwTwips nPage1AnchorTop = pPage1Anchor->getFrameArea().Top(); SwTwips nPage1FlyTop = pPage1Fly->getFrameArea().Top(); CPPUNIT_ASSERT_EQUAL(static_cast(1), nPage1FlyTop - nPage1AnchorTop); // Second page: auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); auto pPage2Anchor = dynamic_cast(pPage2->FindLastBodyContent()); CPPUNIT_ASSERT(pPage2Anchor); // No offset between the fly and its anchor: SwTwips nPage2AnchorTop = pPage2Anchor->getFrameArea().Top(); SwTwips nPage2FlyTop = pPage2Fly->getFrameArea().Top(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 0 // - Actual : -1440 // i.e. the 2nd page's fly had a wrong position. CPPUNIT_ASSERT_EQUAL(static_cast(0), nPage2FlyTop - nPage2AnchorTop); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyEnable) { // Given a document with a table in a textframe: Create1x2SplitFly(); // Then make sure that the layout is updated and we have 2 pages: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); auto pPage2 = dynamic_cast(pPage1->GetNext()); // Without the accompanying fix in place, this test would have failed, there was no 2nd page. CPPUNIT_ASSERT(pPage2); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyDisable) { // Given a document with a floating table, table split on 2 pages: Create1x2SplitFly(); SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(pPage1->GetNext()); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); CPPUNIT_ASSERT(pPage1Fly->GetFollow()); // When turning the "split fly" checkbox off: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->StartAllAction(); auto& rFlys = *pDoc->GetSpzFrameFormats(); auto pFly = rFlys[0]; SwAttrSet aSet(pFly->GetAttrSet()); // Note how the UI puts a SwFormatFrameSize into this item set with a slightly different size // (e.g. 3823 twips width -> 3821). This means that by accident the UI works even without the // explicit RES_FLY_SPLIT handling in SwFlyFrame::UpdateAttr_(), but this test will fail when // that is missing. aSet.Put(SwFormatFlySplit(false)); pDoc->SetAttr(aSet, *pFly); pWrtShell->EndAllAction(); // Then make sure the extra page and follow fly frame is joined: CPPUNIT_ASSERT(!pPage1->GetNext()); // Without the accompanying fix in place, this test would have failed, the follow fly frame was // moved to page 1, but it wasn't deleted. CPPUNIT_ASSERT(!pPage1Fly->GetFollow()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyFooter) { // Given a document with a floattable, table split on 2 pages with headers/footers: createSwDoc("floattable-footer.docx"); // When laying out that document: calcLayout(); // Then make sure that the table is split to 2 pages: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); SwTwips nPage1Top = pPage1->getFrameArea().Top(); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); SwTwips nPage1FlyTop = pPage1Fly->getFrameArea().Top(); // from the bugdoc. CPPUNIT_ASSERT_EQUAL(static_cast(3286), nPage1FlyTop - nPage1Top); // Second page: auto pPage2 = dynamic_cast(pPage1->GetNext()); // Without the accompanying fix in place, this test would have failed, there was no 2nd page. CPPUNIT_ASSERT(pPage2); SwTwips nPage2Top = pPage2->getFrameArea().Top(); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); SwTwips nPage2FlyTop = pPage2Fly->getFrameArea().Top(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1440 // - Actual : 0 // i.e. from the bugdoc was lost, the follow fly had no vertical offset. CPPUNIT_ASSERT_EQUAL(static_cast(1440), nPage2FlyTop - nPage2Top); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyFooter2Rows) { // Given a document with a 2nd page that contains the second half of a split row + a last row: createSwDoc("floattable-footer-2rows.docx"); // When laying out that document: calcLayout(); // Then make sure that the table is split to 2 pages: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); // Without the accompanying fix in place, this test would have failed. The 2nd page only had the // 2nd half of the split row and the last row went to a 3rd page. CPPUNIT_ASSERT(!pPage2->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFly2Cols) { // Given a document with a 2nd page that contains the second half of a split row and 2 columns: createSwDoc("floattable-2cols.docx"); // When laying out that document: calcLayout(); // Then make sure that the table is split to 2 pages: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); // Without the accompanying fix in place, this test would have failed. The 2nd page only had the // 2nd half of the split row and the very last row went to a 3rd page. CPPUNIT_ASSERT(!pPage2->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWidow) { // Given a document with a 2nd page that contains 2 lines, due to widow control: createSwDoc("floattable-widow.docx"); // When laying out that document: calcLayout(); // Then make sure that widow control works: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); SwFrame* pTab1 = pPage1Fly->GetLower(); SwFrame* pRow1 = pTab1->GetLower(); SwFrame* pCell1 = pRow1->GetLower(); auto pText1 = dynamic_cast(pCell1->GetLower()); // Without the accompanying fix in place, this test would have failed with: // - Expected: 6 // - Actual : 7 // i.e. widow control was disabled, layout didn't match Word. CPPUNIT_ASSERT_EQUAL(static_cast(6), pText1->GetThisLines()); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); SwFrame* pTab2 = pPage2Fly->GetLower(); SwFrame* pRow2 = pTab2->GetLower(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1014 // - Actual : 553 // i.e. from the file was ignored. CPPUNIT_ASSERT_EQUAL(static_cast(1014), pRow2->getFrameArea().Height()); SwFrame* pCell2 = pRow2->GetLower(); auto pText2 = dynamic_cast(pCell2->GetLower()); // And then similarly this was 1, not 2. CPPUNIT_ASSERT_EQUAL(static_cast(2), pText2->GetThisLines()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyCompat14) { // Given a Word 2010 document with 2 pages, one table row each: createSwDoc("floattable-compat14.docx"); // When laying out that document: calcLayout(); // Then make sure that the first row is entirely on page 1: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); SwFrame* pTab1 = pPage1Fly->GetLower(); SwFrame* pRow1 = pTab1->GetLower(); auto pCell1 = dynamic_cast(pRow1->GetLower()); // Without the accompanying fix in place, this test would have failed, the first row was split, // but not in Word. CPPUNIT_ASSERT(!pCell1->GetFollowCell()); // Also make sure that the second row is entirely on page 2: auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); SwFrame* pTab2 = pPage2Fly->GetLower(); SwFrame* pRow2 = pTab2->GetLower(); auto pCell2 = dynamic_cast(pRow2->GetLower()); // Without the accompanying fix in place, this test would have failed, the second row was split, // but not in Word. CPPUNIT_ASSERT(!pCell2->GetPreviousCell()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyCompat14Nosplit) { // Given a Word 2010 document with 2 pages, 2 rows on page 1, 1 row on page 2: createSwDoc("floattable-compat14-nosplit.docx"); // When laying out that document: calcLayout(); // Then make sure that the last row is on a separate page: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); SwFrame* pTab1 = pPage1Fly->GetLower(); SwFrame* pRow1 = pTab1->GetLower(); CPPUNIT_ASSERT(pRow1->GetNext()); auto pPage2 = dynamic_cast(pPage1->GetNext()); // Without the accompanying fix in place, this test would have failed, all rows were on page 1. CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); SwFrame* pTab2 = pPage2Fly->GetLower(); SwFrame* pRow2 = pTab2->GetLower(); CPPUNIT_ASSERT(!pRow2->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyCompat14Body) { // Given a Word 2010 document with 2 pages, 1 row on page 1, 1 row on page 2: createSwDoc("floattable-compat14-body.docx"); // When laying out that document: // (This is legacy mode, but still Word doesn't split row 2 because 1) row 2 has a minimal // height, so even the first part of row 2 would not fit the body frame and 2) Word allows using // the bottom margin area in legacy mode, but only in case the fly height <= body height.) calcLayout(); // Then make sure that the second row is on a page 2: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); SwFrame* pTab1 = pPage1Fly->GetLower(); SwFrame* pRow1 = pTab1->GetLower(); // Without the accompanying fix in place, this test would have failed, part of row 2 was on page // 1. CPPUNIT_ASSERT(!pRow1->GetNext()); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); SwFrame* pTab2 = pPage2Fly->GetLower(); SwFrame* pRow2 = pTab2->GetLower(); CPPUNIT_ASSERT(!pRow2->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyFollowHorizontalPosition) { // Given a document with 2 pages, master fly on page 1, follow fly on page 2: createSwDoc("floattable-hori-pos.docx"); // When laying out that document: calcLayout(); // Then make sure that the follow fly doesn't have a different horizontal position: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); tools::Long nPage1FlyLeft = pPage1Fly->getFrameArea().Left(); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); CPPUNIT_ASSERT(pPage2Fly); tools::Long nPage2FlyLeft = pPage2Fly->getFrameArea().Left(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 5528 // - Actual : 284 // i.e. the follow fly was pushed towards the left, instead of having the same position as the // master fly. CPPUNIT_ASSERT_EQUAL(nPage1FlyLeft, nPage2FlyLeft); } CPPUNIT_TEST_FIXTURE(Test, testCursorTraversal) { // Given a document with a multi-page floating table: Create1x2SplitFly(); // When going from A1 to A2: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->GotoTable("Table1"); SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); CPPUNIT_ASSERT_EQUAL(OUString("A1"), pTextNode->GetText()); pWrtShell->Down(/*bSelect=*/false); // Then make sure we get to A2 and don't stay in A1: pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); // Without the accompanying fix in place, this test would have failed with: // - Expected: A2 // - Actual : A1 // i.e. the cursor didn't get from A1 to A2. CPPUNIT_ASSERT_EQUAL(OUString("A2"), pTextNode->GetText()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyRowDelete) { // Given a document with a multi-page floating table: Create1x2SplitFly(); // When deleting the row of A2: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->GotoTable("Table1"); pWrtShell->Down(/*bSelect=*/false); SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); // We delete the right row: CPPUNIT_ASSERT_EQUAL(OUString("A2"), pTextNode->GetText()); pWrtShell->DeleteRow(); // Then make sure we only have 1 page: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(!pPage1->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFly1stRowDelete) { // Given a document with a multi-page floating table: Create1x2SplitFly(); // When deleting the row of A1: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->GotoTable("Table1"); SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); // We delete the right row: CPPUNIT_ASSERT_EQUAL(OUString("A1"), pTextNode->GetText()); pWrtShell->DeleteRow(); // Then make sure we only have 1 page: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); // Without the accompanying fix in place, this test would have failed, the follow fly was still // on page 2. CPPUNIT_ASSERT(!pPage1->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFly3rdRowDelete) { // Given a document with a floattable, split on 3 pages: createSwDoc("floattable-3pages.docx"); // When deleting the row of A3: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->GotoTable("Table1"); pWrtShell->Down(/*bSelect=*/false); pWrtShell->Down(/*bSelect=*/false); SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); // We delete the right row: CPPUNIT_ASSERT_EQUAL(OUString("A3"), pTextNode->GetText()); pWrtShell->DeleteRow(); // Then make sure we only have 2 pages: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); auto pPage2 = dynamic_cast(pPage1->GetNext()); // Without the accompanying fix in place, this test would have failed, page 3 was not deleted. CPPUNIT_ASSERT(!pPage2->GetNext()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFly2ndRowSelect) { // Given a document with a multi-page floating table: createSwDoc("floattable.docx"); // When selecting the second row: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); auto pPage2 = dynamic_cast(pPage1->GetNext()); SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); auto pPage2Fly = dynamic_cast(rPage2Objs[0]); const SwRect& aFollowArea = pPage2Fly->getFrameArea(); Point aTopCenter((aFollowArea.Left() + aFollowArea.Right()) / 2, aFollowArea.Top()); SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->SelectObj(aTopCenter); // Then make sure the first row is selected: const SdrMarkList& rMarkList = pWrtShell->GetDrawView()->GetMarkedObjectList(); SdrObject* pSelectedObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); auto pSelectedVirtObj = dynamic_cast(pSelectedObj); auto pSelected = static_cast(pSelectedVirtObj->GetFlyFrame()); // Without the accompanying fix in place, this test would have failed with: // - Expected: 5 // - Actual : 17 // i.e. a follow fly was possible to select (instead of its master) CPPUNIT_ASSERT_EQUAL(pPage2Fly->GetPrecede()->GetFrameId(), pSelected->GetFrameId()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyInSection) { // This crashed, the layout assumed that the floating table is directly under the body frame. createSwDoc("floattable-in-section.docx"); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyThenTable) { // Given a document with a 2 page floating table, followed by an other table: // Intentionally load the document as hidden to avoid layout during load (see TestTdf150616): uno::Sequence aFilterOptions = { comphelper::makePropertyValue("Hidden", true), }; mxComponent = loadFromDesktop(m_directories.getURLFromSrc(u"/sw/qa/core/layout/data/") + "floattable-then-table.docx", "com.sun.star.text.TextDocument", aFilterOptions); // When layout is calculated during PDF export: // Then make sure that finishes without errors: // This crashed, due to a stack overflow in layout code. save("writer_pdf_Export"); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyInTextSection) { // The document contains a DOCX cont sect break, which is mapped to a TextSection. // This crashed, the anchor was split directly, so the follow anchor was moved outside the // section frame, which is broken. createSwDoc("floattable-in-text-section.docx"); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyTableRowKeep) { // Given a document with a floating table, 2.5 rows on the first page: createSwDoc("floattable-table-row-keep.docx"); // When laying out that document: calcLayout(); // Then make sure that the expected amount of rows is on the first page: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); SwFrame* pTab1 = pPage1Fly->GetLower(); SwFrame* pRow1 = pTab1->GetLower(); CPPUNIT_ASSERT(pRow1); SwFrame* pRow2 = pRow1->GetNext(); // Without the accompanying fix in place, this test would have failed, the table on the first // page only had 1 row, due to TableRowKeep kicking in for floating tables, which is incorrect. CPPUNIT_ASSERT(pRow2); SwFrame* pRow3 = pRow2->GetNext(); CPPUNIT_ASSERT(pRow3); auto pCell3 = dynamic_cast(pRow3->GetLower()); CPPUNIT_ASSERT(pCell3->GetFollowCell()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyDeletedAnchor) { // Given a document with a floating table that spans over 3 pages: createSwDoc("floattable-deleted-anchor.docx"); // When laying out that document: calcLayout(); // Then make sure that there are 3 anchors for the 3 pages: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); SwFrame* pBody1 = pPage1->GetLower(); CPPUNIT_ASSERT(pBody1); auto pAnchor1 = dynamic_cast(pBody1->GetLower()->GetNext()); CPPUNIT_ASSERT(pAnchor1); SwTextFrame* pAnchor2 = pAnchor1->GetFollow(); CPPUNIT_ASSERT(pAnchor2); SwTextFrame* pAnchor3 = pAnchor2->GetFollow(); // Without the accompanying fix in place, this test would have failed, the fly frame on the 3rd // page was anchored to a text frame on the 2nd page, leading to a negative frame height. CPPUNIT_ASSERT(pAnchor3); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyMultiCol) { // Given a document with a floating table that is in a multi-col section: createSwDoc("floattable-multi-col.docx"); // When laying out that document: calcLayout(); // Then make sure that the fly frame is not split, matching Word: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = dynamic_cast(rPage1Objs[0]); CPPUNIT_ASSERT(pPage1Fly); // Without the accompanying fix in place, this test would have failed, we tried to split and // then hit an assertion failure. CPPUNIT_ASSERT(!pPage1Fly->GetFollow()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyTabJoin) { // Given a document with 3 pages and 2 tables: table on first and second page, 3rd page has no // table: createSwDoc("floattable-tab-join.docx"); // When laying out that document: calcLayout(); // Then make sure that all pages have the expected amount of fly frames: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage2 = dynamic_cast(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. the 2nd page had 2 fly frames, hosting a split table, instead of joining that table and // having 1 fly frame. CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage3 = dynamic_cast(pPage2->GetNext()); CPPUNIT_ASSERT(pPage3); CPPUNIT_ASSERT(!pPage3->GetSortedObjs()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyTabJoinLegacy) { // Given a document with 3 pages and 2 tables: table on first and second page, 3rd page has no // table (Word 2010 mode): createSwDoc("floattable-tab-join-legacy.docx"); // When laying out that document: calcLayout(); // Then make sure that all pages have the expected amount of fly frames: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage2 = dynamic_cast(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. the 2nd page had 2 fly frames, hosting a split table, instead of joining that table and // having 1 fly frame (even after the non-legacy case was fixed already). CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage3 = dynamic_cast(pPage2->GetNext()); CPPUNIT_ASSERT(pPage3); CPPUNIT_ASSERT(!pPage3->GetSortedObjs()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyObjectFormatter) { // Given a document with 3 pages and 2 tables: table on first and second page, 3rd page has no // table: createSwDoc("floattable-object-formatter.docx"); // When calculating the layout: calcLayout(); // Then make sure we don't crash and also that all pages have the expected amount of fly frames: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage3 = dynamic_cast(pPage2->GetNext()); CPPUNIT_ASSERT(pPage3); CPPUNIT_ASSERT(!pPage3->GetSortedObjs()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNextLeafInSection) { // Given a document with 4 pages: page 1 had a floating table, page 2 & 3 had a second floating // table and finally page 4 is empty: createSwDoc("floattable-next-leaf-in-section.docx"); // When calculating the layout: // Then this never returned, the loop in SwFrame::GetNextFlyLeaf() never finished. calcLayout(); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyAnchorKeepWithNext) { // Given a document with 2 pages, a split floating table on both pages: createSwDoc("floattable-anchor-keep-with-next.docx"); // When calculating the layout: calcLayout(); // Then make sure the pages have the expected amount of anchored objects: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); // Without the accompanying fix in place, this test would have failed, page 1 had no floating // table, it was entirely on page 2. CPPUNIT_ASSERT(pPage1->GetSortedObjs()); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage2 = dynamic_cast(pPage1->GetNext()); CPPUNIT_ASSERT(pPage2); CPPUNIT_ASSERT(pPage2->GetSortedObjs()); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNoFooterOverlap) { // Given a document with 2 pages, a floating table on both pages: createSwDoc("floattable-no-footer-overlap.doc"); // When calculating the layout: calcLayout(); // Then make sure the second page has a floating table: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(pPage1->GetSortedObjs()); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); // Without the accompanying fix in place, this test would have failed with: // - Expected: 1 // - Actual : 2 // i.e. part of the second table was on page 1. CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage2 = dynamic_cast(pPage1->GetNext()); // Without the accompanying fix in place, this test would have failed, there was no page 2, both // floating tables were on page 1. CPPUNIT_ASSERT(pPage2); CPPUNIT_ASSERT(pPage2->GetSortedObjs()); const SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyGrowFromBottom) { // Given a document with a floating table that grows from the bottom: createSwDoc("floattable-from-bottom.docx"); // When calculating the layout: calcLayout(); // Then make sure that such a floating table is not split, matching Word: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = dynamic_cast(pLayout->Lower()); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(pPage1->GetSortedObjs()); const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); const auto pFly = dynamic_cast(rPage1Objs[0]); // Without the accompanying fix in place, this test would have failed, we tried to split the fly // frame on page 1 even when it would fit, and this lead to a crash on export later. CPPUNIT_ASSERT(!pFly->GetFollow()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyIntoTable) { // Given a document with a floating table, then an inline table on the next page: createSwDoc("floattable-then-table.doc"); // When inserting a page break: // Then make sure the layout doesn't crash: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->InsertPageBreak(); // Without the accompanying fix in place, this test would have crashed, we tried to insert the // second part of a floating table into a table on the next page, not before that table. calcLayout(); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyFromAsCharAnchor) { // Given a document with a footnote that has a table (imported in an as-char anchored frame in // Writer): createSwDoc("table-in-footnote.docx"); // When changing the anchor type of that frame to to-para: // Then make sure we don't crash: selectShape(1); // Without the accompanying fix in place, this test would have crashed, we tried to split a // frame+table inside a footnote. dispatchCommand(mxComponent, ".uno:SetAnchorToPara", {}); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNested) { // Given a document with a nested, multi-page floating table: // When calculating the layout: createSwDoc("floattable-nested.odt"); calcLayout(); // Then make sure we don't crash: // Without the accompanying fix in place, this test would have crashed. // Check that we have exactly 4 fly frames, all of them on the expected pages: master outer, // follow outer, master inner and follow inner. SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(pPage1->GetSortedObjs()); SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(2), rPage1Objs.size()); auto pPage1Fly1 = rPage1Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage1Fly1); CPPUNIT_ASSERT(pPage1Fly1->GetAnchorFrameContainingAnchPos()->IsInFly()); CPPUNIT_ASSERT(pPage1Fly1->GetFollow()); auto pPage1Fly2 = rPage1Objs[1]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage1Fly2); CPPUNIT_ASSERT(!pPage1Fly2->GetAnchorFrameContainingAnchPos()->IsInFly()); CPPUNIT_ASSERT(pPage1Fly2->GetFollow()); auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage2); CPPUNIT_ASSERT(pPage2->GetSortedObjs()); SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(2), rPage2Objs.size()); auto pPage2Fly1 = rPage2Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage2Fly1); CPPUNIT_ASSERT(pPage2Fly1->GetAnchorFrameContainingAnchPos()->IsInFly()); CPPUNIT_ASSERT(pPage2Fly1->GetPrecede()); // Without the accompanying fix in place, this test would have failed with: // - Expected greater than: 6204 // - Actual : 1725 // i.e. the inner follow fly had a bad position, it was outside the page rectangle, it was not // rendered and this way the inner anchor had no fly portion, either. CPPUNIT_ASSERT_GREATER(pPage2->getFrameArea().Top(), pPage2Fly1->getFrameArea().Top()); // Without the accompanying fix in place, this test would have failed with: // - Expected less than: 12523 // - Actual : 15312 // i.e. the inner follow fly was not "moved back" to its place to have the wanted 4400 position, // which makes the "Inner A2" text visible. CPPUNIT_ASSERT_LESS(pPage2->getFrameArea().Right(), pPage2Fly1->getFrameArea().Right()); auto pPage2Fly2 = rPage2Objs[1]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage2Fly2); CPPUNIT_ASSERT(!pPage2Fly2->GetAnchorFrameContainingAnchPos()->IsInFly()); CPPUNIT_ASSERT(pPage2Fly2->GetPrecede()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyNestedOverlap) { // Given a document with a nested, multi-page floating table, enabling the "don't overlap" logic: // When calculating the layout: createSwDoc("floattable-nested-overlap.odt"); calcLayout(); // Then make sure we get 2 pages (2 flys on each page): // Without the accompanying fix in place, this test would have failed with a layout loop. SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(pPage1->GetSortedObjs()); SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(2), rPage1Objs.size()); auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage2); CPPUNIT_ASSERT(pPage2->GetSortedObjs()); SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(2), rPage2Objs.size()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyMoveMaster) { // Given a document with a multi-page floating table on pages 1 -> 2 -> 3: createSwDoc("floattable-move-master.docx"); // When adding an empty para at the start of the anchor text of the table: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->SttEndDoc(/*bStt=*/true); pWrtShell->Down(/*bSelect=*/false, /*nCount=*/4); pWrtShell->SplitNode(); // Then make sure page the fly chain is pages 1 -> 2 -> 3, still: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage1); // Without the accompanying fix in place, this test would have failed, the fly chain was pages 1 // -> 4 -> 2. CPPUNIT_ASSERT(pPage1->GetSortedObjs()); CPPUNIT_ASSERT(pPage1->GetSortedObjs()); SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage1Objs.size()); auto pPage1Fly = rPage1Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage1Fly); CPPUNIT_ASSERT(!pPage1Fly->GetPrecede()); CPPUNIT_ASSERT(pPage1Fly->HasFollow()); auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage2); CPPUNIT_ASSERT(pPage2->GetSortedObjs()); SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage2Fly = rPage2Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage2Fly); CPPUNIT_ASSERT(pPage2Fly->GetPrecede()); CPPUNIT_ASSERT(pPage2Fly->HasFollow()); auto pPage3 = pPage2->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage3); CPPUNIT_ASSERT(pPage3->GetSortedObjs()); SwSortedObjs& rPage3Objs = *pPage3->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage3Objs.size()); auto pPage3Fly = rPage3Objs[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame(); CPPUNIT_ASSERT(pPage3Fly); CPPUNIT_ASSERT(pPage3Fly->GetPrecede()); CPPUNIT_ASSERT(!pPage3Fly->GetFollow()); auto pPage4 = pPage3->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage4); CPPUNIT_ASSERT(!pPage4->GetSortedObjs()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyDelEmpty) { // Given a document with multiple floating tables and 7 pages: // When loading that document: // Without the accompanying fix in place, this test would have crashed due to a // heap-use-after-free problem (visible with e.g. MALLOC_PERTURB_=153). createSwDoc("floattable-del-empty.docx"); // Then make sure that the page count matches Word: CPPUNIT_ASSERT_EQUAL(7, getPages()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyInTableInSection) { // Given a document where page 2 and page 3 has a floating table inside an inline table, inside // a section: // Without the accompanying fix in place, this test would have crashed, we created a follow // anchor which was marked as "in table", but had no table parent. createSwDoc("floattable-in-inltbl-in-sect.docx"); // Then make sure that the floating table is on page 2 and page 3: SwDoc* pDoc = getSwDoc(); SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage1); CPPUNIT_ASSERT(!pPage1->GetSortedObjs()); auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage2); CPPUNIT_ASSERT(pPage2->GetSortedObjs()); SwSortedObjs& rPage2Objs = *pPage2->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage2Objs.size()); auto pPage3 = pPage2->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage3); CPPUNIT_ASSERT(pPage3->GetSortedObjs()); SwSortedObjs& rPage3Objs = *pPage3->GetSortedObjs(); CPPUNIT_ASSERT_EQUAL(static_cast(1), rPage3Objs.size()); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyWrapOnAllPages) { // Given a document where we want to wrap on all pages, around a split floating table: createSwDoc("floattable-wrap-on-all-pages.docx"); SwDoc* pDoc = getSwDoc(); pDoc->getIDocumentSettingAccess().set(DocumentSettingId::ALLOW_TEXT_AFTER_FLOATING_TABLE_BREAK, true); // When formatting that document: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->Reformat(); // Then make sure that the anchor text is also split between page 1 and page 2: SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage1); auto pPage1Anchor = pPage1->FindLastBodyContent()->DynCastTextFrame(); CPPUNIT_ASSERT(pPage1Anchor); OUString aAnchor1Text(pPage1Anchor->GetText().subView( static_cast(pPage1Anchor->GetOffset()), static_cast(pPage1Anchor->GetFollow()->GetOffset() - pPage1Anchor->GetOffset()))); // Without the accompanying fix in place, this test would have failed with: // - Expected: He heard quiet steps behind him. That // - Actual : // i.e. the first page had no anchor text, only the second. CPPUNIT_ASSERT_EQUAL(OUString("He heard quiet steps behind him. That "), aAnchor1Text); auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage2); auto pPage2Anchor = pPage2->FindLastBodyContent()->DynCastTextFrame(); CPPUNIT_ASSERT(pPage2Anchor); OUString aAnchor2Text( pPage2Anchor->GetText().subView(static_cast(pPage2Anchor->GetOffset()))); CPPUNIT_ASSERT(!pPage2Anchor->GetFollow()); CPPUNIT_ASSERT_EQUAL(OUString("didn't bode well."), aAnchor2Text); } CPPUNIT_TEST_FIXTURE(Test, testSplitFlyPerFrameWrapOnAllPages) { // Given a document where we want to wrap on all pages, around a split floating table: createSwDoc("floattable-wrap-on-all-pages.docx"); SwDoc* pDoc = getSwDoc(); sw::SpzFrameFormats& rFlys = *pDoc->GetSpzFrameFormats(); sw::SpzFrameFormat* pFly = rFlys[0]; SfxItemSet aSet(pFly->GetAttrSet()); SwFormatWrapTextAtFlyStart aItem(true); aSet.Put(aItem); pDoc->SetFlyFrameAttr(*pFly, aSet); // When formatting that document: SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); pWrtShell->Reformat(); // Then make sure that the anchor text is also split between page 1 and page 2: SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); auto pPage1 = pLayout->Lower()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage1); auto pPage1Anchor = pPage1->FindLastBodyContent()->DynCastTextFrame(); CPPUNIT_ASSERT(pPage1Anchor); OUString aAnchor1Text(pPage1Anchor->GetText().subView( static_cast(pPage1Anchor->GetOffset()), static_cast(pPage1Anchor->GetFollow()->GetOffset() - pPage1Anchor->GetOffset()))); // Without the accompanying fix in place, this test would have failed with: // - Expected: He heard quiet steps behind him. That // - Actual : // i.e. the first page had no anchor text, only the second. CPPUNIT_ASSERT_EQUAL(OUString("He heard quiet steps behind him. That "), aAnchor1Text); auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); CPPUNIT_ASSERT(pPage2); auto pPage2Anchor = pPage2->FindLastBodyContent()->DynCastTextFrame(); CPPUNIT_ASSERT(pPage2Anchor); OUString aAnchor2Text( pPage2Anchor->GetText().subView(static_cast(pPage2Anchor->GetOffset()))); CPPUNIT_ASSERT(!pPage2Anchor->GetFollow()); CPPUNIT_ASSERT_EQUAL(OUString("didn't bode well."), aAnchor2Text); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */