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 <memory>
23 #include <com/sun/star/i18n/ScriptType.hpp>
24 #include <editeng/lspcitem.hxx>
25 #include <dcontact.hxx>
26 #include <txtflcnt.hxx>
27 #include <txtftn.hxx>
28 #include <flyfrms.hxx>
29 #include <fmtflcnt.hxx>
30 #include <fmtftn.hxx>
31 #include <ftninfo.hxx>
32 #include <charfmt.hxx>
33 #include <editeng/charrotateitem.hxx>
34 #include <layfrm.hxx>
35 #include <viewsh.hxx>
36 #include <viewopt.hxx>
37 #include <paratr.hxx>
38 #include "itrform2.hxx"
39 #include "porrst.hxx"
40 #include "portab.hxx"
41 #include "porfly.hxx"
42 #include "portox.hxx"
43 #include "porref.hxx"
44 #include "porfld.hxx"
45 #include "porftn.hxx"
46 #include "porhyph.hxx"
47 #include "pordrop.hxx"
48 #include "guess.hxx"
49 #include <blink.hxx>
50 #include <ftnfrm.hxx>
51 #include "redlnitr.hxx"
52 #include <pagefrm.hxx>
53 #include <pagedesc.hxx>
54 #include <tgrditem.hxx>
55 #include <doc.hxx>
56 #include "pormulti.hxx"
57 #include <unotools/charclass.hxx>
58 #include <xmloff/odffields.hxx>
59 #include <IDocumentSettingAccess.hxx>
60 #include <IMark.hxx>
61 #include <IDocumentMarkAccess.hxx>
62 #include <svl/zforlist.hxx>
63
64 #include <vector>
65
66 using namespace ::com::sun::star;
67
68 namespace {
69 //! Calculates and sets optimal repaint offset for the current line
70 long lcl_CalcOptRepaint( SwTextFormatter &rThis,
71 SwLineLayout const &rCurr,
72 TextFrameIndex nOldLineEnd,
73 const std::vector<long> &rFlyStarts );
74 //! Determine if we need to build hidden portions
75 bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos);
76
77 // Check whether the two font has the same border
78 bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond);
79 }
80
ClearFly(SwTextFormatInfo & rInf)81 static void ClearFly( SwTextFormatInfo &rInf )
82 {
83 delete rInf.GetFly();
84 rInf.SetFly(nullptr);
85 }
86
CtorInitTextFormatter(SwTextFrame * pNewFrame,SwTextFormatInfo * pNewInf)87 void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf )
88 {
89 CtorInitTextPainter( pNewFrame, pNewInf );
90 m_pInf = pNewInf;
91 m_pDropFormat = GetInfo().GetDropFormat();
92 m_pMulti = nullptr;
93
94 m_bOnceMore = false;
95 m_bFlyInContentBase = false;
96 m_bTruncLines = false;
97 m_nContentEndHyph = 0;
98 m_nContentMidHyph = 0;
99 m_nLeftScanIdx = TextFrameIndex(COMPLETE_STRING);
100 m_nRightScanIdx = TextFrameIndex(0);
101 m_pByEndIter.reset();
102 m_pFirstOfBorderMerge = nullptr;
103
104 if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength()))
105 {
106 OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" );
107 m_nStart = TextFrameIndex(GetInfo().GetText().getLength());
108 }
109
110 }
111
~SwTextFormatter()112 SwTextFormatter::~SwTextFormatter()
113 {
114 // Extremely unlikely, but still possible
115 // e.g.: field splits up, widows start to matter
116 if( GetInfo().GetRest() )
117 {
118 delete GetInfo().GetRest();
119 GetInfo().SetRest(nullptr);
120 }
121 }
122
Insert(SwLineLayout * pLay)123 void SwTextFormatter::Insert( SwLineLayout *pLay )
124 {
125 // Insert BEHIND the current element
126 if ( m_pCurr )
127 {
128 pLay->SetNext( m_pCurr->GetNext() );
129 m_pCurr->SetNext( pLay );
130 }
131 else
132 m_pCurr = pLay;
133 }
134
GetFrameRstHeight() const135 sal_uInt16 SwTextFormatter::GetFrameRstHeight() const
136 {
137 // We want the rest height relative to the page.
138 // If we're in a table, then pFrame->GetUpper() is not the page.
139
140 // GetFrameRstHeight() is being called with Footnote.
141 // Wrong: const SwFrame *pUpper = pFrame->GetUpper();
142 const SwFrame *pPage = m_pFrame->FindPageFrame();
143 const SwTwips nHeight = pPage->getFrameArea().Top()
144 + pPage->getFramePrintArea().Top()
145 + pPage->getFramePrintArea().Height() - Y();
146 if( 0 > nHeight )
147 return m_pCurr->Height();
148 else
149 return sal_uInt16( nHeight );
150 }
151
Underflow(SwTextFormatInfo & rInf)152 SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf )
153 {
154 // Save values and initialize rInf
155 SwLinePortion *pUnderflow = rInf.GetUnderflow();
156 if( !pUnderflow )
157 return nullptr;
158
159 // We format backwards, i.e. attribute changes can happen the next
160 // line again.
161 // Can be seen in 8081.sdw, if you enter text in the first line
162
163 TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos();
164 TextFrameIndex const nUnderScorePos = rInf.GetUnderScorePos();
165
166 // Save flys and set to 0, or else segmentation fault
167 // Not ClearFly(rInf) !
168 SwFlyPortion *pFly = rInf.GetFly();
169 rInf.SetFly( nullptr );
170
171 FeedInf( rInf );
172 rInf.SetLast( m_pCurr );
173 // pUnderflow does not need to be deleted, because it will drown in the following
174 // Truncate()
175 rInf.SetUnderflow(nullptr);
176 rInf.SetSoftHyphPos( nSoftHyphPos );
177 rInf.SetUnderScorePos( nUnderScorePos );
178 rInf.SetPaintOfst( GetLeftMargin() );
179
180 // We look for the portion with the under-flow position
181 SwLinePortion *pPor = m_pCurr->GetFirstPortion();
182 if( pPor != pUnderflow )
183 {
184 // pPrev will be the last portion before pUnderflow,
185 // which still has a real width.
186 // Exception: SoftHyphPortion must not be forgotten, of course!
187 // Although they don't have a width.
188 SwLinePortion *pTmpPrev = pPor;
189 while( pPor && pPor != pUnderflow )
190 {
191 if( !pPor->IsKernPortion() &&
192 ( pPor->Width() || pPor->IsSoftHyphPortion() ) )
193 {
194 while( pTmpPrev != pPor )
195 {
196 pTmpPrev->Move( rInf );
197 rInf.SetLast( pTmpPrev );
198 pTmpPrev = pTmpPrev->GetNextPortion();
199 OSL_ENSURE( pTmpPrev, "Underflow: losing control!" );
200 };
201 }
202 pPor = pPor->GetNextPortion();
203 }
204 pPor = pTmpPrev;
205 if( pPor && // Skip flys and initials when underflow.
206 ( pPor->IsFlyPortion() || pPor->IsDropPortion() ||
207 pPor->IsFlyCntPortion() ) )
208 {
209 pPor->Move( rInf );
210 rInf.SetLast( pPor );
211 rInf.SetStopUnderflow( true );
212 pPor = pUnderflow;
213 }
214 }
215
216 // What? The under-flow portion is not in the portion chain?
217 OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" );
218
219 // Snapshot
220 if ( pPor==rInf.GetLast() )
221 {
222 // We end up here, if the portion triggering the under-flow
223 // spans over the whole line. E.g. if a word spans across
224 // multiple lines and flows into a fly in the second line.
225 rInf.SetFly( pFly );
226 pPor->Truncate();
227 return pPor; // Is that enough?
228 }
229 // End the snapshot
230
231 // X + Width == 0 with SoftHyph > Line?!
232 if( !pPor || !(rInf.X() + pPor->Width()) )
233 {
234 delete pFly;
235 return nullptr;
236 }
237
238 // Preparing for Format()
239 // We need to chip off the chain behind pLast, because we Insert after the Format()
240 SeekAndChg( rInf );
241
242 // line width is adjusted, so that pPor does not fit to current
243 // line anymore
244 rInf.Width( static_cast<sal_uInt16>(rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0)) );
245 rInf.SetLen( pPor->GetLen() );
246 rInf.SetFull( false );
247 if( pFly )
248 {
249 // We need to recalculate the FlyPortion due to the following reason:
250 // If the base line is lowered by a big font in the middle of the line,
251 // causing overlapping with a fly, the FlyPortion has a wrong size/fixed
252 // size.
253 rInf.SetFly( pFly );
254 CalcFlyWidth( rInf );
255 }
256 rInf.GetLast()->SetNextPortion(nullptr);
257
258 // The SwLineLayout is an exception to this, which splits at the first
259 // portion change.
260 // Here only the other way around:
261 if( rInf.GetLast() == m_pCurr )
262 {
263 if( pPor->InTextGrp() && !pPor->InExpGrp() )
264 {
265 const PortionType nOldWhich = m_pCurr->GetWhichPor();
266 *static_cast<SwLinePortion*>(m_pCurr) = *pPor;
267 m_pCurr->SetNextPortion( pPor->GetNextPortion() );
268 m_pCurr->SetWhichPor( nOldWhich );
269 pPor->SetNextPortion( nullptr );
270 delete pPor;
271 pPor = m_pCurr;
272 }
273 }
274
275 // Make sure that m_pFirstOfBorderMerge does not point to a portion which
276 // will be deleted by Truncate() below.
277 SwLinePortion* pNext = pPor->GetNextPortion();
278 while (pNext)
279 {
280 if (pNext == m_pFirstOfBorderMerge)
281 {
282 m_pFirstOfBorderMerge = nullptr;
283 break;
284 }
285 pNext = pNext->GetNextPortion();
286 }
287 pPor->Truncate();
288 SwLinePortion *const pRest( rInf.GetRest() );
289 if (pRest && pRest->InFieldGrp() &&
290 static_cast<SwFieldPortion*>(pRest)->IsNoLength())
291 {
292 // HACK: decrement again, so we pick up the suffix in next line!
293 m_pByEndIter->PrevAttr();
294 }
295 delete pRest;
296 rInf.SetRest(nullptr);
297 return pPor;
298 }
299
InsertPortion(SwTextFormatInfo & rInf,SwLinePortion * pPor)300 void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf,
301 SwLinePortion *pPor )
302 {
303 SwLinePortion *pLast = nullptr;
304 // The new portion is inserted, but everything's different for
305 // LineLayout...
306 if( pPor == m_pCurr )
307 {
308 if ( m_pCurr->GetNextPortion() )
309 {
310 pLast = pPor;
311 pPor = m_pCurr->GetNextPortion();
312 }
313
314 // i#112181 - Prevent footnote anchor being wrapped to next line
315 // without preceding word
316 rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
317 }
318 else
319 {
320 pLast = rInf.GetLast();
321 if( pLast->GetNextPortion() )
322 {
323 while( pLast->GetNextPortion() )
324 pLast = pLast->GetNextPortion();
325 rInf.SetLast( pLast );
326 }
327 pLast->Insert( pPor );
328
329 rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
330
331 // Adjust maxima
332 if( m_pCurr->Height() < pPor->Height() )
333 m_pCurr->Height( pPor->Height() );
334 if( m_pCurr->GetAscent() < pPor->GetAscent() )
335 m_pCurr->SetAscent( pPor->GetAscent() );
336
337 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY))
338 {
339 // For DOCX with compat=14 the only shape in line defines height of the line inspite of used font
340 if (pLast->IsFlyCntPortion() && pPor->IsTextPortion() && pPor->GetLen() == TextFrameIndex(0))
341 {
342 m_pCurr->SetAscent(pLast->GetAscent());
343 m_pCurr->Height(pLast->Height());
344 }
345 }
346 }
347
348 // Sometimes chains are constructed (e.g. by hyphenate)
349 rInf.SetLast( pPor );
350 while( pPor )
351 {
352 if (!pPor->IsDropPortion())
353 MergeCharacterBorder(*pPor, pLast, rInf);
354
355 pPor->Move( rInf );
356 rInf.SetLast( pPor );
357 pLast = pPor;
358 pPor = pPor->GetNextPortion();
359 }
360 }
361
BuildPortions(SwTextFormatInfo & rInf)362 void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf )
363 {
364 OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING,
365 "SwTextFormatter::BuildPortions: bad text length in info" );
366
367 rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
368
369 // First NewTextPortion() decides whether pCurr ends up in pPor.
370 // We need to make sure that the font is being set in any case.
371 // This is done automatically in CalcAscent.
372 rInf.SetLast( m_pCurr );
373 rInf.ForcedLeftMargin( 0 );
374
375 OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" );
376
377 if( !m_pCurr->GetAscent() && !m_pCurr->Height() )
378 CalcAscent( rInf, m_pCurr );
379
380 SeekAndChg( rInf );
381
382 // Width() is shortened in CalcFlyWidth if we have a FlyPortion
383 OSL_ENSURE( !rInf.X() || m_pMulti, "SwTextFormatter::BuildPortion X=0?" );
384 CalcFlyWidth( rInf );
385 SwFlyPortion *pFly = rInf.GetFly();
386 if( pFly )
387 {
388 if ( 0 < pFly->GetFix() )
389 ClearFly( rInf );
390 else
391 rInf.SetFull(true);
392 }
393
394 SwLinePortion *pPor = NewPortion( rInf );
395
396 // Asian grid stuff
397 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
398 const bool bHasGrid = pGrid && rInf.SnapToGrid() &&
399 GRID_LINES_CHARS == pGrid->GetGridType();
400
401
402 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
403 const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0;
404
405 // used for grid mode only:
406 // the pointer is stored, because after formatting of non-asian text,
407 // the width of the kerning portion has to be adjusted
408 // Inserting a SwKernPortion before a SwTabPortion isn't necessary
409 // and will break the SwTabPortion.
410 SwKernPortion* pGridKernPortion = nullptr;
411
412 bool bFull = false;
413 SwTwips nUnderLineStart = 0;
414 rInf.Y( Y() );
415
416 while( pPor && !rInf.IsStop() )
417 {
418 OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) &&
419 rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()),
420 "SwTextFormatter::BuildPortions: bad length in info" );
421
422 // We have to check the script for fields in order to set the
423 // correct nActual value for the font.
424 if( pPor->InFieldGrp() )
425 static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf );
426
427 if( ! bHasGrid && rInf.HasScriptSpace() &&
428 rInf.GetLast() && rInf.GetLast()->InTextGrp() &&
429 rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() )
430 {
431 SwFontScript nNxtActual = rInf.GetFont()->GetActual();
432 SwFontScript nLstActual = nNxtActual;
433 sal_uInt16 nLstHeight = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight());
434 bool bAllowBehind = false;
435 const CharClass& rCC = GetAppCharClass();
436
437 // are there any punctuation characters on both sides
438 // of the kerning portion?
439 if ( pPor->InFieldGrp() )
440 {
441 OUString aAltText;
442 if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) &&
443 !aAltText.isEmpty() )
444 {
445 bAllowBehind = rCC.isLetterNumeric( aAltText, 0 );
446
447 const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
448 if ( pTmpFnt )
449 nNxtActual = pTmpFnt->GetActual();
450 }
451 }
452 else
453 {
454 const OUString& rText = rInf.GetText();
455 sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
456 bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx);
457 }
458
459 const SwLinePortion* pLast = rInf.GetLast();
460 if ( bAllowBehind && pLast )
461 {
462 bool bAllowBefore = false;
463
464 if ( pLast->InFieldGrp() )
465 {
466 OUString aAltText;
467 if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) &&
468 !aAltText.isEmpty() )
469 {
470 bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 );
471
472 const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont();
473 if ( pTmpFnt )
474 {
475 nLstActual = pTmpFnt->GetActual();
476 nLstHeight = static_cast<sal_uInt16>(pTmpFnt->GetHeight());
477 }
478 }
479 }
480 else if ( rInf.GetIdx() )
481 {
482 bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1);
483 // Note: ScriptType returns values in [1,4]
484 if ( bAllowBefore )
485 nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1);
486 }
487
488 nLstHeight /= 5;
489 // does the kerning portion still fit into the line?
490 if( bAllowBefore && ( nLstActual != nNxtActual ) &&
491 nLstHeight && rInf.X() + nLstHeight <= rInf.Width() &&
492 ! pPor->InTabGrp() )
493 {
494 SwKernPortion* pKrn =
495 new SwKernPortion( *rInf.GetLast(), nLstHeight,
496 pLast->InFieldGrp() && pPor->InFieldGrp() );
497 rInf.GetLast()->SetNextPortion( nullptr );
498 InsertPortion( rInf, pKrn );
499 }
500 }
501 }
502 else if ( bHasGrid && pGrid->IsSnapToChars() && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
503 {
504 // insert a grid kerning portion
505 pGridKernPortion = pPor->IsKernPortion() ?
506 static_cast<SwKernPortion*>(pPor) :
507 new SwKernPortion( *m_pCurr );
508
509 // if we have a new GridKernPortion, we initially calculate
510 // its size so that its ends on the grid
511 const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
512 const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
513 SwRectFnSet aRectFnSet(pPageFrame);
514
515 const long nGridOrigin = pBody ?
516 aRectFnSet.GetPrtLeft(*pBody) :
517 aRectFnSet.GetPrtLeft(*pPageFrame);
518
519 SwTwips nStartX = rInf.X() + GetLeftMargin();
520 if ( aRectFnSet.IsVert() )
521 {
522 Point aPoint( nStartX, 0 );
523 m_pFrame->SwitchHorizontalToVertical( aPoint );
524 nStartX = aPoint.Y();
525 }
526
527 const SwTwips nOfst = nStartX - nGridOrigin;
528 if ( nOfst )
529 {
530 const sal_uLong i = ( nOfst > 0 ) ?
531 ( ( nOfst - 1 ) / nGridWidth + 1 ) :
532 0;
533 const SwTwips nKernWidth = i * nGridWidth - nOfst;
534 const SwTwips nRestWidth = rInf.Width() - rInf.X();
535
536 if ( nKernWidth <= nRestWidth )
537 pGridKernPortion->Width( static_cast<sal_uInt16>(nKernWidth) );
538 }
539
540 if ( pGridKernPortion != pPor )
541 InsertPortion( rInf, pGridKernPortion );
542 }
543
544 if( pPor->IsDropPortion() )
545 MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor));
546
547 // the multi-portion has its own format function
548 if( pPor->IsMultiPortion() && ( !m_pMulti || m_pMulti->IsBidi() ) )
549 bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) );
550 else
551 bFull = pPor->Format( rInf );
552
553 if( rInf.IsRuby() && !rInf.GetRest() )
554 bFull = true;
555
556 // if we are underlined, we store the beginning of this underlined
557 // segment for repaint optimization
558 if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart )
559 nUnderLineStart = GetLeftMargin() + rInf.X();
560
561 if ( pPor->IsFlyPortion() )
562 m_pCurr->SetFly( true );
563 // some special cases, where we have to take care for the repaint
564 // offset:
565 // 1. Underlined portions due to special underline feature
566 // 2. Right Tab
567 // 3. BidiPortions
568 // 4. other Multiportions
569 // 5. DropCaps
570 // 6. Grid Mode
571 else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) &&
572 // 1. Underlined portions
573 nUnderLineStart &&
574 // reformat is at end of an underlined portion and next portion
575 // is not underlined
576 ( ( rInf.GetReformatStart() == rInf.GetIdx() &&
577 LINESTYLE_NONE == m_pFont->GetUnderline()
578 ) ||
579 // reformat is inside portion and portion is underlined
580 ( rInf.GetReformatStart() >= rInf.GetIdx() &&
581 rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() &&
582 LINESTYLE_NONE != m_pFont->GetUnderline() ) ) )
583 rInf.SetPaintOfst( nUnderLineStart );
584 else if ( ! rInf.GetPaintOfst() &&
585 // 2. Right Tab
586 ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) ||
587 // 3. BidiPortions
588 ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ||
589 // 4. Multi Portion and 5. Drop Caps
590 ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) &&
591 rInf.GetReformatStart() >= rInf.GetIdx() &&
592 rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() )
593 // 6. Grid Mode
594 || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() )
595 )
596 )
597 // we store the beginning of the critical portion as our
598 // paint offset
599 rInf.SetPaintOfst( GetLeftMargin() + rInf.X() );
600
601 // under one of these conditions we are allowed to delete the
602 // start of the underline portion
603 if ( IsUnderlineBreak( *pPor, *m_pFont ) )
604 nUnderLineStart = 0;
605
606 if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() &&
607 static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) )
608 SetFlyInCntBase();
609 // bUnderflow needs to be reset or we wrap again at the next softhyphen
610 if ( !bFull )
611 {
612 rInf.ClrUnderflow();
613 if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() &&
614 pPor->GetLen() && !pPor->InFieldGrp() )
615 {
616 // The distance between two different scripts is set
617 // to 20% of the fontheight.
618 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
619 if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1)) &&
620 nTmp != TextFrameIndex(rInf.GetText().getLength()) &&
621 (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN ||
622 m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) )
623 {
624 const sal_uInt16 nDist = static_cast<sal_uInt16>(rInf.GetFont()->GetHeight()/5);
625
626 if( nDist )
627 {
628 // we do not want a kerning portion if any end
629 // would be a punctuation character
630 const CharClass& rCC = GetAppCharClass();
631 if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1)
632 && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp)))
633 {
634 // does the kerning portion still fit into the line?
635 if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() )
636 new SwKernPortion( *pPor, nDist );
637 else
638 bFull = true;
639 }
640 }
641 }
642 }
643 }
644
645 if ( bHasGrid && pGrid->IsSnapToChars() && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
646 {
647 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
648 const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width();
649
650 const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() );
651 const SwFontScript nNextScript =
652 nTmp >= TextFrameIndex(rInf.GetText().getLength())
653 ? SwFontScript::CJK
654 : m_pScriptInfo->WhichFont(nTmp);
655
656 // snap non-asian text to grid if next portion is ASIAN or
657 // there are no more portions in this line
658 // be careful when handling an underflow event: the gridkernportion
659 // could have been deleted
660 if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript &&
661 ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) )
662 {
663 OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" );
664
665 // calculate size
666 SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion();
667 sal_uInt16 nSumWidth = pPor->Width();
668 while ( pTmpPor )
669 {
670 nSumWidth = nSumWidth + pTmpPor->Width();
671 pTmpPor = pTmpPor->GetNextPortion();
672 }
673
674 const SwTwips i = nSumWidth ?
675 ( nSumWidth - 1 ) / nGridWidth + 1 :
676 0;
677 const SwTwips nTmpWidth = i * nGridWidth;
678 const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth);
679 const sal_uInt16 nKernWidth_1 = static_cast<sal_uInt16>(nKernWidth / 2);
680
681 OSL_ENSURE( nKernWidth <= nRestWidth,
682 "Not enough space left for adjusting non-asian text in grid mode" );
683
684 pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 );
685 rInf.X( rInf.X() + nKernWidth_1 );
686
687 if ( ! bFull )
688 new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1),
689 false, true );
690
691 pGridKernPortion = nullptr;
692 }
693 else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() ||
694 pPor->IsFlyCntPortion() || pPor->InNumberGrp() ||
695 pPor->InFieldGrp() || nCurrScript != nNextScript )
696 // next portion should snap to grid
697 pGridKernPortion = nullptr;
698 }
699
700 rInf.SetFull( bFull );
701
702 // Restportions from fields with multiple lines don't yet have the right ascent
703 if ( !pPor->GetLen() && !pPor->IsFlyPortion()
704 && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp()
705 && !pPor->IsMultiPortion() )
706 CalcAscent( rInf, pPor );
707
708 InsertPortion( rInf, pPor );
709 pPor = NewPortion( rInf );
710 }
711
712 if( !rInf.IsStop() )
713 {
714 // The last right centered, decimal tab
715 SwTabPortion *pLastTab = rInf.GetLastTab();
716 if( pLastTab )
717 pLastTab->FormatEOL( rInf );
718 else if( rInf.GetLast() && rInf.LastKernPortion() )
719 rInf.GetLast()->FormatEOL( rInf );
720 }
721 if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp()
722 && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() )
723 rInf.SetNumDone( false );
724
725 // Delete fly in any case
726 ClearFly( rInf );
727
728 // Reinit the tab overflow flag after the line
729 rInf.SetTabOverflow( false );
730 }
731
CalcAdjustLine(SwLineLayout * pCurrent)732 void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent )
733 {
734 if( SvxAdjust::Left != GetAdjust() && !m_pMulti)
735 {
736 pCurrent->SetFormatAdj(true);
737 if( IsFlyInCntBase() )
738 {
739 CalcAdjLine( pCurrent );
740 // For e.g. centered fly we need to switch the RefPoint
741 // That's why bAlways = true
742 UpdatePos( pCurrent, GetTopLeft(), GetStart(), true );
743 }
744 }
745 }
746
CalcAscent(SwTextFormatInfo & rInf,SwLinePortion * pPor)747 void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor )
748 {
749 bool bCalc = false;
750 if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() )
751 {
752 // Numbering + InterNetFields can keep an own font, then their size is
753 // independent from hard attribute values
754 SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get();
755 SwFontSave aSave( rInf, pFieldFnt );
756 pPor->Height( rInf.GetTextHeight() );
757 pPor->SetAscent( rInf.GetAscent() );
758 bCalc = true;
759 }
760 // i#89179
761 // tab portion representing the list tab of a list label gets the
762 // same height and ascent as the corresponding number portion
763 else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) &&
764 rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
765 static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() )
766 {
767 const SwLinePortion* pLast = rInf.GetLast();
768 pPor->Height( pLast->Height() );
769 pPor->SetAscent( pLast->GetAscent() );
770 }
771 else
772 {
773 const SwLinePortion *pLast = rInf.GetLast();
774 bool bChg = false;
775
776 // In empty lines the attributes are switched on via SeekStart
777 const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
778 if ( pPor->IsQuoVadisPortion() )
779 bChg = SeekStartAndChg( rInf, true );
780 else
781 {
782 if( bFirstPor )
783 {
784 if( !rInf.GetText().isEmpty() )
785 {
786 if ( pPor->GetLen() || !rInf.GetIdx()
787 || ( m_pCurr != pLast && !pLast->IsFlyPortion() )
788 || !m_pCurr->IsRest() ) // instead of !rInf.GetRest()
789 bChg = SeekAndChg( rInf );
790 else
791 bChg = SeekAndChgBefore( rInf );
792 }
793 else if ( m_pMulti )
794 // do not open attributes starting at 0 in empty multi
795 // portions (rotated numbering followed by a footnote
796 // can cause trouble, because the footnote attribute
797 // starts at 0, but if we open it, the attribute handler
798 // cannot handle it.
799 bChg = false;
800 else
801 bChg = SeekStartAndChg( rInf );
802 }
803 else
804 bChg = SeekAndChg( rInf );
805 }
806 if( bChg || bFirstPor || !pPor->GetAscent()
807 || !rInf.GetLast()->InTextGrp() )
808 {
809 pPor->SetAscent( rInf.GetAscent() );
810 pPor->Height( rInf.GetTextHeight() );
811 bCalc = true;
812 }
813 else
814 {
815 pPor->Height( pLast->Height() );
816 pPor->SetAscent( pLast->GetAscent() );
817 }
818 }
819
820 if( pPor->InTextGrp() && bCalc )
821 {
822 pPor->SetAscent(pPor->GetAscent() +
823 rInf.GetFont()->GetTopBorderSpace());
824 pPor->Height(pPor->Height() +
825 rInf.GetFont()->GetTopBorderSpace() +
826 rInf.GetFont()->GetBottomBorderSpace() );
827 }
828 }
829
830 class SwMetaPortion : public SwTextPortion
831 {
832 public:
SwMetaPortion()833 SwMetaPortion() { SetWhichPor( PortionType::Meta ); }
834 virtual void Paint( const SwTextPaintInfo &rInf ) const override;
835 };
836
Paint(const SwTextPaintInfo & rInf) const837 void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const
838 {
839 if ( Width() )
840 {
841 rInf.DrawViewOpt( *this, PortionType::Meta );
842 SwTextPortion::Paint( rInf );
843 }
844 }
845
846 namespace sw { namespace mark {
ExpandFieldmark(IFieldmark * pBM)847 OUString ExpandFieldmark(IFieldmark* pBM)
848 {
849 const IFieldmark::parameter_map_t* const pParameters = pBM->GetParameters();
850 sal_Int32 nCurrentIdx = 0;
851 const IFieldmark::parameter_map_t::const_iterator pResult = pParameters->find(OUString(ODF_FORMDROPDOWN_RESULT));
852 if(pResult != pParameters->end())
853 pResult->second >>= nCurrentIdx;
854
855 const IFieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(OUString(ODF_FORMDROPDOWN_LISTENTRY));
856 if (pListEntries != pParameters->end())
857 {
858 uno::Sequence< OUString > vListEntries;
859 pListEntries->second >>= vListEntries;
860 if (nCurrentIdx < vListEntries.getLength())
861 return vListEntries[nCurrentIdx];
862 }
863
864 static const sal_Unicode vEnSpaces[ODF_FORMFIELD_DEFAULT_LENGTH] = {8194, 8194, 8194, 8194, 8194};
865 return OUString(vEnSpaces, ODF_FORMFIELD_DEFAULT_LENGTH);
866 }
867 } }
868
WhichTextPor(SwTextFormatInfo & rInf) const869 SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
870 {
871 SwTextPortion *pPor = nullptr;
872 if( GetFnt()->IsTox() )
873 {
874 pPor = new SwToxPortion;
875 }
876 else if ( GetFnt()->IsInputField() )
877 {
878 pPor = new SwTextInputFieldPortion();
879 }
880 else
881 {
882 if( GetFnt()->IsRef() )
883 pPor = new SwRefPortion;
884 else if (GetFnt()->IsMeta())
885 {
886 pPor = new SwMetaPortion;
887 }
888 else
889 {
890 // Only at the End!
891 // If pCurr does not have a width, it can however already have content.
892 // E.g. for non-displayable characters
893
894 auto const ch(rInf.GetText()[sal_Int32(rInf.GetIdx())]);
895 SwTextFrame const*const pFrame(rInf.GetTextFrame());
896 SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
897 sw::mark::IFieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getFieldmarkFor(aPosition);
898 if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE)
899 {
900 if (ch == CH_TXT_ATR_FIELDSTART)
901 pPor = new SwFieldFormDatePortion(pBM, true);
902 else if (ch == CH_TXT_ATR_FIELDSEP)
903 pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark?
904 else if (ch == CH_TXT_ATR_FIELDEND)
905 pPor = new SwFieldFormDatePortion(pBM, false);
906 }
907 else if (ch == CH_TXT_ATR_FIELDSTART)
908 pPor = new SwFieldMarkPortion();
909 else if (ch == CH_TXT_ATR_FIELDSEP)
910 pPor = new SwFieldMarkPortion();
911 else if (ch == CH_TXT_ATR_FIELDEND)
912 pPor = new SwFieldMarkPortion();
913 else if (ch == CH_TXT_ATR_FORMELEMENT)
914 {
915 OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???");
916 if (pBM != nullptr)
917 {
918 if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
919 {
920 pPor = new SwFieldFormCheckboxPortion();
921 }
922 else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN)
923 {
924 pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM));
925 }
926 /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT.
927 * Otherwise file will crash on open.
928 */
929 else if (pBM->GetFieldname( ) == ODF_FORMTEXT)
930 {
931 pPor = new SwFieldMarkPortion();
932 }
933 }
934 }
935 if( !pPor )
936 {
937 if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() )
938 pPor = m_pCurr;
939 else
940 pPor = new SwTextPortion;
941 }
942 }
943 }
944 return pPor;
945 }
946
947 // We calculate the length, the following portion limits are defined:
948 // 1) Tabs
949 // 2) Linebreaks
950 // 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD
951 // 4) next attribute change
952
NewTextPortion(SwTextFormatInfo & rInf)953 SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
954 {
955 // If we're at the line's beginning, we take pCurr
956 // If pCurr is not derived from SwTextPortion, we need to duplicate
957 Seek( rInf.GetIdx() );
958 SwTextPortion *pPor = WhichTextPor( rInf );
959
960 // until next attribute change:
961 const TextFrameIndex nNextAttr = GetNextAttr();
962 TextFrameIndex nNextChg = std::min(nNextAttr, TextFrameIndex(rInf.GetText().getLength()));
963
964 // end of script type:
965 const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx());
966 nNextChg = std::min( nNextChg, nNextScript );
967
968 // end of direction:
969 const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx());
970 nNextChg = std::min( nNextChg, nNextDir );
971
972 // hidden change (potentially via bookmark):
973 const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx());
974 nNextChg = std::min( nNextChg, nNextHidden );
975
976 // Turbo boost:
977 // We assume that font characters are not larger than twice
978 // as wide as height.
979 // Very crazy: we need to take the ascent into account.
980
981 // Mind the trap! GetSize() contains the wished-for height, the real height
982 // is only known in CalcAscent!
983
984 // The ratio is even crazier: a blank in Times New Roman has an ascent of
985 // 182, a height of 200 and a width of 53!
986 // It follows that a line with a lot of blanks is processed incorrectly.
987 // Therefore we increase from factor 2 to 8 (due to negative kerning).
988
989 pPor->SetLen(TextFrameIndex(1));
990 CalcAscent( rInf, pPor );
991
992 const SwFont* pTmpFnt = rInf.GetFont();
993 sal_Int32 nExpect = std::min( sal_Int32( pTmpFnt->GetHeight() ),
994 sal_Int32( pPor->GetAscent() ) ) / 8;
995 if ( !nExpect )
996 nExpect = 1;
997 nExpect = sal_Int32(rInf.GetIdx()) + (rInf.GetLineWidth() / nExpect);
998 if (TextFrameIndex(nExpect) > rInf.GetIdx() && nNextChg > TextFrameIndex(nExpect))
999 nNextChg = TextFrameIndex(std::min(nExpect, rInf.GetText().getLength()));
1000
1001 // we keep an invariant during method calls:
1002 // there are no portion ending characters like hard spaces
1003 // or tabs in [ nLeftScanIdx, nRightScanIdx ]
1004 if ( m_nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= m_nRightScanIdx )
1005 {
1006 if ( nNextChg > m_nRightScanIdx )
1007 nNextChg = m_nRightScanIdx =
1008 rInf.ScanPortionEnd( m_nRightScanIdx, nNextChg );
1009 }
1010 else
1011 {
1012 m_nLeftScanIdx = rInf.GetIdx();
1013 nNextChg = m_nRightScanIdx =
1014 rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg );
1015 }
1016
1017 pPor->SetLen( nNextChg - rInf.GetIdx() );
1018 rInf.SetLen( pPor->GetLen() );
1019 return pPor;
1020 }
1021
WhichFirstPortion(SwTextFormatInfo & rInf)1022 SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf)
1023 {
1024 SwLinePortion *pPor = nullptr;
1025
1026 if( rInf.GetRest() )
1027 {
1028 // Tabs and fields
1029 if( '\0' != rInf.GetHookChar() )
1030 return nullptr;
1031
1032 pPor = rInf.GetRest();
1033 if( pPor->IsErgoSumPortion() )
1034 rInf.SetErgoDone(true);
1035 else
1036 if( pPor->IsFootnoteNumPortion() )
1037 rInf.SetFootnoteDone(true);
1038 else
1039 if( pPor->InNumberGrp() )
1040 rInf.SetNumDone(true);
1041
1042 rInf.SetRest(nullptr);
1043 m_pCurr->SetRest( true );
1044 return pPor;
1045 }
1046
1047 // We can stand in the follow, it's crucial that
1048 // pFrame->GetOfst() == 0!
1049 if( rInf.GetIdx() )
1050 {
1051 // We now too can elongate FootnotePortions and ErgoSumPortions
1052
1053 // 1. The ErgoSumTexts
1054 if( !rInf.IsErgoDone() )
1055 {
1056 if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
1057 pPor = NewErgoSumPortion( rInf );
1058 rInf.SetErgoDone( true );
1059 }
1060
1061 // 2. Arrow portions
1062 if( !pPor && !rInf.IsArrowDone() )
1063 {
1064 if( m_pFrame->GetOfst() && !m_pFrame->IsFollow() &&
1065 rInf.GetIdx() == m_pFrame->GetOfst() )
1066 pPor = new SwArrowPortion( *m_pCurr );
1067 rInf.SetArrowDone( true );
1068 }
1069
1070 // 3. Kerning portions at beginning of line in grid mode
1071 if ( ! pPor && ! m_pCurr->GetNextPortion() )
1072 {
1073 SwTextGridItem const*const pGrid(
1074 GetGridItem(GetTextFrame()->FindPageFrame()));
1075 if ( pGrid )
1076 pPor = new SwKernPortion( *m_pCurr );
1077 }
1078
1079 // 4. The line rests (multiline fields)
1080 if( !pPor )
1081 {
1082 pPor = rInf.GetRest();
1083 // Only for pPor of course
1084 if( pPor )
1085 {
1086 m_pCurr->SetRest( true );
1087 rInf.SetRest(nullptr);
1088 }
1089 }
1090 }
1091 else
1092 {
1093 // 5. The foot note count
1094 if( !rInf.IsFootnoteDone() )
1095 {
1096 OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
1097 "Rotated number portion trouble" );
1098
1099 const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame();
1100 rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum );
1101 if( bFootnoteNum )
1102 pPor = NewFootnoteNumPortion( rInf );
1103 rInf.SetFootnoteDone( true );
1104 }
1105
1106 // 6. The ErgoSumTexts of course also exist in the TextMaster,
1107 // it's crucial whether the SwFootnoteFrame is aFollow
1108 if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() )
1109 {
1110 if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
1111 pPor = NewErgoSumPortion( rInf );
1112 rInf.SetErgoDone( true );
1113 }
1114
1115 // 7. The numbering
1116 if( !rInf.IsNumDone() && !pPor )
1117 {
1118 OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
1119 "Rotated number portion trouble" );
1120
1121 // If we're in the follow, then of course not
1122 if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule())
1123 pPor = NewNumberPortion( rInf );
1124 rInf.SetNumDone( true );
1125 }
1126 // 8. The DropCaps
1127 if( !pPor && GetDropFormat() && ! rInf.IsMulti() )
1128 pPor = NewDropPortion( rInf );
1129
1130 // 9. Kerning portions at beginning of line in grid mode
1131 if ( !pPor && !m_pCurr->GetNextPortion() )
1132 {
1133 SwTextGridItem const*const pGrid(
1134 GetGridItem(GetTextFrame()->FindPageFrame()));
1135 if ( pGrid )
1136 pPor = new SwKernPortion( *m_pCurr );
1137 }
1138 }
1139
1140 // 10. Decimal tab portion at the beginning of each line in table cells
1141 if ( !pPor && !m_pCurr->GetNextPortion() &&
1142 GetTextFrame()->IsInTab() &&
1143 GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT))
1144 {
1145 pPor = NewTabPortion( rInf, true );
1146 }
1147
1148 // 11. suffix of meta-field
1149 if (!pPor)
1150 {
1151 pPor = TryNewNoLengthPortion(rInf);
1152 }
1153
1154 return pPor;
1155 }
1156
lcl_OldFieldRest(const SwLineLayout * pCurr)1157 static bool lcl_OldFieldRest( const SwLineLayout* pCurr )
1158 {
1159 if( !pCurr->GetNext() )
1160 return false;
1161 const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion();
1162 bool bRet = false;
1163 while( pPor && !bRet )
1164 {
1165 bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) ||
1166 (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField());
1167 if( !pPor->GetLen() )
1168 break;
1169 pPor = pPor->GetNextPortion();
1170 }
1171 return bRet;
1172 }
1173
1174 /* NewPortion sets rInf.nLen
1175 * A SwTextPortion is limited by a tab, break, txtatr or attr change
1176 * We can have three cases:
1177 * 1) The line is full and the wrap was not emulated
1178 * -> return 0;
1179 * 2) The line is full and a wrap was emulated
1180 * -> Reset width and return new FlyPortion
1181 * 3) We need to construct a new portion
1182 * -> CalcFlyWidth emulates the width and return portion, if needed
1183 */
1184
NewPortion(SwTextFormatInfo & rInf)1185 SwLinePortion *SwTextFormatter::NewPortion( SwTextFormatInfo &rInf )
1186 {
1187 // Underflow takes precedence
1188 rInf.SetStopUnderflow( false );
1189 if( rInf.GetUnderflow() )
1190 {
1191 OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" );
1192 return Underflow( rInf );
1193 }
1194
1195 // If the line is full, flys and Underflow portions could be waiting ...
1196 if( rInf.IsFull() )
1197 {
1198 // LineBreaks and Flys (bug05.sdw)
1199 // IsDummy()
1200 if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) )
1201 return nullptr;
1202
1203 // When the text bumps into the Fly, or when the Fly comes first because
1204 // it juts out over the left edge, GetFly() is returned.
1205 // When IsFull() and no GetFly() is available, naturally zero is returned.
1206 if( rInf.GetFly() )
1207 {
1208 if( rInf.GetLast()->IsBreakPortion() )
1209 {
1210 delete rInf.GetFly();
1211 rInf.SetFly( nullptr );
1212 }
1213
1214 return rInf.GetFly();
1215 }
1216
1217 // A nasty special case: A frame without wrap overlaps the Footnote area.
1218 // We must declare the Footnote portion as rest of line, so that
1219 // SwTextFrame::Format doesn't abort (the text mass already was formatted).
1220 if( rInf.GetRest() )
1221 rInf.SetNewLine( true );
1222 else
1223 {
1224 // When the next line begins with a rest of a field, but now no
1225 // rest remains, the line must definitely be formatted anew!
1226 if( lcl_OldFieldRest( GetCurr() ) )
1227 rInf.SetNewLine( true );
1228 else
1229 {
1230 SwLinePortion *pFirst = WhichFirstPortion( rInf );
1231 if( pFirst )
1232 {
1233 rInf.SetNewLine( true );
1234 if( pFirst->InNumberGrp() )
1235 rInf.SetNumDone( false) ;
1236 delete pFirst;
1237 }
1238 }
1239 }
1240
1241 return nullptr;
1242 }
1243
1244 SwLinePortion *pPor = WhichFirstPortion( rInf );
1245
1246 // Check for Hidden Portion:
1247 if ( !pPor )
1248 {
1249 TextFrameIndex nEnd = rInf.GetIdx();
1250 if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) )
1251 pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() );
1252 }
1253
1254 if( !pPor )
1255 {
1256 if( ( !m_pMulti || m_pMulti->IsBidi() ) &&
1257 // i#42734
1258 // No multi portion if there is a hook character waiting:
1259 ( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) )
1260 {
1261 // We open a multiportion part, if we enter a multi-line part
1262 // of the paragraph.
1263 TextFrameIndex nEnd = rInf.GetIdx();
1264 std::unique_ptr<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti );
1265 if( pCreate )
1266 {
1267 SwMultiPortion* pTmp = nullptr;
1268
1269 if ( SwMultiCreatorId::Bidi == pCreate->nId )
1270 pTmp = new SwBidiPortion( nEnd, pCreate->nLevel );
1271 else if ( SwMultiCreatorId::Ruby == pCreate->nId )
1272 {
1273 pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(),
1274 GetTextFrame()->GetDoc().getIDocumentSettingAccess(),
1275 nEnd, TextFrameIndex(0), rInf );
1276 }
1277 else if( SwMultiCreatorId::Rotate == pCreate->nId )
1278 {
1279 pTmp = new SwRotatedPortion( *pCreate, nEnd,
1280 GetTextFrame()->IsRightToLeft() );
1281 GetTextFrame()->SetHasRotatedPortions(true);
1282 }
1283 else
1284 pTmp = new SwDoubleLinePortion( *pCreate, nEnd );
1285
1286 pCreate.reset();
1287 CalcFlyWidth( rInf );
1288
1289 return pTmp;
1290 }
1291 }
1292 // Tabs and Fields
1293 sal_Unicode cChar = rInf.GetHookChar();
1294
1295 if( cChar )
1296 {
1297 /* We fetch cChar again to be sure that the tab is pending now and
1298 * didn't move to the next line (as happens behind frames).
1299 * However, when a FieldPortion is in the rest, we must naturally fetch
1300 * the cChar from the field content, e.g. DecimalTabs and fields (22615)
1301 */
1302 if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() )
1303 cChar = rInf.GetChar( rInf.GetIdx() );
1304 rInf.ClearHookChar();
1305 }
1306 else
1307 {
1308 if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()))
1309 {
1310 rInf.SetFull(true);
1311 CalcFlyWidth( rInf );
1312 return pPor;
1313 }
1314 cChar = rInf.GetChar( rInf.GetIdx() );
1315 }
1316
1317 switch( cChar )
1318 {
1319 case CH_TAB:
1320 pPor = NewTabPortion( rInf, false ); break;
1321
1322 case CH_BREAK:
1323 pPor = new SwBreakPortion( *rInf.GetLast() ); break;
1324
1325 case CHAR_SOFTHYPHEN: // soft hyphen
1326 pPor = new SwSoftHyphPortion; break;
1327
1328 case CHAR_HARDBLANK: // no-break space
1329 // Please check tdf#115067 if you want to edit the char
1330 pPor = new SwBlankPortion( cChar ); break;
1331
1332 case CHAR_HARDHYPHEN: // non-breaking hyphen
1333 pPor = new SwBlankPortion( '-' ); break;
1334
1335 case CHAR_ZWSP: // zero width space
1336 case CHAR_ZWNBSP : // word joiner
1337 pPor = new SwControlCharPortion( cChar ); break;
1338
1339 case CH_TXTATR_BREAKWORD:
1340 case CH_TXTATR_INWORD:
1341 if( rInf.HasHint( rInf.GetIdx() ) )
1342 {
1343 pPor = NewExtraPortion( rInf );
1344 break;
1345 }
1346 [[fallthrough]];
1347 default :
1348 {
1349 SwTabPortion* pLastTabPortion = rInf.GetLastTab();
1350 if ( pLastTabPortion && cChar == rInf.GetTabDecimal() )
1351 {
1352 // Abandon dec. tab position if line is full
1353 // We have a decimal tab portion in the line and the next character has to be
1354 // aligned at the tab stop position. We store the width from the beginning of
1355 // the tab stop portion up to the portion containing the decimal separator:
1356 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ &&
1357 PortionType::TabDecimal == pLastTabPortion->GetWhichPor() )
1358 {
1359 OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" );
1360 const sal_uInt16 nWidthOfPortionsUpToDecimalPosition = static_cast<sal_uInt16>(rInf.X() - pLastTabPortion->GetFix() );
1361 static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition );
1362 rInf.SetTabDecimal( 0 );
1363 }
1364 else
1365 rInf.SetFull( rInf.GetLastTab()->Format( rInf ) );
1366 }
1367
1368 if( rInf.GetRest() )
1369 {
1370 if( rInf.IsFull() )
1371 {
1372 rInf.SetNewLine(true);
1373 return nullptr;
1374 }
1375 pPor = rInf.GetRest();
1376 rInf.SetRest(nullptr);
1377 }
1378 else
1379 {
1380 if( rInf.IsFull() )
1381 return nullptr;
1382 pPor = NewTextPortion( rInf );
1383 }
1384 break;
1385 }
1386 }
1387
1388 // if a portion is created despite there being a pending RestPortion,
1389 // then it is a field which has been split (e.g. because it contains a Tab)
1390 if( pPor && rInf.GetRest() )
1391 pPor->SetLen(TextFrameIndex(0));
1392
1393 // robust:
1394 if( !pPor || rInf.IsStop() )
1395 {
1396 delete pPor;
1397 return nullptr;
1398 }
1399 }
1400
1401 assert(pPor && "can only reach here with pPor existing");
1402
1403 // Special portions containing numbers (footnote anchor, footnote number,
1404 // numbering) can be contained in a rotated portion, if the user
1405 // choose a rotated character attribute.
1406 if (!m_pMulti)
1407 {
1408 if ( pPor->IsFootnotePortion() )
1409 {
1410 const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote();
1411
1412 if ( pTextFootnote )
1413 {
1414 SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote());
1415 const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc();
1416 const SwEndNoteInfo* pInfo;
1417 if( rFootnote.IsEndNote() )
1418 pInfo = &pDoc->GetEndNoteInfo();
1419 else
1420 pInfo = &pDoc->GetFootnoteInfo();
1421 const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet();
1422
1423 const SfxPoolItem* pItem;
1424 sal_uInt16 nDir = 0;
1425 if( SfxItemState::SET == rSet.GetItemState( RES_CHRATR_ROTATE,
1426 true, &pItem ))
1427 nDir = static_cast<const SvxCharRotateItem*>(pItem)->GetValue();
1428
1429 if ( 0 != nDir )
1430 {
1431 delete pPor;
1432 pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1),
1433 900 == nDir
1434 ? DIR_BOTTOM2TOP
1435 : DIR_TOP2BOTTOM );
1436 }
1437 }
1438 }
1439 else if ( pPor->InNumberGrp() )
1440 {
1441 const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
1442
1443 if ( pNumFnt )
1444 {
1445 sal_uInt16 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() );
1446 if ( 0 != nDir )
1447 {
1448 delete pPor;
1449 pPor = new SwRotatedPortion(TextFrameIndex(0), 900 == nDir
1450 ? DIR_BOTTOM2TOP
1451 : DIR_TOP2BOTTOM );
1452
1453 rInf.SetNumDone( false );
1454 rInf.SetFootnoteDone( false );
1455 }
1456 }
1457 }
1458 }
1459
1460 // The font is set in output device,
1461 // the ascent and the height will be calculated.
1462 if( !pPor->GetAscent() && !pPor->Height() )
1463 CalcAscent( rInf, pPor );
1464 rInf.SetLen( pPor->GetLen() );
1465
1466 // In CalcFlyWidth Width() will be shortened if a FlyPortion is present.
1467 CalcFlyWidth( rInf );
1468
1469 // One must not forget that pCurr as GetLast() must provide reasonable values:
1470 if( !m_pCurr->Height() )
1471 {
1472 OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" );
1473 m_pCurr->Height( pPor->Height() );
1474 m_pCurr->SetAscent( pPor->GetAscent() );
1475 }
1476
1477 OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong");
1478 if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() )
1479 {
1480 delete pPor;
1481 pPor = rInf.GetFly();
1482 }
1483 return pPor;
1484 }
1485
FormatLine(TextFrameIndex const nStartPos)1486 TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos)
1487 {
1488 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
1489 "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" );
1490
1491 // For the formatting routines, we set pOut to the reference device.
1492 SwHookOut aHook( GetInfo() );
1493 if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength()))
1494 GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength()));
1495
1496 bool bBuild = true;
1497 SetFlyInCntBase( false );
1498 GetInfo().SetLineHeight( 0 );
1499 GetInfo().SetLineNetHeight( 0 );
1500
1501 // Recycling must be suppressed by changed line height and also
1502 // by changed ascent (lowering of baseline).
1503 const sal_uInt16 nOldHeight = m_pCurr->Height();
1504 const sal_uInt16 nOldAscent = m_pCurr->GetAscent();
1505
1506 m_pCurr->SetEndHyph( false );
1507 m_pCurr->SetMidHyph( false );
1508
1509 // fly positioning can make it necessary format a line several times
1510 // for this, we have to keep a copy of our rest portion
1511 SwLinePortion* pField = GetInfo().GetRest();
1512 std::unique_ptr<SwFieldPortion> xSaveField;
1513
1514 if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() )
1515 xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) ));
1516
1517 // for an optimal repaint rectangle, we want to compare fly portions
1518 // before and after the BuildPortions call
1519 const bool bOptimizeRepaint = AllowRepaintOpt();
1520 TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen();
1521 std::vector<long> flyStarts;
1522
1523 // these are the conditions for a fly position comparison
1524 if ( bOptimizeRepaint && m_pCurr->IsFly() )
1525 {
1526 SwLinePortion* pPor = m_pCurr->GetFirstPortion();
1527 long nPOfst = 0;
1528 while ( pPor )
1529 {
1530 if ( pPor->IsFlyPortion() )
1531 // insert start value of fly portion
1532 flyStarts.push_back( nPOfst );
1533
1534 nPOfst += pPor->Width();
1535 pPor = pPor->GetNextPortion();
1536 }
1537 }
1538
1539 // Here soon the underflow check follows.
1540 while( bBuild )
1541 {
1542 GetInfo().SetFootnoteInside( false );
1543 GetInfo().SetOtherThanFootnoteInside( false );
1544
1545 // These values must not be reset by FormatReset();
1546 const bool bOldNumDone = GetInfo().IsNumDone();
1547 const bool bOldArrowDone = GetInfo().IsArrowDone();
1548 const bool bOldErgoDone = GetInfo().IsErgoDone();
1549
1550 // besides other things, this sets the repaint offset to 0
1551 FormatReset( GetInfo() );
1552
1553 GetInfo().SetNumDone( bOldNumDone );
1554 GetInfo().SetArrowDone( bOldArrowDone );
1555 GetInfo().SetErgoDone( bOldErgoDone );
1556
1557 // build new portions for this line
1558 BuildPortions( GetInfo() );
1559
1560 if( GetInfo().IsStop() )
1561 {
1562 m_pCurr->SetLen(TextFrameIndex(0));
1563 m_pCurr->Height( GetFrameRstHeight() + 1 );
1564 m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 );
1565 m_pCurr->Width(0);
1566 m_pCurr->Truncate();
1567 return nStartPos;
1568 }
1569 else if( GetInfo().IsDropInit() )
1570 {
1571 DropInit();
1572 GetInfo().SetDropInit( false );
1573 }
1574
1575 m_pCurr->CalcLine( *this, GetInfo() );
1576 CalcRealHeight( GetInfo().IsNewLine() );
1577
1578 //i#120864 For Special case that at the first calculation couldn't get
1579 //correct height. And need to recalculate for the right height.
1580 SwLinePortion* pPorTmp = m_pCurr->GetNextPortion();
1581 if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() &&
1582 m_pCurr->Height() > pPorTmp->Height())))
1583 {
1584 sal_uInt16 nTmpAscent, nTmpHeight;
1585 CalcAscentAndHeight( nTmpAscent, nTmpHeight );
1586 AlignFlyInCntBase( Y() + long( nTmpAscent ) );
1587 m_pCurr->CalcLine( *this, GetInfo() );
1588 CalcRealHeight();
1589 }
1590
1591 // bBuild decides if another lap of honor is done
1592 if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() )
1593 {
1594 m_pCurr->SetRealHeight( GetInfo().GetLineHeight() );
1595 bBuild = false;
1596 }
1597 else
1598 {
1599 bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) )
1600 || GetInfo().CheckFootnotePortion(m_pCurr);
1601 if( bBuild )
1602 {
1603 GetInfo().SetNumDone( bOldNumDone );
1604 GetInfo().ResetMaxWidthDiff();
1605
1606 // delete old rest
1607 if ( GetInfo().GetRest() )
1608 {
1609 delete GetInfo().GetRest();
1610 GetInfo().SetRest( nullptr );
1611 }
1612
1613 // set original rest portion
1614 if ( xSaveField )
1615 GetInfo().SetRest( new SwFieldPortion( *xSaveField ) );
1616
1617 m_pCurr->SetLen(TextFrameIndex(0));
1618 m_pCurr->Width(0);
1619 m_pCurr->Truncate();
1620 }
1621 }
1622 }
1623
1624 // In case of compat mode, it's possible that a tab portion is wider after
1625 // formatting than before. If this is the case, we also have to make sure
1626 // the SwLineLayout is wider as well.
1627 if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN))
1628 {
1629 sal_uInt16 nSum = 0;
1630 SwLinePortion* pPor = m_pCurr->GetFirstPortion();
1631
1632 while (pPor)
1633 {
1634 nSum += pPor->Width();
1635 pPor = pPor->GetNextPortion();
1636 }
1637
1638 if (nSum > m_pCurr->Width())
1639 m_pCurr->Width(nSum);
1640 }
1641
1642 // calculate optimal repaint rectangle
1643 if ( bOptimizeRepaint )
1644 {
1645 GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) );
1646 flyStarts.clear();
1647 }
1648 else
1649 // Special case: we do not allow an optimization of the repaint
1650 // area, but during formatting the repaint offset is set to indicate
1651 // a maximum value for the offset. This value has to be reset:
1652 GetInfo().SetPaintOfst( 0 );
1653
1654 // This corrects the start of the reformat range if something has
1655 // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt
1656 // will give us a wrong result if we have to reformat another line
1657 GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() );
1658
1659 // delete master copy of rest portion
1660 xSaveField.reset();
1661
1662 TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen();
1663
1664 // adjust text if kana compression is enabled
1665 if ( GetInfo().CompressLine() )
1666 {
1667 SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr );
1668
1669 // adjust repaint offset
1670 if ( nRepaintOfst < GetInfo().GetPaintOfst() )
1671 GetInfo().SetPaintOfst( nRepaintOfst );
1672 }
1673
1674 CalcAdjustLine( m_pCurr );
1675
1676 if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() )
1677 {
1678 SetFlyInCntBase();
1679 GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling
1680 // all following line must be painted and when Flys are around,
1681 // also formatted
1682 GetInfo().SetShift( true );
1683 }
1684
1685 if ( IsFlyInCntBase() && !IsQuick() )
1686 UpdatePos( m_pCurr, GetTopLeft(), GetStart() );
1687
1688 return nNewStart;
1689 }
1690
RecalcRealHeight()1691 void SwTextFormatter::RecalcRealHeight()
1692 {
1693 do
1694 {
1695 CalcRealHeight();
1696 } while (Next());
1697 }
1698
CalcRealHeight(bool bNewLine)1699 void SwTextFormatter::CalcRealHeight( bool bNewLine )
1700 {
1701 sal_uInt16 nLineHeight = m_pCurr->Height();
1702 m_pCurr->SetClipping( false );
1703
1704 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
1705 if ( pGrid && GetInfo().SnapToGrid() )
1706 {
1707 const sal_uInt16 nGridWidth = pGrid->GetBaseHeight();
1708 const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight();
1709 const bool bRubyTop = ! pGrid->GetRubyTextBelow();
1710
1711 nLineHeight = nGridWidth + nRubyHeight;
1712 const sal_uInt16 nAmpRatio = (m_pCurr->Height() + nLineHeight - 1)/nLineHeight;
1713 nLineHeight *= nAmpRatio;
1714
1715 const sal_uInt16 nAsc = m_pCurr->GetAscent() +
1716 ( bRubyTop ?
1717 ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 :
1718 ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 );
1719
1720 m_pCurr->Height( nLineHeight );
1721 m_pCurr->SetAscent( nAsc );
1722 m_pInf->GetParaPortion()->SetFixLineHeight();
1723
1724 // we ignore any line spacing options except from ...
1725 const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing();
1726 if ( ! IsParaLine() && pSpace &&
1727 SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() )
1728 {
1729 sal_uLong nTmp = pSpace->GetPropLineSpace();
1730
1731 if( nTmp < 100 )
1732 nTmp = 100;
1733
1734 nTmp *= nLineHeight;
1735 nLineHeight = static_cast<sal_uInt16>(nTmp / 100);
1736 }
1737
1738 m_pCurr->SetRealHeight( nLineHeight );
1739 return;
1740 }
1741
1742 // The dummy flag is set on lines that only contain flyportions, these shouldn't
1743 // consider register-true and so on. Unfortunately an empty line can be at
1744 // the end of a paragraph (empty paragraphs or behind a Shift-Return),
1745 // which should consider the register.
1746 if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext()
1747 && GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength())
1748 && !bNewLine))
1749 {
1750 const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing();
1751 if( pSpace )
1752 {
1753 switch( pSpace->GetLineSpaceRule() )
1754 {
1755 case SvxLineSpaceRule::Auto:
1756 // shrink first line of paragraph too on spacing < 100%
1757 if (IsParaLine() &&
1758 pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop
1759 && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
1760 {
1761 long nTmp = pSpace->GetPropLineSpace();
1762 // Word will render < 50% too but it's just not readable
1763 if( nTmp < 50 )
1764 nTmp = nTmp ? 50 : 100;
1765 if (nTmp<100) { // code adapted from fixed line height
1766 nTmp *= nLineHeight;
1767 nTmp /= 100;
1768 if( !nTmp )
1769 ++nTmp;
1770 nLineHeight = static_cast<sal_uInt16>(nTmp);
1771 sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80%
1772 #if 0
1773 // could do clipping here (like Word does)
1774 // but at 0.5 its unreadable either way...
1775 if( nAsc < pCurr->GetAscent() ||
1776 nLineHeight - nAsc < pCurr->Height() -
1777 pCurr->GetAscent() )
1778 pCurr->SetClipping( true );
1779 #endif
1780 m_pCurr->SetAscent( nAsc );
1781 m_pCurr->Height( nLineHeight );
1782 m_pInf->GetParaPortion()->SetFixLineHeight();
1783 }
1784 }
1785 break;
1786 case SvxLineSpaceRule::Min:
1787 {
1788 if( nLineHeight < pSpace->GetLineHeight() )
1789 nLineHeight = pSpace->GetLineHeight();
1790 break;
1791 }
1792 case SvxLineSpaceRule::Fix:
1793 {
1794 nLineHeight = pSpace->GetLineHeight();
1795 const sal_uInt16 nAsc = ( 4 * nLineHeight ) / 5; // 80%
1796 if( nAsc < m_pCurr->GetAscent() ||
1797 nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() )
1798 m_pCurr->SetClipping( true );
1799 m_pCurr->Height( nLineHeight );
1800 m_pCurr->SetAscent( nAsc );
1801 m_pInf->GetParaPortion()->SetFixLineHeight();
1802 }
1803 break;
1804 default: OSL_FAIL( ": unknown LineSpaceRule" );
1805 }
1806 // Note: for the _first_ line the line spacing of the previous
1807 // paragraph is applied in SwFlowFrame::CalcUpperSpace()
1808 if( !IsParaLine() )
1809 switch( pSpace->GetInterLineSpaceRule() )
1810 {
1811 case SvxInterLineSpaceRule::Off:
1812 break;
1813 case SvxInterLineSpaceRule::Prop:
1814 {
1815 long nTmp = pSpace->GetPropLineSpace();
1816 // 50% is the minimum, if 0% we switch to the
1817 // default value 100% ...
1818 if( nTmp < 50 )
1819 nTmp = nTmp ? 50 : 100;
1820
1821 nTmp *= nLineHeight;
1822 nTmp /= 100;
1823 if( !nTmp )
1824 ++nTmp;
1825 nLineHeight = static_cast<sal_uInt16>(nTmp);
1826 break;
1827 }
1828 case SvxInterLineSpaceRule::Fix:
1829 {
1830 nLineHeight = nLineHeight + pSpace->GetInterLineSpace();
1831 break;
1832 }
1833 default: OSL_FAIL( ": unknown InterLineSpaceRule" );
1834 }
1835 }
1836
1837 if( IsRegisterOn() )
1838 {
1839 SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height();
1840 SwRectFnSet aRectFnSet(m_pFrame);
1841 if ( aRectFnSet.IsVert() )
1842 nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY );
1843 nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() );
1844 const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() );
1845 if( nDiff )
1846 nLineHeight += RegDiff() - nDiff;
1847 }
1848 }
1849 m_pCurr->SetRealHeight( nLineHeight );
1850 }
1851
FeedInf(SwTextFormatInfo & rInf) const1852 void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const
1853 {
1854 // delete Fly in any case!
1855 ClearFly( rInf );
1856 rInf.Init();
1857
1858 rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
1859 rInf.SetRoot( m_pCurr );
1860 rInf.SetLineStart( m_nStart );
1861 rInf.SetIdx( m_nStart );
1862 rInf.Left( Left() );
1863 rInf.Right( Right() );
1864 rInf.First( FirstLeft() );
1865 rInf.LeftMargin(GetLeftMargin());
1866
1867 rInf.RealWidth( sal_uInt16(rInf.Right() - GetLeftMargin()) );
1868 rInf.Width( rInf.RealWidth() );
1869 if( const_cast<SwTextFormatter*>(this)->GetRedln() )
1870 {
1871 const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() );
1872 const_cast<SwTextFormatter*>(this)->GetRedln()->Reset();
1873 }
1874 }
1875
FormatReset(SwTextFormatInfo & rInf)1876 void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf )
1877 {
1878 m_pFirstOfBorderMerge = nullptr;
1879 m_pCurr->Truncate();
1880 m_pCurr->Init();
1881 if( pBlink && m_pCurr->IsBlinking() )
1882 pBlink->Delete( m_pCurr );
1883
1884 // delete pSpaceAdd and pKanaComp
1885 m_pCurr->FinishSpaceAdd();
1886 m_pCurr->FinishKanaComp();
1887 m_pCurr->ResetFlags();
1888 FeedInf( rInf );
1889 }
1890
CalcOnceMore()1891 bool SwTextFormatter::CalcOnceMore()
1892 {
1893 if( m_pDropFormat )
1894 {
1895 const sal_uInt16 nOldDrop = GetDropHeight();
1896 CalcDropHeight( m_pDropFormat->GetLines() );
1897 m_bOnceMore = nOldDrop != GetDropHeight();
1898 }
1899 else
1900 m_bOnceMore = false;
1901 return m_bOnceMore;
1902 }
1903
CalcBottomLine() const1904 SwTwips SwTextFormatter::CalcBottomLine() const
1905 {
1906 SwTwips nRet = Y() + GetLineHeight();
1907 SwTwips nMin = GetInfo().GetTextFly().GetMinBottom();
1908 if( nMin && ++nMin > nRet )
1909 {
1910 SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height()
1911 - m_pFrame->getFramePrintArea().Top();
1912 if( nRet + nDist < nMin )
1913 {
1914 const bool bRepaint = HasTruncLines() &&
1915 GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1;
1916 nRet = nMin - nDist;
1917 if( bRepaint )
1918 {
1919 const_cast<SwRepaint&>(GetInfo().GetParaPortion()
1920 ->GetRepaint()).Bottom( nRet-1 );
1921 const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 );
1922 }
1923 }
1924 }
1925 return nRet;
1926 }
1927
1928 // FME/OD: This routine does a limited text formatting.
CalcFitToContent_()1929 SwTwips SwTextFormatter::CalcFitToContent_()
1930 {
1931 FormatReset( GetInfo() );
1932 BuildPortions( GetInfo() );
1933 m_pCurr->CalcLine( *this, GetInfo() );
1934 return m_pCurr->Width();
1935 }
1936
1937 // determines if the calculation of a repaint offset is allowed
1938 // otherwise each line is painted from 0 (this is a copy of the beginning
1939 // of the former SwTextFormatter::Recycle() function
AllowRepaintOpt() const1940 bool SwTextFormatter::AllowRepaintOpt() const
1941 {
1942 // reformat position in front of current line? Only in this case
1943 // we want to set the repaint offset
1944 bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() &&
1945 m_pCurr->GetLen();
1946
1947 // a special case is the last line of a block adjusted paragraph:
1948 if ( bOptimizeRepaint )
1949 {
1950 switch( GetAdjust() )
1951 {
1952 case SvxAdjust::Block:
1953 {
1954 if( IsLastBlock() || IsLastCenter() )
1955 bOptimizeRepaint = false;
1956 else
1957 {
1958 // ????: blank in the last master line (blocksat.sdw)
1959 bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow();
1960 if ( bOptimizeRepaint )
1961 {
1962 SwLinePortion *pPos = m_pCurr->GetFirstPortion();
1963 while ( pPos && !pPos->IsFlyPortion() )
1964 pPos = pPos->GetNextPortion();
1965 bOptimizeRepaint = !pPos;
1966 }
1967 }
1968 break;
1969 }
1970 case SvxAdjust::Center:
1971 case SvxAdjust::Right:
1972 bOptimizeRepaint = false;
1973 break;
1974 default: ;
1975 }
1976 }
1977
1978 // Again another special case: invisible SoftHyphs
1979 const TextFrameIndex nReformat = GetInfo().GetReformatStart();
1980 if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat)
1981 {
1982 const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength())
1983 ? 0
1984 : GetInfo().GetText()[ sal_Int32(nReformat) ];
1985 bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh )
1986 || ! GetInfo().HasHint( nReformat );
1987 }
1988
1989 return bOptimizeRepaint;
1990 }
1991
CalcUnclipped(SwTwips & rTop,SwTwips & rBottom)1992 void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom )
1993 {
1994 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
1995 "SwTextFormatter::CalcUnclipped with unswapped frame" );
1996
1997 long nFlyAsc, nFlyDesc;
1998 m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc );
1999 rTop = Y() + GetCurr()->GetAscent();
2000 rBottom = rTop + nFlyDesc;
2001 rTop -= nFlyAsc;
2002 }
2003
UpdatePos(SwLineLayout * pCurrent,Point aStart,TextFrameIndex const nStartIdx,bool bAlways) const2004 void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart,
2005 TextFrameIndex const nStartIdx, bool bAlways) const
2006 {
2007 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
2008 "SwTextFormatter::UpdatePos with unswapped frame" );
2009
2010 if( GetInfo().IsTest() )
2011 return;
2012 SwLinePortion *pFirst = pCurrent->GetFirstPortion();
2013 SwLinePortion *pPos = pFirst;
2014 SwTextPaintInfo aTmpInf( GetInfo() );
2015 aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() );
2016 aTmpInf.ResetSpaceIdx();
2017 aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() );
2018 aTmpInf.ResetKanaIdx();
2019
2020 // The frame's size
2021 aTmpInf.SetIdx( nStartIdx );
2022 aTmpInf.SetPos( aStart );
2023
2024 long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2025 pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2026
2027 const sal_uInt16 nTmpHeight = pCurrent->GetRealHeight();
2028 sal_uInt16 nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height();
2029 AsCharFlags nFlags = AsCharFlags::UlSpace;
2030 if( GetMulti() )
2031 {
2032 aTmpInf.SetDirection( GetMulti()->GetDirection() );
2033 if( GetMulti()->HasRotation() )
2034 {
2035 nFlags |= AsCharFlags::Rotate;
2036 if( GetMulti()->IsRevers() )
2037 {
2038 nFlags |= AsCharFlags::Reverse;
2039 aTmpInf.X( aTmpInf.X() - nAscent );
2040 }
2041 else
2042 aTmpInf.X( aTmpInf.X() + nAscent );
2043 }
2044 else
2045 {
2046 if ( GetMulti()->IsBidi() )
2047 nFlags |= AsCharFlags::Bidi;
2048 aTmpInf.Y( aTmpInf.Y() + nAscent );
2049 }
2050 }
2051 else
2052 aTmpInf.Y( aTmpInf.Y() + nAscent );
2053
2054 while( pPos )
2055 {
2056 // We only know one case where changing the position (caused by the
2057 // adjustment) could be relevant for a portion: We need to SetRefPoint
2058 // for FlyCntPortions.
2059 if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
2060 && ( bAlways || !IsQuick() ) )
2061 {
2062 pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
2063
2064 if( pPos->IsGrfNumPortion() )
2065 {
2066 if( !nFlyAsc && !nFlyDesc )
2067 {
2068 nTmpAscent = nAscent;
2069 nFlyAsc = nAscent;
2070 nTmpDescent = nTmpHeight - nAscent;
2071 nFlyDesc = nTmpDescent;
2072 }
2073 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
2074 nFlyAsc, nFlyDesc );
2075 }
2076 else
2077 {
2078 Point aBase( aTmpInf.GetPos() );
2079 if ( GetInfo().GetTextFrame()->IsVertical() )
2080 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase );
2081
2082 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(),
2083 aBase, nTmpAscent, nTmpDescent, nFlyAsc,
2084 nFlyDesc, nFlags );
2085 }
2086 }
2087 if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() )
2088 {
2089 OSL_ENSURE( !GetMulti(), "Too much multi" );
2090 const_cast<SwTextFormatter*>(this)->m_pMulti = static_cast<SwMultiPortion*>(pPos);
2091 SwLineLayout *pLay = &GetMulti()->GetRoot();
2092 Point aSt( aTmpInf.X(), aStart.Y() );
2093
2094 if ( GetMulti()->HasBrackets() )
2095 {
2096 OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles");
2097 aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() );
2098 }
2099 else if( GetMulti()->HasRotation() )
2100 {
2101 aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() );
2102 if( GetMulti()->IsRevers() )
2103 aSt.AdjustX(GetMulti()->Width() );
2104 else
2105 aSt.AdjustY(GetMulti()->Height() );
2106 }
2107 else if ( GetMulti()->IsBidi() )
2108 // jump to end of the bidi portion
2109 aSt.AdjustX(pLay->Width() );
2110
2111 TextFrameIndex nStIdx = aTmpInf.GetIdx();
2112 do
2113 {
2114 UpdatePos( pLay, aSt, nStIdx, bAlways );
2115 nStIdx = nStIdx + pLay->GetLen();
2116 aSt.AdjustY(pLay->Height() );
2117 pLay = pLay->GetNext();
2118 } while ( pLay );
2119 const_cast<SwTextFormatter*>(this)->m_pMulti = nullptr;
2120 }
2121 pPos->Move( aTmpInf );
2122 pPos = pPos->GetNextPortion();
2123 }
2124 }
2125
AlignFlyInCntBase(long nBaseLine) const2126 void SwTextFormatter::AlignFlyInCntBase( long nBaseLine ) const
2127 {
2128 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
2129 "SwTextFormatter::AlignFlyInCntBase with unswapped frame" );
2130
2131 if( GetInfo().IsTest() )
2132 return;
2133 SwLinePortion *pFirst = m_pCurr->GetFirstPortion();
2134 SwLinePortion *pPos = pFirst;
2135 AsCharFlags nFlags = AsCharFlags::None;
2136 if( GetMulti() && GetMulti()->HasRotation() )
2137 {
2138 nFlags |= AsCharFlags::Rotate;
2139 if( GetMulti()->IsRevers() )
2140 nFlags |= AsCharFlags::Reverse;
2141 }
2142
2143 long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2144
2145 while( pPos )
2146 {
2147 if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
2148 {
2149 m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
2150
2151 if( pPos->IsGrfNumPortion() )
2152 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
2153 nFlyAsc, nFlyDesc );
2154 else
2155 {
2156 Point aBase;
2157 if ( GetInfo().GetTextFrame()->IsVertical() )
2158 {
2159 nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine );
2160 aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() );
2161 }
2162 else
2163 aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine );
2164
2165 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent,
2166 nFlyAsc, nFlyDesc, nFlags );
2167 }
2168 }
2169 pPos = pPos->GetNextPortion();
2170 }
2171 }
2172
ChkFlyUnderflow(SwTextFormatInfo & rInf) const2173 bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const
2174 {
2175 OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" );
2176 if( GetCurr() )
2177 {
2178 // First we check, whether a fly overlaps with the line.
2179 // = GetLineHeight()
2180 const sal_uInt16 nHeight = GetCurr()->GetRealHeight();
2181 SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight );
2182
2183 SwRect aLineVert( aLine );
2184 if ( m_pFrame->IsVertical() )
2185 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2186 SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) );
2187 if ( m_pFrame->IsVertical() )
2188 m_pFrame->SwitchVerticalToHorizontal( aInter );
2189
2190 if( !aInter.HasArea() )
2191 return false;
2192
2193 // We now check every portion that could have lowered for overlapping
2194 // with the fly.
2195 const SwLinePortion *pPos = GetCurr()->GetFirstPortion();
2196 aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() );
2197 aLine.Height( GetCurr()->Height() );
2198
2199 while( pPos )
2200 {
2201 aLine.Width( pPos->Width() );
2202
2203 aLineVert = aLine;
2204 if ( m_pFrame->IsVertical() )
2205 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2206 aInter = rInf.GetTextFly().GetFrame( aLineVert );
2207 if ( m_pFrame->IsVertical() )
2208 m_pFrame->SwitchVerticalToHorizontal( aInter );
2209
2210 // New flys from below?
2211 if( !pPos->IsFlyPortion() )
2212 {
2213 if( aInter.IsOver( aLine ) )
2214 {
2215 aInter.Intersection_( aLine );
2216 if( aInter.HasArea() )
2217 {
2218 // To be evaluated during reformat of this line:
2219 // RealHeight including spacing
2220 rInf.SetLineHeight( nHeight );
2221 // Height without extra spacing
2222 rInf.SetLineNetHeight( m_pCurr->Height() );
2223 return true;
2224 }
2225 }
2226 }
2227 else
2228 {
2229 // The fly portion is not intersected by a fly anymore
2230 if ( ! aInter.IsOver( aLine ) )
2231 {
2232 rInf.SetLineHeight( nHeight );
2233 rInf.SetLineNetHeight( m_pCurr->Height() );
2234 return true;
2235 }
2236 else
2237 {
2238 aInter.Intersection_( aLine );
2239
2240 // No area means a fly has become invalid because of
2241 // lowering the line => reformat the line
2242 // we also have to reformat the line, if the fly size
2243 // differs from the intersection interval's size.
2244 if( ! aInter.HasArea() ||
2245 static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() )
2246 {
2247 rInf.SetLineHeight( nHeight );
2248 rInf.SetLineNetHeight( m_pCurr->Height() );
2249 return true;
2250 }
2251 }
2252 }
2253
2254 aLine.Left( aLine.Left() + pPos->Width() );
2255 pPos = pPos->GetNextPortion();
2256 }
2257 }
2258 return false;
2259 }
2260
CalcFlyWidth(SwTextFormatInfo & rInf)2261 void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf )
2262 {
2263 if( GetMulti() || rInf.GetFly() )
2264 return;
2265
2266 SwTextFly& rTextFly = rInf.GetTextFly();
2267 if( !rTextFly.IsOn() || rInf.IsIgnoreFly() )
2268 return;
2269
2270 const SwLinePortion *pLast = rInf.GetLast();
2271
2272 long nAscent;
2273 long nTop = Y();
2274 long nHeight;
2275
2276 if( rInf.GetLineHeight() )
2277 {
2278 // Real line height has already been calculated, we only have to
2279 // search for intersections in the lower part of the strip
2280 nAscent = m_pCurr->GetAscent();
2281 nHeight = rInf.GetLineNetHeight();
2282 nTop += rInf.GetLineHeight() - nHeight;
2283 }
2284 else
2285 {
2286 // We make a first guess for the lines real height
2287 if ( ! m_pCurr->GetRealHeight() )
2288 CalcRealHeight();
2289
2290 nAscent = pLast->GetAscent();
2291 nHeight = pLast->Height();
2292
2293 if ( m_pCurr->GetRealHeight() > nHeight )
2294 nTop += m_pCurr->GetRealHeight() - nHeight;
2295 else
2296 // Important for fixed space between lines
2297 nHeight = m_pCurr->GetRealHeight();
2298 }
2299
2300 const long nLeftMar = GetLeftMargin();
2301 const long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin();
2302
2303 SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X()
2304 + nLeftMar - nLeftMin , nHeight );
2305
2306 // tdf#116486: consider also the upper margin from getFramePrintArea because intersections
2307 // with this additional space should lead to repositioning of paragraphs
2308 // For compatibility we grab a related compat flag:
2309 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS)
2310 && IsFirstTextLine())
2311 {
2312 const long nUpper = m_pFrame->getFramePrintArea().Top();
2313 // Increase the rectangle
2314 if( nUpper > 0 && nTop >= nUpper )
2315 aLine.SubTop( nUpper );
2316 }
2317 SwRect aLineVert( aLine );
2318 if ( m_pFrame->IsRightToLeft() )
2319 m_pFrame->SwitchLTRtoRTL( aLineVert );
2320
2321 if ( m_pFrame->IsVertical() )
2322 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2323
2324 // GetFrame(...) determines and returns the intersection rectangle
2325 SwRect aInter( rTextFly.GetFrame( aLineVert ) );
2326
2327 if ( m_pFrame->IsRightToLeft() )
2328 m_pFrame->SwitchRTLtoLTR( aInter );
2329
2330 if ( m_pFrame->IsVertical() )
2331 m_pFrame->SwitchVerticalToHorizontal( aInter );
2332
2333 if (!aInter.IsEmpty() && aInter.Bottom() < nTop)
2334 {
2335 // Intersects with the frame area (with upper margin), but not with the print area (without
2336 // upper margin). Don't reserve space for the fly portion in this case, text is allowed to
2337 // flow there.
2338 aInter.Height(0);
2339 }
2340
2341 if( !aInter.IsOver( aLine ) )
2342 return;
2343
2344 aLine.Left( rInf.X() + nLeftMar );
2345 bool bForced = false;
2346 if( aInter.Left() <= nLeftMin )
2347 {
2348 SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left();
2349 if( GetTextFrame()->getFramePrintArea().Left() < 0 )
2350 nFrameLeft += GetTextFrame()->getFramePrintArea().Left();
2351 if( aInter.Left() < nFrameLeft )
2352 aInter.Left( nFrameLeft );
2353
2354 long nAddMar = 0;
2355 if ( m_pFrame->IsRightToLeft() )
2356 {
2357 nAddMar = m_pFrame->getFrameArea().Right() - Right();
2358 if ( nAddMar < 0 )
2359 nAddMar = 0;
2360 }
2361 else
2362 nAddMar = nLeftMar - nFrameLeft;
2363
2364 aInter.Width( aInter.Width() + nAddMar );
2365 // For a negative first line indent, we set this flag to show
2366 // that the indentation/margin has been moved.
2367 // This needs to be respected by the DefaultTab at the zero position.
2368 if( IsFirstTextLine() && HasNegFirst() )
2369 bForced = true;
2370 }
2371 aInter.Intersection( aLine );
2372 if( !aInter.HasArea() )
2373 return;
2374
2375 const bool bFullLine = aLine.Left() == aInter.Left() &&
2376 aLine.Right() == aInter.Right();
2377
2378 // Although no text is left, we need to format another line,
2379 // because also empty lines need to avoid a Fly with no wrapping.
2380 if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
2381 {
2382 rInf.SetNewLine( true );
2383 // We know that for dummies, it holds ascent == height
2384 m_pCurr->SetDummy(true);
2385 }
2386
2387 // aInter becomes frame-local
2388 aInter.Pos().AdjustX( -nLeftMar );
2389 SwFlyPortion *pFly = new SwFlyPortion( aInter );
2390 if( bForced )
2391 {
2392 m_pCurr->SetForcedLeftMargin();
2393 rInf.ForcedLeftMargin( static_cast<sal_uInt16>(aInter.Width()) );
2394 }
2395
2396 if( bFullLine )
2397 {
2398 // In order to properly flow around Flys with different
2399 // wrapping attributes, we need to increase by units of line height.
2400 // The last avoiding line should be adjusted in height, so that
2401 // we don't get a frame spacing effect.
2402 // It is important that ascent == height, because the FlyPortion
2403 // values are transferred to pCurr in CalcLine and IsDummy() relies
2404 // on this behaviour.
2405 // To my knowledge we only have two places where DummyLines can be
2406 // created: here and in MakeFlyDummies.
2407 // IsDummy() is evaluated in IsFirstTextLine(), when moving lines
2408 // and in relation with DropCaps.
2409 pFly->Height( sal_uInt16(aInter.Height()) );
2410
2411 // nNextTop now contains the margin's bottom edge, which we avoid
2412 // or the next margin's top edge, which we need to respect.
2413 // That means we can comfortably grow up to this value; that's how
2414 // we save a few empty lines.
2415 long nNextTop = rTextFly.GetNextTop();
2416 if ( m_pFrame->IsVertical() )
2417 nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop );
2418 if( nNextTop > aInter.Bottom() )
2419 {
2420 SwTwips nH = nNextTop - aInter.Top();
2421 if( nH < SAL_MAX_UINT16 )
2422 pFly->Height( sal_uInt16( nH ) );
2423 }
2424 if( nAscent < pFly->Height() )
2425 pFly->SetAscent( sal_uInt16(nAscent) );
2426 else
2427 pFly->SetAscent( pFly->Height() );
2428 }
2429 else
2430 {
2431 if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
2432 {
2433 // Don't use nHeight, or we have a huge descent
2434 pFly->Height( pLast->Height() );
2435 pFly->SetAscent( pLast->GetAscent() );
2436 }
2437 else
2438 {
2439 pFly->Height( sal_uInt16(aInter.Height()) );
2440 if( nAscent < pFly->Height() )
2441 pFly->SetAscent( sal_uInt16(nAscent) );
2442 else
2443 pFly->SetAscent( pFly->Height() );
2444 }
2445 }
2446
2447 rInf.SetFly( pFly );
2448
2449 if( pFly->GetFix() < rInf.Width() )
2450 rInf.Width( pFly->GetFix() );
2451
2452 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
2453 if ( !pGrid )
2454 return;
2455
2456 const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
2457 const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
2458
2459 SwRectFnSet aRectFnSet(pPageFrame);
2460
2461 const long nGridOrigin = pBody ?
2462 aRectFnSet.GetPrtLeft(*pBody) :
2463 aRectFnSet.GetPrtLeft(*pPageFrame);
2464
2465 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
2466 const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc);
2467
2468 SwTwips nStartX = GetLeftMargin();
2469 if ( aRectFnSet.IsVert() )
2470 {
2471 Point aPoint( nStartX, 0 );
2472 m_pFrame->SwitchHorizontalToVertical( aPoint );
2473 nStartX = aPoint.Y();
2474 }
2475
2476 const SwTwips nOfst = nStartX - nGridOrigin;
2477 const SwTwips nTmpWidth = rInf.Width() + nOfst;
2478
2479 const sal_uLong i = nTmpWidth / nGridWidth + 1;
2480
2481 const long nNewWidth = ( i - 1 ) * nGridWidth - nOfst;
2482 if ( nNewWidth > 0 )
2483 rInf.Width( static_cast<sal_uInt16>(nNewWidth) );
2484 else
2485 rInf.Width( 0 );
2486
2487
2488 }
2489
NewFlyCntPortion(SwTextFormatInfo & rInf,SwTextAttr * pHint) const2490 SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf,
2491 SwTextAttr *pHint ) const
2492 {
2493 const SwFrame *pFrame = m_pFrame;
2494
2495 SwFlyInContentFrame *pFly;
2496 SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
2497 if( RES_FLYFRMFMT == pFrameFormat->Which() )
2498 pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame);
2499 else
2500 pFly = nullptr;
2501 // aBase is the document-global position, from which the new extra portion is placed
2502 // aBase.X() = Offset in the line after the current position
2503 // aBase.Y() = LineIter.Y() + Ascent of the current position
2504
2505 long nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2506 // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)>
2507 // to change line spacing behaviour at paragraph - Compatibility to MS Word
2508 //SwLinePortion *pPos = pCurr->GetFirstPortion();
2509 //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2510 m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2511
2512 // If the ascent of the frame is larger than the ascent of the current position,
2513 // we use this one when calculating the base, or the frame would be positioned
2514 // too much to the top, sliding down after all causing a repaint in an area
2515 // he actually never was in.
2516 sal_uInt16 nAscent = 0;
2517
2518 const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical();
2519
2520 const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() &&
2521 0 != ( bTextFrameVertical ?
2522 pFly->GetRefPoint().X() :
2523 pFly->GetRefPoint().Y() );
2524
2525 if ( bUseFlyAscent )
2526 nAscent = static_cast<sal_uInt16>( std::abs( int( bTextFrameVertical ?
2527 pFly->GetRelPos().X() :
2528 pFly->GetRelPos().Y() ) ) );
2529
2530 // Check if be prefer to use the ascent of the last portion:
2531 if ( IsQuick() ||
2532 !bUseFlyAscent ||
2533 nAscent < rInf.GetLast()->GetAscent() )
2534 {
2535 nAscent = rInf.GetLast()->GetAscent();
2536 }
2537 else if( nAscent > nFlyAsc )
2538 nFlyAsc = nAscent;
2539
2540 Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent );
2541 AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None;
2542 if( GetMulti() && GetMulti()->HasRotation() )
2543 {
2544 nMode |= AsCharFlags::Rotate;
2545 if( GetMulti()->IsRevers() )
2546 nMode |= AsCharFlags::Reverse;
2547 }
2548
2549 Point aTmpBase( aBase );
2550 if ( GetInfo().GetTextFrame()->IsVertical() )
2551 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
2552
2553 SwFlyCntPortion* pRet(nullptr);
2554 if( pFly )
2555 {
2556 pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
2557 // We need to make sure that our font is set again in the OutputDevice
2558 // It could be that the FlyInCnt was added anew and GetFlyFrame() would
2559 // in turn cause, that it'd be created anew again.
2560 // This one's frames get formatted right away, which change the font.
2561 rInf.SelectFont();
2562 if( pRet->GetAscent() > nAscent )
2563 {
2564 aBase.setY( Y() + pRet->GetAscent() );
2565 nMode |= AsCharFlags::UlSpace;
2566 if( !rInf.IsTest() )
2567 {
2568 aTmpBase = aBase;
2569 if ( GetInfo().GetTextFrame()->IsVertical() )
2570 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
2571
2572 pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent,
2573 nTmpDescent, nFlyAsc, nFlyDesc, nMode );
2574 }
2575 }
2576 }
2577 else
2578 {
2579 pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
2580 }
2581 return pRet;
2582 }
2583
2584 /* Drop portion is a special case, because it has parts which aren't portions
2585 but we have handle them just like portions */
MergeCharacterBorder(SwDropPortion const & rPortion)2586 void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion )
2587 {
2588 if( rPortion.GetLines() > 1 )
2589 {
2590 SwDropPortionPart* pCurrPart = rPortion.GetPart();
2591 while( pCurrPart )
2592 {
2593 if( pCurrPart->GetFollow() &&
2594 ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) )
2595 {
2596 pCurrPart->SetJoinBorderWithNext(true);
2597 pCurrPart->GetFollow()->SetJoinBorderWithPrev(true);
2598 }
2599 pCurrPart = pCurrPart->GetFollow();
2600 }
2601 }
2602 }
2603
MergeCharacterBorder(SwLinePortion & rPortion,SwLinePortion const * pPrev,SwTextFormatInfo & rInf)2604 void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf )
2605 {
2606 const SwFont aCurFont = *rInf.GetFont();
2607 if( aCurFont.HasBorder() )
2608 {
2609 if (pPrev && pPrev->GetJoinBorderWithNext() )
2610 {
2611 // In some case border merge is called twice to the portion
2612 if( !rPortion.GetJoinBorderWithPrev() )
2613 {
2614 rPortion.SetJoinBorderWithPrev(true);
2615 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() )
2616 rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace());
2617 }
2618 }
2619 else
2620 {
2621 rPortion.SetJoinBorderWithPrev(false);
2622 m_pFirstOfBorderMerge = &rPortion;
2623 }
2624
2625 // Get next portion's font
2626 bool bSeek = false;
2627 if (!rInf.IsFull() && // Not the last portion of the line (in case of line break)
2628 rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph
2629 {
2630 bSeek = Seek(rInf.GetIdx() + rPortion.GetLen());
2631 }
2632 // Don't join the next portion if SwKernPortion sits between two different boxes.
2633 bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev();
2634 // If next portion has the same border then merge
2635 if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect )
2636 {
2637 // In some case border merge is called twice to the portion
2638 if( !rPortion.GetJoinBorderWithNext() )
2639 {
2640 rPortion.SetJoinBorderWithNext(true);
2641 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() )
2642 rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace());
2643 }
2644 }
2645 // If this is the last portion of the merge group then make the real height merge
2646 else
2647 {
2648 rPortion.SetJoinBorderWithNext(false);
2649 if( m_pFirstOfBorderMerge != &rPortion )
2650 {
2651 // Calculate maximum height and ascent
2652 SwLinePortion* pActPor = m_pFirstOfBorderMerge;
2653 sal_uInt16 nMaxAscent = 0;
2654 sal_uInt16 nMaxHeight = 0;
2655 bool bReachCurrent = false;
2656 while( pActPor )
2657 {
2658 if( nMaxHeight < pActPor->Height() )
2659 nMaxHeight = pActPor->Height();
2660 if( nMaxAscent < pActPor->GetAscent() )
2661 nMaxAscent = pActPor->GetAscent();
2662
2663 pActPor = pActPor->GetNextPortion();
2664 if( !pActPor && !bReachCurrent )
2665 {
2666 pActPor = &rPortion;
2667 bReachCurrent = true;
2668 }
2669 }
2670
2671 // Change all portion's height and ascent
2672 pActPor = m_pFirstOfBorderMerge;
2673 bReachCurrent = false;
2674 while( pActPor )
2675 {
2676 if( nMaxHeight > pActPor->Height() )
2677 pActPor->Height(nMaxHeight);
2678 if( nMaxAscent > pActPor->GetAscent() )
2679 pActPor->SetAscent(nMaxAscent);
2680
2681 pActPor = pActPor->GetNextPortion();
2682 if( !pActPor && !bReachCurrent )
2683 {
2684 pActPor = &rPortion;
2685 bReachCurrent = true;
2686 }
2687 }
2688 m_pFirstOfBorderMerge = nullptr;
2689 }
2690 }
2691 Seek(rInf.GetIdx());
2692 }
2693 }
2694
2695 namespace {
2696 // calculates and sets optimal repaint offset for the current line
lcl_CalcOptRepaint(SwTextFormatter & rThis,SwLineLayout const & rCurr,TextFrameIndex const nOldLineEnd,const std::vector<long> & rFlyStarts)2697 long lcl_CalcOptRepaint( SwTextFormatter &rThis,
2698 SwLineLayout const &rCurr,
2699 TextFrameIndex const nOldLineEnd,
2700 const std::vector<long> &rFlyStarts )
2701 {
2702 SwTextFormatInfo& txtFormatInfo = rThis.GetInfo();
2703 if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() )
2704 // the reformat position is behind our new line, that means
2705 // something of our text has moved to the next line
2706 return 0;
2707
2708 TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd);
2709
2710 // in case we do not have any fly in our line, our repaint position
2711 // is the changed position - 1
2712 if ( rFlyStarts.empty() && ! rCurr.IsFly() )
2713 {
2714 // this is the maximum repaint offset determined during formatting
2715 // for example: the beginning of the first right tab stop
2716 // if this value is 0, this means that we do not have an upper
2717 // limit for the repaint offset
2718 const long nFormatRepaint = txtFormatInfo.GetPaintOfst();
2719
2720 if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3))
2721 return 0;
2722
2723 // step back two positions for smoother repaint
2724 nReformat -= TextFrameIndex(2);
2725
2726 // i#28795, i#34607, i#38388
2727 // step back more characters, this is required by complex scripts
2728 // e.g., for Khmer (thank you, Javier!)
2729 static const TextFrameIndex nMaxContext(10);
2730 if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext)
2731 nReformat = nReformat - nMaxContext;
2732 else
2733 {
2734 nReformat = txtFormatInfo.GetLineStart();
2735 //reset the margin flag - prevent loops
2736 SwTextCursor::SetRightMargin(false);
2737 }
2738
2739 // Weird situation: Our line used to end with a hole portion
2740 // and we delete some characters at the end of our line. We have
2741 // to take care for repainting the blanks which are not anymore
2742 // covered by the hole portion
2743 while ( nReformat > txtFormatInfo.GetLineStart() &&
2744 CH_BLANK == txtFormatInfo.GetChar( nReformat ) )
2745 --nReformat;
2746
2747 OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" );
2748 SwRect aRect;
2749
2750 // Note: GetChareRect is not const. It definitely changes the
2751 // bMulti flag. We have to save and restore the old value.
2752 bool bOldMulti = txtFormatInfo.IsMulti();
2753 rThis.GetCharRect( &aRect, nReformat );
2754 txtFormatInfo.SetMulti( bOldMulti );
2755
2756 return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) :
2757 aRect.Left();
2758 }
2759 else
2760 {
2761 // nReformat may be wrong, if something around flys has changed:
2762 // we compare the former and the new fly positions in this line
2763 // if anything has changed, we carefully have to adjust the right
2764 // repaint position
2765 long nPOfst = 0;
2766 size_t nCnt = 0;
2767 long nX = 0;
2768 TextFrameIndex nIdx = rThis.GetInfo().GetLineStart();
2769 SwLinePortion* pPor = rCurr.GetFirstPortion();
2770
2771 while ( pPor )
2772 {
2773 if ( pPor->IsFlyPortion() )
2774 {
2775 // compare start of fly with former start of fly
2776 if (nCnt < rFlyStarts.size() &&
2777 nX == rFlyStarts[ nCnt ] &&
2778 nIdx < nReformat
2779 )
2780 // found fix position, nothing has changed left from nX
2781 nPOfst = nX + pPor->Width();
2782 else
2783 break;
2784
2785 nCnt++;
2786 }
2787 nX = nX + pPor->Width();
2788 nIdx = nIdx + pPor->GetLen();
2789 pPor = pPor->GetNextPortion();
2790 }
2791
2792 return nPOfst + rThis.GetLeftMargin();
2793 }
2794 }
2795
2796 // Determine if we need to build hidden portions
lcl_BuildHiddenPortion(const SwTextSizeInfo & rInf,TextFrameIndex & rPos)2797 bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos)
2798 {
2799 // Only if hidden text should not be shown:
2800 // if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() )
2801 const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar();
2802 const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting();
2803 if (bShowInDocView || bShowForPrinting)
2804 return false;
2805
2806 const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
2807 TextFrameIndex nHiddenStart;
2808 TextFrameIndex nHiddenEnd;
2809 rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd );
2810 if ( nHiddenEnd )
2811 {
2812 rPos = nHiddenEnd;
2813 return true;
2814 }
2815
2816 return false;
2817 }
2818
lcl_HasSameBorder(const SwFont & rFirst,const SwFont & rSecond)2819 bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond)
2820 {
2821 return
2822 rFirst.GetTopBorder() == rSecond.GetTopBorder() &&
2823 rFirst.GetBottomBorder() == rSecond.GetBottomBorder() &&
2824 rFirst.GetLeftBorder() == rSecond.GetLeftBorder() &&
2825 rFirst.GetRightBorder() == rSecond.GetRightBorder() &&
2826 rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() &&
2827 rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() &&
2828 rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() &&
2829 rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() &&
2830 rFirst.GetOrientation() == rSecond.GetOrientation() &&
2831 rFirst.GetShadowColor() == rSecond.GetShadowColor() &&
2832 rFirst.GetShadowWidth() == rSecond.GetShadowWidth() &&
2833 rFirst.GetShadowLocation() == rSecond.GetShadowLocation();
2834 }
2835
2836 } //end unnamed namespace
2837
2838 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2839