1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include <swmodeltestbase.hxx>
12 #include <svx/svdview.hxx>
13 #include <comphelper/propertyvalue.hxx>
15 #include <IDocumentLayoutAccess.hxx>
16 #include <anchoredobject.hxx>
17 #include <flyfrms.hxx>
18 #include <formatflysplit.hxx>
19 #include <pagefrm.hxx>
20 #include <rootfrm.hxx>
21 #include <sortedobjs.hxx>
24 #include <fmtfsize.hxx>
27 #include <itabenum.hxx>
29 #include <frameformats.hxx>
30 #include <cellfrm.hxx>
32 #include <dflyobj.hxx>
36 /// Covers sw/source/core/layout/flycnt.cxx fixes, i.e. mostly SwFlyAtContentFrame.
37 class Test
: public SwModelTestBase
41 : SwModelTestBase("/sw/qa/core/layout/data/")
45 /// Creates a document with a multi-page floating table: 1 columns and 2 rows.
46 void Create1x2SplitFly();
49 void Test::Create1x2SplitFly()
52 SwDoc
* pDoc
= getSwDoc();
53 SwPageDesc
aStandard(pDoc
->GetPageDesc(0));
54 SwFormatFrameSize
aPageSize(aStandard
.GetMaster().GetFrameSize());
55 // 10 cm for the page height, 2cm are the top and bottom margins, so 6cm remains for the body
57 aPageSize
.SetHeight(5669);
58 aStandard
.GetMaster().SetFormatAttr(aPageSize
);
59 pDoc
->ChgPageDesc(0, aStandard
);
61 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
62 SwInsertTableOptions
aTableOptions(SwInsertTableFlags::DefaultBorder
, 0);
63 pWrtShell
->InsertTable(aTableOptions
, /*nRows=*/2, /*nCols=*/1);
64 pWrtShell
->MoveTable(GotoPrevTable
, fnTableStart
);
65 pWrtShell
->GoPrevCell();
66 pWrtShell
->Insert("A1");
67 SwFormatFrameSize
aRowSize(SwFrameSize::Minimum
);
68 // 4 cm, so 2 rows don't fit 1 page.
69 aRowSize
.SetHeight(2267);
70 pWrtShell
->SetRowHeight(aRowSize
);
71 pWrtShell
->GoNextCell();
72 pWrtShell
->Insert("A2");
73 pWrtShell
->SetRowHeight(aRowSize
);
78 // Wrap the table in a text frame:
79 SwFlyFrameAttrMgr
aMgr(true, pWrtShell
, Frmmgr_Type::TEXT
, nullptr);
80 pWrtShell
->StartAllAction();
81 aMgr
.InsertFlyFrame(RndStdIds::FLY_AT_PARA
, aMgr
.GetPos(), aMgr
.GetSize());
82 pWrtShell
->EndAllAction();
83 // Allow the text frame to split:
84 pWrtShell
->StartAllAction();
85 auto& rFlys
= *pDoc
->GetSpzFrameFormats();
87 SwAttrSet
aSet(pFly
->GetAttrSet());
88 aSet
.Put(SwFormatFlySplit(true));
89 pDoc
->SetAttr(aSet
, *pFly
);
90 pWrtShell
->EndAllAction();
91 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
92 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
93 CPPUNIT_ASSERT(pPage1
);
95 CPPUNIT_ASSERT(pPage1
->GetNext());
98 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyWithTable
)
100 // Given a document with a multi-page floating table:
101 createSwDoc("floattable.docx");
103 // When laying out that document:
106 // Then make sure that the first row goes to page 1 and the second row goes to page 2, while the
107 // table is floating:
108 SwDoc
* pDoc
= getSwDoc();
109 // Without the accompanying fix in place, this test would have failed with a stack overflow
110 // because the follow frame of the anchor was moved into the follow frame of the fly, so the fly
111 // was anchored in itself.
112 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
113 // Page 1 has a master fly, which contains a master table:
114 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
115 CPPUNIT_ASSERT(pPage1
);
116 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
117 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
118 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
119 CPPUNIT_ASSERT(pPage1Fly
);
120 CPPUNIT_ASSERT(!pPage1Fly
->GetPrecede());
121 CPPUNIT_ASSERT(pPage1Fly
->GetFollow());
122 auto pPage1Table
= dynamic_cast<SwTabFrame
*>(pPage1Fly
->GetLower());
123 CPPUNIT_ASSERT(pPage1Table
);
124 CPPUNIT_ASSERT(!pPage1Table
->GetPrecede());
125 CPPUNIT_ASSERT(pPage1Table
->GetFollow());
126 // Page 2 has a follow fly, which contains a follow table:
127 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
128 CPPUNIT_ASSERT(pPage2
);
129 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
130 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
131 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
132 CPPUNIT_ASSERT(pPage2Fly
);
133 CPPUNIT_ASSERT(pPage2Fly
->GetPrecede());
134 CPPUNIT_ASSERT(!pPage2Fly
->GetFollow());
135 auto pPage2Table
= dynamic_cast<SwTabFrame
*>(pPage2Fly
->GetLower());
136 CPPUNIT_ASSERT(pPage2Table
);
137 CPPUNIT_ASSERT(pPage2Table
->GetPrecede());
138 CPPUNIT_ASSERT(!pPage2Table
->GetFollow());
139 // Page 1 anchor has no text:
140 auto pPage1Anchor
= dynamic_cast<SwTextFrame
*>(pPage1
->FindLastBodyContent());
141 CPPUNIT_ASSERT(pPage1Anchor
);
142 // This failed, page 1 anchor had unexpected, leftover text.
143 CPPUNIT_ASSERT(!pPage1Anchor
->HasPara());
146 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyVertOffset
)
148 // Given a document with a floattable, split on 2 pages and a positive vertical offset:
149 createSwDoc("floattable-vertoffset.docx");
151 // When laying out that document:
154 // Then make sure that the vert offset has an effect on the master fly, but not on follow flys:
155 SwDoc
* pDoc
= getSwDoc();
156 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
157 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
158 CPPUNIT_ASSERT(pPage1
);
159 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
160 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
161 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
162 CPPUNIT_ASSERT(pPage1Fly
);
163 auto pPage1Anchor
= dynamic_cast<SwTextFrame
*>(pPage1
->FindLastBodyContent());
164 CPPUNIT_ASSERT(pPage1Anchor
);
165 SwTwips nPage1AnchorTop
= pPage1Anchor
->getFrameArea().Top();
166 SwTwips nPage1FlyTop
= pPage1Fly
->getFrameArea().Top();
167 // First page, the vert offset should be there. This comes from word/document.xml:
168 // <w:tblpPr ... w:tblpY="1135"/>
169 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(1135), nPage1FlyTop
- nPage1AnchorTop
);
171 // Also verify that the 2nd page has no such offset:
172 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
173 CPPUNIT_ASSERT(pPage2
);
174 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
175 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
176 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
177 CPPUNIT_ASSERT(pPage2Fly
);
178 auto pPage2Anchor
= dynamic_cast<SwTextFrame
*>(pPage2
->FindLastBodyContent());
179 CPPUNIT_ASSERT(pPage2Anchor
);
180 SwTwips nPage2AnchorTop
= pPage2Anchor
->getFrameArea().Top();
181 SwTwips nPage2FlyTop
= pPage2Fly
->getFrameArea().Top();
182 // Without the accompanying fix in place, this test would have failed with:
185 // i.e. the fly frame on the 2nd page was also shifted down in Writer, but not in Word.
186 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(0), nPage2FlyTop
- nPage2AnchorTop
);
189 CPPUNIT_TEST_FIXTURE(Test
, testSplitFly3Pages
)
191 // Given a document with a floattable, split on 3 pages:
192 createSwDoc("floattable-3pages.docx");
194 // When laying out that document:
197 // Then make sure that row 1, 2 & 3 go to page 1, 2 & 3, while the table is floating:
198 SwDoc
* pDoc
= getSwDoc();
199 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
200 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
201 CPPUNIT_ASSERT(pPage1
);
202 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
203 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
204 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
205 CPPUNIT_ASSERT(pPage1Fly
);
206 auto pPage1Anchor
= dynamic_cast<SwTextFrame
*>(pPage1
->FindLastBodyContent());
207 CPPUNIT_ASSERT(pPage1Anchor
);
208 SwTwips nPage1AnchorTop
= pPage1Anchor
->getFrameArea().Top();
209 SwTwips nPage1FlyTop
= pPage1Fly
->getFrameArea().Top();
210 // The vert offset should be there on the first page:
211 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(1135), nPage1FlyTop
- nPage1AnchorTop
);
213 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
214 CPPUNIT_ASSERT(pPage2
);
215 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
216 // Without the accompanying fix in place, this test would have failed with:
219 // i.e. both the 2nd and 3rd fly was anchored on page 2.
220 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
221 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
222 CPPUNIT_ASSERT(pPage2Fly
);
223 auto pPage2Anchor
= dynamic_cast<SwTextFrame
*>(pPage2
->FindLastBodyContent());
224 CPPUNIT_ASSERT(pPage2Anchor
);
225 SwTwips nPage2AnchorTop
= pPage2Anchor
->getFrameArea().Top();
226 SwTwips nPage2FlyTop
= pPage2Fly
->getFrameArea().Top();
227 // No vert offset on the second page:
228 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(0), nPage2FlyTop
- nPage2AnchorTop
);
230 auto pPage3
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
231 CPPUNIT_ASSERT(pPage3
);
232 const SwSortedObjs
& rPage3Objs
= *pPage3
->GetSortedObjs();
233 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage3Objs
.size());
234 auto pPage3Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage3Objs
[0]);
235 CPPUNIT_ASSERT(pPage3Fly
);
236 auto pPage3Anchor
= dynamic_cast<SwTextFrame
*>(pPage3
->FindLastBodyContent());
237 CPPUNIT_ASSERT(pPage3Anchor
);
238 SwTwips nPage3AnchorTop
= pPage3Anchor
->getFrameArea().Top();
239 SwTwips nPage3FlyTop
= pPage3Fly
->getFrameArea().Top();
240 // No vert offset on the 3rd page:
241 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(0), nPage3FlyTop
- nPage3AnchorTop
);
244 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyRow
)
246 // Given a document with a floattable, single row split on 2 pages:
247 createSwDoc("floattable-rowsplit.docx");
249 // When laying out that document:
252 // Then make sure that the single row is split to 2 pages, and the fly frames have the correct
254 SwDoc
* pDoc
= getSwDoc();
255 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
256 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
257 CPPUNIT_ASSERT(pPage1
);
258 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
259 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
260 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
261 CPPUNIT_ASSERT(pPage1Fly
);
262 auto pPage1Anchor
= dynamic_cast<SwTextFrame
*>(pPage1
->FindLastBodyContent());
263 CPPUNIT_ASSERT(pPage1Anchor
);
264 // ~No offset between the fly and its anchor:
265 SwTwips nPage1AnchorTop
= pPage1Anchor
->getFrameArea().Top();
266 SwTwips nPage1FlyTop
= pPage1Fly
->getFrameArea().Top();
267 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(1), nPage1FlyTop
- nPage1AnchorTop
);
269 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
270 CPPUNIT_ASSERT(pPage2
);
271 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
272 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
273 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
274 CPPUNIT_ASSERT(pPage2Fly
);
275 auto pPage2Anchor
= dynamic_cast<SwTextFrame
*>(pPage2
->FindLastBodyContent());
276 CPPUNIT_ASSERT(pPage2Anchor
);
277 // No offset between the fly and its anchor:
278 SwTwips nPage2AnchorTop
= pPage2Anchor
->getFrameArea().Top();
279 SwTwips nPage2FlyTop
= pPage2Fly
->getFrameArea().Top();
280 // Without the accompanying fix in place, this test would have failed with:
283 // i.e. the 2nd page's fly had a wrong position.
284 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(0), nPage2FlyTop
- nPage2AnchorTop
);
287 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyEnable
)
289 // Given a document with a table in a textframe:
292 // Then make sure that the layout is updated and we have 2 pages:
293 SwDoc
* pDoc
= getSwDoc();
294 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
295 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
296 CPPUNIT_ASSERT(pPage1
);
297 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
298 // Without the accompanying fix in place, this test would have failed, there was no 2nd page.
299 CPPUNIT_ASSERT(pPage2
);
302 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyDisable
)
304 // Given a document with a floating table, table split on 2 pages:
306 SwDoc
* pDoc
= getSwDoc();
307 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
308 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
309 CPPUNIT_ASSERT(pPage1
);
310 CPPUNIT_ASSERT(pPage1
->GetNext());
311 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
312 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
313 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
314 CPPUNIT_ASSERT(pPage1Fly
);
315 CPPUNIT_ASSERT(pPage1Fly
->GetFollow());
317 // When turning the "split fly" checkbox off:
318 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
319 pWrtShell
->StartAllAction();
320 auto& rFlys
= *pDoc
->GetSpzFrameFormats();
321 auto pFly
= rFlys
[0];
322 SwAttrSet
aSet(pFly
->GetAttrSet());
323 // Note how the UI puts a SwFormatFrameSize into this item set with a slightly different size
324 // (e.g. 3823 twips width -> 3821). This means that by accident the UI works even without the
325 // explicit RES_FLY_SPLIT handling in SwFlyFrame::UpdateAttr_(), but this test will fail when
327 aSet
.Put(SwFormatFlySplit(false));
328 pDoc
->SetAttr(aSet
, *pFly
);
329 pWrtShell
->EndAllAction();
331 // Then make sure the extra page and follow fly frame is joined:
332 CPPUNIT_ASSERT(!pPage1
->GetNext());
333 // Without the accompanying fix in place, this test would have failed, the follow fly frame was
334 // moved to page 1, but it wasn't deleted.
335 CPPUNIT_ASSERT(!pPage1Fly
->GetFollow());
338 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyFooter
)
340 // Given a document with a floattable, table split on 2 pages with headers/footers:
341 createSwDoc("floattable-footer.docx");
343 // When laying out that document:
346 // Then make sure that the table is split to 2 pages:
347 SwDoc
* pDoc
= getSwDoc();
348 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
349 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
350 CPPUNIT_ASSERT(pPage1
);
351 SwTwips nPage1Top
= pPage1
->getFrameArea().Top();
352 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
353 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
354 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
355 CPPUNIT_ASSERT(pPage1Fly
);
356 SwTwips nPage1FlyTop
= pPage1Fly
->getFrameArea().Top();
357 // <w:tblpPr w:tblpY="3286"> from the bugdoc.
358 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(3286), nPage1FlyTop
- nPage1Top
);
360 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
361 // Without the accompanying fix in place, this test would have failed, there was no 2nd page.
362 CPPUNIT_ASSERT(pPage2
);
363 SwTwips nPage2Top
= pPage2
->getFrameArea().Top();
364 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
365 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
366 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
367 CPPUNIT_ASSERT(pPage2Fly
);
368 SwTwips nPage2FlyTop
= pPage2Fly
->getFrameArea().Top();
369 // Without the accompanying fix in place, this test would have failed with:
372 // i.e. <w:pgMar w:top="1440"> from the bugdoc was lost, the follow fly had no vertical offset.
373 CPPUNIT_ASSERT_EQUAL(static_cast<SwTwips
>(1440), nPage2FlyTop
- nPage2Top
);
376 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyFooter2Rows
)
378 // Given a document with a 2nd page that contains the second half of a split row + a last row:
379 createSwDoc("floattable-footer-2rows.docx");
381 // When laying out that document:
384 // Then make sure that the table is split to 2 pages:
385 SwDoc
* pDoc
= getSwDoc();
386 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
387 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
388 CPPUNIT_ASSERT(pPage1
);
389 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
390 CPPUNIT_ASSERT(pPage2
);
391 // Without the accompanying fix in place, this test would have failed. The 2nd page only had the
392 // 2nd half of the split row and the last row went to a 3rd page.
393 CPPUNIT_ASSERT(!pPage2
->GetNext());
396 CPPUNIT_TEST_FIXTURE(Test
, testSplitFly2Cols
)
398 // Given a document with a 2nd page that contains the second half of a split row and 2 columns:
399 createSwDoc("floattable-2cols.docx");
401 // When laying out that document:
404 // Then make sure that the table is split to 2 pages:
405 SwDoc
* pDoc
= getSwDoc();
406 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
407 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
408 CPPUNIT_ASSERT(pPage1
);
409 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
410 CPPUNIT_ASSERT(pPage2
);
411 // Without the accompanying fix in place, this test would have failed. The 2nd page only had the
412 // 2nd half of the split row and the very last row went to a 3rd page.
413 CPPUNIT_ASSERT(!pPage2
->GetNext());
416 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyWidow
)
418 // Given a document with a 2nd page that contains 2 lines, due to widow control:
419 createSwDoc("floattable-widow.docx");
421 // When laying out that document:
424 // Then make sure that widow control works:
425 SwDoc
* pDoc
= getSwDoc();
426 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
427 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
428 CPPUNIT_ASSERT(pPage1
);
429 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
430 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
431 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
432 CPPUNIT_ASSERT(pPage1Fly
);
433 SwFrame
* pTab1
= pPage1Fly
->GetLower();
434 SwFrame
* pRow1
= pTab1
->GetLower();
435 SwFrame
* pCell1
= pRow1
->GetLower();
436 auto pText1
= dynamic_cast<SwTextFrame
*>(pCell1
->GetLower());
437 // Without the accompanying fix in place, this test would have failed with:
440 // i.e. widow control was disabled, layout didn't match Word.
441 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32
>(6), pText1
->GetThisLines());
442 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
443 CPPUNIT_ASSERT(pPage2
);
444 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
445 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
446 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
447 CPPUNIT_ASSERT(pPage2Fly
);
448 SwFrame
* pTab2
= pPage2Fly
->GetLower();
449 SwFrame
* pRow2
= pTab2
->GetLower();
450 // Without the accompanying fix in place, this test would have failed with:
453 // i.e. <w:trHeight w:val="1014"> from the file was ignored.
454 CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long
>(1014), pRow2
->getFrameArea().Height());
455 SwFrame
* pCell2
= pRow2
->GetLower();
456 auto pText2
= dynamic_cast<SwTextFrame
*>(pCell2
->GetLower());
457 // And then similarly this was 1, not 2.
458 CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32
>(2), pText2
->GetThisLines());
461 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyCompat14
)
463 // Given a Word 2010 document with 2 pages, one table row each:
464 createSwDoc("floattable-compat14.docx");
466 // When laying out that document:
469 // Then make sure that the first row is entirely on page 1:
470 SwDoc
* pDoc
= getSwDoc();
471 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
472 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
473 CPPUNIT_ASSERT(pPage1
);
474 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
475 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
476 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
477 CPPUNIT_ASSERT(pPage1Fly
);
478 SwFrame
* pTab1
= pPage1Fly
->GetLower();
479 SwFrame
* pRow1
= pTab1
->GetLower();
480 auto pCell1
= dynamic_cast<SwCellFrame
*>(pRow1
->GetLower());
481 // Without the accompanying fix in place, this test would have failed, the first row was split,
483 CPPUNIT_ASSERT(!pCell1
->GetFollowCell());
484 // Also make sure that the second row is entirely on page 2:
485 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
486 CPPUNIT_ASSERT(pPage2
);
487 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
488 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
489 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
490 CPPUNIT_ASSERT(pPage2Fly
);
491 SwFrame
* pTab2
= pPage2Fly
->GetLower();
492 SwFrame
* pRow2
= pTab2
->GetLower();
493 auto pCell2
= dynamic_cast<SwCellFrame
*>(pRow2
->GetLower());
494 // Without the accompanying fix in place, this test would have failed, the second row was split,
496 CPPUNIT_ASSERT(!pCell2
->GetPreviousCell());
499 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyCompat14Nosplit
)
501 // Given a Word 2010 document with 2 pages, 2 rows on page 1, 1 row on page 2:
502 createSwDoc("floattable-compat14-nosplit.docx");
504 // When laying out that document:
507 // Then make sure that the last row is on a separate page:
508 SwDoc
* pDoc
= getSwDoc();
509 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
510 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
511 CPPUNIT_ASSERT(pPage1
);
512 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
513 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
514 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
515 CPPUNIT_ASSERT(pPage1Fly
);
516 SwFrame
* pTab1
= pPage1Fly
->GetLower();
517 SwFrame
* pRow1
= pTab1
->GetLower();
518 CPPUNIT_ASSERT(pRow1
->GetNext());
519 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
520 // Without the accompanying fix in place, this test would have failed, all rows were on page 1.
521 CPPUNIT_ASSERT(pPage2
);
522 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
523 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
524 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
525 CPPUNIT_ASSERT(pPage2Fly
);
526 SwFrame
* pTab2
= pPage2Fly
->GetLower();
527 SwFrame
* pRow2
= pTab2
->GetLower();
528 CPPUNIT_ASSERT(!pRow2
->GetNext());
531 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyCompat14Body
)
533 // Given a Word 2010 document with 2 pages, 1 row on page 1, 1 row on page 2:
534 createSwDoc("floattable-compat14-body.docx");
536 // When laying out that document:
537 // (This is legacy mode, but still Word doesn't split row 2 because 1) row 2 has a minimal
538 // height, so even the first part of row 2 would not fit the body frame and 2) Word allows using
539 // the bottom margin area in legacy mode, but only in case the fly height <= body height.)
542 // Then make sure that the second row is on a page 2:
543 SwDoc
* pDoc
= getSwDoc();
544 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
545 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
546 CPPUNIT_ASSERT(pPage1
);
547 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
548 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
549 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
550 CPPUNIT_ASSERT(pPage1Fly
);
551 SwFrame
* pTab1
= pPage1Fly
->GetLower();
552 SwFrame
* pRow1
= pTab1
->GetLower();
553 // Without the accompanying fix in place, this test would have failed, part of row 2 was on page
555 CPPUNIT_ASSERT(!pRow1
->GetNext());
556 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
557 CPPUNIT_ASSERT(pPage2
);
558 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
559 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
560 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
561 CPPUNIT_ASSERT(pPage2Fly
);
562 SwFrame
* pTab2
= pPage2Fly
->GetLower();
563 SwFrame
* pRow2
= pTab2
->GetLower();
564 CPPUNIT_ASSERT(!pRow2
->GetNext());
567 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyFollowHorizontalPosition
)
569 // Given a document with 2 pages, master fly on page 1, follow fly on page 2:
570 createSwDoc("floattable-hori-pos.docx");
572 // When laying out that document:
575 // Then make sure that the follow fly doesn't have a different horizontal position:
576 SwDoc
* pDoc
= getSwDoc();
577 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
578 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
579 CPPUNIT_ASSERT(pPage1
);
580 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
581 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
582 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
583 CPPUNIT_ASSERT(pPage1Fly
);
584 tools::Long nPage1FlyLeft
= pPage1Fly
->getFrameArea().Left();
585 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
586 CPPUNIT_ASSERT(pPage2
);
587 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
588 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
589 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
590 CPPUNIT_ASSERT(pPage2Fly
);
591 tools::Long nPage2FlyLeft
= pPage2Fly
->getFrameArea().Left();
592 // Without the accompanying fix in place, this test would have failed with:
595 // i.e. the follow fly was pushed towards the left, instead of having the same position as the
597 CPPUNIT_ASSERT_EQUAL(nPage1FlyLeft
, nPage2FlyLeft
);
600 CPPUNIT_TEST_FIXTURE(Test
, testCursorTraversal
)
602 // Given a document with a multi-page floating table:
605 // When going from A1 to A2:
606 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
607 pWrtShell
->GotoTable("Table1");
608 SwTextNode
* pTextNode
= pWrtShell
->GetCursor()->GetPointNode().GetTextNode();
609 CPPUNIT_ASSERT_EQUAL(OUString("A1"), pTextNode
->GetText());
610 pWrtShell
->Down(/*bSelect=*/false);
612 // Then make sure we get to A2 and don't stay in A1:
613 pTextNode
= pWrtShell
->GetCursor()->GetPointNode().GetTextNode();
614 // Without the accompanying fix in place, this test would have failed with:
617 // i.e. the cursor didn't get from A1 to A2.
618 CPPUNIT_ASSERT_EQUAL(OUString("A2"), pTextNode
->GetText());
621 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyRowDelete
)
623 // Given a document with a multi-page floating table:
626 // When deleting the row of A2:
627 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
628 pWrtShell
->GotoTable("Table1");
629 pWrtShell
->Down(/*bSelect=*/false);
630 SwTextNode
* pTextNode
= pWrtShell
->GetCursor()->GetPointNode().GetTextNode();
631 // We delete the right row:
632 CPPUNIT_ASSERT_EQUAL(OUString("A2"), pTextNode
->GetText());
633 pWrtShell
->DeleteRow();
635 // Then make sure we only have 1 page:
636 SwDoc
* pDoc
= getSwDoc();
637 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
638 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
639 CPPUNIT_ASSERT(pPage1
);
640 CPPUNIT_ASSERT(!pPage1
->GetNext());
643 CPPUNIT_TEST_FIXTURE(Test
, testSplitFly1stRowDelete
)
645 // Given a document with a multi-page floating table:
648 // When deleting the row of A1:
649 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
650 pWrtShell
->GotoTable("Table1");
651 SwTextNode
* pTextNode
= pWrtShell
->GetCursor()->GetPointNode().GetTextNode();
652 // We delete the right row:
653 CPPUNIT_ASSERT_EQUAL(OUString("A1"), pTextNode
->GetText());
654 pWrtShell
->DeleteRow();
656 // Then make sure we only have 1 page:
657 SwDoc
* pDoc
= getSwDoc();
658 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
659 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
660 CPPUNIT_ASSERT(pPage1
);
661 // Without the accompanying fix in place, this test would have failed, the follow fly was still
663 CPPUNIT_ASSERT(!pPage1
->GetNext());
666 CPPUNIT_TEST_FIXTURE(Test
, testSplitFly3rdRowDelete
)
668 // Given a document with a floattable, split on 3 pages:
669 createSwDoc("floattable-3pages.docx");
671 // When deleting the row of A3:
672 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
673 pWrtShell
->GotoTable("Table1");
674 pWrtShell
->Down(/*bSelect=*/false);
675 pWrtShell
->Down(/*bSelect=*/false);
676 SwTextNode
* pTextNode
= pWrtShell
->GetCursor()->GetPointNode().GetTextNode();
677 // We delete the right row:
678 CPPUNIT_ASSERT_EQUAL(OUString("A3"), pTextNode
->GetText());
679 pWrtShell
->DeleteRow();
681 // Then make sure we only have 2 pages:
682 SwDoc
* pDoc
= getSwDoc();
683 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
684 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
685 CPPUNIT_ASSERT(pPage1
);
686 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
687 // Without the accompanying fix in place, this test would have failed, page 3 was not deleted.
688 CPPUNIT_ASSERT(!pPage2
->GetNext());
691 CPPUNIT_TEST_FIXTURE(Test
, testSplitFly2ndRowSelect
)
693 // Given a document with a multi-page floating table:
694 createSwDoc("floattable.docx");
696 // When selecting the second row:
697 SwDoc
* pDoc
= getSwDoc();
698 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
699 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
700 CPPUNIT_ASSERT(pPage1
);
701 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
702 SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
703 auto pPage2Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage2Objs
[0]);
704 const SwRect
& aFollowArea
= pPage2Fly
->getFrameArea();
705 Point
aTopCenter((aFollowArea
.Left() + aFollowArea
.Right()) / 2, aFollowArea
.Top());
706 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
707 pWrtShell
->SelectObj(aTopCenter
);
709 // Then make sure the first row is selected:
710 const SdrMarkList
& rMarkList
= pWrtShell
->GetDrawView()->GetMarkedObjectList();
711 SdrObject
* pSelectedObj
= rMarkList
.GetMark(0)->GetMarkedSdrObj();
712 auto pSelectedVirtObj
= dynamic_cast<SwVirtFlyDrawObj
*>(pSelectedObj
);
713 auto pSelected
= static_cast<SwFlyAtContentFrame
*>(pSelectedVirtObj
->GetFlyFrame());
714 // Without the accompanying fix in place, this test would have failed with:
717 // i.e. a follow fly was possible to select (instead of its master)
718 CPPUNIT_ASSERT_EQUAL(pPage2Fly
->GetPrecede()->GetFrameId(), pSelected
->GetFrameId());
721 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyInSection
)
723 // This crashed, the layout assumed that the floating table is directly under the body frame.
724 createSwDoc("floattable-in-section.docx");
727 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyThenTable
)
729 // Given a document with a 2 page floating table, followed by an other table:
730 // Intentionally load the document as hidden to avoid layout during load (see TestTdf150616):
731 uno::Sequence
<beans::PropertyValue
> aFilterOptions
= {
732 comphelper::makePropertyValue("Hidden", true),
734 mxComponent
= loadFromDesktop(m_directories
.getURLFromSrc(u
"/sw/qa/core/layout/data/")
735 + "floattable-then-table.docx",
736 "com.sun.star.text.TextDocument", aFilterOptions
);
738 // When layout is calculated during PDF export:
739 // Then make sure that finishes without errors:
740 // This crashed, due to a stack overflow in layout code.
741 save("writer_pdf_Export");
744 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyInTextSection
)
746 // The document contains a DOCX cont sect break, which is mapped to a TextSection.
747 // This crashed, the anchor was split directly, so the follow anchor was moved outside the
748 // section frame, which is broken.
749 createSwDoc("floattable-in-text-section.docx");
752 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyTableRowKeep
)
754 // Given a document with a floating table, 2.5 rows on the first page:
755 createSwDoc("floattable-table-row-keep.docx");
757 // When laying out that document:
760 // Then make sure that the expected amount of rows is on the first page:
761 SwDoc
* pDoc
= getSwDoc();
762 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
763 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
764 CPPUNIT_ASSERT(pPage1
);
765 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
766 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
767 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
768 CPPUNIT_ASSERT(pPage1Fly
);
769 SwFrame
* pTab1
= pPage1Fly
->GetLower();
770 SwFrame
* pRow1
= pTab1
->GetLower();
771 CPPUNIT_ASSERT(pRow1
);
772 SwFrame
* pRow2
= pRow1
->GetNext();
773 // Without the accompanying fix in place, this test would have failed, the table on the first
774 // page only had 1 row, due to TableRowKeep kicking in for floating tables, which is incorrect.
775 CPPUNIT_ASSERT(pRow2
);
776 SwFrame
* pRow3
= pRow2
->GetNext();
777 CPPUNIT_ASSERT(pRow3
);
778 auto pCell3
= dynamic_cast<SwCellFrame
*>(pRow3
->GetLower());
779 CPPUNIT_ASSERT(pCell3
->GetFollowCell());
782 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyDeletedAnchor
)
784 // Given a document with a floating table that spans over 3 pages:
785 createSwDoc("floattable-deleted-anchor.docx");
787 // When laying out that document:
790 // Then make sure that there are 3 anchors for the 3 pages:
791 SwDoc
* pDoc
= getSwDoc();
792 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
793 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
794 CPPUNIT_ASSERT(pPage1
);
795 SwFrame
* pBody1
= pPage1
->GetLower();
796 CPPUNIT_ASSERT(pBody1
);
797 auto pAnchor1
= dynamic_cast<SwTextFrame
*>(pBody1
->GetLower()->GetNext());
798 CPPUNIT_ASSERT(pAnchor1
);
799 SwTextFrame
* pAnchor2
= pAnchor1
->GetFollow();
800 CPPUNIT_ASSERT(pAnchor2
);
801 SwTextFrame
* pAnchor3
= pAnchor2
->GetFollow();
802 // Without the accompanying fix in place, this test would have failed, the fly frame on the 3rd
803 // page was anchored to a text frame on the 2nd page, leading to a negative frame height.
804 CPPUNIT_ASSERT(pAnchor3
);
807 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyMultiCol
)
809 // Given a document with a floating table that is in a multi-col section:
810 createSwDoc("floattable-multi-col.docx");
812 // When laying out that document:
815 // Then make sure that the fly frame is not split, matching Word:
816 SwDoc
* pDoc
= getSwDoc();
817 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
818 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
819 CPPUNIT_ASSERT(pPage1
);
820 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
821 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
822 auto pPage1Fly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
823 CPPUNIT_ASSERT(pPage1Fly
);
824 // Without the accompanying fix in place, this test would have failed, we tried to split and
825 // then hit an assertion failure.
826 CPPUNIT_ASSERT(!pPage1Fly
->GetFollow());
829 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyTabJoin
)
831 // Given a document with 3 pages and 2 tables: table on first and second page, 3rd page has no
833 createSwDoc("floattable-tab-join.docx");
835 // When laying out that document:
838 // Then make sure that all pages have the expected amount of fly frames:
839 SwDoc
* pDoc
= getSwDoc();
840 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
841 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
842 CPPUNIT_ASSERT(pPage1
);
843 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
844 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
845 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
846 CPPUNIT_ASSERT(pPage2
);
847 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
848 // Without the accompanying fix in place, this test would have failed with:
851 // i.e. the 2nd page had 2 fly frames, hosting a split table, instead of joining that table and
852 // having 1 fly frame.
853 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
854 auto pPage3
= dynamic_cast<SwPageFrame
*>(pPage2
->GetNext());
855 CPPUNIT_ASSERT(pPage3
);
856 CPPUNIT_ASSERT(!pPage3
->GetSortedObjs());
859 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyTabJoinLegacy
)
861 // Given a document with 3 pages and 2 tables: table on first and second page, 3rd page has no
862 // table (Word 2010 mode):
863 createSwDoc("floattable-tab-join-legacy.docx");
865 // When laying out that document:
868 // Then make sure that all pages have the expected amount of fly frames:
869 SwDoc
* pDoc
= getSwDoc();
870 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
871 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
872 CPPUNIT_ASSERT(pPage1
);
873 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
874 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
875 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
876 CPPUNIT_ASSERT(pPage2
);
877 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
878 // Without the accompanying fix in place, this test would have failed with:
881 // i.e. the 2nd page had 2 fly frames, hosting a split table, instead of joining that table and
882 // having 1 fly frame (even after the non-legacy case was fixed already).
883 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
884 auto pPage3
= dynamic_cast<SwPageFrame
*>(pPage2
->GetNext());
885 CPPUNIT_ASSERT(pPage3
);
886 CPPUNIT_ASSERT(!pPage3
->GetSortedObjs());
889 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyObjectFormatter
)
891 // Given a document with 3 pages and 2 tables: table on first and second page, 3rd page has no
893 createSwDoc("floattable-object-formatter.docx");
895 // When calculating the layout:
898 // Then make sure we don't crash and also that all pages have the expected amount of fly frames:
899 SwDoc
* pDoc
= getSwDoc();
900 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
901 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
902 CPPUNIT_ASSERT(pPage1
);
903 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
904 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
905 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
906 CPPUNIT_ASSERT(pPage2
);
907 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
908 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
909 auto pPage3
= dynamic_cast<SwPageFrame
*>(pPage2
->GetNext());
910 CPPUNIT_ASSERT(pPage3
);
911 CPPUNIT_ASSERT(!pPage3
->GetSortedObjs());
914 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyNextLeafInSection
)
916 // Given a document with 4 pages: page 1 had a floating table, page 2 & 3 had a second floating
917 // table and finally page 4 is empty:
918 createSwDoc("floattable-next-leaf-in-section.docx");
920 // When calculating the layout:
921 // Then this never returned, the loop in SwFrame::GetNextFlyLeaf() never finished.
925 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyAnchorKeepWithNext
)
927 // Given a document with 2 pages, a split floating table on both pages:
928 createSwDoc("floattable-anchor-keep-with-next.docx");
930 // When calculating the layout:
933 // Then make sure the pages have the expected amount of anchored objects:
934 SwDoc
* pDoc
= getSwDoc();
935 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
936 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
937 CPPUNIT_ASSERT(pPage1
);
938 // Without the accompanying fix in place, this test would have failed, page 1 had no floating
939 // table, it was entirely on page 2.
940 CPPUNIT_ASSERT(pPage1
->GetSortedObjs());
941 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
942 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
943 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
944 CPPUNIT_ASSERT(pPage2
);
945 CPPUNIT_ASSERT(pPage2
->GetSortedObjs());
946 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
947 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
950 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyNoFooterOverlap
)
952 // Given a document with 2 pages, a floating table on both pages:
953 createSwDoc("floattable-no-footer-overlap.doc");
955 // When calculating the layout:
958 // Then make sure the second page has a floating table:
959 SwDoc
* pDoc
= getSwDoc();
960 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
961 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
962 CPPUNIT_ASSERT(pPage1
);
963 CPPUNIT_ASSERT(pPage1
->GetSortedObjs());
964 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
965 // Without the accompanying fix in place, this test would have failed with:
968 // i.e. part of the second table was on page 1.
969 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage1Objs
.size());
970 auto pPage2
= dynamic_cast<SwPageFrame
*>(pPage1
->GetNext());
971 // Without the accompanying fix in place, this test would have failed, there was no page 2, both
972 // floating tables were on page 1.
973 CPPUNIT_ASSERT(pPage2
);
974 CPPUNIT_ASSERT(pPage2
->GetSortedObjs());
975 const SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
976 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
979 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyGrowFromBottom
)
981 // Given a document with a floating table that grows from the bottom:
982 createSwDoc("floattable-from-bottom.docx");
984 // When calculating the layout:
987 // Then make sure that such a floating table is not split, matching Word:
988 SwDoc
* pDoc
= getSwDoc();
989 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
990 auto pPage1
= dynamic_cast<SwPageFrame
*>(pLayout
->Lower());
991 CPPUNIT_ASSERT(pPage1
);
992 CPPUNIT_ASSERT(pPage1
->GetSortedObjs());
993 const SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
994 const auto pFly
= dynamic_cast<SwFlyAtContentFrame
*>(rPage1Objs
[0]);
995 // Without the accompanying fix in place, this test would have failed, we tried to split the fly
996 // frame on page 1 even when it would fit, and this lead to a crash on export later.
997 CPPUNIT_ASSERT(!pFly
->GetFollow());
1000 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyIntoTable
)
1002 // Given a document with a floating table, then an inline table on the next page:
1003 createSwDoc("floattable-then-table.doc");
1005 // When inserting a page break:
1006 // Then make sure the layout doesn't crash:
1007 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
1008 pWrtShell
->InsertPageBreak();
1009 // Without the accompanying fix in place, this test would have crashed, we tried to insert the
1010 // second part of a floating table into a table on the next page, not before that table.
1014 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyFromAsCharAnchor
)
1016 // Given a document with a footnote that has a table (imported in an as-char anchored frame in
1018 createSwDoc("table-in-footnote.docx");
1020 // When changing the anchor type of that frame to to-para:
1021 // Then make sure we don't crash:
1023 // Without the accompanying fix in place, this test would have crashed, we tried to split a
1024 // frame+table inside a footnote.
1025 dispatchCommand(mxComponent
, ".uno:SetAnchorToPara", {});
1028 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyNested
)
1030 // Given a document with a nested, multi-page floating table:
1031 // When calculating the layout:
1032 createSwDoc("floattable-nested.odt");
1035 // Then make sure we don't crash:
1036 // Without the accompanying fix in place, this test would have crashed.
1037 // Check that we have exactly 4 fly frames, all of them on the expected pages: master outer,
1038 // follow outer, master inner and follow inner.
1039 SwDoc
* pDoc
= getSwDoc();
1040 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
1041 auto pPage1
= pLayout
->Lower()->DynCastPageFrame();
1042 CPPUNIT_ASSERT(pPage1
);
1043 CPPUNIT_ASSERT(pPage1
->GetSortedObjs());
1044 SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
1045 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rPage1Objs
.size());
1046 auto pPage1Fly1
= rPage1Objs
[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
1047 CPPUNIT_ASSERT(pPage1Fly1
);
1048 CPPUNIT_ASSERT(pPage1Fly1
->GetAnchorFrameContainingAnchPos()->IsInFly());
1049 CPPUNIT_ASSERT(pPage1Fly1
->GetFollow());
1050 auto pPage1Fly2
= rPage1Objs
[1]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
1051 CPPUNIT_ASSERT(pPage1Fly2
);
1052 CPPUNIT_ASSERT(!pPage1Fly2
->GetAnchorFrameContainingAnchPos()->IsInFly());
1053 CPPUNIT_ASSERT(pPage1Fly2
->GetFollow());
1054 auto pPage2
= pPage1
->GetNext()->DynCastPageFrame();
1055 CPPUNIT_ASSERT(pPage2
);
1056 CPPUNIT_ASSERT(pPage2
->GetSortedObjs());
1057 SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
1058 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rPage2Objs
.size());
1059 auto pPage2Fly1
= rPage2Objs
[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
1060 CPPUNIT_ASSERT(pPage2Fly1
);
1061 CPPUNIT_ASSERT(pPage2Fly1
->GetAnchorFrameContainingAnchPos()->IsInFly());
1062 CPPUNIT_ASSERT(pPage2Fly1
->GetPrecede());
1064 // Without the accompanying fix in place, this test would have failed with:
1065 // - Expected greater than: 6204
1067 // i.e. the inner follow fly had a bad position, it was outside the page rectangle, it was not
1068 // rendered and this way the inner anchor had no fly portion, either.
1069 CPPUNIT_ASSERT_GREATER(pPage2
->getFrameArea().Top(), pPage2Fly1
->getFrameArea().Top());
1071 // Without the accompanying fix in place, this test would have failed with:
1072 // - Expected less than: 12523
1074 // i.e. the inner follow fly was not "moved back" to its place to have the wanted 4400 position,
1075 // which makes the "Inner A2" text visible.
1076 CPPUNIT_ASSERT_LESS(pPage2
->getFrameArea().Right(), pPage2Fly1
->getFrameArea().Right());
1078 auto pPage2Fly2
= rPage2Objs
[1]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
1079 CPPUNIT_ASSERT(pPage2Fly2
);
1080 CPPUNIT_ASSERT(!pPage2Fly2
->GetAnchorFrameContainingAnchPos()->IsInFly());
1081 CPPUNIT_ASSERT(pPage2Fly2
->GetPrecede());
1084 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyNestedOverlap
)
1086 // Given a document with a nested, multi-page floating table, enabling the "don't overlap" logic:
1087 // When calculating the layout:
1088 createSwDoc("floattable-nested-overlap.odt");
1091 // Then make sure we get 2 pages (2 flys on each page):
1092 // Without the accompanying fix in place, this test would have failed with a layout loop.
1093 SwDoc
* pDoc
= getSwDoc();
1094 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
1095 auto pPage1
= pLayout
->Lower()->DynCastPageFrame();
1096 CPPUNIT_ASSERT(pPage1
);
1097 CPPUNIT_ASSERT(pPage1
->GetSortedObjs());
1098 SwSortedObjs
& rPage1Objs
= *pPage1
->GetSortedObjs();
1099 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rPage1Objs
.size());
1100 auto pPage2
= pPage1
->GetNext()->DynCastPageFrame();
1101 CPPUNIT_ASSERT(pPage2
);
1102 CPPUNIT_ASSERT(pPage2
->GetSortedObjs());
1103 SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
1104 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rPage2Objs
.size());
1107 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyMoveMaster
)
1109 // Given a document with a multi-page floating table on pages 1 -> 2 -> 3:
1110 createSwDoc("floattable-move-master.docx");
1112 // When adding an empty para before the table, so the table gets shifted to pages 2 -> 3:
1113 SwWrtShell
* pWrtShell
= getSwDocShell()->GetWrtShell();
1114 pWrtShell
->SttEndDoc(/*bStt=*/true);
1115 pWrtShell
->Down(/*bSelect=*/false, /*nCount=*/4);
1116 pWrtShell
->SplitNode();
1118 // Then make sure page 1 has no flys, page 2 and 3 has the split fly and no flys on page 4:
1119 SwDoc
* pDoc
= getSwDoc();
1120 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
1121 auto pPage1
= pLayout
->Lower()->DynCastPageFrame();
1122 CPPUNIT_ASSERT(pPage1
);
1123 // Without the accompanying fix in place, this test would have failed, the start of the fly was
1124 // still on page 1 instead of page 2.
1125 CPPUNIT_ASSERT(!pPage1
->GetSortedObjs());
1126 auto pPage2
= pPage1
->GetNext()->DynCastPageFrame();
1127 CPPUNIT_ASSERT(pPage2
);
1128 CPPUNIT_ASSERT(pPage2
->GetSortedObjs());
1129 SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
1130 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
1131 auto pPage2Fly
= rPage2Objs
[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
1132 CPPUNIT_ASSERT(pPage2Fly
);
1133 CPPUNIT_ASSERT(!pPage2Fly
->GetPrecede());
1134 CPPUNIT_ASSERT(pPage2Fly
->HasFollow());
1135 auto pPage3
= pPage2
->GetNext()->DynCastPageFrame();
1136 CPPUNIT_ASSERT(pPage3
);
1137 CPPUNIT_ASSERT(pPage3
->GetSortedObjs());
1138 SwSortedObjs
& rPage3Objs
= *pPage3
->GetSortedObjs();
1139 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage3Objs
.size());
1140 auto pPage3Fly
= rPage3Objs
[0]->DynCastFlyFrame()->DynCastFlyAtContentFrame();
1141 CPPUNIT_ASSERT(pPage3Fly
);
1142 CPPUNIT_ASSERT(pPage3Fly
->GetPrecede());
1143 CPPUNIT_ASSERT(!pPage3Fly
->GetFollow());
1144 auto pPage4
= pPage3
->GetNext()->DynCastPageFrame();
1145 CPPUNIT_ASSERT(pPage4
);
1146 CPPUNIT_ASSERT(!pPage4
->GetSortedObjs());
1149 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyDelEmpty
)
1151 // Given a document with multiple floating tables and 7 pages:
1152 // When loading that document:
1153 // Without the accompanying fix in place, this test would have crashed due to a
1154 // heap-use-after-free problem (visible with e.g. MALLOC_PERTURB_=153).
1155 createSwDoc("floattable-del-empty.docx");
1157 // Then make sure that the page count matches Word:
1158 CPPUNIT_ASSERT_EQUAL(7, getPages());
1161 CPPUNIT_TEST_FIXTURE(Test
, testSplitFlyInTableInSection
)
1163 // Given a document where page 2 and page 3 has a floating table inside an inline table, inside
1165 // Without the accompanying fix in place, this test would have crashed, we created a follow
1166 // anchor which was marked as "in table", but had no table parent.
1167 createSwDoc("floattable-in-inltbl-in-sect.docx");
1169 // Then make sure that the floating table is on page 2 and page 3:
1170 SwDoc
* pDoc
= getSwDoc();
1171 SwRootFrame
* pLayout
= pDoc
->getIDocumentLayoutAccess().GetCurrentLayout();
1172 auto pPage1
= pLayout
->Lower()->DynCastPageFrame();
1173 CPPUNIT_ASSERT(pPage1
);
1174 CPPUNIT_ASSERT(!pPage1
->GetSortedObjs());
1175 auto pPage2
= pPage1
->GetNext()->DynCastPageFrame();
1176 CPPUNIT_ASSERT(pPage2
);
1177 CPPUNIT_ASSERT(pPage2
->GetSortedObjs());
1178 SwSortedObjs
& rPage2Objs
= *pPage2
->GetSortedObjs();
1179 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage2Objs
.size());
1180 auto pPage3
= pPage2
->GetNext()->DynCastPageFrame();
1181 CPPUNIT_ASSERT(pPage3
);
1182 CPPUNIT_ASSERT(pPage3
->GetSortedObjs());
1183 SwSortedObjs
& rPage3Objs
= *pPage3
->GetSortedObjs();
1184 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rPage3Objs
.size());
1188 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */