Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
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/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include "hintids.hxx"
21 :
22 : #include "layfrm.hxx"
23 : #include "ftnboss.hxx"
24 : #include "ndtxt.hxx"
25 : #include "paratr.hxx"
26 : #include <editeng/orphitem.hxx>
27 : #include <editeng/widwitem.hxx>
28 : #include <editeng/keepitem.hxx>
29 : #include <editeng/spltitem.hxx>
30 : #include <frmatr.hxx>
31 : #include <txtftn.hxx>
32 : #include <fmtftn.hxx>
33 : #include <rowfrm.hxx>
34 :
35 : #include "widorp.hxx"
36 : #include "txtfrm.hxx"
37 : #include "itrtxt.hxx"
38 : #include "sectfrm.hxx"
39 : #include "ftnfrm.hxx"
40 :
41 : #undef WIDOWTWIPS
42 :
43 : namespace
44 : {
45 :
46 : // A Follow on the same page as its master is nasty.
47 53057 : inline bool IsNastyFollow( const SwTxtFrm *pFrm )
48 : {
49 : OSL_ENSURE( !pFrm->IsFollow() || !pFrm->GetPrev() ||
50 : ((const SwTxtFrm*)pFrm->GetPrev())->GetFollow() == pFrm,
51 : "IsNastyFollow: Was ist denn hier los?" );
52 53057 : return pFrm->IsFollow() && pFrm->GetPrev();
53 : }
54 :
55 : }
56 :
57 94974 : SwTxtFrmBreak::SwTxtFrmBreak( SwTxtFrm *pNewFrm, const SwTwips nRst )
58 94974 : : nRstHeight(nRst), pFrm(pNewFrm)
59 : {
60 94974 : SWAP_IF_SWAPPED( pFrm )
61 94974 : SWRECTFN( pFrm )
62 94974 : nOrigin = (pFrm->*fnRect->fnGetPrtTop)();
63 : SwSectionFrm* pSct;
64 201088 : bKeep = !pFrm->IsMoveable() || IsNastyFollow( pFrm ) ||
65 65099 : ( pFrm->IsInSct() && (pSct=pFrm->FindSctFrm())->Lower()->IsColumnFrm()
66 62171 : && !pSct->MoveAllowed( pFrm ) ) ||
67 199146 : !pFrm->GetTxtNode()->GetSwAttrSet().GetSplit().GetValue() ||
68 146851 : pFrm->GetTxtNode()->GetSwAttrSet().GetKeep().GetValue();
69 94974 : bBreak = false;
70 :
71 94974 : if( !nRstHeight && !pFrm->IsFollow() && pFrm->IsInFtn() && pFrm->HasPara() )
72 : {
73 510 : nRstHeight = pFrm->GetFtnFrmHeight();
74 510 : nRstHeight += (pFrm->Prt().*fnRect->fnGetHeight)() -
75 510 : (pFrm->Frm().*fnRect->fnGetHeight)();
76 510 : if( nRstHeight < 0 )
77 6 : nRstHeight = 0;
78 : }
79 :
80 94974 : UNDO_SWAP( pFrm )
81 94974 : }
82 :
83 : /**
84 : * BP 18.6.93: Widows.
85 : * In contrast to the first implementation the Widows are not calculated
86 : * in advance but detected when formatting the split Follow.
87 : * In Master the Widows-calculation is dropped completely
88 : * (nWidows is manipulated). If the Follow detects that the
89 : * Widows rule applies it sends a Prepare to its predecessor.
90 : * A special problem is when the Widow rule applies but in Master
91 : * there are some lines available.
92 : *
93 : * BP(22.07.92): Calculation of Widows and Orphans.
94 : * The method returns true if one of the rules matches.
95 : *
96 : * One difficulty with Widows and different formats between
97 : * Master- and Follow-Frame:
98 : * Example: If the first column is 3cm and the second is 4cm and
99 : * Widows is set to 3, the decision if the Widows rule matches can not
100 : * be done until the Follow is formated. Unfortunately this is crucial
101 : * to decide if the whole paragraph goes to the next page or not.
102 : */
103 153820 : bool SwTxtFrmBreak::IsInside( SwTxtMargin &rLine ) const
104 : {
105 153820 : bool bFit = false;
106 :
107 153820 : SWAP_IF_SWAPPED( pFrm )
108 153820 : SWRECTFN( pFrm )
109 : // nOrigin is an absolut value, rLine referes to the swapped situation.
110 :
111 : SwTwips nTmpY;
112 153820 : if ( pFrm->IsVertical() )
113 0 : nTmpY = pFrm->SwitchHorizontalToVertical( rLine.Y() + rLine.GetLineHeight() );
114 : else
115 153820 : nTmpY = rLine.Y() + rLine.GetLineHeight();
116 :
117 153820 : SwTwips nLineHeight = (*fnRect->fnYDiff)( nTmpY , nOrigin );
118 :
119 : // 7455 und 6114: Calculate extra space for bottom border.
120 153820 : nLineHeight += (pFrm->*fnRect->fnGetBottomMargin)();
121 :
122 153820 : if( nRstHeight )
123 4508 : bFit = nRstHeight >= nLineHeight;
124 : else
125 : {
126 : // The Frm has a height to fit on the page.
127 : SwTwips nHeight =
128 149312 : (*fnRect->fnYDiff)( (pFrm->GetUpper()->*fnRect->fnGetPrtBottom)(), nOrigin );
129 : // If everything is inside the existing frame the result is true;
130 149312 : bFit = nHeight >= nLineHeight;
131 :
132 : // --> OD #i103292#
133 149312 : if ( !bFit )
134 : {
135 23084 : if ( rLine.GetNext() &&
136 11479 : pFrm->IsInTab() && !pFrm->GetFollow() && !pFrm->GetIndNext() )
137 : {
138 : // add additional space taken as lower space as last content in a table
139 : // for all text lines except the last one.
140 0 : nHeight += pFrm->CalcAddLowerSpaceAsLastInTableCell();
141 0 : bFit = nHeight >= nLineHeight;
142 : }
143 : }
144 : // <--
145 149312 : if( !bFit )
146 : {
147 : // The LineHeight exceeds the current Frm height.
148 : // Call a test Grow to detect if the Frame could
149 : // grow the requested area.
150 11435 : nHeight += pFrm->GrowTst( LONG_MAX );
151 :
152 : // The Grow() returns the height by which the Upper of the TxtFrm
153 : // would let the TxtFrm grow.
154 : // The TxtFrm itself can grow as much as it wants.
155 11435 : bFit = nHeight >= nLineHeight;
156 : }
157 : }
158 :
159 153820 : UNDO_SWAP( pFrm );
160 :
161 153820 : return bFit;
162 : }
163 :
164 320533 : bool SwTxtFrmBreak::IsBreakNow( SwTxtMargin &rLine )
165 : {
166 320533 : SWAP_IF_SWAPPED( pFrm )
167 :
168 : // bKeep is stronger than IsBreakNow()
169 : // Is there enough space ?
170 320533 : if( bKeep || IsInside( rLine ) )
171 311720 : bBreak = false;
172 : else
173 : {
174 : /* This class assumes that the SwTxtMargin is processed from Top to
175 : * Bottom. Because of performance reasons we stop splitting in the
176 : * following cases:
177 : * If only one line does not fit.
178 : * Special case: with DummyPortions there is LineNr == 1, though we
179 : * want to split.
180 : */
181 : // 6010: include DropLines
182 :
183 8813 : bool bFirstLine = 1 == rLine.GetLineNr() && !rLine.GetPrev();
184 8813 : bBreak = true;
185 12783 : if( ( bFirstLine && pFrm->GetIndPrev() )
186 16698 : || ( rLine.GetLineNr() <= rLine.GetDropLines() ) )
187 : {
188 928 : bKeep = true;
189 928 : bBreak = false;
190 : }
191 7885 : else if(bFirstLine && pFrm->IsInFtn() && !pFrm->FindFtnFrm()->GetPrev())
192 : {
193 0 : SwLayoutFrm* pTmp = pFrm->FindFtnBossFrm()->FindBodyCont();
194 0 : if( !pTmp || !pTmp->Lower() )
195 0 : bBreak = false;
196 : }
197 : }
198 :
199 320533 : UNDO_SWAP( pFrm )
200 :
201 320533 : return bBreak;
202 : }
203 :
204 : /// OD 2004-02-27 #106629# - no longer inline
205 702 : void SwTxtFrmBreak::SetRstHeight( const SwTxtMargin &rLine )
206 : {
207 : // OD, FME 2004-02-27 #106629# - consider bottom margin
208 702 : SWRECTFN( pFrm )
209 :
210 702 : nRstHeight = (pFrm->*fnRect->fnGetBottomMargin)();
211 :
212 702 : if ( bVert )
213 : {
214 0 : if ( pFrm->IsVertLR() )
215 0 : nRstHeight = (*fnRect->fnYDiff)( pFrm->SwitchHorizontalToVertical( rLine.Y() ) , nOrigin );
216 : else
217 0 : nRstHeight += nOrigin - pFrm->SwitchHorizontalToVertical( rLine.Y() );
218 : }
219 : else
220 702 : nRstHeight += rLine.Y() - nOrigin;
221 702 : }
222 :
223 94974 : WidowsAndOrphans::WidowsAndOrphans( SwTxtFrm *pNewFrm, const SwTwips nRst,
224 : bool bChkKeep )
225 94974 : : SwTxtFrmBreak( pNewFrm, nRst ), nWidLines( 0 ), nOrphLines( 0 )
226 : {
227 94974 : SWAP_IF_SWAPPED( pFrm )
228 :
229 94974 : if( bKeep )
230 : {
231 : // 5652: If pararagraph should not be split but is larger than
232 : // the page, then bKeep is overruled.
233 123711 : if( bChkKeep && !pFrm->GetPrev() && !pFrm->IsInFtn() &&
234 81676 : pFrm->IsMoveable() &&
235 2612 : ( !pFrm->IsInSct() || pFrm->FindSctFrm()->MoveAllowed(pFrm) ) )
236 880 : bKeep = false;
237 : // Even if Keep is set, Orphans has to be respected.
238 : // e.g. if there are chained frames where a Follow in the last frame
239 : // receives a Keep, because it is not (forward) movable -
240 : // nevertheless the paragraph can request lines from the Master
241 : // because of the Orphan rule.
242 45017 : if( pFrm->IsFollow() )
243 730 : nWidLines = pFrm->GetTxtNode()->GetSwAttrSet().GetWidows().GetValue();
244 : }
245 : else
246 : {
247 49957 : const SwAttrSet& rSet = pFrm->GetTxtNode()->GetSwAttrSet();
248 49957 : const SvxOrphansItem &rOrph = rSet.GetOrphans();
249 49957 : if ( rOrph.GetValue() > 1 )
250 32466 : nOrphLines = rOrph.GetValue();
251 49957 : if ( pFrm->IsFollow() )
252 2880 : nWidLines = rSet.GetWidows().GetValue();
253 :
254 : }
255 :
256 94974 : if ( bKeep || nWidLines || nOrphLines )
257 : {
258 76603 : bool bResetFlags = false;
259 :
260 76603 : if ( pFrm->IsInTab() )
261 : {
262 : // For compatibility reasons, we disable Keep/Widows/Orphans
263 : // inside splittable row frames:
264 21958 : if ( pFrm->GetNextCellLeaf( MAKEPAGE_NONE ) || pFrm->IsInFollowFlowRow() )
265 : {
266 1902 : const SwFrm* pTmpFrm = pFrm->GetUpper();
267 5706 : while ( !pTmpFrm->IsRowFrm() )
268 1902 : pTmpFrm = pTmpFrm->GetUpper();
269 1902 : if ( static_cast<const SwRowFrm*>(pTmpFrm)->IsRowSplitAllowed() )
270 1902 : bResetFlags = true;
271 : }
272 : }
273 :
274 76603 : if( pFrm->IsInFtn() && !pFrm->GetIndPrev() )
275 : {
276 : // Inside of footnotes there are good reasons to turn off the Keep attribute
277 : // as well as Widows/Orphans.
278 90 : SwFtnFrm *pFtn = pFrm->FindFtnFrm();
279 90 : const bool bFt = !pFtn->GetAttr()->GetFtn().IsEndNote();
280 252 : if( !pFtn->GetPrev() &&
281 72 : pFtn->FindFtnBossFrm( bFt ) != pFtn->GetRef()->FindFtnBossFrm( bFt )
282 108 : && ( !pFrm->IsInSct() || pFrm->FindSctFrm()->MoveAllowed(pFrm) ) )
283 : {
284 18 : bResetFlags = true;
285 : }
286 : }
287 :
288 76603 : if ( bResetFlags )
289 : {
290 1920 : bKeep = false;
291 1920 : nOrphLines = 0;
292 1920 : nWidLines = 0;
293 : }
294 : }
295 :
296 94974 : UNDO_SWAP( pFrm )
297 94974 : }
298 :
299 : /**
300 : * The Find*-Methodes do not only search, but adjust the SwTxtMargin to the
301 : * line where the paragraph should have a break and truncate the paragraph there.
302 : * FindBreak()
303 : */
304 91923 : bool WidowsAndOrphans::FindBreak( SwTxtFrm *pFrame, SwTxtMargin &rLine,
305 : bool bHasToFit )
306 : {
307 : // OD 2004-02-25 #i16128# - Why member <pFrm> _*and*_ parameter <pFrame>??
308 : // Thus, assertion on situation, that these are different to figure out why.
309 : OSL_ENSURE( pFrm == pFrame, "<WidowsAndOrphans::FindBreak> - pFrm != pFrame" );
310 :
311 91923 : SWAP_IF_SWAPPED( pFrm )
312 :
313 91923 : bool bRet = true;
314 91923 : sal_uInt16 nOldOrphans = nOrphLines;
315 91923 : if( bHasToFit )
316 38 : nOrphLines = 0;
317 91923 : rLine.Bottom();
318 : // OD 2004-02-25 #i16128# - method renamed
319 91923 : if( !IsBreakNowWidAndOrp( rLine ) )
320 89694 : bRet = false;
321 91923 : if( !FindWidows( pFrame, rLine ) )
322 : {
323 91585 : bool bBack = false;
324 : // OD 2004-02-25 #i16128# - method renamed
325 184599 : while( IsBreakNowWidAndOrp( rLine ) )
326 : {
327 2201 : if( rLine.PrevLine() )
328 1429 : bBack = true;
329 : else
330 772 : break;
331 : }
332 : // Usually Orphans are not taken into account for HasToFit.
333 : // But if Dummy-Lines are concerned and the Orphans rule is violated
334 : // we make an exception: We leave behind one Dummyline and take
335 : // the whole text to the next page/column.
336 208974 : if( rLine.GetLineNr() <= nOldOrphans &&
337 98701 : rLine.GetInfo().GetParaPortion()->IsDummy() &&
338 12 : ( ( bHasToFit && bRet ) || IsBreakNow( rLine ) ) )
339 438 : rLine.Top();
340 :
341 91585 : rLine.TruncLines( true );
342 91585 : bRet = bBack;
343 : }
344 91923 : nOrphLines = nOldOrphans;
345 :
346 91923 : UNDO_SWAP( pFrm )
347 :
348 91923 : return bRet;
349 : }
350 :
351 : /**
352 : * FindWidows positions the SwTxtMargin of the Master to the line where to
353 : * break by examining and formatting the Follow.
354 : * Returns true if the Widows-rule matches, that means that the
355 : * paragraph should not be split (keep) !
356 : */
357 91923 : bool WidowsAndOrphans::FindWidows( SwTxtFrm *pFrame, SwTxtMargin &rLine )
358 : {
359 : OSL_ENSURE( ! pFrame->IsVertical() || ! pFrame->IsSwapped(),
360 : "WidowsAndOrphans::FindWidows with swapped frame" );
361 :
362 91923 : if( !nWidLines || !pFrame->IsFollow() )
363 90553 : return false;
364 :
365 1370 : rLine.Bottom();
366 :
367 : // We can still cut something off
368 1370 : SwTxtFrm *pMaster = pFrame->FindMaster();
369 : OSL_ENSURE(pMaster, "+WidowsAndOrphans::FindWidows: Widows in a master?");
370 1370 : if( !pMaster )
371 0 : return false;
372 :
373 : // 5156: If the first line of the Follow does not fit, the master
374 : // probably is full of Dummies. In this case a PREP_WIDOWS would be fatal.
375 1370 : if( pMaster->GetOfst() == pFrame->GetOfst() )
376 8 : return false;
377 :
378 : // Remaining height of the master
379 1362 : SWRECTFN( pFrame )
380 :
381 1362 : const SwTwips nDocPrtTop = (pFrame->*fnRect->fnGetPrtTop)();
382 : SwTwips nOldHeight;
383 1362 : SwTwips nTmpY = rLine.Y() + rLine.GetLineHeight();
384 :
385 1362 : if ( bVert )
386 : {
387 0 : nTmpY = pFrame->SwitchHorizontalToVertical( nTmpY );
388 0 : nOldHeight = -(pFrame->Prt().*fnRect->fnGetHeight)();
389 : }
390 : else
391 1362 : nOldHeight = (pFrame->Prt().*fnRect->fnGetHeight)();
392 :
393 1362 : const SwTwips nChg = (*fnRect->fnYDiff)( nTmpY, nDocPrtTop + nOldHeight );
394 :
395 : // below the Widows-treshold...
396 1362 : if( rLine.GetLineNr() >= nWidLines )
397 : {
398 : // 8575: Follow to Master I
399 : // If the Follow *grows*, there is the chance for the Master to
400 : // receive lines, that it was forced to hand over to the Follow lately:
401 : // Prepare(Need); check that below nChg!
402 : // (0W, 2O, 2M, 2F) + 1F = 3M, 2F
403 290 : if( rLine.GetLineNr() > nWidLines && pFrame->IsJustWidow() )
404 : {
405 : // If the Master is locked, it has probably just donated a line
406 : // to us, we don't return that just because we turned it into
407 : // multiple lines (e.g. via frames).
408 0 : if( !pMaster->IsLocked() && pMaster->GetUpper() )
409 : {
410 0 : const SwTwips nTmpRstHeight = (pMaster->Frm().*fnRect->fnBottomDist)
411 0 : ( (pMaster->GetUpper()->*fnRect->fnGetPrtBottom)() );
412 0 : if ( nTmpRstHeight >=
413 0 : SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) )
414 : {
415 0 : pMaster->Prepare( PREP_ADJUST_FRM );
416 0 : pMaster->_InvalidateSize();
417 0 : pMaster->InvalidatePage();
418 : }
419 : }
420 :
421 0 : pFrame->SetJustWidow( false );
422 : }
423 290 : return false;
424 : }
425 :
426 : // 8575: Follow to Master II
427 : // If the Follow *shrinks*, maybe the Master can absorb the whole Orphan.
428 : // (0W, 2O, 2M, 1F) - 1F = 3M, 0F -> PREP_ADJUST_FRM
429 : // (0W, 2O, 3M, 2F) - 1F = 2M, 2F -> PREP_WIDOWS
430 :
431 1072 : if( 0 > nChg && !pMaster->IsLocked() && pMaster->GetUpper() )
432 : {
433 0 : SwTwips nTmpRstHeight = (pMaster->Frm().*fnRect->fnBottomDist)
434 0 : ( (pMaster->GetUpper()->*fnRect->fnGetPrtBottom)() );
435 0 : if( nTmpRstHeight >= SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) )
436 : {
437 0 : pMaster->Prepare( PREP_ADJUST_FRM );
438 0 : pMaster->_InvalidateSize();
439 0 : pMaster->InvalidatePage();
440 0 : pFrame->SetJustWidow( false );
441 0 : return false;
442 : }
443 : }
444 :
445 : // Master to Follow
446 : // If the Follow contains fewer rows than Widows after formatting,
447 : // we still can cut off some rows from the Master. If the Orphans
448 : // rule of the Master hereby comes into effect, we need to enlarge
449 : // the Frame in CalcPrep() of the Master Frame, as it won't fit into
450 : // the original page anymore.
451 : // If the Master Frame can still miss a few more rows, we need to
452 : // do a Shrink() in the CalcPrep(): the Follow with the Widows then
453 : // moves onto the page of the Master, but remains unsplit, so that
454 : // it (finally) moves onto the next page. So much for the theory!
455 : //
456 : // We only request one row at a time for now, because a Master's row could
457 : // result in multiple lines for us.
458 : // Therefore, the CalcFollow() remains in control until the Follow got all
459 : // necessary rows.
460 1072 : sal_uInt16 nNeed = 1; // was: nWidLines - rLine.GetLineNr();
461 :
462 : // Special case: Master cannot give lines to follow
463 : // #i91421#
464 1072 : if ( !pMaster->GetIndPrev() )
465 : {
466 804 : sal_uLong nLines = pMaster->GetThisLines();
467 804 : if(nLines == 0 && pMaster->HasPara())
468 : {
469 30 : const SwParaPortion *pMasterPara = pMaster->GetPara();
470 30 : if(pMasterPara && pMasterPara->GetNext())
471 6 : nLines = 2;
472 : }
473 804 : if( nLines <= nNeed )
474 734 : return false;
475 : }
476 :
477 338 : pMaster->Prepare( PREP_WIDOWS, (void*)&nNeed );
478 338 : return true;
479 : }
480 :
481 228 : bool WidowsAndOrphans::WouldFit( SwTxtMargin &rLine, SwTwips &rMaxHeight, bool bTst )
482 : {
483 : // Here it does not matter, if pFrm is swapped or not.
484 : // IsInside() takes care of itself
485 :
486 : // We expect that rLine is set to the last line
487 : OSL_ENSURE( !rLine.GetNext(), "WouldFit: aLine::Bottom missed!" );
488 228 : sal_uInt16 nLineCnt = rLine.GetLineNr();
489 :
490 : // First satisfy the Orphans-rule and the wish for initials ...
491 228 : const sal_uInt16 nMinLines = std::max( GetOrphansLines(), rLine.GetDropLines() );
492 228 : if ( nLineCnt < nMinLines )
493 112 : return false;
494 :
495 116 : rLine.Top();
496 116 : SwTwips nLineSum = rLine.GetLineHeight();
497 :
498 260 : while( nMinLines > rLine.GetLineNr() )
499 : {
500 28 : if( !rLine.NextLine() )
501 0 : return false;
502 28 : nLineSum += rLine.GetLineHeight();
503 : }
504 :
505 : // We do not fit
506 116 : if( !IsInside( rLine ) )
507 98 : return false;
508 :
509 : // Check the Widows-rule
510 18 : if( !nWidLines && !pFrm->IsFollow() )
511 : {
512 : // Usually we only have to check for Widows if we are a Follow.
513 : // On WouldFit the rule has to be checked for the Master too,
514 : // because we are just in the middle of calculating the break.
515 : // In Ctor of WidowsAndOrphans the nWidLines are only calced for
516 : // Follows from the AttrSet - so we catch up now:
517 18 : const SwAttrSet& rSet = pFrm->GetTxtNode()->GetSwAttrSet();
518 18 : nWidLines = rSet.GetWidows().GetValue();
519 : }
520 :
521 : // After Orphans/Initials, do enough lines remain for Widows?
522 : // #111937#: If we are currently doing a test formatting, we may not
523 : // consider the widows rule for two reasons:
524 : // 1. The columns may have different widths.
525 : // Widow lines would have wrong width.
526 : // 2. Test formatting is only done up to the given space.
527 : // we do not have any lines for widows at all.
528 18 : if( bTst || nLineCnt - nMinLines >= GetWidowsLines() )
529 : {
530 6 : if( rMaxHeight >= nLineSum )
531 : {
532 6 : rMaxHeight -= nLineSum;
533 6 : return true;
534 : }
535 : }
536 12 : return false;
537 270 : }
538 :
539 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|