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