ITEM: Refactor ItemType
[LibreOffice.git] / sw / source / core / text / itrform2.cxx
blob8fc8764470b56f013e380f7ac3067f6c6e746a7e
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <hintids.hxx>
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 <sortedobjs.hxx>
49 #include <fmtanchr.hxx>
50 #include <pagefrm.hxx>
51 #include <tgrditem.hxx>
52 #include <doc.hxx>
53 #include "pormulti.hxx"
54 #include <unotools/charclass.hxx>
55 #include <xmloff/odffields.hxx>
56 #include <IDocumentSettingAccess.hxx>
57 #include <IMark.hxx>
58 #include <IDocumentMarkAccess.hxx>
59 #include <comphelper/processfactory.hxx>
60 #include <vcl/pdfextoutdevdata.hxx>
61 #include <comphelper/string.hxx>
62 #include <docsh.hxx>
63 #include <unocrsrhelper.hxx>
64 #include <textcontentcontrol.hxx>
65 #include <EnhancedPDFExportHelper.hxx>
66 #include <com/sun/star/rdf/Statement.hpp>
67 #include <com/sun/star/rdf/URI.hpp>
68 #include <com/sun/star/rdf/URIs.hpp>
69 #include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
70 #include <com/sun/star/rdf/XLiteral.hpp>
71 #include <com/sun/star/text/XTextContent.hpp>
72 #include <unotxdoc.hxx>
74 using namespace ::com::sun::star;
76 namespace {
77 //! Calculates and sets optimal repaint offset for the current line
78 tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
79 SwLineLayout const &rCurr,
80 TextFrameIndex nOldLineEnd,
81 const std::vector<tools::Long> &rFlyStarts );
82 //! Determine if we need to build hidden portions
83 bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex &rPos);
85 // Check whether the two font has the same border
86 bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond);
89 static void ClearFly( SwTextFormatInfo &rInf )
91 delete rInf.GetFly();
92 rInf.SetFly(nullptr);
95 void SwTextFormatter::CtorInitTextFormatter( SwTextFrame *pNewFrame, SwTextFormatInfo *pNewInf )
97 CtorInitTextPainter( pNewFrame, pNewInf );
98 m_pInf = pNewInf;
99 m_pDropFormat = GetInfo().GetDropFormat();
100 m_pMulti = nullptr;
102 m_bOnceMore = false;
103 m_bFlyInContentBase = false;
104 m_bTruncLines = false;
105 m_nContentEndHyph = 0;
106 m_nContentMidHyph = 0;
107 m_nLeftScanIdx = TextFrameIndex(COMPLETE_STRING);
108 m_nRightScanIdx = TextFrameIndex(0);
109 m_pByEndIter.reset();
110 m_pFirstOfBorderMerge = nullptr;
112 if (m_nStart > TextFrameIndex(GetInfo().GetText().getLength()))
114 OSL_ENSURE( false, "+SwTextFormatter::CTOR: bad offset" );
115 m_nStart = TextFrameIndex(GetInfo().GetText().getLength());
120 SwTextFormatter::~SwTextFormatter()
122 // Extremely unlikely, but still possible
123 // e.g.: field splits up, widows start to matter
124 if( GetInfo().GetRest() )
126 delete GetInfo().GetRest();
127 GetInfo().SetRest(nullptr);
131 void SwTextFormatter::Insert( SwLineLayout *pLay )
133 // Insert BEHIND the current element
134 if ( m_pCurr )
136 pLay->SetNext( m_pCurr->GetNext() );
137 m_pCurr->SetNext( pLay );
139 else
140 m_pCurr = pLay;
143 SwTwips SwTextFormatter::GetFrameRstHeight() const
145 // We want the rest height relative to the page.
146 // If we're in a table, then pFrame->GetUpper() is not the page.
148 // GetFrameRstHeight() is being called with Footnote.
149 // Wrong: const SwFrame *pUpper = pFrame->GetUpper();
150 const SwFrame *pPage = m_pFrame->FindPageFrame();
151 const SwTwips nHeight = pPage->getFrameArea().Top()
152 + pPage->getFramePrintArea().Top()
153 + pPage->getFramePrintArea().Height() - Y();
154 if( 0 > nHeight )
155 return m_pCurr->Height();
156 else
157 return nHeight;
160 bool SwTextFormatter::ClearIfIsFirstOfBorderMerge(const SwLinePortion* pPortion)
162 if (pPortion == m_pFirstOfBorderMerge)
164 m_pFirstOfBorderMerge = nullptr;
165 return true;
167 return false;
170 SwLinePortion *SwTextFormatter::Underflow( SwTextFormatInfo &rInf )
172 // Save values and initialize rInf
173 SwLinePortion *pUnderflow = rInf.GetUnderflow();
174 if( !pUnderflow )
175 return nullptr;
177 // We format backwards, i.e. attribute changes can happen the next
178 // line again.
179 // Can be seen in 8081.sdw, if you enter text in the first line
181 TextFrameIndex const nSoftHyphPos = rInf.GetSoftHyphPos();
183 // Save flys and set to 0, or else segmentation fault
184 // Not ClearFly(rInf) !
185 SwFlyPortion *pFly = rInf.GetFly();
186 rInf.SetFly( nullptr );
188 FeedInf( rInf );
189 rInf.SetLast( m_pCurr );
190 // pUnderflow does not need to be deleted, because it will drown in the following
191 // Truncate()
192 rInf.SetUnderflow(nullptr);
193 rInf.SetSoftHyphPos( nSoftHyphPos );
194 rInf.SetPaintOfst( GetLeftMargin() );
196 // We look for the portion with the under-flow position
197 SwLinePortion *pPor = m_pCurr->GetFirstPortion();
198 if( pPor != pUnderflow )
200 // pPrev will be the last portion before pUnderflow,
201 // which still has a real width.
202 // Exception: SoftHyphPortion must not be forgotten, of course!
203 // Although they don't have a width.
204 SwLinePortion *pTmpPrev = pPor;
205 while( pPor && pPor != pUnderflow )
207 if( !pPor->IsKernPortion() &&
208 ( pPor->Width() || pPor->IsSoftHyphPortion() ) )
210 while( pTmpPrev != pPor )
212 pTmpPrev->Move( rInf );
213 rInf.SetLast( pTmpPrev );
214 pTmpPrev = pTmpPrev->GetNextPortion();
215 OSL_ENSURE( pTmpPrev, "Underflow: losing control!" );
218 pPor = pPor->GetNextPortion();
220 pPor = pTmpPrev;
221 if( pPor && // Skip flys and initials when underflow.
222 ( pPor->IsFlyPortion() || pPor->IsDropPortion() ||
223 pPor->IsFlyCntPortion() ) )
225 pPor->Move( rInf );
226 rInf.SetLast( pPor );
227 rInf.SetStopUnderflow( true );
228 pPor = pUnderflow;
232 // What? The under-flow portion is not in the portion chain?
233 OSL_ENSURE( pPor, "SwTextFormatter::Underflow: overflow but underflow" );
235 // Snapshot
236 if ( pPor==rInf.GetLast() )
238 // We end up here, if the portion triggering the under-flow
239 // spans over the whole line. E.g. if a word spans across
240 // multiple lines and flows into a fly in the second line.
241 rInf.SetFly( pFly );
242 pPor->Truncate();
243 return pPor; // Is that enough?
245 // End the snapshot
247 // X + Width == 0 with SoftHyph > Line?!
248 if( !pPor || !(rInf.X() + pPor->Width()) )
250 delete pFly;
251 return nullptr;
254 // Preparing for Format()
255 // We need to chip off the chain behind pLast, because we Insert after the Format()
256 SeekAndChg( rInf );
258 // line width is adjusted, so that pPor does not fit to current
259 // line anymore
260 rInf.Width( rInf.X() + (pPor->Width() ? pPor->Width() - 1 : 0) );
261 rInf.SetLen( pPor->GetLen() );
262 rInf.SetFull( false );
263 if( pFly )
265 // We need to recalculate the FlyPortion due to the following reason:
266 // If the base line is lowered by a big font in the middle of the line,
267 // causing overlapping with a fly, the FlyPortion has a wrong size/fixed
268 // size.
269 rInf.SetFly( pFly );
270 CalcFlyWidth( rInf );
272 rInf.GetLast()->SetNextPortion(nullptr);
274 // The SwLineLayout is an exception to this, which splits at the first
275 // portion change.
276 // Here only the other way around:
277 if( rInf.GetLast() == m_pCurr )
279 if( pPor->InTextGrp() && !pPor->InExpGrp() )
281 const PortionType nOldWhich = m_pCurr->GetWhichPor();
282 *static_cast<SwLinePortion*>(m_pCurr) = *pPor;
283 m_pCurr->SetNextPortion( pPor->GetNextPortion() );
284 m_pCurr->SetWhichPor( nOldWhich );
285 pPor->SetNextPortion( nullptr );
286 delete pPor;
287 pPor = m_pCurr;
291 // Make sure that m_pFirstOfBorderMerge does not point to a portion which
292 // will be deleted by Truncate() below.
293 SwLinePortion* pNext = pPor->GetNextPortion();
294 while (pNext)
296 if (ClearIfIsFirstOfBorderMerge(pNext))
297 break;
298 pNext = pNext->GetNextPortion();
300 pPor->Truncate();
301 SwLinePortion *const pRest( rInf.GetRest() );
302 if (pRest && pRest->InFieldGrp() &&
303 static_cast<SwFieldPortion*>(pRest)->IsNoLength())
305 // HACK: decrement again, so we pick up the suffix in next line!
306 m_pByEndIter->PrevAttr();
308 delete pRest;
309 rInf.SetRest(nullptr);
310 return pPor;
313 void SwTextFormatter::InsertPortion( SwTextFormatInfo &rInf,
314 SwLinePortion *pPor )
316 SwLinePortion *pLast = nullptr;
317 // The new portion is inserted, but everything's different for
318 // LineLayout...
319 if( pPor == m_pCurr )
321 if ( m_pCurr->GetNextPortion() )
323 pLast = pPor;
324 pPor = m_pCurr->GetNextPortion();
327 // i#112181 - Prevent footnote anchor being wrapped to next line
328 // without preceding word
329 rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
331 else
333 pLast = rInf.GetLast();
334 if( pLast->GetNextPortion() )
336 while( pLast->GetNextPortion() )
337 pLast = pLast->GetNextPortion();
338 rInf.SetLast( pLast );
340 pLast->Insert( pPor );
342 rInf.SetOtherThanFootnoteInside( rInf.IsOtherThanFootnoteInside() || !pPor->IsFootnotePortion() );
344 // Adjust maxima
345 if( m_pCurr->Height() < pPor->Height() )
346 m_pCurr->Height( pPor->Height(), pPor->IsTextPortion() );
347 if( m_pCurr->GetAscent() < pPor->GetAscent() )
348 m_pCurr->SetAscent( pPor->GetAscent() );
349 if( m_pCurr->GetHangingBaseline() < pPor->GetHangingBaseline() )
350 m_pCurr->SetHangingBaseline( pPor->GetHangingBaseline() );
352 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY))
354 // For DOCX with compat=14 the only shape in line defines height of the line in spite of used font
355 if (pLast->IsFlyCntPortion() && pPor->IsTextPortion() && pPor->GetLen() == TextFrameIndex(0))
357 m_pCurr->SetAscent(pLast->GetAscent());
358 m_pCurr->Height(pLast->Height());
363 // Sometimes chains are constructed (e.g. by hyphenate)
364 rInf.SetLast( pPor );
365 while( pPor )
367 if (!pPor->IsDropPortion())
368 MergeCharacterBorder(*pPor, pLast, rInf);
370 pPor->Move( rInf );
371 rInf.SetLast( pPor );
372 pLast = pPor;
373 pPor = pPor->GetNextPortion();
377 void SwTextFormatter::BuildPortions( SwTextFormatInfo &rInf )
379 OSL_ENSURE( rInf.GetText().getLength() < COMPLETE_STRING,
380 "SwTextFormatter::BuildPortions: bad text length in info" );
382 rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
384 // First NewTextPortion() decides whether pCurr ends up in pPor.
385 // We need to make sure that the font is being set in any case.
386 // This is done automatically in CalcAscent.
387 rInf.SetLast( m_pCurr );
388 rInf.ForcedLeftMargin( 0 );
390 OSL_ENSURE( m_pCurr->FindLastPortion() == m_pCurr, "pLast supposed to equal pCurr" );
392 if( !m_pCurr->GetAscent() && !m_pCurr->Height() )
393 CalcAscent( rInf, m_pCurr );
395 SeekAndChg( rInf );
397 // Width() is shortened in CalcFlyWidth if we have a FlyPortion
398 OSL_ENSURE( !rInf.X() || m_pMulti, "SwTextFormatter::BuildPortion X=0?" );
399 CalcFlyWidth( rInf );
400 SwFlyPortion *pFly = rInf.GetFly();
401 if( pFly )
403 if ( 0 < pFly->GetFix() )
404 ClearFly( rInf );
405 else
406 rInf.SetFull(true);
409 ::std::optional<TextFrameIndex> oMovedFlyIndex;
410 if (SwTextFrame const*const pFollow = GetTextFrame()->GetFollow())
412 // flys are always on master!
413 if (GetTextFrame()->GetDrawObjs() && pFollow->GetUpper() != GetTextFrame()->GetUpper())
415 for (SwAnchoredObject const*const pAnchoredObj : *GetTextFrame()->GetDrawObjs())
417 // tdf#146500 try to stop where a fly is anchored in the follow
418 // that has recently been moved (presumably by splitting this
419 // frame); similar to check in SwFlowFrame::MoveBwd()
420 if (pAnchoredObj->RestartLayoutProcess()
421 && !pAnchoredObj->IsTmpConsiderWrapInfluence())
423 SwFormatAnchor const& rAnchor(pAnchoredObj->GetFrameFormat()->GetAnchor());
424 assert(rAnchor.GetAnchorId() == RndStdIds::FLY_AT_CHAR || rAnchor.GetAnchorId() == RndStdIds::FLY_AT_PARA);
425 TextFrameIndex const nAnchor(GetTextFrame()->MapModelToViewPos(*rAnchor.GetContentAnchor()));
426 if (pFollow->GetOffset() <= nAnchor
427 && (pFollow->GetFollow() == nullptr
428 || nAnchor < pFollow->GetFollow()->GetOffset()))
430 if (!oMovedFlyIndex || nAnchor < *oMovedFlyIndex)
432 oMovedFlyIndex.emplace(nAnchor);
440 SwLinePortion *pPor = NewPortion(rInf, oMovedFlyIndex);
442 // Asian grid stuff
443 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
444 const bool bHasGrid = pGrid && rInf.SnapToGrid() &&
445 GRID_LINES_CHARS == pGrid->GetGridType();
448 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
449 const sal_uInt16 nGridWidth = bHasGrid ? GetGridWidth(*pGrid, rDoc) : 0;
451 // used for grid mode only:
452 // the pointer is stored, because after formatting of non-asian text,
453 // the width of the kerning portion has to be adjusted
454 // Inserting a SwKernPortion before a SwTabPortion isn't necessary
455 // and will break the SwTabPortion.
456 SwKernPortion* pGridKernPortion = nullptr;
458 bool bFull = false;
459 SwTwips nUnderLineStart = 0;
460 rInf.Y( Y() );
462 while( pPor && !rInf.IsStop() )
464 OSL_ENSURE(rInf.GetLen() < TextFrameIndex(COMPLETE_STRING) &&
465 rInf.GetIdx() <= TextFrameIndex(rInf.GetText().getLength()),
466 "SwTextFormatter::BuildPortions: bad length in info" );
468 // We have to check the script for fields in order to set the
469 // correct nActual value for the font.
470 if( pPor->InFieldGrp() )
471 static_cast<SwFieldPortion*>(pPor)->CheckScript( rInf );
473 if( ! bHasGrid && rInf.HasScriptSpace() &&
474 rInf.GetLast() && rInf.GetLast()->InTextGrp() &&
475 rInf.GetLast()->Width() && !rInf.GetLast()->InNumberGrp() )
477 SwFontScript nNxtActual = rInf.GetFont()->GetActual();
478 SwFontScript nLstActual = nNxtActual;
479 tools::Long nLstHeight = rInf.GetFont()->GetHeight();
480 bool bAllowBehind = false;
481 const CharClass& rCC = GetAppCharClass();
483 // are there any punctuation characters on both sides
484 // of the kerning portion?
485 if ( pPor->InFieldGrp() )
487 OUString aAltText;
488 if ( static_cast<SwFieldPortion*>(pPor)->GetExpText( rInf, aAltText ) &&
489 !aAltText.isEmpty() )
491 bAllowBehind = rCC.isLetterNumeric( aAltText, 0 );
493 const SwFont* pTmpFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
494 if ( pTmpFnt )
495 nNxtActual = pTmpFnt->GetActual();
498 else
500 const OUString& rText = rInf.GetText();
501 sal_Int32 nIdx = sal_Int32(rInf.GetIdx());
502 bAllowBehind = nIdx < rText.getLength() && rCC.isLetterNumeric(rText, nIdx);
505 const SwLinePortion* pLast = rInf.GetLast();
506 if ( bAllowBehind && pLast )
508 bool bAllowBefore = false;
510 if ( pLast->InFieldGrp() )
512 OUString aAltText;
513 if ( static_cast<const SwFieldPortion*>(pLast)->GetExpText( rInf, aAltText ) &&
514 !aAltText.isEmpty() )
516 bAllowBefore = rCC.isLetterNumeric( aAltText, aAltText.getLength() - 1 );
518 const SwFont* pTmpFnt = static_cast<const SwFieldPortion*>(pLast)->GetFont();
519 if ( pTmpFnt )
521 nLstActual = pTmpFnt->GetActual();
522 nLstHeight = pTmpFnt->GetHeight();
526 else if ( rInf.GetIdx() )
528 bAllowBefore = rCC.isLetterNumeric(rInf.GetText(), sal_Int32(rInf.GetIdx()) - 1);
529 // Note: ScriptType returns values in [1,4]
530 if ( bAllowBefore )
531 nLstActual = SwFontScript(m_pScriptInfo->ScriptType(rInf.GetIdx() - TextFrameIndex(1)) - 1);
534 nLstHeight /= 5;
535 // does the kerning portion still fit into the line?
536 if( bAllowBefore && ( nLstActual != nNxtActual ) &&
537 // tdf#89288 we want to insert space between CJK and non-CJK text only.
538 ( nLstActual == SwFontScript::CJK || nNxtActual == SwFontScript::CJK ) &&
539 nLstHeight && rInf.X() + nLstHeight <= rInf.Width() &&
540 ! pPor->InTabGrp() )
542 SwKernPortion* pKrn =
543 new SwKernPortion( *rInf.GetLast(), nLstHeight,
544 pLast->InFieldGrp() && pPor->InFieldGrp() );
546 // ofz#58550 Direct-leak, pKrn adds itself as the NextPortion
547 // of rInf.GetLast(), but may use CopyLinePortion to add a copy
548 // of itself, which will then be left dangling with the following
549 // SetNextPortion(nullptr)
550 SwLinePortion *pNext = rInf.GetLast()->GetNextPortion();
551 if (pNext != pKrn)
552 delete pNext;
554 rInf.GetLast()->SetNextPortion( nullptr );
555 InsertPortion( rInf, pKrn );
559 else if ( bHasGrid && ! pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
561 // insert a grid kerning portion
562 pGridKernPortion = pPor->IsKernPortion() ?
563 static_cast<SwKernPortion*>(pPor) :
564 new SwKernPortion( *m_pCurr );
566 // if we have a new GridKernPortion, we initially calculate
567 // its size so that its ends on the grid
568 const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
569 const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
570 SwRectFnSet aRectFnSet(pPageFrame);
572 const tools::Long nGridOrigin = pBody ?
573 aRectFnSet.GetPrtLeft(*pBody) :
574 aRectFnSet.GetPrtLeft(*pPageFrame);
576 SwTwips nStartX = rInf.X() + GetLeftMargin();
577 if ( aRectFnSet.IsVert() )
579 Point aPoint( nStartX, 0 );
580 m_pFrame->SwitchHorizontalToVertical( aPoint );
581 nStartX = aPoint.Y();
584 const SwTwips nOfst = nStartX - nGridOrigin;
585 if ( nOfst )
587 const sal_uLong i = ( nOfst > 0 ) ?
588 ( ( nOfst - 1 ) / nGridWidth + 1 ) :
590 const SwTwips nKernWidth = i * nGridWidth - nOfst;
591 const SwTwips nRestWidth = rInf.Width() - rInf.X();
593 if ( nKernWidth <= nRestWidth )
594 pGridKernPortion->Width( nKernWidth );
597 if ( pGridKernPortion != pPor )
598 InsertPortion( rInf, pGridKernPortion );
601 if( pPor->IsDropPortion() )
602 MergeCharacterBorder(*static_cast<SwDropPortion*>(pPor));
604 // the multi-portion has its own format function
605 if( pPor->IsMultiPortion() && ( !m_pMulti || m_pMulti->IsBidi() ) )
606 bFull = BuildMultiPortion( rInf, *static_cast<SwMultiPortion*>(pPor) );
607 else
608 bFull = pPor->Format( rInf );
610 if( rInf.IsRuby() && !rInf.GetRest() )
611 bFull = true;
613 // if we are underlined, we store the beginning of this underlined
614 // segment for repaint optimization
615 if ( LINESTYLE_NONE != m_pFont->GetUnderline() && ! nUnderLineStart )
616 nUnderLineStart = GetLeftMargin() + rInf.X();
618 if ( pPor->IsFlyPortion() )
619 m_pCurr->SetFly( true );
620 // some special cases, where we have to take care for the repaint
621 // offset:
622 // 1. Underlined portions due to special underline feature
623 // 2. Right Tab
624 // 3. BidiPortions
625 // 4. other Multiportions
626 // 5. DropCaps
627 // 6. Grid Mode
628 else if ( ( ! rInf.GetPaintOfst() || nUnderLineStart < rInf.GetPaintOfst() ) &&
629 // 1. Underlined portions
630 nUnderLineStart &&
631 // reformat is at end of an underlined portion and next portion
632 // is not underlined
633 ( ( rInf.GetReformatStart() == rInf.GetIdx() &&
634 LINESTYLE_NONE == m_pFont->GetUnderline()
635 ) ||
636 // reformat is inside portion and portion is underlined
637 ( rInf.GetReformatStart() >= rInf.GetIdx() &&
638 rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() &&
639 LINESTYLE_NONE != m_pFont->GetUnderline() ) ) )
640 rInf.SetPaintOfst( nUnderLineStart );
641 else if ( ! rInf.GetPaintOfst() &&
642 // 2. Right Tab
643 ( ( pPor->InTabGrp() && !pPor->IsTabLeftPortion() ) ||
644 // 3. BidiPortions
645 ( pPor->IsMultiPortion() && static_cast<SwMultiPortion*>(pPor)->IsBidi() ) ||
646 // 4. Multi Portion and 5. Drop Caps
647 ( ( pPor->IsDropPortion() || pPor->IsMultiPortion() ) &&
648 rInf.GetReformatStart() >= rInf.GetIdx() &&
649 rInf.GetReformatStart() <= rInf.GetIdx() + pPor->GetLen() )
650 // 6. Grid Mode
651 || ( bHasGrid && SwFontScript::CJK != m_pFont->GetActual() )
654 // we store the beginning of the critical portion as our
655 // paint offset
656 rInf.SetPaintOfst( GetLeftMargin() + rInf.X() );
658 // under one of these conditions we are allowed to delete the
659 // start of the underline portion
660 if ( IsUnderlineBreak( *pPor, *m_pFont ) )
661 nUnderLineStart = 0;
663 if( pPor->IsFlyCntPortion() || ( pPor->IsMultiPortion() &&
664 static_cast<SwMultiPortion*>(pPor)->HasFlyInContent() ) )
665 SetFlyInCntBase();
666 // bUnderflow needs to be reset or we wrap again at the next softhyphen
667 if ( !bFull )
669 rInf.ClrUnderflow();
670 if( ! bHasGrid && rInf.HasScriptSpace() && pPor->InTextGrp() &&
671 pPor->GetLen() && !pPor->InFieldGrp() )
673 // The distance between two different scripts is set
674 // to 20% of the fontheight.
675 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
676 if (nTmp == m_pScriptInfo->NextScriptChg(nTmp - TextFrameIndex(1)) &&
677 nTmp != TextFrameIndex(rInf.GetText().getLength()) &&
678 (m_pScriptInfo->ScriptType(nTmp - TextFrameIndex(1)) == css::i18n::ScriptType::ASIAN ||
679 m_pScriptInfo->ScriptType(nTmp) == css::i18n::ScriptType::ASIAN) )
681 const SwTwips nDist = rInf.GetFont()->GetHeight()/5;
683 if( nDist )
685 // we do not want a kerning portion if any end
686 // would be a punctuation character
687 const CharClass& rCC = GetAppCharClass();
688 if (rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp) - 1)
689 && rCC.isLetterNumeric(rInf.GetText(), sal_Int32(nTmp)))
691 // does the kerning portion still fit into the line?
692 if ( rInf.X() + pPor->Width() + nDist <= rInf.Width() )
693 new SwKernPortion( *pPor, nDist );
694 else
695 bFull = true;
702 if ( bHasGrid && pPor != pGridKernPortion && ! m_pMulti && ! pPor->InTabGrp() )
704 TextFrameIndex const nTmp = rInf.GetIdx() + pPor->GetLen();
705 const SwTwips nRestWidth = rInf.Width() - rInf.X() - pPor->Width();
707 const SwFontScript nCurrScript = m_pFont->GetActual(); // pScriptInfo->ScriptType( rInf.GetIdx() );
708 const SwFontScript nNextScript =
709 nTmp >= TextFrameIndex(rInf.GetText().getLength())
710 ? SwFontScript::CJK
711 : m_pScriptInfo->WhichFont(nTmp);
713 // snap non-asian text to grid if next portion is ASIAN or
714 // there are no more portions in this line
715 // be careful when handling an underflow event: the gridkernportion
716 // could have been deleted
717 if ( nRestWidth > 0 && SwFontScript::CJK != nCurrScript &&
718 ! rInf.IsUnderflow() && ( bFull || SwFontScript::CJK == nNextScript ) )
720 OSL_ENSURE( pGridKernPortion, "No GridKernPortion available" );
722 // calculate size
723 SwLinePortion* pTmpPor = pGridKernPortion->GetNextPortion();
724 SwTwips nSumWidth = pPor->Width();
725 while ( pTmpPor )
727 nSumWidth = nSumWidth + pTmpPor->Width();
728 pTmpPor = pTmpPor->GetNextPortion();
731 const SwTwips i = nSumWidth ?
732 ( nSumWidth - 1 ) / nGridWidth + 1 :
734 const SwTwips nTmpWidth = i * nGridWidth;
735 const SwTwips nKernWidth = std::min(nTmpWidth - nSumWidth, nRestWidth);
736 const SwTwips nKernWidth_1 = pGrid->IsSnapToChars() ?
737 nKernWidth / 2 : 0;
739 OSL_ENSURE( nKernWidth <= nRestWidth,
740 "Not enough space left for adjusting non-asian text in grid mode" );
741 if (nKernWidth_1)
743 pGridKernPortion->Width( pGridKernPortion->Width() + nKernWidth_1 );
744 rInf.X( rInf.X() + nKernWidth_1 );
747 if ( ! bFull && nKernWidth - nKernWidth_1 > 0 )
748 new SwKernPortion( *pPor, static_cast<short>(nKernWidth - nKernWidth_1),
749 false, true );
751 pGridKernPortion = nullptr;
753 else if ( pPor->IsMultiPortion() || pPor->InFixMargGrp() ||
754 pPor->IsFlyCntPortion() || pPor->InNumberGrp() ||
755 pPor->InFieldGrp() || nCurrScript != nNextScript )
756 // next portion should snap to grid
757 pGridKernPortion = nullptr;
760 rInf.SetFull( bFull );
762 // Restportions from fields with multiple lines don't yet have the right ascent
763 if ( !pPor->GetLen() && !pPor->IsFlyPortion()
764 && !pPor->IsGrfNumPortion() && ! pPor->InNumberGrp()
765 && !pPor->IsMultiPortion() )
766 CalcAscent( rInf, pPor );
768 InsertPortion( rInf, pPor );
769 if (pPor->IsMultiPortion() && (!m_pMulti || m_pMulti->IsBidi()))
771 (void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion!
773 pPor = NewPortion(rInf, oMovedFlyIndex);
776 if( !rInf.IsStop() )
778 // The last right centered, decimal tab
779 SwTabPortion *pLastTab = rInf.GetLastTab();
780 if( pLastTab )
781 pLastTab->FormatEOL( rInf );
782 else if( rInf.GetLast() && rInf.LastKernPortion() )
783 rInf.GetLast()->FormatEOL( rInf );
785 if( m_pCurr->GetNextPortion() && m_pCurr->GetNextPortion()->InNumberGrp()
786 && static_cast<SwNumberPortion*>(m_pCurr->GetNextPortion())->IsHide() )
787 rInf.SetNumDone( false );
789 // Delete fly in any case
790 ClearFly( rInf );
792 // Reinit the tab overflow flag after the line
793 rInf.SetTabOverflow( false );
796 void SwTextFormatter::CalcAdjustLine( SwLineLayout *pCurrent )
798 if( SvxAdjust::Left != GetAdjust() && !m_pMulti)
800 pCurrent->SetFormatAdj(true);
801 if( IsFlyInCntBase() )
803 CalcAdjLine( pCurrent );
804 // For e.g. centered fly we need to switch the RefPoint
805 // That's why bAlways = true
806 UpdatePos( pCurrent, GetTopLeft(), GetStart(), true );
811 void SwTextFormatter::CalcAscent( SwTextFormatInfo &rInf, SwLinePortion *pPor )
813 bool bCalc = false;
814 if ( pPor->InFieldGrp() && static_cast<SwFieldPortion*>(pPor)->GetFont() )
816 // Numbering + InterNetFields can keep an own font, then their size is
817 // independent from hard attribute values
818 SwFont* pFieldFnt = static_cast<SwFieldPortion*>(pPor)->m_pFont.get();
819 SwFontSave aSave( rInf, pFieldFnt );
820 pPor->Height( rInf.GetTextHeight() );
821 pPor->SetAscent( rInf.GetAscent() );
822 bCalc = true;
824 // i#89179
825 // tab portion representing the list tab of a list label gets the
826 // same height and ascent as the corresponding number portion
827 else if ( pPor->InTabGrp() && pPor->GetLen() == TextFrameIndex(0) &&
828 rInf.GetLast() && rInf.GetLast()->InNumberGrp() &&
829 static_cast<const SwNumberPortion*>(rInf.GetLast())->HasFont() )
831 const SwLinePortion* pLast = rInf.GetLast();
832 pPor->Height( pLast->Height() );
833 pPor->SetAscent( pLast->GetAscent() );
835 else if (pPor->GetWhichPor() == PortionType::Bookmark
836 && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
838 // bookmark at end of paragraph: *don't* advance iterator, use the
839 // current font instead; it's possible that there's a font size on the
840 // paragraph and it's overridden on the last line of the paragraph and
841 // we don't want to apply it via SwBookmarkPortion and grow the line
842 // height (example: n758883.docx)
843 SwLinePortion const*const pLast = rInf.GetLast();
844 assert(pLast);
845 pPor->Height( pLast->Height(), false );
846 pPor->SetAscent( pLast->GetAscent() );
848 else
850 const SwLinePortion *pLast = rInf.GetLast();
851 bool bChg = false;
853 // In empty lines the attributes are switched on via SeekStart
854 const bool bFirstPor = rInf.GetLineStart() == rInf.GetIdx();
856 if ( pPor->IsQuoVadisPortion() )
857 bChg = SeekStartAndChg( rInf, true );
858 else
860 if( bFirstPor )
862 if( !rInf.GetText().isEmpty() )
864 if ((rInf.GetIdx() != TextFrameIndex(rInf.GetText().getLength())
865 || rInf.GetRest() // field continued - not empty
866 || !GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
867 DocumentSettingId::APPLY_TEXT_ATTR_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH))
868 && (pPor->GetLen() || !rInf.GetIdx()
869 || (m_pCurr != pLast && !pLast->IsFlyPortion())
870 || !m_pCurr->IsRest())) // instead of !rInf.GetRest()
872 bChg = SeekAndChg( rInf );
874 else
875 bChg = SeekAndChgBefore( rInf );
877 else if ( m_pMulti )
878 // do not open attributes starting at 0 in empty multi
879 // portions (rotated numbering followed by a footnote
880 // can cause trouble, because the footnote attribute
881 // starts at 0, but if we open it, the attribute handler
882 // cannot handle it.
883 bChg = false;
884 else
885 bChg = SeekStartAndChg( rInf );
887 else
888 bChg = SeekAndChg( rInf );
890 if( bChg || bFirstPor || !pPor->GetAscent()
891 || !rInf.GetLast()->InTextGrp() )
893 pPor->SetHangingBaseline( rInf.GetHangingBaseline() );
894 pPor->SetAscent( rInf.GetAscent() );
895 pPor->Height(rInf.GetTextHeight());
896 bCalc = true;
898 else
900 pPor->Height( pLast->Height() );
901 pPor->SetAscent( pLast->GetAscent() );
905 if( pPor->InTextGrp() && bCalc )
907 pPor->SetAscent(pPor->GetAscent() +
908 rInf.GetFont()->GetTopBorderSpace());
909 pPor->Height(pPor->Height() +
910 rInf.GetFont()->GetTopBorderSpace() +
911 rInf.GetFont()->GetBottomBorderSpace() );
915 namespace {
917 class SwMetaPortion : public SwTextPortion
919 Color m_aShadowColor;
920 public:
921 SwMetaPortion() { SetWhichPor( PortionType::Meta ); }
922 virtual void Paint( const SwTextPaintInfo &rInf ) const override;
923 void SetShadowColor(const Color& rCol ) { m_aShadowColor = rCol; }
926 /// A content control portion is a text portion that is inside RES_TXTATR_CONTENTCONTROL.
927 class SwContentControlPortion : public SwTextPortion
929 SwTextContentControl* m_pTextContentControl;
930 public:
931 SwContentControlPortion(SwTextContentControl* pTextContentControl);
932 virtual void Paint(const SwTextPaintInfo& rInf) const override;
934 /// Emits a PDF form widget for this portion on success, does nothing on failure.
935 bool DescribePDFControl(const SwTextPaintInfo& rInf) const;
939 void SwMetaPortion::Paint( const SwTextPaintInfo &rInf ) const
941 if ( Width() )
943 rInf.DrawViewOpt( *this, PortionType::Meta,
944 // custom shading (RDF metadata)
945 COL_BLACK == m_aShadowColor
946 ? nullptr
947 : &m_aShadowColor );
949 SwTextPortion::Paint( rInf );
953 SwContentControlPortion::SwContentControlPortion(SwTextContentControl* pTextContentControl)
954 : m_pTextContentControl(pTextContentControl)
956 SetWhichPor(PortionType::ContentControl);
959 bool SwContentControlPortion::DescribePDFControl(const SwTextPaintInfo& rInf) const
961 auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(rInf.GetOut()->GetExtOutDevData());
962 if (!pPDFExtOutDevData)
964 return false;
967 if (!pPDFExtOutDevData->GetIsExportFormFields())
969 return false;
972 if (!m_pTextContentControl)
974 return false;
977 const SwFormatContentControl& rFormatContentControl = m_pTextContentControl->GetContentControl();
978 const std::shared_ptr<SwContentControl>& pContentControl = rFormatContentControl.GetContentControl();
979 if (!pContentControl)
981 return false;
984 SwTextNode* pTextNode = pContentControl->GetTextNode();
985 SwDoc& rDoc = pTextNode->GetDoc();
986 if (rDoc.IsInHeaderFooter(*pTextNode))
988 // Form control in header/footer makes no sense, would allow multiple values for the same
989 // control.
990 return false;
993 // Check if this is the first content control portion of this content control.
994 sal_Int32 nStart = m_pTextContentControl->GetStart();
995 sal_Int32 nEnd = *m_pTextContentControl->GetEnd();
996 TextFrameIndex nViewStart = rInf.GetTextFrame()->MapModelToView(pTextNode, nStart);
997 TextFrameIndex nViewEnd = rInf.GetTextFrame()->MapModelToView(pTextNode, nEnd);
998 // The content control portion starts 1 char after the starting dummy character.
999 if (rInf.GetIdx() != nViewStart + TextFrameIndex(1))
1001 // Ignore: don't process and also don't emit plain text fallback.
1002 return true;
1005 const SwPaM aPam(*pTextNode, nEnd, *pTextNode, nStart);
1006 static sal_Unicode const aForbidden[] = {
1007 CH_TXTATR_BREAKWORD,
1010 const OUString aText = comphelper::string::removeAny(aPam.GetText(), aForbidden);
1012 std::unique_ptr<vcl::PDFWriter::AnyWidget> pDescriptor;
1013 switch (pContentControl->GetType())
1015 case SwContentControlType::RICH_TEXT:
1016 case SwContentControlType::PLAIN_TEXT:
1018 pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>();
1019 auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
1020 pEditWidget->MultiLine = true;
1021 break;
1023 case SwContentControlType::CHECKBOX:
1025 pDescriptor = std::make_unique<vcl::PDFWriter::CheckBoxWidget>();
1026 auto pCheckBoxWidget = static_cast<vcl::PDFWriter::CheckBoxWidget*>(pDescriptor.get());
1027 pCheckBoxWidget->Checked = pContentControl->GetChecked();
1028 // If it's checked already, then leave the default "Yes" OnValue unchanged, so the
1029 // appropriate appearance is found by PDF readers.
1030 if (!pCheckBoxWidget->Checked)
1032 pCheckBoxWidget->OnValue = pContentControl->GetCheckedState();
1033 pCheckBoxWidget->OffValue = pContentControl->GetUncheckedState();
1035 break;
1037 case SwContentControlType::DROP_DOWN_LIST:
1039 pDescriptor = std::make_unique<vcl::PDFWriter::ListBoxWidget>();
1040 auto pListWidget = static_cast<vcl::PDFWriter::ListBoxWidget*>(pDescriptor.get());
1041 pListWidget->DropDown = true;
1042 sal_Int32 nIndex = 0;
1043 bool bTextFound = false;
1044 for (const auto& rItem : pContentControl->GetListItems())
1046 pListWidget->Entries.push_back(rItem.m_aDisplayText);
1047 if (rItem.m_aDisplayText == aText)
1049 pListWidget->SelectedEntries.push_back(nIndex);
1050 bTextFound = true;
1052 ++nIndex;
1054 if (!aText.isEmpty() && !bTextFound)
1056 // The selected entry has to be an index, if there is no index for it, insert one at
1057 // the start.
1058 pListWidget->Entries.insert(pListWidget->Entries.begin(), aText);
1059 pListWidget->SelectedEntries.push_back(0);
1061 break;
1063 case SwContentControlType::COMBO_BOX:
1065 pDescriptor = std::make_unique<vcl::PDFWriter::ComboBoxWidget>();
1066 auto pComboWidget = static_cast<vcl::PDFWriter::ComboBoxWidget*>(pDescriptor.get());
1067 for (const auto& rItem : pContentControl->GetListItems())
1069 pComboWidget->Entries.push_back(rItem.m_aDisplayText);
1071 break;
1073 case SwContentControlType::DATE:
1075 pDescriptor = std::make_unique<vcl::PDFWriter::EditWidget>();
1076 auto pEditWidget = static_cast<vcl::PDFWriter::EditWidget*>(pDescriptor.get());
1077 pEditWidget->Format = vcl::PDFWriter::Date;
1078 // GetDateFormat() uses a syntax that works with SvNumberFormatter::PutEntry(), PDF's
1079 // AFDate_FormatEx() uses a similar syntax, but uses lowercase characters in case of
1080 // "Y", "M" and "D" at least.
1081 pEditWidget->DateFormat = pContentControl->GetDateFormat().toAsciiLowerCase();
1082 break;
1084 default:
1085 break;
1088 if (!pDescriptor)
1090 return false;
1093 bool bShrinkPageForPostIts = pPDFExtOutDevData->GetIsExportNotesInMargin()
1094 && sw_GetPostIts(rDoc.getIDocumentFieldsAccess(), nullptr);
1095 const SwFont* pFont = rInf.GetFont();
1096 if (pFont)
1098 pDescriptor->TextFont = pFont->GetActualFont();
1099 if (bShrinkPageForPostIts)
1101 // Page area is scaled down so we have space for comments. Scale down the font height
1102 // for the content of the widgets, too.
1103 double fScale = SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale();
1104 pDescriptor->TextFont.SetFontHeight(pDescriptor->TextFont.GetFontHeight() * fScale);
1107 // Need to transport the color explicitly, so it's applied to both already filled in and
1108 // future content.
1109 pDescriptor->TextColor = pFont->GetColor();
1112 // Description for accessibility purposes.
1113 if (!pContentControl->GetAlias().isEmpty())
1115 pDescriptor->Description = pContentControl->GetAlias();
1118 // Map the text of the content control to the descriptor's text.
1119 pDescriptor->Text = aText;
1121 // Calculate the bounding rectangle of this content control, which can be one or more layout
1122 // portions in one or more lines.
1123 SwRect aLocation;
1124 auto pTextFrame = const_cast<SwTextFrame*>(rInf.GetTextFrame());
1125 SwTextSizeInfo aInf(pTextFrame);
1126 SwTextCursor aLine(pTextFrame, &aInf);
1127 SwRect aStartRect, aEndRect;
1128 aLine.GetCharRect(&aStartRect, nViewStart);
1129 aLine.GetCharRect(&aEndRect, nViewEnd);
1131 // Handling RTL text direction
1132 if(rInf.GetTextFrame()->IsRightToLeft())
1134 rInf.GetTextFrame()->SwitchLTRtoRTL( aStartRect );
1135 rInf.GetTextFrame()->SwitchLTRtoRTL( aEndRect );
1137 // TODO: handle rInf.GetTextFrame()->IsVertical()
1139 aLocation = aStartRect;
1140 aLocation.Union(aEndRect);
1142 // PDF spec 12.5.2 Annotation Dictionaries says the default border with is 1pt wide, increase
1143 // the rectangle to compensate for that, otherwise the text will be cut off at the end.
1144 aLocation.AddTop(-20);
1145 aLocation.AddBottom(20);
1146 aLocation.AddLeft(-20);
1147 aLocation.AddRight(20);
1149 tools::Rectangle aRect = aLocation.SVRect();
1150 if (bShrinkPageForPostIts)
1152 // Map the rectangle of the form widget, similar to how it's done for e.g. hyperlinks.
1153 const SwPageFrame* pPageFrame = pTextFrame->FindPageFrame();
1154 if (pPageFrame)
1156 aRect = SwEnhancedPDFExportHelper::MapSwRectToPDFRect(pPageFrame, aRect);
1159 pDescriptor->Location = aRect;
1161 pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form);
1162 pPDFExtOutDevData->CreateControl(*pDescriptor);
1163 pPDFExtOutDevData->EndStructureElement();
1165 return true;
1168 void SwContentControlPortion::Paint(const SwTextPaintInfo& rInf) const
1170 if (Width())
1172 rInf.DrawViewOpt(*this, PortionType::ContentControl);
1174 if (DescribePDFControl(rInf))
1176 return;
1179 SwTextPortion::Paint(rInf);
1183 namespace sw::mark {
1184 OUString ExpandFieldmark(Fieldmark* pBM)
1186 if (pBM->GetFieldname() == ODF_FORMCHECKBOX)
1188 ::sw::mark::CheckboxFieldmark const*const pCheckboxFm(
1189 dynamic_cast<CheckboxFieldmark const*>(pBM));
1190 assert(pCheckboxFm);
1191 return pCheckboxFm->IsChecked()
1192 ? u"\u2612"_ustr
1193 : u"\u2610"_ustr;
1195 assert(pBM->GetFieldname() == ODF_FORMDROPDOWN);
1196 const Fieldmark::parameter_map_t* const pParameters = pBM->GetParameters();
1197 sal_Int32 nCurrentIdx = 0;
1198 const Fieldmark::parameter_map_t::const_iterator pResult = pParameters->find(ODF_FORMDROPDOWN_RESULT);
1199 if(pResult != pParameters->end())
1200 pResult->second >>= nCurrentIdx;
1202 const Fieldmark::parameter_map_t::const_iterator pListEntries = pParameters->find(ODF_FORMDROPDOWN_LISTENTRY);
1203 if (pListEntries != pParameters->end())
1205 uno::Sequence< OUString > vListEntries;
1206 pListEntries->second >>= vListEntries;
1207 if (nCurrentIdx < vListEntries.getLength())
1208 return vListEntries[nCurrentIdx];
1211 return vEnSpaces;
1215 SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const
1217 SwTextPortion *pPor = nullptr;
1218 if( GetFnt()->IsTox() )
1220 pPor = new SwToxPortion;
1222 else if ( GetFnt()->IsInputField() )
1224 if (rInf.GetOpt().IsFieldName())
1226 // assume this is only the *first* portion and follows will be created elsewhere => input field must start at Idx
1227 assert(rInf.GetText()[sal_Int32(rInf.GetIdx())] == CH_TXT_ATR_INPUTFIELDSTART);
1228 TextFrameIndex nFieldLen(-1);
1229 for (TextFrameIndex i = rInf.GetIdx() + TextFrameIndex(1); ; ++i)
1231 assert(rInf.GetText()[sal_Int32(i)] != CH_TXT_ATR_INPUTFIELDSTART); // can't nest
1232 if (rInf.GetText()[sal_Int32(i)] == CH_TXT_ATR_INPUTFIELDEND)
1234 nFieldLen = i + TextFrameIndex(1) - rInf.GetIdx();
1235 break;
1238 assert(2 <= sal_Int32(nFieldLen));
1239 pPor = new SwFieldPortion(SwFieldType::GetTypeStr(SwFieldTypesEnum::Input), nullptr, nFieldLen);
1241 else
1243 pPor = new SwTextInputFieldPortion();
1246 else
1248 if( GetFnt()->IsRef() )
1249 pPor = new SwRefPortion;
1250 else if (GetFnt()->IsMeta())
1252 auto pMetaPor = new SwMetaPortion;
1254 // set custom LO_EXT_SHADING color, if it exists
1255 SwTextFrame const*const pFrame(rInf.GetTextFrame());
1256 SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
1257 SwPaM aPam(aPosition);
1258 uno::Reference<text::XTextContent> const xRet(
1259 SwUnoCursorHelper::GetNestedTextContent(
1260 *aPam.GetPointNode().GetTextNode(), aPosition.GetContentIndex(), false) );
1261 if (xRet.is())
1263 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
1264 static uno::Reference< uno::XComponentContext > xContext(
1265 ::comphelper::getProcessComponentContext());
1267 static uno::Reference< rdf::XURI > xODF_SHADING(
1268 rdf::URI::createKnown(xContext, rdf::URIs::LO_EXT_SHADING), uno::UNO_SET_THROW);
1270 if (const SwDocShell* pShell = rDoc.GetDocShell())
1272 rtl::Reference<SwXTextDocument> xDocumentMetadataAccess(pShell->GetBaseModel());
1274 const css::uno::Reference<css::rdf::XResource> xSubject(xRet, uno::UNO_QUERY);
1275 const uno::Reference<rdf::XRepository> xRepository =
1276 xDocumentMetadataAccess->getRDFRepository();
1277 const uno::Reference<container::XEnumeration> xEnum(
1278 xRepository->getStatements(xSubject, xODF_SHADING, nullptr), uno::UNO_SET_THROW);
1280 while (xEnum->hasMoreElements())
1282 rdf::Statement stmt;
1283 if (!(xEnum->nextElement() >>= stmt)) {
1284 throw uno::RuntimeException();
1286 const uno::Reference<rdf::XLiteral> xObject(stmt.Object, uno::UNO_QUERY);
1287 if (!xObject.is()) continue;
1288 if (xEnum->hasMoreElements()) {
1289 SAL_INFO("sw.uno", "ignoring other odf:shading statements");
1291 Color rColor = Color::STRtoRGB(xObject->getValue());
1292 pMetaPor->SetShadowColor(rColor);
1293 break;
1297 pPor = pMetaPor;
1299 else if (GetFnt()->IsContentControl())
1301 SwTextFrame const*const pFrame(rInf.GetTextFrame());
1302 SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
1303 SwTextNode* pTextNode = aPosition.GetNode().GetTextNode();
1304 SwTextContentControl* pTextContentControl = nullptr;
1305 if (pTextNode)
1307 sal_Int32 nIndex = aPosition.GetContentIndex();
1308 if (SwTextAttr* pAttr = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent))
1310 pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
1313 pPor = new SwContentControlPortion(pTextContentControl);
1315 else
1317 // Only at the End!
1318 // If pCurr does not have a width, it can however already have content.
1319 // E.g. for non-displayable characters
1321 auto const ch(rInf.GetChar(rInf.GetIdx()));
1322 SwTextFrame const*const pFrame(rInf.GetTextFrame());
1323 SwPosition aPosition(pFrame->MapViewToModelPos(rInf.GetIdx()));
1324 sw::mark::Fieldmark *pBM = pFrame->GetDoc().getIDocumentMarkAccess()->getInnerFieldmarkFor(aPosition);
1325 if(pBM != nullptr && pBM->GetFieldname( ) == ODF_FORMDATE)
1327 if (ch == CH_TXT_ATR_FIELDSTART)
1328 pPor = new SwFieldFormDatePortion(pBM, true);
1329 else if (ch == CH_TXT_ATR_FIELDSEP)
1330 pPor = new SwFieldMarkPortion(); // it's added in DateFieldmark?
1331 else if (ch == CH_TXT_ATR_FIELDEND)
1332 pPor = new SwFieldFormDatePortion(pBM, false);
1334 else if (ch == CH_TXT_ATR_FIELDSTART)
1335 pPor = new SwFieldMarkPortion();
1336 else if (ch == CH_TXT_ATR_FIELDSEP)
1337 pPor = new SwFieldMarkPortion();
1338 else if (ch == CH_TXT_ATR_FIELDEND)
1339 pPor = new SwFieldMarkPortion();
1340 else if (ch == CH_TXT_ATR_FORMELEMENT)
1342 OSL_ENSURE(pBM != nullptr, "Where is my form field bookmark???");
1343 if (pBM != nullptr)
1345 if (pBM->GetFieldname( ) == ODF_FORMCHECKBOX)
1347 pPor = new SwFieldFormCheckboxPortion();
1349 else if (pBM->GetFieldname( ) == ODF_FORMDROPDOWN)
1351 pPor = new SwFieldFormDropDownPortion(pBM, sw::mark::ExpandFieldmark(pBM));
1353 /* we need to check for ODF_FORMTEXT for scenario having FormFields inside FORMTEXT.
1354 * Otherwise file will crash on open.
1356 else if (pBM->GetFieldname( ) == ODF_FORMTEXT)
1358 pPor = new SwFieldMarkPortion();
1362 if( !pPor )
1364 if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() && !GetFnt()->IsURL() )
1365 pPor = m_pCurr;
1366 else
1368 pPor = new SwTextPortion;
1369 if (pBM && pBM->GetFieldname() == ODF_FORMTEXT)
1370 pPor->SetFieldmarkText(true);
1375 return pPor;
1378 // We calculate the length, the following portion limits are defined:
1379 // 1) Tabs
1380 // 2) Linebreaks
1381 // 3) CH_TXTATR_BREAKWORD / CH_TXTATR_INWORD
1382 // 4) next attribute change
1384 SwTextPortion *SwTextFormatter::NewTextPortion( SwTextFormatInfo &rInf )
1386 // If we're at the line's beginning, we take pCurr
1387 // If pCurr is not derived from SwTextPortion, we need to duplicate
1388 Seek( rInf.GetIdx() );
1389 SwTextPortion *pPor = WhichTextPor( rInf );
1391 TextFrameIndex nNextChg(rInf.GetText().getLength());
1393 // until next attribute change:
1394 const TextFrameIndex nNextAttr = GetNextAttr();
1395 // until next layout-breaking attribute change:
1396 const TextFrameIndex nNextLayoutBreakAttr = GetNextLayoutBreakAttr();
1397 // end of script type:
1398 const TextFrameIndex nNextScript = m_pScriptInfo->NextScriptChg(rInf.GetIdx());
1399 // end of direction:
1400 const TextFrameIndex nNextDir = m_pScriptInfo->NextDirChg(rInf.GetIdx());
1401 // hidden change (potentially via bookmark):
1402 const TextFrameIndex nNextHidden = m_pScriptInfo->NextHiddenChg(rInf.GetIdx());
1403 // bookmarks
1404 const TextFrameIndex nNextBookmark = m_pScriptInfo->NextBookmark(rInf.GetIdx());
1406 auto nNextContext = std::min({ nNextChg, nNextLayoutBreakAttr, nNextScript, nNextDir });
1407 nNextChg = std::min({ nNextChg, nNextAttr, nNextScript, nNextDir, nNextHidden, nNextBookmark });
1409 // Turbo boost:
1410 // We assume that font characters are not larger than twice
1411 // as wide as height.
1412 // Very crazy: we need to take the ascent into account.
1414 // Mind the trap! GetSize() contains the wished-for height, the real height
1415 // is only known in CalcAscent!
1417 // The ratio is even crazier: a blank in Times New Roman has an ascent of
1418 // 182, a height of 200 and a width of 53!
1419 // It follows that a line with a lot of blanks is processed incorrectly.
1420 // Therefore we increase from factor 2 to 8 (due to negative kerning).
1422 pPor->SetLen(TextFrameIndex(1));
1423 CalcAscent( rInf, pPor );
1425 const SwFont* pTmpFnt = rInf.GetFont();
1426 auto nCharWidthGuess = std::min(pTmpFnt->GetHeight(), pPor->GetAscent()) / 8;
1427 if (!nCharWidthGuess)
1428 nCharWidthGuess = 1;
1429 auto nExpect = rInf.GetIdx() + TextFrameIndex(rInf.GetLineWidth() / nCharWidthGuess);
1430 if (nExpect > rInf.GetIdx())
1432 nNextChg = std::min(nNextChg, nExpect);
1433 nNextContext = std::min(nNextContext, nExpect);
1436 // we keep an invariant during method calls:
1437 // there are no portion ending characters like hard spaces
1438 // or tabs in [ nLeftScanIdx, nRightScanIdx ]
1439 if ( m_nLeftScanIdx <= rInf.GetIdx() && rInf.GetIdx() <= m_nRightScanIdx )
1441 if ( nNextChg > m_nRightScanIdx )
1442 nNextChg = m_nRightScanIdx =
1443 rInf.ScanPortionEnd( m_nRightScanIdx, nNextChg );
1445 else
1447 m_nLeftScanIdx = rInf.GetIdx();
1448 nNextChg = m_nRightScanIdx =
1449 rInf.ScanPortionEnd( rInf.GetIdx(), nNextChg );
1452 pPor->SetLen( nNextChg - rInf.GetIdx() );
1453 rInf.SetLen( pPor->GetLen() );
1455 // Generate a new layout context for the text portion. This is necessary
1456 // for the first text portion in a paragraph, or for any successive
1457 // portions that are outside of the bounds of the previous context.
1458 if (!rInf.GetLayoutContext().has_value()
1459 || rInf.GetLayoutContext()->m_nBegin < rInf.GetLineStart().get()
1460 || rInf.GetLayoutContext()->m_nEnd < nNextChg.get())
1462 // The layout context must terminate at special characters
1463 sal_Int32 nEnd = rInf.GetIdx().get();
1464 for (; nEnd < nNextContext.get(); ++nEnd)
1466 bool bAtEnd = false;
1467 switch (rInf.GetText()[nEnd])
1469 case CH_TXTATR_BREAKWORD:
1470 case CH_TXTATR_INWORD:
1471 case CH_TXTATR_TAB:
1472 case CH_TXTATR_NEWLINE:
1473 case CH_TXT_ATR_INPUTFIELDSTART:
1474 case CH_TXT_ATR_INPUTFIELDEND:
1475 case CH_TXT_ATR_FORMELEMENT:
1476 case CH_TXT_ATR_FIELDSTART:
1477 case CH_TXT_ATR_FIELDSEP:
1478 case CH_TXT_ATR_FIELDEND:
1479 case CHAR_SOFTHYPHEN:
1480 bAtEnd = true;
1481 break;
1483 default:
1484 break;
1487 if (bAtEnd)
1489 break;
1493 std::optional<SwLinePortionLayoutContext> nNewContext;
1494 if (rInf.GetIdx().get() != nEnd)
1496 nNewContext = SwLinePortionLayoutContext{ rInf.GetIdx().get(), nEnd };
1499 rInf.SetLayoutContext(nNewContext);
1502 pPor->SetLayoutContext(rInf.GetLayoutContext());
1504 return pPor;
1507 // first portions have no length
1508 SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf)
1510 SwLinePortion *pPor = nullptr;
1512 if( rInf.GetRest() )
1514 // Tabs and fields
1515 if( '\0' != rInf.GetHookChar() )
1516 return nullptr;
1518 pPor = rInf.GetRest();
1519 if( pPor->IsErgoSumPortion() )
1520 rInf.SetErgoDone(true);
1521 else
1522 if( pPor->IsFootnoteNumPortion() )
1523 rInf.SetFootnoteDone(true);
1524 else
1525 if( pPor->InNumberGrp() )
1526 rInf.SetNumDone(true);
1528 rInf.SetRest(nullptr);
1529 m_pCurr->SetRest( true );
1530 return pPor;
1533 // We can stand in the follow, it's crucial that
1534 // pFrame->GetOffset() == 0!
1535 if( rInf.GetIdx() )
1537 // We now too can elongate FootnotePortions and ErgoSumPortions
1539 // 1. The ErgoSumTexts
1540 if( !rInf.IsErgoDone() )
1542 if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
1543 pPor = NewErgoSumPortion( rInf );
1544 rInf.SetErgoDone( true );
1547 // 2. Arrow portions
1548 if( !pPor && !rInf.IsArrowDone() )
1550 if( m_pFrame->GetOffset() && !m_pFrame->IsFollow() &&
1551 rInf.GetIdx() == m_pFrame->GetOffset() )
1552 pPor = new SwArrowPortion( *m_pCurr );
1553 rInf.SetArrowDone( true );
1556 // 3. Kerning portions at beginning of line in grid mode
1557 if ( ! pPor && ! m_pCurr->GetNextPortion() )
1559 SwTextGridItem const*const pGrid(
1560 GetGridItem(GetTextFrame()->FindPageFrame()));
1561 if ( pGrid )
1562 pPor = new SwKernPortion( *m_pCurr );
1565 // 4. The line rests (multiline fields)
1566 if( !pPor )
1568 pPor = rInf.GetRest();
1569 // Only for pPor of course
1570 if( pPor )
1572 m_pCurr->SetRest( true );
1573 rInf.SetRest(nullptr);
1577 else
1579 // 5. The foot note count
1580 if( !rInf.IsFootnoteDone() )
1582 OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
1583 "Rotated number portion trouble" );
1585 const bool bFootnoteNum = m_pFrame->IsFootnoteNumFrame();
1586 rInf.GetParaPortion()->SetFootnoteNum( bFootnoteNum );
1587 if( bFootnoteNum )
1588 pPor = NewFootnoteNumPortion( rInf );
1589 rInf.SetFootnoteDone( true );
1592 // 6. The ErgoSumTexts of course also exist in the TextMaster,
1593 // it's crucial whether the SwFootnoteFrame is aFollow
1594 if( !rInf.IsErgoDone() && !pPor && ! rInf.IsMulti() )
1596 if( m_pFrame->IsInFootnote() && !m_pFrame->GetIndPrev() )
1597 pPor = NewErgoSumPortion( rInf );
1598 rInf.SetErgoDone( true );
1601 // 7. The numbering
1602 if( !rInf.IsNumDone() && !pPor )
1604 OSL_ENSURE( ( ! rInf.IsMulti() && ! m_pMulti ) || m_pMulti->HasRotation(),
1605 "Rotated number portion trouble" );
1607 // If we're in the follow, then of course not
1608 if (GetTextFrame()->GetTextNodeForParaProps()->GetNumRule())
1609 pPor = NewNumberPortion( rInf );
1610 rInf.SetNumDone( true );
1612 // 8. The DropCaps
1613 if( !pPor && GetDropFormat() && ! rInf.IsMulti() )
1614 pPor = NewDropPortion( rInf );
1616 // 9. Kerning portions at beginning of line in grid mode
1617 if ( !pPor && !m_pCurr->GetNextPortion() )
1619 SwTextGridItem const*const pGrid(
1620 GetGridItem(GetTextFrame()->FindPageFrame()));
1621 if ( pGrid )
1622 pPor = new SwKernPortion( *m_pCurr );
1626 // 10. Decimal tab portion at the beginning of each line in table cells
1627 if ( !pPor && !m_pCurr->GetNextPortion() &&
1628 GetTextFrame()->IsInTab() &&
1629 GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT))
1631 pPor = NewTabPortion( rInf, true );
1634 // 11. suffix of meta-field
1635 if (!pPor)
1637 pPor = TryNewNoLengthPortion(rInf);
1640 // 12. bookmarks
1641 // check this *last* so that BuildMultiPortion() can find it!
1642 if (!pPor && rInf.CheckCurrentPosBookmark())
1644 const auto bookmark = m_pScriptInfo->GetBookmarks(rInf.GetIdx());
1645 if (!bookmark.empty())
1647 // only for character width, maybe replaced with ] later
1648 sal_Unicode mark = '[';
1650 pPor = new SwBookmarkPortion(mark, bookmark);
1654 return pPor;
1657 static bool lcl_OldFieldRest( const SwLineLayout* pCurr )
1659 if( !pCurr->GetNext() )
1660 return false;
1661 const SwLinePortion *pPor = pCurr->GetNext()->GetNextPortion();
1662 bool bRet = false;
1663 while( pPor && !bRet )
1665 bRet = (pPor->InFieldGrp() && static_cast<const SwFieldPortion*>(pPor)->IsFollow()) ||
1666 (pPor->IsMultiPortion() && static_cast<const SwMultiPortion*>(pPor)->IsFollowField());
1667 if( !pPor->GetLen() )
1668 break;
1669 pPor = pPor->GetNextPortion();
1671 return bRet;
1674 /* NewPortion sets rInf.nLen
1675 * A SwTextPortion is limited by a tab, break, txtatr or attr change
1676 * We can have three cases:
1677 * 1) The line is full and the wrap was not emulated
1678 * -> return 0;
1679 * 2) The line is full and a wrap was emulated
1680 * -> Reset width and return new FlyPortion
1681 * 3) We need to construct a new portion
1682 * -> CalcFlyWidth emulates the width and return portion, if needed
1685 SwLinePortion *SwTextFormatter::NewPortion(SwTextFormatInfo &rInf,
1686 ::std::optional<TextFrameIndex> const oMovedFlyIndex)
1688 if (oMovedFlyIndex && *oMovedFlyIndex <= rInf.GetIdx())
1690 SAL_WARN_IF(*oMovedFlyIndex != rInf.GetIdx(), "sw.core", "stopping too late, no portion break at fly anchor?");
1691 rInf.SetStop(true);
1692 return nullptr;
1695 // Underflow takes precedence
1696 rInf.SetStopUnderflow( false );
1697 if( rInf.GetUnderflow() )
1699 OSL_ENSURE( rInf.IsFull(), "SwTextFormatter::NewPortion: underflow but not full" );
1700 return Underflow( rInf );
1703 // If the line is full, flys and Underflow portions could be waiting ...
1704 if( rInf.IsFull() )
1706 // LineBreaks and Flys (bug05.sdw)
1707 // IsDummy()
1708 if( rInf.IsNewLine() && (!rInf.GetFly() || !m_pCurr->IsDummy()) )
1709 return nullptr;
1711 // When the text bumps into the Fly, or when the Fly comes first because
1712 // it juts out over the left edge, GetFly() is returned.
1713 // When IsFull() and no GetFly() is available, naturally zero is returned.
1714 if( rInf.GetFly() )
1716 if( rInf.GetLast()->IsBreakPortion() )
1718 delete rInf.GetFly();
1719 rInf.SetFly( nullptr );
1722 return rInf.GetFly();
1725 // A nasty special case: A frame without wrap overlaps the Footnote area.
1726 // We must declare the Footnote portion as rest of line, so that
1727 // SwTextFrame::Format doesn't abort (the text mass already was formatted).
1728 if( rInf.GetRest() )
1729 rInf.SetNewLine( true );
1730 else
1732 // When the next line begins with a rest of a field, but now no
1733 // rest remains, the line must definitely be formatted anew!
1734 if( lcl_OldFieldRest( GetCurr() ) )
1735 rInf.SetNewLine( true );
1736 else
1738 SwLinePortion *pFirst = WhichFirstPortion( rInf );
1739 if( pFirst )
1741 rInf.SetNewLine( true );
1742 if( pFirst->InNumberGrp() )
1743 rInf.SetNumDone( false) ;
1744 delete pFirst;
1749 return nullptr;
1752 SwLinePortion *pPor = WhichFirstPortion( rInf );
1754 // Check for Hidden Portion:
1755 if ( !pPor )
1757 TextFrameIndex nEnd = rInf.GetIdx();
1758 if ( ::lcl_BuildHiddenPortion( rInf, nEnd ) )
1759 pPor = new SwHiddenTextPortion( nEnd - rInf.GetIdx() );
1762 if( !pPor )
1764 if( ( !m_pMulti || m_pMulti->IsBidi() ) &&
1765 // i#42734
1766 // No multi portion if there is a hook character waiting:
1767 ( !rInf.GetRest() || '\0' == rInf.GetHookChar() ) )
1769 // We open a multiportion part, if we enter a multi-line part
1770 // of the paragraph.
1771 TextFrameIndex nEnd = rInf.GetIdx();
1772 std::optional<SwMultiCreator> pCreate = rInf.GetMultiCreator( nEnd, m_pMulti );
1773 if( pCreate )
1775 SwMultiPortion* pTmp = nullptr;
1777 if ( SwMultiCreatorId::Bidi == pCreate->nId )
1778 pTmp = new SwBidiPortion( nEnd, pCreate->nLevel );
1779 else if ( SwMultiCreatorId::Ruby == pCreate->nId )
1781 pTmp = new SwRubyPortion( *pCreate, *rInf.GetFont(),
1782 GetTextFrame()->GetDoc().getIDocumentSettingAccess(),
1783 nEnd, TextFrameIndex(0), rInf );
1785 else if( SwMultiCreatorId::Rotate == pCreate->nId )
1787 pTmp = new SwRotatedPortion( *pCreate, nEnd,
1788 GetTextFrame()->IsRightToLeft() );
1789 GetTextFrame()->SetHasRotatedPortions(true);
1791 else
1792 pTmp = new SwDoubleLinePortion( *pCreate, nEnd );
1794 pCreate.reset();
1795 CalcFlyWidth( rInf );
1797 return pTmp;
1800 // Tabs and Fields
1801 sal_Unicode cChar = rInf.GetHookChar();
1803 if( cChar )
1805 /* We fetch cChar again to be sure that the tab is pending now and
1806 * didn't move to the next line (as happens behind frames).
1807 * However, when a FieldPortion is in the rest, we must naturally fetch
1808 * the cChar from the field content, e.g. DecimalTabs and fields (22615)
1810 if( !rInf.GetRest() || !rInf.GetRest()->InFieldGrp() )
1811 cChar = rInf.GetChar( rInf.GetIdx() );
1812 rInf.ClearHookChar();
1814 else
1816 if (rInf.GetIdx() >= TextFrameIndex(rInf.GetText().getLength()))
1818 rInf.SetFull(true);
1819 CalcFlyWidth( rInf );
1820 return pPor;
1822 cChar = rInf.GetChar( rInf.GetIdx() );
1825 switch( cChar )
1827 case CH_TAB:
1828 pPor = NewTabPortion( rInf, false ); break;
1830 case CH_BREAK:
1832 SwTextAttr* pHint = GetAttr(rInf.GetIdx());
1833 pPor = new SwBreakPortion(*rInf.GetLast(), pHint);
1834 break;
1837 case CHAR_SOFTHYPHEN: // soft hyphen
1838 pPor = new SwSoftHyphPortion; break;
1840 case CHAR_HARDBLANK: // no-break space
1841 // Please check tdf#115067 if you want to edit the char
1842 pPor = new SwBlankPortion( cChar ); break;
1844 case CHAR_HARDHYPHEN: // non-breaking hyphen
1845 pPor = new SwBlankPortion( '-' ); break;
1847 case CHAR_ZWSP: // zero width space
1848 case CHAR_WJ : // word joiner
1849 pPor = new SwControlCharPortion( cChar ); break;
1851 case CH_TXTATR_BREAKWORD:
1852 case CH_TXTATR_INWORD:
1853 if( rInf.HasHint( rInf.GetIdx() ) )
1855 pPor = NewExtraPortion( rInf );
1856 break;
1858 [[fallthrough]];
1859 default :
1861 SwTabPortion* pLastTabPortion = rInf.GetLastTab();
1862 if ( pLastTabPortion && cChar == rInf.GetTabDecimal() )
1864 // Abandon dec. tab position if line is full
1865 // We have a decimal tab portion in the line and the next character has to be
1866 // aligned at the tab stop position. We store the width from the beginning of
1867 // the tab stop portion up to the portion containing the decimal separator:
1868 if (GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_COMPAT) /*rInf.GetVsh()->IsTabCompat();*/ &&
1869 PortionType::TabDecimal == pLastTabPortion->GetWhichPor() )
1871 OSL_ENSURE( rInf.X() >= pLastTabPortion->GetFix(), "Decimal tab stop position cannot be calculated" );
1872 const SwTwips nWidthOfPortionsUpToDecimalPosition = rInf.X() - pLastTabPortion->GetFix();
1873 static_cast<SwTabDecimalPortion*>(pLastTabPortion)->SetWidthOfPortionsUpToDecimalPosition( nWidthOfPortionsUpToDecimalPosition );
1874 rInf.SetTabDecimal( 0 );
1876 else
1877 rInf.SetFull( rInf.GetLastTab()->Format( rInf ) );
1880 if( rInf.GetRest() )
1882 if( rInf.IsFull() )
1884 rInf.SetNewLine(true);
1885 return nullptr;
1887 pPor = rInf.GetRest();
1888 rInf.SetRest(nullptr);
1890 else
1892 if( rInf.IsFull() )
1893 return nullptr;
1894 pPor = NewTextPortion( rInf );
1896 break;
1900 // if a portion is created despite there being a pending RestPortion,
1901 // then it is a field which has been split (e.g. because it contains a Tab)
1902 if( pPor && rInf.GetRest() )
1903 pPor->SetLen(TextFrameIndex(0));
1905 // robust:
1906 if( !pPor || rInf.IsStop() )
1908 delete pPor;
1909 return nullptr;
1913 assert(pPor && "can only reach here with pPor existing");
1915 // Special portions containing numbers (footnote anchor, footnote number,
1916 // numbering) can be contained in a rotated portion, if the user
1917 // choose a rotated character attribute.
1918 if (!m_pMulti)
1920 if ( pPor->IsFootnotePortion() )
1922 const SwTextFootnote* pTextFootnote = static_cast<SwFootnotePortion*>(pPor)->GetTextFootnote();
1924 if ( pTextFootnote )
1926 SwFormatFootnote& rFootnote = const_cast<SwFormatFootnote&>(pTextFootnote->GetFootnote());
1927 const SwDoc *const pDoc = &rInf.GetTextFrame()->GetDoc();
1928 const SwEndNoteInfo* pInfo;
1929 if( rFootnote.IsEndNote() )
1930 pInfo = &pDoc->GetEndNoteInfo();
1931 else
1932 pInfo = &pDoc->GetFootnoteInfo();
1933 const SwAttrSet& rSet = pInfo->GetAnchorCharFormat(const_cast<SwDoc&>(*pDoc))->GetAttrSet();
1935 Degree10 nDir(0);
1936 if( const SvxCharRotateItem* pItem = rSet.GetItemIfSet( RES_CHRATR_ROTATE ) )
1937 nDir = pItem->GetValue();
1939 if ( nDir )
1941 delete pPor;
1942 pPor = new SwRotatedPortion(rInf.GetIdx() + TextFrameIndex(1),
1943 900_deg10 == nDir
1944 ? DIR_BOTTOM2TOP
1945 : DIR_TOP2BOTTOM );
1949 else if ( pPor->InNumberGrp() )
1951 const SwFont* pNumFnt = static_cast<SwFieldPortion*>(pPor)->GetFont();
1953 if ( pNumFnt )
1955 Degree10 nDir = pNumFnt->GetOrientation( rInf.GetTextFrame()->IsVertical() );
1956 if ( nDir )
1958 delete pPor;
1959 pPor = new SwRotatedPortion(TextFrameIndex(0), 900_deg10 == nDir
1960 ? DIR_BOTTOM2TOP
1961 : DIR_TOP2BOTTOM );
1963 rInf.SetNumDone( false );
1964 rInf.SetFootnoteDone( false );
1970 // The font is set in output device,
1971 // the ascent and the height will be calculated.
1972 if( !pPor->GetAscent() && !pPor->Height() )
1973 CalcAscent( rInf, pPor );
1974 rInf.SetLen( pPor->GetLen() );
1976 // In CalcFlyWidth Width() will be shortened if a FlyPortion is present.
1977 CalcFlyWidth( rInf );
1979 // One must not forget that pCurr as GetLast() must provide reasonable values:
1980 if( !m_pCurr->Height() )
1982 OSL_ENSURE( m_pCurr->Height(), "SwTextFormatter::NewPortion: limbo dance" );
1983 m_pCurr->Height( pPor->Height(), false );
1984 m_pCurr->SetAscent( pPor->GetAscent() );
1987 OSL_ENSURE(pPor->Height(), "SwTextFormatter::NewPortion: something went wrong");
1988 if( pPor->IsPostItsPortion() && rInf.X() >= rInf.Width() && rInf.GetFly() )
1990 delete pPor;
1991 pPor = rInf.GetFly();
1993 return pPor;
1996 TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos)
1998 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
1999 "SwTextFormatter::FormatLine( nStartPos ) with unswapped frame" );
2001 // For the formatting routines, we set pOut to the reference device.
2002 SwHookOut aHook( GetInfo() );
2003 if (GetInfo().GetLen() < TextFrameIndex(GetInfo().GetText().getLength()))
2004 GetInfo().SetLen(TextFrameIndex(GetInfo().GetText().getLength()));
2006 bool bBuild = true;
2007 SetFlyInCntBase( false );
2008 GetInfo().SetLineHeight( 0 );
2009 GetInfo().SetLineNetHeight( 0 );
2011 // Recycling must be suppressed by changed line height and also
2012 // by changed ascent (lowering of baseline).
2013 const SwTwips nOldHeight = m_pCurr->Height();
2014 const SwTwips nOldAscent = m_pCurr->GetAscent();
2016 m_pCurr->SetEndHyph( false );
2017 m_pCurr->SetMidHyph( false );
2018 m_pCurr->SetLastHyph( false );
2020 // fly positioning can make it necessary format a line several times
2021 // for this, we have to keep a copy of our rest portion
2022 SwLinePortion* pField = GetInfo().GetRest();
2023 std::unique_ptr<SwFieldPortion> xSaveField;
2025 if ( pField && pField->InFieldGrp() && !pField->IsFootnotePortion() )
2026 xSaveField.reset(new SwFieldPortion( *static_cast<SwFieldPortion*>(pField) ));
2028 // for an optimal repaint rectangle, we want to compare fly portions
2029 // before and after the BuildPortions call
2030 const bool bOptimizeRepaint = AllowRepaintOpt();
2031 TextFrameIndex const nOldLineEnd = nStartPos + m_pCurr->GetLen();
2032 std::vector<tools::Long> flyStarts;
2034 // these are the conditions for a fly position comparison
2035 if ( bOptimizeRepaint && m_pCurr->IsFly() )
2037 SwLinePortion* pPor = m_pCurr->GetFirstPortion();
2038 tools::Long nPOfst = 0;
2039 while ( pPor )
2041 if ( pPor->IsFlyPortion() )
2042 // insert start value of fly portion
2043 flyStarts.push_back( nPOfst );
2045 nPOfst += pPor->Width();
2046 pPor = pPor->GetNextPortion();
2050 // Here soon the underflow check follows.
2051 while( bBuild )
2053 GetInfo().SetFootnoteInside( false );
2054 GetInfo().SetOtherThanFootnoteInside( false );
2056 // These values must not be reset by FormatReset();
2057 const bool bOldNumDone = GetInfo().IsNumDone();
2058 const bool bOldFootnoteDone = GetInfo().IsFootnoteDone();
2059 const bool bOldArrowDone = GetInfo().IsArrowDone();
2060 const bool bOldErgoDone = GetInfo().IsErgoDone();
2062 // besides other things, this sets the repaint offset to 0
2063 FormatReset( GetInfo() );
2065 GetInfo().SetNumDone( bOldNumDone );
2066 GetInfo().SetFootnoteDone(bOldFootnoteDone);
2067 GetInfo().SetArrowDone( bOldArrowDone );
2068 GetInfo().SetErgoDone( bOldErgoDone );
2070 // build new portions for this line
2071 BuildPortions( GetInfo() );
2073 if( GetInfo().IsStop() )
2075 m_pCurr->SetLen(TextFrameIndex(0));
2076 m_pCurr->Height( GetFrameRstHeight() + 1, false );
2077 m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 );
2079 // Don't oversize the line in case of split flys, so we don't try to move the anchor
2080 // of a precede fly forward, next to its follow.
2081 if (m_pFrame->HasNonLastSplitFlyDrawObj())
2083 m_pCurr->SetRealHeight(GetFrameRstHeight());
2086 m_pCurr->Width(0);
2087 m_pCurr->Truncate();
2088 return nStartPos;
2090 else if( GetInfo().IsDropInit() )
2092 DropInit();
2093 GetInfo().SetDropInit( false );
2096 m_pCurr->CalcLine( *this, GetInfo() );
2097 CalcRealHeight( GetInfo().IsNewLine() );
2099 //i#120864 For Special case that at the first calculation couldn't get
2100 //correct height. And need to recalculate for the right height.
2101 SwLinePortion* pPorTmp = m_pCurr->GetNextPortion();
2102 if ( IsFlyInCntBase() && (!IsQuick() || (pPorTmp && pPorTmp->IsFlyCntPortion() && !pPorTmp->GetNextPortion() &&
2103 m_pCurr->Height() > pPorTmp->Height())))
2105 SwTwips nTmpAscent, nTmpHeight;
2106 CalcAscentAndHeight( nTmpAscent, nTmpHeight );
2107 AlignFlyInCntBase( Y() + tools::Long( nTmpAscent ) );
2108 m_pCurr->CalcLine( *this, GetInfo() );
2109 CalcRealHeight();
2112 // bBuild decides if another lap of honor is done
2113 if ( m_pCurr->GetRealHeight() <= GetInfo().GetLineHeight() )
2115 m_pCurr->SetRealHeight( GetInfo().GetLineHeight() );
2116 bBuild = false;
2118 else
2120 bBuild = ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) )
2121 || GetInfo().CheckFootnotePortion(m_pCurr);
2122 if( bBuild )
2124 // fdo44018-2.doc: only restore m_bNumDone if a SwNumberPortion will be truncated
2125 for (SwLinePortion * pPor = m_pCurr->GetNextPortion(); pPor; pPor = pPor->GetNextPortion())
2127 if (pPor->InNumberGrp())
2129 GetInfo().SetNumDone( bOldNumDone );
2130 break;
2133 GetInfo().ResetMaxWidthDiff();
2134 GetInfo().SetExtraSpace(0);
2136 // delete old rest
2137 if ( GetInfo().GetRest() )
2139 delete GetInfo().GetRest();
2140 GetInfo().SetRest( nullptr );
2143 // set original rest portion
2144 if ( xSaveField )
2145 GetInfo().SetRest( new SwFieldPortion( *xSaveField ) );
2147 m_pCurr->SetLen(TextFrameIndex(0));
2148 m_pCurr->Width(0);
2149 m_pCurr->ExtraShrunkWidth(0);
2150 m_pCurr->Truncate();
2155 // In case of compat mode, it's possible that a tab portion is wider after
2156 // formatting than before. If this is the case, we also have to make sure
2157 // the SwLineLayout is wider as well.
2158 if (GetInfo().GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN))
2160 SwTwips nSum = 0;
2161 SwLinePortion* pPor = m_pCurr->GetFirstPortion();
2163 while (pPor)
2165 nSum += pPor->Width();
2166 pPor = pPor->GetNextPortion();
2169 if (nSum > m_pCurr->Width())
2170 m_pCurr->Width(nSum);
2173 // calculate optimal repaint rectangle
2174 if ( bOptimizeRepaint )
2176 GetInfo().SetPaintOfst( ::lcl_CalcOptRepaint( *this, *m_pCurr, nOldLineEnd, flyStarts ) );
2177 flyStarts.clear();
2179 else
2180 // Special case: we do not allow an optimization of the repaint
2181 // area, but during formatting the repaint offset is set to indicate
2182 // a maximum value for the offset. This value has to be reset:
2183 GetInfo().SetPaintOfst( 0 );
2185 // This corrects the start of the reformat range if something has
2186 // moved to the next line. Otherwise IsFirstReformat in AllowRepaintOpt
2187 // will give us a wrong result if we have to reformat another line
2188 GetInfo().GetParaPortion()->GetReformat().LeftMove( GetInfo().GetIdx() );
2190 // delete master copy of rest portion
2191 xSaveField.reset();
2193 TextFrameIndex const nNewStart = nStartPos + m_pCurr->GetLen();
2195 // adjust text if kana compression is enabled
2196 if ( GetInfo().CompressLine() )
2198 SwTwips nRepaintOfst = CalcKanaAdj( m_pCurr );
2200 // adjust repaint offset
2201 if ( nRepaintOfst < GetInfo().GetPaintOfst() )
2202 GetInfo().SetPaintOfst( nRepaintOfst );
2205 CalcAdjustLine( m_pCurr );
2207 if( nOldHeight != m_pCurr->Height() || nOldAscent != m_pCurr->GetAscent() )
2209 SetFlyInCntBase();
2210 GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling
2211 // all following line must be painted and when Flys are around,
2212 // also formatted
2213 GetInfo().SetShift( true );
2216 if ( IsFlyInCntBase() && !IsQuick() )
2217 UpdatePos( m_pCurr, GetTopLeft(), GetStart() );
2219 return nNewStart;
2222 void SwTextFormatter::RecalcRealHeight()
2226 CalcRealHeight();
2227 } while (Next());
2230 void SwTextFormatter::CalcRealHeight( bool bNewLine )
2232 SwTwips nLineHeight = m_pCurr->Height();
2233 m_pCurr->SetClipping( false );
2235 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
2236 if ( pGrid && GetInfo().SnapToGrid() )
2238 const sal_uInt16 nGridWidth = pGrid->GetBaseHeight();
2239 const sal_uInt16 nRubyHeight = pGrid->GetRubyHeight();
2240 const bool bRubyTop = ! pGrid->GetRubyTextBelow();
2242 nLineHeight = nGridWidth + nRubyHeight;
2243 const auto nAmpRatio = (m_pCurr->Height() + nLineHeight - 1) / nLineHeight;
2244 nLineHeight *= nAmpRatio;
2246 const SwTwips nAsc = m_pCurr->GetAscent() +
2247 ( bRubyTop ?
2248 ( nLineHeight - m_pCurr->Height() + nRubyHeight ) / 2 :
2249 ( nLineHeight - m_pCurr->Height() - nRubyHeight ) / 2 );
2251 m_pCurr->Height( nLineHeight, false );
2252 m_pCurr->SetAscent( nAsc );
2253 m_pInf->GetParaPortion()->SetFixLineHeight();
2255 // we ignore any line spacing options except from ...
2256 const SvxLineSpacingItem* pSpace = m_aLineInf.GetLineSpacing();
2257 if ( ! IsParaLine() && pSpace &&
2258 SvxInterLineSpaceRule::Prop == pSpace->GetInterLineSpaceRule() )
2260 sal_uLong nTmp = pSpace->GetPropLineSpace();
2262 if( nTmp < 100 )
2263 nTmp = 100;
2265 nTmp *= nLineHeight;
2266 nLineHeight = nTmp / 100;
2269 m_pCurr->SetRealHeight( nLineHeight );
2270 return;
2273 // The dummy flag is set on lines that only contain flyportions, these shouldn't
2274 // consider register-true and so on. Unfortunately an empty line can be at
2275 // the end of a paragraph (empty paragraphs or behind a Shift-Return),
2276 // which should consider the register.
2277 if (!m_pCurr->IsDummy() || (!m_pCurr->GetNext()
2278 && GetStart() >= TextFrameIndex(GetTextFrame()->GetText().getLength())
2279 && !bNewLine))
2281 const SvxLineSpacingItem *pSpace = m_aLineInf.GetLineSpacing();
2282 if( pSpace )
2284 switch( pSpace->GetLineSpaceRule() )
2286 case SvxLineSpaceRule::Auto:
2287 // shrink first line of paragraph too on spacing < 100%
2288 if (IsParaLine() &&
2289 pSpace->GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop
2290 && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
2292 tools::Long nTmp = pSpace->GetPropLineSpace();
2293 // Word will render < 50% too but it's just not readable
2294 if( nTmp < 50 )
2295 nTmp = nTmp ? 50 : 100;
2296 if (nTmp<100) { // code adapted from fixed line height
2297 nTmp *= nLineHeight;
2298 nTmp /= 100;
2299 if( !nTmp )
2300 ++nTmp;
2301 nLineHeight = nTmp;
2302 SwTwips nAsc = (4 * nLineHeight) / 5; // 80%
2303 #if 0
2304 // could do clipping here (like Word does)
2305 // but at 0.5 its unreadable either way...
2306 if( nAsc < pCurr->GetAscent() ||
2307 nLineHeight - nAsc < pCurr->Height() -
2308 pCurr->GetAscent() )
2309 pCurr->SetClipping( true );
2310 #endif
2311 m_pCurr->SetAscent( nAsc );
2312 m_pCurr->Height( nLineHeight, false );
2313 m_pInf->GetParaPortion()->SetFixLineHeight();
2316 break;
2317 case SvxLineSpaceRule::Min:
2319 if( nLineHeight < pSpace->GetLineHeight() )
2320 nLineHeight = pSpace->GetLineHeight();
2321 break;
2323 case SvxLineSpaceRule::Fix:
2325 nLineHeight = pSpace->GetLineHeight();
2326 const SwTwips nAsc = (4 * nLineHeight) / 5; // 80%
2327 if( nAsc < m_pCurr->GetAscent() ||
2328 nLineHeight - nAsc < m_pCurr->Height() - m_pCurr->GetAscent() )
2329 m_pCurr->SetClipping( true );
2330 m_pCurr->Height( nLineHeight, false );
2331 m_pCurr->SetAscent( nAsc );
2332 m_pInf->GetParaPortion()->SetFixLineHeight();
2334 break;
2335 default: OSL_FAIL( ": unknown LineSpaceRule" );
2337 // Note: for the _first_ line the line spacing of the previous
2338 // paragraph is applied in SwFlowFrame::CalcUpperSpace()
2339 if( !IsParaLine() )
2340 switch( pSpace->GetInterLineSpaceRule() )
2342 case SvxInterLineSpaceRule::Off:
2343 break;
2344 case SvxInterLineSpaceRule::Prop:
2346 tools::Long nTmp = pSpace->GetPropLineSpace();
2347 // 50% is the minimum, if 0% we switch to the
2348 // default value 100% ...
2349 if( nTmp < 50 )
2350 nTmp = nTmp ? 50 : 100;
2352 bool bPropLineShrinks = (nTmp < 100);
2354 // extend line height by (nPropLineSpace - 100) percent of the font height
2355 nTmp -= 100;
2356 nTmp *= m_pCurr->GetTextHeight();
2357 nTmp /= 100;
2358 nTmp += nLineHeight;
2359 if (nTmp < 1)
2360 nTmp = 1;
2361 nLineHeight = nTmp;
2363 // tdf#146081: The height and ascent of the first line may have been
2364 // adjusted above. In order to have consistent line spacing when rendering,
2365 // the same adjustments must be made to the following lines.
2366 if (bPropLineShrinks
2367 && GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(
2368 DocumentSettingId::PROP_LINE_SPACING_SHRINKS_FIRST_LINE))
2370 SwTwips nAsc = (4 * nLineHeight) / 5; // 80%
2371 m_pCurr->SetAscent(nAsc);
2372 m_pCurr->Height(nLineHeight, false);
2373 m_pInf->GetParaPortion()->SetFixLineHeight();
2375 break;
2377 case SvxInterLineSpaceRule::Fix:
2379 nLineHeight = nLineHeight + pSpace->GetInterLineSpace();
2380 break;
2382 default: OSL_FAIL( ": unknown InterLineSpaceRule" );
2386 if( IsRegisterOn() )
2388 SwTwips nTmpY = Y() + m_pCurr->GetAscent() + nLineHeight - m_pCurr->Height();
2389 SwRectFnSet aRectFnSet(m_pFrame);
2390 if ( aRectFnSet.IsVert() )
2391 nTmpY = m_pFrame->SwitchHorizontalToVertical( nTmpY );
2392 nTmpY = aRectFnSet.YDiff( nTmpY, RegStart() );
2393 const sal_uInt16 nDiff = sal_uInt16( nTmpY % RegDiff() );
2394 if( nDiff )
2395 nLineHeight += RegDiff() - nDiff;
2398 m_pCurr->SetRealHeight( nLineHeight );
2401 void SwTextFormatter::FeedInf( SwTextFormatInfo &rInf ) const
2403 // delete Fly in any case!
2404 ClearFly( rInf );
2405 rInf.Init();
2407 rInf.ChkNoHyph( CntEndHyph(), CntMidHyph() );
2408 rInf.SetRoot( m_pCurr );
2409 rInf.SetLineStart( m_nStart );
2410 rInf.SetIdx( m_nStart );
2411 rInf.Left( Left() );
2412 rInf.Right( Right() );
2413 rInf.First( FirstLeft() );
2414 rInf.LeftMargin(GetLeftMargin());
2416 rInf.RealWidth(rInf.Right() - GetLeftMargin());
2417 rInf.Width(std::max(rInf.RealWidth(), SwTwips(0)));
2418 if( const_cast<SwTextFormatter*>(this)->GetRedln() )
2420 const_cast<SwTextFormatter*>(this)->GetRedln()->Clear( const_cast<SwTextFormatter*>(this)->GetFnt() );
2421 const_cast<SwTextFormatter*>(this)->GetRedln()->Reset();
2425 void SwTextFormatter::FormatReset( SwTextFormatInfo &rInf )
2427 m_pFirstOfBorderMerge = nullptr;
2428 m_pCurr->Truncate();
2429 m_pCurr->Init();
2431 // delete pSpaceAdd and pKanaComp
2432 m_pCurr->FinishSpaceAdd();
2433 m_pCurr->FinishKanaComp();
2434 m_pCurr->ResetFlags();
2435 FeedInf( rInf );
2438 bool SwTextFormatter::CalcOnceMore()
2440 if( m_pDropFormat )
2442 const sal_uInt16 nOldDrop = GetDropHeight();
2443 CalcDropHeight( m_pDropFormat->GetLines() );
2444 m_bOnceMore = nOldDrop != GetDropHeight();
2446 else
2447 m_bOnceMore = false;
2448 return m_bOnceMore;
2451 SwTwips SwTextFormatter::CalcBottomLine() const
2453 SwTwips nRet = Y() + GetLineHeight();
2454 SwTwips nMin = GetInfo().GetTextFly().GetMinBottom();
2455 if( nMin && ++nMin > nRet )
2457 SwTwips nDist = m_pFrame->getFrameArea().Height() - m_pFrame->getFramePrintArea().Height()
2458 - m_pFrame->getFramePrintArea().Top();
2459 if( nRet + nDist < nMin )
2461 const bool bRepaint = HasTruncLines() &&
2462 GetInfo().GetParaPortion()->GetRepaint().Bottom() == nRet-1;
2463 nRet = nMin - nDist;
2464 if( bRepaint )
2466 const_cast<SwRepaint&>(GetInfo().GetParaPortion()
2467 ->GetRepaint()).Bottom( nRet-1 );
2468 const_cast<SwTextFormatInfo&>(GetInfo()).SetPaintOfst( 0 );
2472 return nRet;
2475 // FME/OD: This routine does a limited text formatting.
2476 SwTwips SwTextFormatter::CalcFitToContent_()
2478 FormatReset( GetInfo() );
2479 BuildPortions( GetInfo() );
2480 m_pCurr->CalcLine( *this, GetInfo() );
2481 return m_pCurr->Width();
2484 // determines if the calculation of a repaint offset is allowed
2485 // otherwise each line is painted from 0 (this is a copy of the beginning
2486 // of the former SwTextFormatter::Recycle() function
2487 bool SwTextFormatter::AllowRepaintOpt() const
2489 // reformat position in front of current line? Only in this case
2490 // we want to set the repaint offset
2491 bool bOptimizeRepaint = m_nStart < GetInfo().GetReformatStart() &&
2492 m_pCurr->GetLen();
2494 // a special case is the last line of a block adjusted paragraph:
2495 if ( bOptimizeRepaint )
2497 switch( GetAdjust() )
2499 case SvxAdjust::Block:
2501 if( IsLastBlock() || IsLastCenter() )
2502 bOptimizeRepaint = false;
2503 else
2505 // ????: blank in the last master line (blocksat.sdw)
2506 bOptimizeRepaint = nullptr == m_pCurr->GetNext() && !m_pFrame->GetFollow();
2507 if ( bOptimizeRepaint )
2509 SwLinePortion *pPos = m_pCurr->GetFirstPortion();
2510 while ( pPos && !pPos->IsFlyPortion() )
2511 pPos = pPos->GetNextPortion();
2512 bOptimizeRepaint = !pPos;
2515 break;
2517 case SvxAdjust::Center:
2518 case SvxAdjust::Right:
2519 bOptimizeRepaint = false;
2520 break;
2521 default: ;
2525 // Again another special case: invisible SoftHyphs
2526 const TextFrameIndex nReformat = GetInfo().GetReformatStart();
2527 if (bOptimizeRepaint && TextFrameIndex(COMPLETE_STRING) != nReformat)
2529 const sal_Unicode cCh = nReformat >= TextFrameIndex(GetInfo().GetText().getLength())
2531 : GetInfo().GetText()[ sal_Int32(nReformat) ];
2532 bOptimizeRepaint = ( CH_TXTATR_BREAKWORD != cCh && CH_TXTATR_INWORD != cCh )
2533 || ! GetInfo().HasHint( nReformat );
2536 return bOptimizeRepaint;
2539 void SwTextFormatter::CalcUnclipped( SwTwips& rTop, SwTwips& rBottom )
2541 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
2542 "SwTextFormatter::CalcUnclipped with unswapped frame" );
2544 SwTwips nFlyAsc, nFlyDesc;
2545 m_pCurr->MaxAscentDescent( rTop, rBottom, nFlyAsc, nFlyDesc );
2546 rTop = Y() + GetCurr()->GetAscent();
2547 rBottom = rTop + nFlyDesc;
2548 rTop -= nFlyAsc;
2551 void SwTextFormatter::UpdatePos( SwLineLayout *pCurrent, Point aStart,
2552 TextFrameIndex const nStartIdx, bool bAlways) const
2554 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
2555 "SwTextFormatter::UpdatePos with unswapped frame" );
2557 if( GetInfo().IsTest() )
2558 return;
2559 SwLinePortion *pFirst = pCurrent->GetFirstPortion();
2560 SwLinePortion *pPos = pFirst;
2561 SwTextPaintInfo aTmpInf( GetInfo() );
2562 aTmpInf.SetpSpaceAdd( pCurrent->GetpLLSpaceAdd() );
2563 aTmpInf.ResetSpaceIdx();
2564 aTmpInf.SetKanaComp( pCurrent->GetpKanaComp() );
2565 aTmpInf.ResetKanaIdx();
2567 // The frame's size
2568 aTmpInf.SetIdx( nStartIdx );
2569 aTmpInf.SetPos( aStart );
2571 SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2572 pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
2574 const SwTwips nTmpHeight = pCurrent->GetRealHeight();
2575 SwTwips nAscent = pCurrent->GetAscent() + nTmpHeight - pCurrent->Height();
2576 AsCharFlags nFlags = AsCharFlags::UlSpace;
2577 if( GetMulti() )
2579 aTmpInf.SetDirection( GetMulti()->GetDirection() );
2580 if( GetMulti()->HasRotation() )
2582 nFlags |= AsCharFlags::Rotate;
2583 if( GetMulti()->IsRevers() )
2585 nFlags |= AsCharFlags::Reverse;
2586 aTmpInf.X( aTmpInf.X() - nAscent );
2588 else
2589 aTmpInf.X( aTmpInf.X() + nAscent );
2591 else
2593 if ( GetMulti()->IsBidi() )
2594 nFlags |= AsCharFlags::Bidi;
2595 aTmpInf.Y( aTmpInf.Y() + nAscent );
2598 else
2599 aTmpInf.Y( aTmpInf.Y() + nAscent );
2601 while( pPos )
2603 // We only know one case where changing the position (caused by the
2604 // adjustment) could be relevant for a portion: We need to SetRefPoint
2605 // for FlyCntPortions.
2606 if( ( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
2607 && ( bAlways || !IsQuick() ) )
2609 pCurrent->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
2611 if( pPos->IsGrfNumPortion() )
2613 if( !nFlyAsc && !nFlyDesc )
2615 nTmpAscent = nAscent;
2616 nFlyAsc = nAscent;
2617 nTmpDescent = nTmpHeight - nAscent;
2618 nFlyDesc = nTmpDescent;
2620 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
2621 nFlyAsc, nFlyDesc );
2623 else
2625 Point aBase( aTmpInf.GetPos() );
2626 if ( GetInfo().GetTextFrame()->IsVertical() )
2627 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aBase );
2629 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *aTmpInf.GetTextFrame(),
2630 aBase, nTmpAscent, nTmpDescent, nFlyAsc,
2631 nFlyDesc, nFlags );
2634 if( pPos->IsMultiPortion() && static_cast<SwMultiPortion*>(pPos)->HasFlyInContent() )
2636 OSL_ENSURE( !GetMulti(), "Too much multi" );
2637 const_cast<SwTextFormatter*>(this)->m_pMulti = static_cast<SwMultiPortion*>(pPos);
2638 SwLineLayout *pLay = &GetMulti()->GetRoot();
2639 Point aSt( aTmpInf.X(), aStart.Y() );
2641 if ( GetMulti()->HasBrackets() )
2643 OSL_ENSURE( GetMulti()->IsDouble(), "Brackets only for doubles");
2644 aSt.AdjustX(static_cast<SwDoubleLinePortion*>(GetMulti())->PreWidth() );
2646 else if( GetMulti()->HasRotation() )
2648 aSt.AdjustY(pCurrent->GetAscent() - GetMulti()->GetAscent() );
2649 if( GetMulti()->IsRevers() )
2650 aSt.AdjustX(GetMulti()->Width() );
2651 else
2652 aSt.AdjustY(GetMulti()->Height() );
2654 else if ( GetMulti()->IsBidi() )
2655 // jump to end of the bidi portion
2656 aSt.AdjustX(pLay->Width() );
2658 TextFrameIndex nStIdx = aTmpInf.GetIdx();
2661 UpdatePos( pLay, aSt, nStIdx, bAlways );
2662 nStIdx = nStIdx + pLay->GetLen();
2663 aSt.AdjustY(pLay->Height() );
2664 pLay = pLay->GetNext();
2665 } while ( pLay );
2666 const_cast<SwTextFormatter*>(this)->m_pMulti = nullptr;
2668 pPos->Move( aTmpInf );
2669 pPos = pPos->GetNextPortion();
2673 void SwTextFormatter::AlignFlyInCntBase( tools::Long nBaseLine ) const
2675 OSL_ENSURE( ! m_pFrame->IsVertical() || m_pFrame->IsSwapped(),
2676 "SwTextFormatter::AlignFlyInCntBase with unswapped frame" );
2678 if( GetInfo().IsTest() )
2679 return;
2680 SwLinePortion *pFirst = m_pCurr->GetFirstPortion();
2681 SwLinePortion *pPos = pFirst;
2682 AsCharFlags nFlags = AsCharFlags::None;
2683 if( GetMulti() && GetMulti()->HasRotation() )
2685 nFlags |= AsCharFlags::Rotate;
2686 if( GetMulti()->IsRevers() )
2687 nFlags |= AsCharFlags::Reverse;
2690 SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
2692 while( pPos )
2694 if( pPos->IsFlyCntPortion() || pPos->IsGrfNumPortion() )
2696 m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, pPos );
2698 if( pPos->IsGrfNumPortion() )
2699 static_cast<SwGrfNumPortion*>(pPos)->SetBase( nTmpAscent, nTmpDescent,
2700 nFlyAsc, nFlyDesc );
2701 else
2703 Point aBase;
2704 if ( GetInfo().GetTextFrame()->IsVertical() )
2706 nBaseLine = GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine );
2707 aBase = Point( nBaseLine, static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().Y() );
2709 else
2710 aBase = Point( static_cast<SwFlyCntPortion*>(pPos)->GetRefPoint().X(), nBaseLine );
2712 static_cast<SwFlyCntPortion*>(pPos)->SetBase( *GetInfo().GetTextFrame(), aBase, nTmpAscent, nTmpDescent,
2713 nFlyAsc, nFlyDesc, nFlags );
2716 pPos = pPos->GetNextPortion();
2720 bool SwTextFormatter::ChkFlyUnderflow( SwTextFormatInfo &rInf ) const
2722 OSL_ENSURE( rInf.GetTextFly().IsOn(), "SwTextFormatter::ChkFlyUnderflow: why?" );
2723 if( GetCurr() )
2725 // First we check, whether a fly overlaps with the line.
2726 // = GetLineHeight()
2727 const SwTwips nHeight = GetCurr()->GetRealHeight();
2728 SwRect aLine( GetLeftMargin(), Y(), rInf.RealWidth(), nHeight );
2730 SwRect aLineVert( aLine );
2731 if ( m_pFrame->IsVertical() )
2732 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2733 SwRect aInter( rInf.GetTextFly().GetFrame( aLineVert ) );
2734 if ( m_pFrame->IsVertical() )
2735 m_pFrame->SwitchVerticalToHorizontal( aInter );
2737 if( !aInter.HasArea() )
2738 return false;
2740 // We now check every portion that could have lowered for overlapping
2741 // with the fly.
2742 const SwLinePortion *pPos = GetCurr()->GetFirstPortion();
2743 aLine.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() );
2744 aLine.Height( GetCurr()->Height() );
2746 while( pPos )
2748 aLine.Width( pPos->Width() );
2750 aLineVert = aLine;
2751 if ( m_pFrame->IsVertical() )
2752 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2753 aInter = rInf.GetTextFly().GetFrame( aLineVert );
2754 if ( m_pFrame->IsVertical() )
2755 m_pFrame->SwitchVerticalToHorizontal( aInter );
2757 // New flys from below?
2758 if( !pPos->IsFlyPortion() )
2760 if( aInter.Overlaps( aLine ) )
2762 aInter.Intersection_( aLine );
2763 if( aInter.HasArea() )
2765 // To be evaluated during reformat of this line:
2766 // RealHeight including spacing
2767 rInf.SetLineHeight( nHeight );
2768 // Height without extra spacing
2769 rInf.SetLineNetHeight( m_pCurr->Height() );
2770 return true;
2774 else
2776 // The fly portion is not intersected by a fly anymore
2777 if ( ! aInter.Overlaps( aLine ) )
2779 rInf.SetLineHeight( nHeight );
2780 rInf.SetLineNetHeight( m_pCurr->Height() );
2781 return true;
2783 else
2785 aInter.Intersection_( aLine );
2787 // No area means a fly has become invalid because of
2788 // lowering the line => reformat the line
2789 // we also have to reformat the line, if the fly size
2790 // differs from the intersection interval's size.
2791 if( ! aInter.HasArea() ||
2792 static_cast<const SwFlyPortion*>(pPos)->GetFixWidth() != aInter.Width() )
2794 rInf.SetLineHeight( nHeight );
2795 rInf.SetLineNetHeight( m_pCurr->Height() );
2796 return true;
2801 aLine.Left( aLine.Left() + pPos->Width() );
2802 pPos = pPos->GetNextPortion();
2805 return false;
2808 void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo &rInf )
2810 if( GetMulti() || rInf.GetFly() )
2811 return;
2813 SwTextFly& rTextFly = rInf.GetTextFly();
2814 if( !rTextFly.IsOn() || rInf.IsIgnoreFly() )
2815 return;
2817 const SwLinePortion *pLast = rInf.GetLast();
2819 tools::Long nAscent;
2820 tools::Long nTop = Y();
2821 tools::Long nHeight;
2823 if( rInf.GetLineHeight() )
2825 // Real line height has already been calculated, we only have to
2826 // search for intersections in the lower part of the strip
2827 nAscent = m_pCurr->GetAscent();
2828 nHeight = rInf.GetLineNetHeight();
2829 nTop += rInf.GetLineHeight() - nHeight;
2831 else
2833 // We make a first guess for the lines real height
2834 if ( ! m_pCurr->GetRealHeight() )
2835 CalcRealHeight();
2837 nAscent = pLast->GetAscent();
2838 nHeight = pLast->Height();
2840 if ( m_pCurr->GetRealHeight() > nHeight )
2841 nTop += m_pCurr->GetRealHeight() - nHeight;
2842 else
2843 // Important for fixed space between lines
2844 nHeight = m_pCurr->GetRealHeight();
2847 const tools::Long nLeftMar = GetLeftMargin();
2848 const tools::Long nLeftMin = (rInf.X() || GetDropLeft()) ? nLeftMar : GetLeftMin();
2850 SwRect aLine( rInf.X() + nLeftMin, nTop, rInf.RealWidth() - rInf.X()
2851 + nLeftMar - nLeftMin , nHeight );
2853 bool bWordFlyWrap = GetTextFrame()->GetDoc().getIDocumentSettingAccess().get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS);
2854 // tdf#116486: consider also the upper margin from getFramePrintArea because intersections
2855 // with this additional space should lead to repositioning of paragraphs
2856 // For compatibility we grab a related compat flag:
2857 if (bWordFlyWrap && IsFirstTextLine())
2859 tools::Long nUpper = m_pFrame->getFramePrintArea().Top();
2860 // Make sure that increase only happens in case the upper spacing comes from the upper
2861 // margin of the current text frame, not because of a lower spacing of the previous text
2862 // frame.
2863 nUpper -= m_pFrame->GetUpperSpaceAmountConsideredForPrevFrameAndPageGrid();
2864 // Increase the rectangle
2865 if( nUpper > 0 && nTop >= nUpper )
2866 aLine.SubTop( nUpper );
2869 if (IsFirstTextLine())
2871 // Check if a compatibility mode requires considering also the lower margin of this text
2872 // frame, intersections with this additional space should lead to shifting the paragraph
2873 // marker down.
2874 SwTwips nLower = m_pFrame->GetLowerMarginForFlyIntersect();
2875 if (nLower > 0)
2877 aLine.AddBottom(nLower);
2881 SwRect aLineVert( aLine );
2882 if ( m_pFrame->IsRightToLeft() )
2883 m_pFrame->SwitchLTRtoRTL( aLineVert );
2885 if ( m_pFrame->IsVertical() )
2886 m_pFrame->SwitchHorizontalToVertical( aLineVert );
2888 // GetFrame(...) determines and returns the intersection rectangle
2889 SwRect aInter( rTextFly.GetFrame( aLineVert ) );
2891 if ( m_pFrame->IsRightToLeft() )
2892 m_pFrame->SwitchRTLtoLTR( aInter );
2894 if ( m_pFrame->IsVertical() )
2895 m_pFrame->SwitchVerticalToHorizontal( aInter );
2897 if (!aInter.IsEmpty() && aInter.Bottom() < nTop)
2899 // Intersects with the frame area (with upper margin), but not with the print area (without
2900 // upper margin). Don't reserve space for the fly portion in this case, text is allowed to
2901 // flow there.
2902 aInter.Height(0);
2905 if( !aInter.Overlaps( aLine ) )
2906 return;
2908 aLine.Left( rInf.X() + nLeftMar );
2909 bool bForced = false;
2910 bool bSplitFly = false;
2911 for (const auto& pObj : rTextFly.GetAnchoredObjList())
2913 auto pFlyFrame = pObj->DynCastFlyFrame();
2914 if (!pFlyFrame)
2916 continue;
2919 if (!pFlyFrame->IsFlySplitAllowed())
2921 continue;
2924 bSplitFly = true;
2925 break;
2927 if( aInter.Left() <= nLeftMin )
2929 SwTwips nFrameLeft = GetTextFrame()->getFrameArea().Left();
2930 SwTwips nFramePrintAreaLeft = GetTextFrame()->getFramePrintArea().Left();
2931 if( nFramePrintAreaLeft < 0 )
2932 nFrameLeft += GetTextFrame()->getFramePrintArea().Left();
2933 if( aInter.Left() < nFrameLeft )
2935 aInter.Left(nFrameLeft); // both sets left and reduces width
2936 if (bSplitFly && nFramePrintAreaLeft > 0 && nFramePrintAreaLeft < aInter.Width())
2938 // We wrap around a split fly, the fly portion is on the
2939 // left of the paragraph and we have a positive
2940 // paragraph margin. Don't take space twice in this case
2941 // (margin, fly portion), decrease the width of the fly
2942 // portion accordingly.
2943 aInter.Right(aInter.Right() - nFramePrintAreaLeft);
2947 tools::Long nAddMar = 0;
2948 if (GetTextFrame()->IsRightToLeft())
2950 nAddMar = GetTextFrame()->getFrameArea().Right() - Right();
2951 if ( nAddMar < 0 )
2952 nAddMar = 0;
2954 else
2955 nAddMar = nLeftMar - nFrameLeft;
2957 aInter.Width( aInter.Width() + nAddMar );
2958 // For a negative first line indent, we set this flag to show
2959 // that the indentation/margin has been moved.
2960 // This needs to be respected by the DefaultTab at the zero position.
2961 if( IsFirstTextLine() && HasNegFirst() )
2962 bForced = true;
2964 aInter.Intersection( aLine );
2965 if( !aInter.HasArea() )
2966 return;
2968 bool bFullLine = aLine.Left() == aInter.Left() &&
2969 aLine.Right() == aInter.Right();
2970 if (!bFullLine && bWordFlyWrap && !GetTextFrame()->IsInTab())
2972 // Word style: if there is minimal space remaining, then handle that similar to a full line
2973 // and put the actual empty paragraph below the fly.
2974 SwTwips nLimit = MINLAY;
2975 if (bSplitFly)
2977 // We wrap around a floating table, that has a larger minimal wrap distance.
2978 nLimit = TEXT_MIN_SMALL;
2981 bFullLine = std::abs(aLine.Left() - aInter.Left()) < nLimit
2982 && std::abs(aLine.Right() - aInter.Right()) < nLimit;
2985 // Although no text is left, we need to format another line,
2986 // because also empty lines need to avoid a Fly with no wrapping.
2987 if (bFullLine && rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
2989 rInf.SetNewLine( true );
2990 // We know that for dummies, it holds ascent == height
2991 m_pCurr->SetDummy(true);
2994 // aInter becomes frame-local
2995 aInter.Pos().AdjustX( -nLeftMar );
2996 SwFlyPortion *pFly = new SwFlyPortion( aInter );
2997 if( bForced )
2999 m_pCurr->SetForcedLeftMargin();
3000 rInf.ForcedLeftMargin(aInter.Width());
3003 if( bFullLine )
3005 // In order to properly flow around Flys with different
3006 // wrapping attributes, we need to increase by units of line height.
3007 // The last avoiding line should be adjusted in height, so that
3008 // we don't get a frame spacing effect.
3009 // It is important that ascent == height, because the FlyPortion
3010 // values are transferred to pCurr in CalcLine and IsDummy() relies
3011 // on this behaviour.
3012 // To my knowledge we only have two places where DummyLines can be
3013 // created: here and in MakeFlyDummies.
3014 // IsDummy() is evaluated in IsFirstTextLine(), when moving lines
3015 // and in relation with DropCaps.
3016 pFly->Height( aInter.Height() );
3018 // nNextTop now contains the margin's bottom edge, which we avoid
3019 // or the next margin's top edge, which we need to respect.
3020 // That means we can comfortably grow up to this value; that's how
3021 // we save a few empty lines.
3022 tools::Long nNextTop = rTextFly.GetNextTop();
3023 if ( m_pFrame->IsVertical() )
3024 nNextTop = m_pFrame->SwitchVerticalToHorizontal( nNextTop );
3025 if( nNextTop > aInter.Bottom() )
3027 SwTwips nH = nNextTop - aInter.Top();
3028 if( nH < SAL_MAX_UINT16 )
3029 pFly->Height( nH );
3031 if( nAscent < pFly->Height() )
3032 pFly->SetAscent( nAscent );
3033 else
3034 pFly->SetAscent( pFly->Height() );
3036 else
3038 if (rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
3040 // Don't use nHeight, or we have a huge descent
3041 pFly->Height( pLast->Height() );
3042 pFly->SetAscent( pLast->GetAscent() );
3044 else
3046 pFly->Height( aInter.Height() );
3047 if( nAscent < pFly->Height() )
3048 pFly->SetAscent( nAscent );
3049 else
3050 pFly->SetAscent( pFly->Height() );
3054 rInf.SetFly( pFly );
3056 if( pFly->GetFix() < rInf.Width() )
3057 rInf.Width( pFly->GetFix() );
3059 SwTextGridItem const*const pGrid(GetGridItem(m_pFrame->FindPageFrame()));
3060 if ( !pGrid )
3061 return;
3063 const SwPageFrame* pPageFrame = m_pFrame->FindPageFrame();
3064 const SwLayoutFrame* pBody = pPageFrame->FindBodyCont();
3066 SwRectFnSet aRectFnSet(pPageFrame);
3068 const tools::Long nGridOrigin = pBody ?
3069 aRectFnSet.GetPrtLeft(*pBody) :
3070 aRectFnSet.GetPrtLeft(*pPageFrame);
3072 const SwDoc & rDoc = rInf.GetTextFrame()->GetDoc();
3073 const sal_uInt16 nGridWidth = GetGridWidth(*pGrid, rDoc);
3075 SwTwips nStartX = GetLeftMargin();
3076 if ( aRectFnSet.IsVert() )
3078 Point aPoint( nStartX, 0 );
3079 m_pFrame->SwitchHorizontalToVertical( aPoint );
3080 nStartX = aPoint.Y();
3083 const SwTwips nOfst = nStartX - nGridOrigin;
3084 const SwTwips nTmpWidth = rInf.Width() + nOfst;
3086 const SwTwips i = nTmpWidth / nGridWidth + 1;
3088 const SwTwips nNewWidth = ( i - 1 ) * nGridWidth - nOfst;
3089 if ( nNewWidth > 0 )
3090 rInf.Width( nNewWidth );
3091 else
3092 rInf.Width( 0 );
3097 SwFlyCntPortion *SwTextFormatter::NewFlyCntPortion( SwTextFormatInfo &rInf,
3098 SwTextAttr *pHint ) const
3100 const SwFrame *pFrame = m_pFrame;
3102 SwFlyInContentFrame *pFly;
3103 SwFrameFormat* pFrameFormat = static_cast<SwTextFlyCnt*>(pHint)->GetFlyCnt().GetFrameFormat();
3104 if( RES_FLYFRMFMT == pFrameFormat->Which() )
3106 // set Lock pFrame to avoid m_pCurr getting deleted
3107 TextFrameLockGuard aGuard(m_pFrame);
3108 pFly = static_cast<SwTextFlyCnt*>(pHint)->GetFlyFrame(pFrame);
3110 else
3111 pFly = nullptr;
3112 // aBase is the document-global position, from which the new extra portion is placed
3113 // aBase.X() = Offset in the line after the current position
3114 // aBase.Y() = LineIter.Y() + Ascent of the current position
3116 SwTwips nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc;
3117 // i#11859 - use new method <SwLineLayout::MaxAscentDescent(..)>
3118 // to change line spacing behaviour at paragraph - Compatibility to MS Word
3119 //SwLinePortion *pPos = pCurr->GetFirstPortion();
3120 //lcl_MaxAscDescent( pPos, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
3121 m_pCurr->MaxAscentDescent( nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc );
3123 // If the ascent of the frame is larger than the ascent of the current position,
3124 // we use this one when calculating the base, or the frame would be positioned
3125 // too much to the top, sliding down after all causing a repaint in an area
3126 // he actually never was in.
3127 SwTwips nAscent = 0;
3129 const bool bTextFrameVertical = GetInfo().GetTextFrame()->IsVertical();
3131 const bool bUseFlyAscent = pFly && pFly->isFrameAreaPositionValid() &&
3132 0 != ( bTextFrameVertical ?
3133 pFly->GetRefPoint().X() :
3134 pFly->GetRefPoint().Y() );
3136 if ( bUseFlyAscent )
3137 nAscent = std::abs( int( bTextFrameVertical ?
3138 pFly->GetRelPos().X() :
3139 pFly->GetRelPos().Y() ) );
3141 // Check if be prefer to use the ascent of the last portion:
3142 if ( IsQuick() ||
3143 !bUseFlyAscent ||
3144 nAscent < rInf.GetLast()->GetAscent() )
3146 nAscent = rInf.GetLast()->GetAscent();
3148 else if( nAscent > nFlyAsc )
3149 nFlyAsc = nAscent;
3151 Point aBase( GetLeftMargin() + rInf.X(), Y() + nAscent );
3152 AsCharFlags nMode = IsQuick() ? AsCharFlags::Quick : AsCharFlags::None;
3153 if( GetMulti() && GetMulti()->HasRotation() )
3155 nMode |= AsCharFlags::Rotate;
3156 if( GetMulti()->IsRevers() )
3157 nMode |= AsCharFlags::Reverse;
3160 Point aTmpBase( aBase );
3161 if ( GetInfo().GetTextFrame()->IsVertical() )
3162 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
3164 SwFlyCntPortion* pRet(nullptr);
3165 if( pFly )
3167 pRet = sw::FlyContentPortion::Create(*GetInfo().GetTextFrame(), pFly, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
3168 // We need to make sure that our font is set again in the OutputDevice
3169 // It could be that the FlyInCnt was added anew and GetFlyFrame() would
3170 // in turn cause, that it'd be created anew again.
3171 // This one's frames get formatted right away, which change the font.
3172 rInf.SelectFont();
3173 if( pRet->GetAscent() > nAscent )
3175 aBase.setY( Y() + pRet->GetAscent() );
3176 nMode |= AsCharFlags::UlSpace;
3177 if( !rInf.IsTest() )
3179 aTmpBase = aBase;
3180 if ( GetInfo().GetTextFrame()->IsVertical() )
3181 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase );
3183 pRet->SetBase( *rInf.GetTextFrame(), aTmpBase, nTmpAscent,
3184 nTmpDescent, nFlyAsc, nFlyDesc, nMode );
3188 else
3190 pRet = sw::DrawFlyCntPortion::Create(*rInf.GetTextFrame(), *pFrameFormat, aTmpBase, nTmpAscent, nTmpDescent, nFlyAsc, nFlyDesc, nMode);
3192 return pRet;
3195 /* Drop portion is a special case, because it has parts which aren't portions
3196 but we have handle them just like portions */
3197 void SwTextFormatter::MergeCharacterBorder( SwDropPortion const & rPortion )
3199 if( rPortion.GetLines() <= 1 )
3200 return;
3202 SwDropPortionPart* pCurrPart = rPortion.GetPart();
3203 while( pCurrPart )
3205 if( pCurrPart->GetFollow() &&
3206 ::lcl_HasSameBorder(pCurrPart->GetFont(), pCurrPart->GetFollow()->GetFont()) )
3208 pCurrPart->SetJoinBorderWithNext(true);
3209 pCurrPart->GetFollow()->SetJoinBorderWithPrev(true);
3211 pCurrPart = pCurrPart->GetFollow();
3215 void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePortion const *pPrev, SwTextFormatInfo& rInf )
3217 const SwFont aCurFont = *rInf.GetFont();
3218 if( !aCurFont.HasBorder() )
3219 return;
3221 if (pPrev && pPrev->GetJoinBorderWithNext() )
3223 // In some case border merge is called twice to the portion
3224 if( !rPortion.GetJoinBorderWithPrev() )
3226 rPortion.SetJoinBorderWithPrev(true);
3227 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetLeftBorderSpace() )
3228 rPortion.Width(rPortion.Width() - aCurFont.GetLeftBorderSpace());
3231 else
3233 rPortion.SetJoinBorderWithPrev(false);
3234 m_pFirstOfBorderMerge = &rPortion;
3237 // Get next portion's font
3238 bool bSeek = false;
3239 if (!rInf.IsFull() && // Not the last portion of the line (in case of line break)
3240 rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph
3242 bSeek = Seek(rInf.GetIdx() + rPortion.GetLen());
3244 // Don't join the next portion if SwKernPortion sits between two different boxes.
3245 bool bDisconnect = rPortion.IsKernPortion() && !rPortion.GetJoinBorderWithPrev();
3246 // If next portion has the same border then merge
3247 if( bSeek && GetFnt()->HasBorder() && ::lcl_HasSameBorder(aCurFont, *GetFnt()) && !bDisconnect )
3249 // In some case border merge is called twice to the portion
3250 if( !rPortion.GetJoinBorderWithNext() )
3252 rPortion.SetJoinBorderWithNext(true);
3253 if( rPortion.InTextGrp() && rPortion.Width() > aCurFont.GetRightBorderSpace() )
3254 rPortion.Width(rPortion.Width() - aCurFont.GetRightBorderSpace());
3257 // If this is the last portion of the merge group then make the real height merge
3258 else
3260 rPortion.SetJoinBorderWithNext(false);
3261 if( m_pFirstOfBorderMerge != &rPortion )
3263 // Calculate maximum height and ascent
3264 SwLinePortion* pActPor = m_pFirstOfBorderMerge;
3265 SwTwips nMaxAscent = 0;
3266 SwTwips nMaxHeight = 0;
3267 bool bReachCurrent = false;
3268 while( pActPor )
3270 if( nMaxHeight < pActPor->Height() )
3271 nMaxHeight = pActPor->Height();
3272 if( nMaxAscent < pActPor->GetAscent() )
3273 nMaxAscent = pActPor->GetAscent();
3275 pActPor = pActPor->GetNextPortion();
3276 if( !pActPor && !bReachCurrent )
3278 pActPor = &rPortion;
3279 bReachCurrent = true;
3283 // Change all portion's height and ascent
3284 pActPor = m_pFirstOfBorderMerge;
3285 bReachCurrent = false;
3286 while( pActPor )
3288 if( nMaxHeight > pActPor->Height() )
3289 pActPor->Height(nMaxHeight);
3290 if( nMaxAscent > pActPor->GetAscent() )
3291 pActPor->SetAscent(nMaxAscent);
3293 pActPor = pActPor->GetNextPortion();
3294 if( !pActPor && !bReachCurrent )
3296 pActPor = &rPortion;
3297 bReachCurrent = true;
3300 m_pFirstOfBorderMerge = nullptr;
3303 Seek(rInf.GetIdx());
3306 namespace {
3307 // calculates and sets optimal repaint offset for the current line
3308 tools::Long lcl_CalcOptRepaint( SwTextFormatter &rThis,
3309 SwLineLayout const &rCurr,
3310 TextFrameIndex const nOldLineEnd,
3311 const std::vector<tools::Long> &rFlyStarts )
3313 SwTextFormatInfo& txtFormatInfo = rThis.GetInfo();
3314 if ( txtFormatInfo.GetIdx() < txtFormatInfo.GetReformatStart() )
3315 // the reformat position is behind our new line, that means
3316 // something of our text has moved to the next line
3317 return 0;
3319 TextFrameIndex nReformat = std::min(txtFormatInfo.GetReformatStart(), nOldLineEnd);
3321 // in case we do not have any fly in our line, our repaint position
3322 // is the changed position - 1
3323 if ( rFlyStarts.empty() && ! rCurr.IsFly() )
3325 // this is the maximum repaint offset determined during formatting
3326 // for example: the beginning of the first right tab stop
3327 // if this value is 0, this means that we do not have an upper
3328 // limit for the repaint offset
3329 const tools::Long nFormatRepaint = txtFormatInfo.GetPaintOfst();
3331 if (nReformat < txtFormatInfo.GetLineStart() + TextFrameIndex(3))
3332 return 0;
3334 // step back two positions for smoother repaint
3335 nReformat -= TextFrameIndex(2);
3337 // i#28795, i#34607, i#38388
3338 // step back more characters, this is required by complex scripts
3339 // e.g., for Khmer (thank you, Javier!)
3340 static const TextFrameIndex nMaxContext(10);
3341 if (nReformat > txtFormatInfo.GetLineStart() + nMaxContext)
3342 nReformat = nReformat - nMaxContext;
3343 else
3345 nReformat = txtFormatInfo.GetLineStart();
3346 //reset the margin flag - prevent loops
3347 SwTextCursor::SetRightMargin(false);
3350 // Weird situation: Our line used to end with a hole portion
3351 // and we delete some characters at the end of our line. We have
3352 // to take care for repainting the blanks which are not anymore
3353 // covered by the hole portion
3354 while ( nReformat > txtFormatInfo.GetLineStart() &&
3355 CH_BLANK == txtFormatInfo.GetChar( nReformat ) )
3356 --nReformat;
3358 OSL_ENSURE( nReformat < txtFormatInfo.GetIdx(), "Reformat too small for me!" );
3359 SwRect aRect;
3361 // Note: GetChareRect is not const. It definitely changes the
3362 // bMulti flag. We have to save and restore the old value.
3363 bool bOldMulti = txtFormatInfo.IsMulti();
3364 rThis.GetCharRect( &aRect, nReformat );
3365 txtFormatInfo.SetMulti( bOldMulti );
3367 return nFormatRepaint ? std::min( aRect.Left(), nFormatRepaint ) :
3368 aRect.Left();
3370 else
3372 // nReformat may be wrong, if something around flys has changed:
3373 // we compare the former and the new fly positions in this line
3374 // if anything has changed, we carefully have to adjust the right
3375 // repaint position
3376 tools::Long nPOfst = 0;
3377 size_t nCnt = 0;
3378 tools::Long nX = 0;
3379 TextFrameIndex nIdx = rThis.GetInfo().GetLineStart();
3380 SwLinePortion* pPor = rCurr.GetFirstPortion();
3382 while ( pPor )
3384 if ( pPor->IsFlyPortion() )
3386 // compare start of fly with former start of fly
3387 if (nCnt < rFlyStarts.size() &&
3388 nX == rFlyStarts[ nCnt ] &&
3389 nIdx < nReformat
3391 // found fix position, nothing has changed left from nX
3392 nPOfst = nX + pPor->Width();
3393 else
3394 break;
3396 nCnt++;
3398 nX = nX + pPor->Width();
3399 nIdx = nIdx + pPor->GetLen();
3400 pPor = pPor->GetNextPortion();
3403 return nPOfst + rThis.GetLeftMargin();
3407 // Determine if we need to build hidden portions
3408 bool lcl_BuildHiddenPortion(const SwTextSizeInfo& rInf, TextFrameIndex & rPos)
3410 // Only if hidden text should not be shown:
3411 // if ( rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar() )
3412 const bool bShowInDocView = rInf.GetVsh() && rInf.GetVsh()->GetWin() && rInf.GetOpt().IsShowHiddenChar();
3413 const bool bShowForPrinting = rInf.GetOpt().IsShowHiddenChar( true ) && rInf.GetOpt().IsPrinting();
3414 if (bShowInDocView || bShowForPrinting)
3415 return false;
3417 const SwScriptInfo& rSI = rInf.GetParaPortion()->GetScriptInfo();
3418 TextFrameIndex nHiddenStart;
3419 TextFrameIndex nHiddenEnd;
3420 rSI.GetBoundsOfHiddenRange( rPos, nHiddenStart, nHiddenEnd );
3421 if ( nHiddenEnd )
3423 rPos = nHiddenEnd;
3424 return true;
3427 return false;
3430 bool lcl_HasSameBorder(const SwFont& rFirst, const SwFont& rSecond)
3432 return
3433 rFirst.GetTopBorder() == rSecond.GetTopBorder() &&
3434 rFirst.GetBottomBorder() == rSecond.GetBottomBorder() &&
3435 rFirst.GetLeftBorder() == rSecond.GetLeftBorder() &&
3436 rFirst.GetRightBorder() == rSecond.GetRightBorder() &&
3437 rFirst.GetTopBorderDist() == rSecond.GetTopBorderDist() &&
3438 rFirst.GetBottomBorderDist() == rSecond.GetBottomBorderDist() &&
3439 rFirst.GetLeftBorderDist() == rSecond.GetLeftBorderDist() &&
3440 rFirst.GetRightBorderDist() == rSecond.GetRightBorderDist() &&
3441 rFirst.GetOrientation() == rSecond.GetOrientation() &&
3442 rFirst.GetShadowColor() == rSecond.GetShadowColor() &&
3443 rFirst.GetShadowWidth() == rSecond.GetShadowWidth() &&
3444 rFirst.GetShadowLocation() == rSecond.GetShadowLocation();
3447 } //end unnamed namespace
3449 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */