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 30832 : inline bool IsNastyFollow( const SwTextFrm *pFrm )
48 : {
49 : OSL_ENSURE( !pFrm->IsFollow() || !pFrm->GetPrev() ||
50 : static_cast<const SwTextFrm*>(pFrm->GetPrev())->GetFollow() == pFrm,
51 : "IsNastyFollow: Was ist denn hier los?" );
52 30832 : return pFrm->IsFollow() && pFrm->GetPrev();
53 : }
54 :
55 : }
56 :
57 53124 : SwTextFrmBreak::SwTextFrmBreak( SwTextFrm *pNewFrm, const SwTwips nRst )
58 53124 : : nRstHeight(nRst), pFrm(pNewFrm)
59 : {
60 53124 : SWAP_IF_SWAPPED swap(pFrm);
61 53124 : SWRECTFN( pFrm )
62 53124 : nOrigin = (pFrm->*fnRect->fnGetPrtTop)();
63 53124 : bKeep = !pFrm->IsMoveable() || IsNastyFollow( pFrm );
64 53124 : if( !bKeep && pFrm->IsInSct() )
65 : {
66 6408 : const SwSectionFrm* const pSct = pFrm->FindSctFrm();
67 6408 : bKeep = pSct->Lower()->IsColumnFrm() && !pSct->MoveAllowed( pFrm );
68 : }
69 83373 : bKeep = bKeep || !pFrm->GetTextNode()->GetSwAttrSet().GetSplit().GetValue() ||
70 83373 : pFrm->GetTextNode()->GetSwAttrSet().GetKeep().GetValue();
71 53124 : bBreak = false;
72 :
73 53124 : if( !nRstHeight && !pFrm->IsFollow() && pFrm->IsInFootnote() && pFrm->HasPara() )
74 : {
75 392 : nRstHeight = pFrm->GetFootnoteFrmHeight();
76 392 : nRstHeight += (pFrm->Prt().*fnRect->fnGetHeight)() -
77 392 : (pFrm->Frm().*fnRect->fnGetHeight)();
78 392 : if( nRstHeight < 0 )
79 8 : nRstHeight = 0;
80 53124 : }
81 53124 : }
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 formatted. Unfortunately this is crucial
101 : * to decide if the whole paragraph goes to the next page or not.
102 : */
103 102805 : bool SwTextFrmBreak::IsInside( SwTextMargin &rLine ) const
104 : {
105 102805 : bool bFit = false;
106 :
107 102805 : SWAP_IF_SWAPPED swap(pFrm);
108 102805 : SWRECTFN( pFrm )
109 : // nOrigin is an absolut value, rLine referes to the swapped situation.
110 :
111 : SwTwips nTmpY;
112 102805 : if ( pFrm->IsVertical() )
113 0 : nTmpY = pFrm->SwitchHorizontalToVertical( rLine.Y() + rLine.GetLineHeight() );
114 : else
115 102805 : nTmpY = rLine.Y() + rLine.GetLineHeight();
116 :
117 102805 : SwTwips nLineHeight = (*fnRect->fnYDiff)( nTmpY , nOrigin );
118 :
119 : // 7455 und 6114: Calculate extra space for bottom border.
120 102805 : nLineHeight += (pFrm->*fnRect->fnGetBottomMargin)();
121 :
122 102805 : if( nRstHeight )
123 2770 : bFit = nRstHeight >= nLineHeight;
124 : else
125 : {
126 : // The Frm has a height to fit on the page.
127 : SwTwips nHeight =
128 100035 : (*fnRect->fnYDiff)( (pFrm->GetUpper()->*fnRect->fnGetPrtBottom)(), nOrigin );
129 : // If everything is inside the existing frame the result is true;
130 100035 : bFit = nHeight >= nLineHeight;
131 :
132 : // --> OD #i103292#
133 100035 : if ( !bFit )
134 : {
135 12919 : if ( rLine.GetNext() &&
136 6407 : 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 100035 : 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 6389 : nHeight += pFrm->GrowTst( LONG_MAX );
151 :
152 : // The Grow() returns the height by which the Upper of the TextFrm
153 : // would let the TextFrm grow.
154 : // The TextFrm itself can grow as much as it wants.
155 6389 : bFit = nHeight >= nLineHeight;
156 : }
157 : }
158 :
159 102805 : return bFit;
160 : }
161 :
162 191684 : bool SwTextFrmBreak::IsBreakNow( SwTextMargin &rLine )
163 : {
164 191684 : SWAP_IF_SWAPPED swap(pFrm);
165 :
166 : // bKeep is stronger than IsBreakNow()
167 : // Is there enough space ?
168 191684 : if( bKeep || IsInside( rLine ) )
169 186797 : bBreak = false;
170 : else
171 : {
172 : /* This class assumes that the SwTextMargin is processed from Top to
173 : * Bottom. Because of performance reasons we stop splitting in the
174 : * following cases:
175 : * If only one line does not fit.
176 : * Special case: with DummyPortions there is LineNr == 1, though we
177 : * want to split.
178 : */
179 : // 6010: include DropLines
180 :
181 4887 : bool bFirstLine = 1 == rLine.GetLineNr() && !rLine.GetPrev();
182 4887 : bBreak = true;
183 6558 : if( ( bFirstLine && pFrm->GetIndPrev() )
184 9289 : || ( rLine.GetLineNr() <= rLine.GetDropLines() ) )
185 : {
186 485 : bKeep = true;
187 485 : bBreak = false;
188 : }
189 4402 : else if(bFirstLine && pFrm->IsInFootnote() && !pFrm->FindFootnoteFrm()->GetPrev())
190 : {
191 0 : SwLayoutFrm* pTmp = pFrm->FindFootnoteBossFrm()->FindBodyCont();
192 0 : if( !pTmp || !pTmp->Lower() )
193 0 : bBreak = false;
194 : }
195 : }
196 :
197 191684 : return bBreak;
198 : }
199 :
200 : /// OD 2004-02-27 #106629# - no longer inline
201 404 : void SwTextFrmBreak::SetRstHeight( const SwTextMargin &rLine )
202 : {
203 : // OD, FME 2004-02-27 #106629# - consider bottom margin
204 404 : SWRECTFN( pFrm )
205 :
206 404 : nRstHeight = (pFrm->*fnRect->fnGetBottomMargin)();
207 :
208 404 : if ( bVert )
209 : {
210 0 : if ( pFrm->IsVertLR() )
211 0 : nRstHeight = (*fnRect->fnYDiff)( pFrm->SwitchHorizontalToVertical( rLine.Y() ) , nOrigin );
212 : else
213 0 : nRstHeight += nOrigin - pFrm->SwitchHorizontalToVertical( rLine.Y() );
214 : }
215 : else
216 404 : nRstHeight += rLine.Y() - nOrigin;
217 404 : }
218 :
219 53124 : WidowsAndOrphans::WidowsAndOrphans( SwTextFrm *pNewFrm, const SwTwips nRst,
220 : bool bChkKeep )
221 53124 : : SwTextFrmBreak( pNewFrm, nRst ), nWidLines( 0 ), nOrphLines( 0 )
222 : {
223 53124 : SWAP_IF_SWAPPED swap(pFrm);
224 :
225 53124 : if( bKeep )
226 : {
227 : // 5652: If pararagraph should not be split but is larger than
228 : // the page, then bKeep is overruled.
229 66070 : if( bChkKeep && !pFrm->GetPrev() && !pFrm->IsInFootnote() &&
230 43584 : pFrm->IsMoveable() &&
231 1437 : ( !pFrm->IsInSct() || pFrm->FindSctFrm()->MoveAllowed(pFrm) ) )
232 530 : bKeep = false;
233 : // Even if Keep is set, Orphans has to be respected.
234 : // e.g. if there are chained frames where a Follow in the last frame
235 : // receives a Keep, because it is not (forward) movable -
236 : // nevertheless the paragraph can request lines from the Master
237 : // because of the Orphan rule.
238 24191 : if( pFrm->IsFollow() )
239 290 : nWidLines = pFrm->GetTextNode()->GetSwAttrSet().GetWidows().GetValue();
240 : }
241 : else
242 : {
243 28933 : const SwAttrSet& rSet = pFrm->GetTextNode()->GetSwAttrSet();
244 28933 : const SvxOrphansItem &rOrph = rSet.GetOrphans();
245 28933 : if ( rOrph.GetValue() > 1 )
246 16529 : nOrphLines = rOrph.GetValue();
247 28933 : if ( pFrm->IsFollow() )
248 1804 : nWidLines = rSet.GetWidows().GetValue();
249 :
250 : }
251 :
252 53124 : if ( bKeep || nWidLines || nOrphLines )
253 : {
254 40190 : bool bResetFlags = false;
255 :
256 40190 : if ( pFrm->IsInTab() )
257 : {
258 : // For compatibility reasons, we disable Keep/Widows/Orphans
259 : // inside splittable row frames:
260 11071 : if ( pFrm->GetNextCellLeaf( MAKEPAGE_NONE ) || pFrm->IsInFollowFlowRow() )
261 : {
262 790 : const SwFrm* pTmpFrm = pFrm->GetUpper();
263 2370 : while ( !pTmpFrm->IsRowFrm() )
264 790 : pTmpFrm = pTmpFrm->GetUpper();
265 790 : if ( static_cast<const SwRowFrm*>(pTmpFrm)->IsRowSplitAllowed() )
266 790 : bResetFlags = true;
267 : }
268 : }
269 :
270 40190 : if( pFrm->IsInFootnote() && !pFrm->GetIndPrev() )
271 : {
272 : // Inside of footnotes there are good reasons to turn off the Keep attribute
273 : // as well as Widows/Orphans.
274 54 : SwFootnoteFrm *pFootnote = pFrm->FindFootnoteFrm();
275 54 : const bool bFt = !pFootnote->GetAttr()->GetFootnote().IsEndNote();
276 153 : if( !pFootnote->GetPrev() &&
277 45 : pFootnote->FindFootnoteBossFrm( bFt ) != pFootnote->GetRef()->FindFootnoteBossFrm( bFt )
278 72 : && ( !pFrm->IsInSct() || pFrm->FindSctFrm()->MoveAllowed(pFrm) ) )
279 : {
280 10 : bResetFlags = true;
281 : }
282 : }
283 :
284 40190 : if ( bResetFlags )
285 : {
286 800 : bKeep = false;
287 800 : nOrphLines = 0;
288 800 : nWidLines = 0;
289 : }
290 53124 : }
291 53124 : }
292 :
293 : /**
294 : * The Find*-Methodes do not only search, but adjust the SwTextMargin to the
295 : * line where the paragraph should have a break and truncate the paragraph there.
296 : * FindBreak()
297 : */
298 51395 : bool WidowsAndOrphans::FindBreak( SwTextFrm *pFrame, SwTextMargin &rLine,
299 : bool bHasToFit )
300 : {
301 : // OD 2004-02-25 #i16128# - Why member <pFrm> _*and*_ parameter <pFrame>??
302 : // Thus, assertion on situation, that these are different to figure out why.
303 : OSL_ENSURE( pFrm == pFrame, "<WidowsAndOrphans::FindBreak> - pFrm != pFrame" );
304 :
305 51395 : SWAP_IF_SWAPPED swap(pFrm);
306 :
307 51395 : bool bRet = true;
308 51395 : sal_uInt16 nOldOrphans = nOrphLines;
309 51395 : if( bHasToFit )
310 32 : nOrphLines = 0;
311 51395 : rLine.Bottom();
312 : // OD 2004-02-25 #i16128# - method renamed
313 51395 : if( !IsBreakNowWidAndOrp( rLine ) )
314 50128 : bRet = false;
315 51395 : if( !FindWidows( pFrame, rLine ) )
316 : {
317 51225 : bool bBack = false;
318 : // OD 2004-02-25 #i16128# - method renamed
319 103430 : while( IsBreakNowWidAndOrp( rLine ) )
320 : {
321 1266 : if( rLine.PrevLine() )
322 980 : bBack = true;
323 : else
324 286 : break;
325 : }
326 : // Usually Orphans are not taken into account for HasToFit.
327 : // But if Dummy-Lines are concerned and the Orphans rule is violated
328 : // we make an exception: We leave behind one Dummyline and take
329 : // the whole text to the next page/column.
330 115734 : if( rLine.GetLineNr() <= nOldOrphans &&
331 54801 : rLine.GetInfo().GetParaPortion()->IsDummy() &&
332 6 : ( ( bHasToFit && bRet ) || IsBreakNow( rLine ) ) )
333 219 : rLine.Top();
334 :
335 51225 : rLine.TruncLines( true );
336 51225 : bRet = bBack;
337 : }
338 51395 : nOrphLines = nOldOrphans;
339 :
340 51395 : return bRet;
341 : }
342 :
343 : /**
344 : * FindWidows positions the SwTextMargin of the Master to the line where to
345 : * break by examining and formatting the Follow.
346 : * Returns true if the Widows-rule matches, that means that the
347 : * paragraph should not be split (keep) !
348 : */
349 51395 : bool WidowsAndOrphans::FindWidows( SwTextFrm *pFrame, SwTextMargin &rLine )
350 : {
351 : OSL_ENSURE( ! pFrame->IsVertical() || ! pFrame->IsSwapped(),
352 : "WidowsAndOrphans::FindWidows with swapped frame" );
353 :
354 51395 : if( !nWidLines || !pFrame->IsFollow() )
355 50709 : return false;
356 :
357 686 : rLine.Bottom();
358 :
359 : // We can still cut something off
360 686 : SwTextFrm *pMaster = pFrame->FindMaster();
361 : OSL_ENSURE(pMaster, "+WidowsAndOrphans::FindWidows: Widows in a master?");
362 686 : if( !pMaster )
363 0 : return false;
364 :
365 : // 5156: If the first line of the Follow does not fit, the master
366 : // probably is full of Dummies. In this case a PREP_WIDOWS would be fatal.
367 686 : if( pMaster->GetOfst() == pFrame->GetOfst() )
368 4 : return false;
369 :
370 : // Remaining height of the master
371 682 : SWRECTFN( pFrame )
372 :
373 682 : const SwTwips nDocPrtTop = (pFrame->*fnRect->fnGetPrtTop)();
374 : SwTwips nOldHeight;
375 682 : SwTwips nTmpY = rLine.Y() + rLine.GetLineHeight();
376 :
377 682 : if ( bVert )
378 : {
379 0 : nTmpY = pFrame->SwitchHorizontalToVertical( nTmpY );
380 0 : nOldHeight = -(pFrame->Prt().*fnRect->fnGetHeight)();
381 : }
382 : else
383 682 : nOldHeight = (pFrame->Prt().*fnRect->fnGetHeight)();
384 :
385 682 : const SwTwips nChg = (*fnRect->fnYDiff)( nTmpY, nDocPrtTop + nOldHeight );
386 :
387 : // below the Widows-treshold...
388 682 : if( rLine.GetLineNr() >= nWidLines )
389 : {
390 : // 8575: Follow to Master I
391 : // If the Follow *grows*, there is the chance for the Master to
392 : // receive lines, that it was forced to hand over to the Follow lately:
393 : // Prepare(Need); check that below nChg!
394 : // (0W, 2O, 2M, 2F) + 1F = 3M, 2F
395 145 : if( rLine.GetLineNr() > nWidLines && pFrame->IsJustWidow() )
396 : {
397 : // If the Master is locked, it has probably just donated a line
398 : // to us, we don't return that just because we turned it into
399 : // multiple lines (e.g. via frames).
400 0 : if( !pMaster->IsLocked() && pMaster->GetUpper() )
401 : {
402 0 : const SwTwips nTmpRstHeight = (pMaster->Frm().*fnRect->fnBottomDist)
403 0 : ( (pMaster->GetUpper()->*fnRect->fnGetPrtBottom)() );
404 0 : if ( nTmpRstHeight >=
405 0 : SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) )
406 : {
407 0 : pMaster->Prepare( PREP_ADJUST_FRM );
408 0 : pMaster->_InvalidateSize();
409 0 : pMaster->InvalidatePage();
410 : }
411 : }
412 :
413 0 : pFrame->SetJustWidow( false );
414 : }
415 145 : return false;
416 : }
417 :
418 : // 8575: Follow to Master II
419 : // If the Follow *shrinks*, maybe the Master can absorb the whole Orphan.
420 : // (0W, 2O, 2M, 1F) - 1F = 3M, 0F -> PREP_ADJUST_FRM
421 : // (0W, 2O, 3M, 2F) - 1F = 2M, 2F -> PREP_WIDOWS
422 :
423 537 : if( 0 > nChg && !pMaster->IsLocked() && pMaster->GetUpper() )
424 : {
425 0 : SwTwips nTmpRstHeight = (pMaster->Frm().*fnRect->fnBottomDist)
426 0 : ( (pMaster->GetUpper()->*fnRect->fnGetPrtBottom)() );
427 0 : if( nTmpRstHeight >= SwTwips(rLine.GetInfo().GetParaPortion()->Height() ) )
428 : {
429 0 : pMaster->Prepare( PREP_ADJUST_FRM );
430 0 : pMaster->_InvalidateSize();
431 0 : pMaster->InvalidatePage();
432 0 : pFrame->SetJustWidow( false );
433 0 : return false;
434 : }
435 : }
436 :
437 : // Master to Follow
438 : // If the Follow contains fewer rows than Widows after formatting,
439 : // we still can cut off some rows from the Master. If the Orphans
440 : // rule of the Master hereby comes into effect, we need to enlarge
441 : // the Frame in CalcPrep() of the Master Frame, as it won't fit into
442 : // the original page anymore.
443 : // If the Master Frame can still miss a few more rows, we need to
444 : // do a Shrink() in the CalcPrep(): the Follow with the Widows then
445 : // moves onto the page of the Master, but remains unsplit, so that
446 : // it (finally) moves onto the next page. So much for the theory!
447 : //
448 : // We only request one row at a time for now, because a Master's row could
449 : // result in multiple lines for us.
450 : // Therefore, the CalcFollow() remains in control until the Follow got all
451 : // necessary rows.
452 537 : sal_uInt16 nNeed = 1; // was: nWidLines - rLine.GetLineNr();
453 :
454 : // Special case: Master cannot give lines to follow
455 : // #i91421#
456 537 : if ( !pMaster->GetIndPrev() )
457 : {
458 402 : sal_uLong nLines = pMaster->GetThisLines();
459 402 : if(nLines == 0 && pMaster->HasPara())
460 : {
461 15 : const SwParaPortion *pMasterPara = pMaster->GetPara();
462 15 : if(pMasterPara && pMasterPara->GetNext())
463 3 : nLines = 2;
464 : }
465 402 : if( nLines <= nNeed )
466 367 : return false;
467 : }
468 :
469 170 : pMaster->Prepare( PREP_WIDOWS, static_cast<void*>(&nNeed) );
470 170 : return true;
471 : }
472 :
473 94 : bool WidowsAndOrphans::WouldFit( SwTextMargin &rLine, SwTwips &rMaxHeight, bool bTst )
474 : {
475 : // Here it does not matter, if pFrm is swapped or not.
476 : // IsInside() takes care of itself
477 :
478 : // We expect that rLine is set to the last line
479 : OSL_ENSURE( !rLine.GetNext(), "WouldFit: aLine::Bottom missed!" );
480 94 : sal_uInt16 nLineCnt = rLine.GetLineNr();
481 :
482 : // First satisfy the Orphans-rule and the wish for initials ...
483 94 : const sal_uInt16 nMinLines = std::max( GetOrphansLines(), rLine.GetDropLines() );
484 94 : if ( nLineCnt < nMinLines )
485 59 : return false;
486 :
487 35 : rLine.Top();
488 35 : SwTwips nLineSum = rLine.GetLineHeight();
489 :
490 86 : while( nMinLines > rLine.GetLineNr() )
491 : {
492 16 : if( !rLine.NextLine() )
493 0 : return false;
494 16 : nLineSum += rLine.GetLineHeight();
495 : }
496 :
497 : // We do not fit
498 35 : if( !IsInside( rLine ) )
499 23 : return false;
500 :
501 : // Check the Widows-rule
502 12 : if( !nWidLines && !pFrm->IsFollow() )
503 : {
504 : // Usually we only have to check for Widows if we are a Follow.
505 : // On WouldFit the rule has to be checked for the Master too,
506 : // because we are just in the middle of calculating the break.
507 : // In Ctor of WidowsAndOrphans the nWidLines are only calced for
508 : // Follows from the AttrSet - so we catch up now:
509 12 : const SwAttrSet& rSet = pFrm->GetTextNode()->GetSwAttrSet();
510 12 : nWidLines = rSet.GetWidows().GetValue();
511 : }
512 :
513 : // After Orphans/Initials, do enough lines remain for Widows?
514 : // #111937#: If we are currently doing a test formatting, we may not
515 : // consider the widows rule for two reasons:
516 : // 1. The columns may have different widths.
517 : // Widow lines would have wrong width.
518 : // 2. Test formatting is only done up to the given space.
519 : // we do not have any lines for widows at all.
520 12 : if( bTst || nLineCnt - nMinLines >= GetWidowsLines() )
521 : {
522 6 : if( rMaxHeight >= nLineSum )
523 : {
524 6 : rMaxHeight -= nLineSum;
525 6 : return true;
526 : }
527 : }
528 6 : return false;
529 177 : }
530 :
531 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|