1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
23 #include <com/sun/star/i18n/ScriptType.hpp>
24 #include <editeng/lspcitem.hxx>
25 #include <txtflcnt.hxx>
27 #include <flyfrms.hxx>
28 #include <fmtflcnt.hxx>
30 #include <ftninfo.hxx>
31 #include <charfmt.hxx>
32 #include <editeng/charrotateitem.hxx>
35 #include <viewopt.hxx>
37 #include "itrform2.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>
53 #include "pormulti.hxx"
54 #include <unotools/charclass.hxx>
55 #include <xmloff/odffields.hxx>
56 #include <IDocumentSettingAccess.hxx>
58 #include <IDocumentMarkAccess.hxx>
59 #include <comphelper/processfactory.hxx>
60 #include <vcl/pdfextoutdevdata.hxx>
61 #include <comphelper/string.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
;
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
)
95 void SwTextFormatter::CtorInitTextFormatter( SwTextFrame
*pNewFrame
, SwTextFormatInfo
*pNewInf
)
97 CtorInitTextPainter( pNewFrame
, pNewInf
);
99 m_pDropFormat
= GetInfo().GetDropFormat();
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
136 pLay
->SetNext( m_pCurr
->GetNext() );
137 m_pCurr
->SetNext( 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();
155 return m_pCurr
->Height();
160 bool SwTextFormatter::ClearIfIsFirstOfBorderMerge(const SwLinePortion
* pPortion
)
162 if (pPortion
== m_pFirstOfBorderMerge
)
164 m_pFirstOfBorderMerge
= nullptr;
170 SwLinePortion
*SwTextFormatter::Underflow( SwTextFormatInfo
&rInf
)
172 // Save values and initialize rInf
173 SwLinePortion
*pUnderflow
= rInf
.GetUnderflow();
177 // We format backwards, i.e. attribute changes can happen the next
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 );
189 rInf
.SetLast( m_pCurr
);
190 // pUnderflow does not need to be deleted, because it will drown in the following
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();
221 if( pPor
&& // Skip flys and initials when underflow.
222 ( pPor
->IsFlyPortion() || pPor
->IsDropPortion() ||
223 pPor
->IsFlyCntPortion() ) )
226 rInf
.SetLast( pPor
);
227 rInf
.SetStopUnderflow( true );
232 // What? The under-flow portion is not in the portion chain?
233 OSL_ENSURE( pPor
, "SwTextFormatter::Underflow: overflow but underflow" );
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.
243 return pPor
; // Is that enough?
247 // X + Width == 0 with SoftHyph > Line?!
248 if( !pPor
|| !(rInf
.X() + pPor
->Width()) )
254 // Preparing for Format()
255 // We need to chip off the chain behind pLast, because we Insert after the Format()
258 // line width is adjusted, so that pPor does not fit to current
260 rInf
.Width( rInf
.X() + (pPor
->Width() ? pPor
->Width() - 1 : 0) );
261 rInf
.SetLen( pPor
->GetLen() );
262 rInf
.SetFull( false );
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
270 CalcFlyWidth( rInf
);
272 rInf
.GetLast()->SetNextPortion(nullptr);
274 // The SwLineLayout is an exception to this, which splits at the first
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 );
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();
296 if (ClearIfIsFirstOfBorderMerge(pNext
))
298 pNext
= pNext
->GetNextPortion();
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();
309 rInf
.SetRest(nullptr);
313 void SwTextFormatter::InsertPortion( SwTextFormatInfo
&rInf
,
314 SwLinePortion
*pPor
)
316 SwLinePortion
*pLast
= nullptr;
317 // The new portion is inserted, but everything's different for
319 if( pPor
== m_pCurr
)
321 if ( m_pCurr
->GetNextPortion() )
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() );
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() );
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
);
367 if (!pPor
->IsDropPortion())
368 MergeCharacterBorder(*pPor
, pLast
, rInf
);
371 rInf
.SetLast( 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
);
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();
403 if ( 0 < pFly
->GetFix() )
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
);
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;
459 SwTwips nUnderLineStart
= 0;
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() )
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();
495 nNxtActual
= pTmpFnt
->GetActual();
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() )
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();
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]
531 nLstActual
= SwFontScript(m_pScriptInfo
->ScriptType(rInf
.GetIdx() - TextFrameIndex(1)) - 1);
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() &&
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();
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
;
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
) );
608 bFull
= pPor
->Format( rInf
);
610 if( rInf
.IsRuby() && !rInf
.GetRest() )
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
622 // 1. Underlined portions due to special underline feature
625 // 4. other Multiportions
628 else if ( ( ! rInf
.GetPaintOfst() || nUnderLineStart
< rInf
.GetPaintOfst() ) &&
629 // 1. Underlined portions
631 // reformat is at end of an underlined portion and next portion
633 ( ( rInf
.GetReformatStart() == rInf
.GetIdx() &&
634 LINESTYLE_NONE
== m_pFont
->GetUnderline()
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() &&
643 ( ( pPor
->InTabGrp() && !pPor
->IsTabLeftPortion() ) ||
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() )
651 || ( bHasGrid
&& SwFontScript::CJK
!= m_pFont
->GetActual() )
654 // we store the beginning of the critical portion as our
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
) )
663 if( pPor
->IsFlyCntPortion() || ( pPor
->IsMultiPortion() &&
664 static_cast<SwMultiPortion
*>(pPor
)->HasFlyInContent() ) )
666 // bUnderflow needs to be reset or we wrap again at the next softhyphen
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;
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
);
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())
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" );
723 SwLinePortion
* pTmpPor
= pGridKernPortion
->GetNextPortion();
724 SwTwips nSumWidth
= pPor
->Width();
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() ?
739 OSL_ENSURE( nKernWidth
<= nRestWidth
,
740 "Not enough space left for adjusting non-asian text in grid mode" );
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
),
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
);
778 // The last right centered, decimal tab
779 SwTabPortion
*pLastTab
= rInf
.GetLastTab();
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
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
)
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() );
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();
845 pPor
->Height( pLast
->Height(), false );
846 pPor
->SetAscent( pLast
->GetAscent() );
850 const SwLinePortion
*pLast
= rInf
.GetLast();
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 );
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
);
875 bChg
= SeekAndChgBefore( rInf
);
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
885 bChg
= SeekStartAndChg( rInf
);
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());
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() );
917 class SwMetaPortion
: public SwTextPortion
919 Color m_aShadowColor
;
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
;
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
943 rInf
.DrawViewOpt( *this, PortionType::Meta
,
944 // custom shading (RDF metadata)
945 COL_BLACK
== 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
)
967 if (!pPDFExtOutDevData
->GetIsExportFormFields())
972 if (!m_pTextContentControl
)
977 const SwFormatContentControl
& rFormatContentControl
= m_pTextContentControl
->GetContentControl();
978 const std::shared_ptr
<SwContentControl
>& pContentControl
= rFormatContentControl
.GetContentControl();
979 if (!pContentControl
)
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
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.
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;
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();
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
);
1054 if (!aText
.isEmpty() && !bTextFound
)
1056 // The selected entry has to be an index, if there is no index for it, insert one at
1058 pListWidget
->Entries
.insert(pListWidget
->Entries
.begin(), aText
);
1059 pListWidget
->SelectedEntries
.push_back(0);
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
);
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();
1093 bool bShrinkPageForPostIts
= pPDFExtOutDevData
->GetIsExportNotesInMargin()
1094 && sw_GetPostIts(rDoc
.getIDocumentFieldsAccess(), nullptr);
1095 const SwFont
* pFont
= rInf
.GetFont();
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
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.
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();
1156 aRect
= SwEnhancedPDFExportHelper::MapSwRectToPDFRect(pPageFrame
, aRect
);
1159 pDescriptor
->Location
= aRect
;
1161 pPDFExtOutDevData
->WrapBeginStructureElement(vcl::PDFWriter::Form
);
1162 pPDFExtOutDevData
->CreateControl(*pDescriptor
);
1163 pPDFExtOutDevData
->EndStructureElement();
1168 void SwContentControlPortion::Paint(const SwTextPaintInfo
& rInf
) const
1172 rInf
.DrawViewOpt(*this, PortionType::ContentControl
);
1174 if (DescribePDFControl(rInf
))
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()
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
];
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();
1238 assert(2 <= sal_Int32(nFieldLen
));
1239 pPor
= new SwFieldPortion(SwFieldType::GetTypeStr(SwFieldTypesEnum::Input
), nullptr, nFieldLen
);
1243 pPor
= new SwTextInputFieldPortion();
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) );
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
);
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;
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
);
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???");
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();
1364 if( !rInf
.X() && !m_pCurr
->GetNextPortion() && !m_pCurr
->GetLen() && !GetFnt()->IsURL() )
1368 pPor
= new SwTextPortion
;
1369 if (pBM
&& pBM
->GetFieldname() == ODF_FORMTEXT
)
1370 pPor
->SetFieldmarkText(true);
1378 // We calculate the length, the following portion limits are defined:
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());
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
});
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
);
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
:
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
:
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());
1507 // first portions have no length
1508 SwLinePortion
*SwTextFormatter::WhichFirstPortion(SwTextFormatInfo
&rInf
)
1510 SwLinePortion
*pPor
= nullptr;
1512 if( rInf
.GetRest() )
1515 if( '\0' != rInf
.GetHookChar() )
1518 pPor
= rInf
.GetRest();
1519 if( pPor
->IsErgoSumPortion() )
1520 rInf
.SetErgoDone(true);
1522 if( pPor
->IsFootnoteNumPortion() )
1523 rInf
.SetFootnoteDone(true);
1525 if( pPor
->InNumberGrp() )
1526 rInf
.SetNumDone(true);
1528 rInf
.SetRest(nullptr);
1529 m_pCurr
->SetRest( true );
1533 // We can stand in the follow, it's crucial that
1534 // pFrame->GetOffset() == 0!
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()));
1562 pPor
= new SwKernPortion( *m_pCurr
);
1565 // 4. The line rests (multiline fields)
1568 pPor
= rInf
.GetRest();
1569 // Only for pPor of course
1572 m_pCurr
->SetRest( true );
1573 rInf
.SetRest(nullptr);
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
);
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 );
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 );
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()));
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
1637 pPor
= TryNewNoLengthPortion(rInf
);
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
);
1657 static bool lcl_OldFieldRest( const SwLineLayout
* pCurr
)
1659 if( !pCurr
->GetNext() )
1661 const SwLinePortion
*pPor
= pCurr
->GetNext()->GetNextPortion();
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() )
1669 pPor
= pPor
->GetNextPortion();
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
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?");
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 ...
1706 // LineBreaks and Flys (bug05.sdw)
1708 if( rInf
.IsNewLine() && (!rInf
.GetFly() || !m_pCurr
->IsDummy()) )
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.
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 );
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 );
1738 SwLinePortion
*pFirst
= WhichFirstPortion( rInf
);
1741 rInf
.SetNewLine( true );
1742 if( pFirst
->InNumberGrp() )
1743 rInf
.SetNumDone( false) ;
1752 SwLinePortion
*pPor
= WhichFirstPortion( rInf
);
1754 // Check for Hidden Portion:
1757 TextFrameIndex nEnd
= rInf
.GetIdx();
1758 if ( ::lcl_BuildHiddenPortion( rInf
, nEnd
) )
1759 pPor
= new SwHiddenTextPortion( nEnd
- rInf
.GetIdx() );
1764 if( ( !m_pMulti
|| m_pMulti
->IsBidi() ) &&
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
);
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);
1792 pTmp
= new SwDoubleLinePortion( *pCreate
, nEnd
);
1795 CalcFlyWidth( rInf
);
1801 sal_Unicode cChar
= rInf
.GetHookChar();
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();
1816 if (rInf
.GetIdx() >= TextFrameIndex(rInf
.GetText().getLength()))
1819 CalcFlyWidth( rInf
);
1822 cChar
= rInf
.GetChar( rInf
.GetIdx() );
1828 pPor
= NewTabPortion( rInf
, false ); break;
1832 SwTextAttr
* pHint
= GetAttr(rInf
.GetIdx());
1833 pPor
= new SwBreakPortion(*rInf
.GetLast(), pHint
);
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
);
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 );
1877 rInf
.SetFull( rInf
.GetLastTab()->Format( rInf
) );
1880 if( rInf
.GetRest() )
1884 rInf
.SetNewLine(true);
1887 pPor
= rInf
.GetRest();
1888 rInf
.SetRest(nullptr);
1894 pPor
= NewTextPortion( rInf
);
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));
1906 if( !pPor
|| rInf
.IsStop() )
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.
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();
1932 pInfo
= &pDoc
->GetFootnoteInfo();
1933 const SwAttrSet
& rSet
= pInfo
->GetAnchorCharFormat(const_cast<SwDoc
&>(*pDoc
))->GetAttrSet();
1936 if( const SvxCharRotateItem
* pItem
= rSet
.GetItemIfSet( RES_CHRATR_ROTATE
) )
1937 nDir
= pItem
->GetValue();
1942 pPor
= new SwRotatedPortion(rInf
.GetIdx() + TextFrameIndex(1),
1949 else if ( pPor
->InNumberGrp() )
1951 const SwFont
* pNumFnt
= static_cast<SwFieldPortion
*>(pPor
)->GetFont();
1955 Degree10 nDir
= pNumFnt
->GetOrientation( rInf
.GetTextFrame()->IsVertical() );
1959 pPor
= new SwRotatedPortion(TextFrameIndex(0), 900_deg10
== nDir
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() )
1991 pPor
= rInf
.GetFly();
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()));
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;
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.
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());
2087 m_pCurr
->Truncate();
2090 else if( GetInfo().IsDropInit() )
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() );
2112 // bBuild decides if another lap of honor is done
2113 if ( m_pCurr
->GetRealHeight() <= GetInfo().GetLineHeight() )
2115 m_pCurr
->SetRealHeight( GetInfo().GetLineHeight() );
2120 bBuild
= ( GetInfo().GetTextFly().IsOn() && ChkFlyUnderflow(GetInfo()) )
2121 || GetInfo().CheckFootnotePortion(m_pCurr
);
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
);
2133 GetInfo().ResetMaxWidthDiff();
2134 GetInfo().SetExtraSpace(0);
2137 if ( GetInfo().GetRest() )
2139 delete GetInfo().GetRest();
2140 GetInfo().SetRest( nullptr );
2143 // set original rest portion
2145 GetInfo().SetRest( new SwFieldPortion( *xSaveField
) );
2147 m_pCurr
->SetLen(TextFrameIndex(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
))
2161 SwLinePortion
* pPor
= m_pCurr
->GetFirstPortion();
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
) );
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
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() )
2210 GetInfo().SetPaintOfst( 0 ); // changed line height => no recycling
2211 // all following line must be painted and when Flys are around,
2213 GetInfo().SetShift( true );
2216 if ( IsFlyInCntBase() && !IsQuick() )
2217 UpdatePos( m_pCurr
, GetTopLeft(), GetStart() );
2222 void SwTextFormatter::RecalcRealHeight()
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() +
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();
2265 nTmp
*= nLineHeight
;
2266 nLineHeight
= nTmp
/ 100;
2269 m_pCurr
->SetRealHeight( nLineHeight
);
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())
2281 const SvxLineSpacingItem
*pSpace
= m_aLineInf
.GetLineSpacing();
2284 switch( pSpace
->GetLineSpaceRule() )
2286 case SvxLineSpaceRule::Auto
:
2287 // shrink first line of paragraph too on spacing < 100%
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
2295 nTmp
= nTmp
? 50 : 100;
2296 if (nTmp
<100) { // code adapted from fixed line height
2297 nTmp
*= nLineHeight
;
2302 SwTwips nAsc
= (4 * nLineHeight
) / 5; // 80%
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 );
2311 m_pCurr
->SetAscent( nAsc
);
2312 m_pCurr
->Height( nLineHeight
, false );
2313 m_pInf
->GetParaPortion()->SetFixLineHeight();
2317 case SvxLineSpaceRule::Min
:
2319 if( nLineHeight
< pSpace
->GetLineHeight() )
2320 nLineHeight
= pSpace
->GetLineHeight();
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();
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()
2340 switch( pSpace
->GetInterLineSpaceRule() )
2342 case SvxInterLineSpaceRule::Off
:
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% ...
2350 nTmp
= nTmp
? 50 : 100;
2352 bool bPropLineShrinks
= (nTmp
< 100);
2354 // extend line height by (nPropLineSpace - 100) percent of the font height
2356 nTmp
*= m_pCurr
->GetTextHeight();
2358 nTmp
+= nLineHeight
;
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();
2377 case SvxInterLineSpaceRule::Fix
:
2379 nLineHeight
= nLineHeight
+ pSpace
->GetInterLineSpace();
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() );
2395 nLineHeight
+= RegDiff() - nDiff
;
2398 m_pCurr
->SetRealHeight( nLineHeight
);
2401 void SwTextFormatter::FeedInf( SwTextFormatInfo
&rInf
) const
2403 // delete Fly in any case!
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();
2431 // delete pSpaceAdd and pKanaComp
2432 m_pCurr
->FinishSpaceAdd();
2433 m_pCurr
->FinishKanaComp();
2434 m_pCurr
->ResetFlags();
2438 bool SwTextFormatter::CalcOnceMore()
2442 const sal_uInt16 nOldDrop
= GetDropHeight();
2443 CalcDropHeight( m_pDropFormat
->GetLines() );
2444 m_bOnceMore
= nOldDrop
!= GetDropHeight();
2447 m_bOnceMore
= false;
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
;
2466 const_cast<SwRepaint
&>(GetInfo().GetParaPortion()
2467 ->GetRepaint()).Bottom( nRet
-1 );
2468 const_cast<SwTextFormatInfo
&>(GetInfo()).SetPaintOfst( 0 );
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() &&
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;
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
;
2517 case SvxAdjust::Center
:
2518 case SvxAdjust::Right
:
2519 bOptimizeRepaint
= false;
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
;
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() )
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();
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
;
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
);
2589 aTmpInf
.X( aTmpInf
.X() + nAscent
);
2593 if ( GetMulti()->IsBidi() )
2594 nFlags
|= AsCharFlags::Bidi
;
2595 aTmpInf
.Y( aTmpInf
.Y() + nAscent
);
2599 aTmpInf
.Y( aTmpInf
.Y() + nAscent
);
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
;
2617 nTmpDescent
= nTmpHeight
- nAscent
;
2618 nFlyDesc
= nTmpDescent
;
2620 static_cast<SwGrfNumPortion
*>(pPos
)->SetBase( nTmpAscent
, nTmpDescent
,
2621 nFlyAsc
, nFlyDesc
);
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
,
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() );
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();
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() )
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
;
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
);
2704 if ( GetInfo().GetTextFrame()->IsVertical() )
2706 nBaseLine
= GetInfo().GetTextFrame()->SwitchHorizontalToVertical( nBaseLine
);
2707 aBase
= Point( nBaseLine
, static_cast<SwFlyCntPortion
*>(pPos
)->GetRefPoint().Y() );
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?" );
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() )
2740 // We now check every portion that could have lowered for overlapping
2742 const SwLinePortion
*pPos
= GetCurr()->GetFirstPortion();
2743 aLine
.Pos().setY( Y() + GetCurr()->GetRealHeight() - GetCurr()->Height() );
2744 aLine
.Height( GetCurr()->Height() );
2748 aLine
.Width( pPos
->Width() );
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() );
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() );
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() );
2801 aLine
.Left( aLine
.Left() + pPos
->Width() );
2802 pPos
= pPos
->GetNextPortion();
2808 void SwTextFormatter::CalcFlyWidth( SwTextFormatInfo
&rInf
)
2810 if( GetMulti() || rInf
.GetFly() )
2813 SwTextFly
& rTextFly
= rInf
.GetTextFly();
2814 if( !rTextFly
.IsOn() || rInf
.IsIgnoreFly() )
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
;
2833 // We make a first guess for the lines real height
2834 if ( ! m_pCurr
->GetRealHeight() )
2837 nAscent
= pLast
->GetAscent();
2838 nHeight
= pLast
->Height();
2840 if ( m_pCurr
->GetRealHeight() > nHeight
)
2841 nTop
+= m_pCurr
->GetRealHeight() - nHeight
;
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
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
2874 SwTwips nLower
= m_pFrame
->GetLowerMarginForFlyIntersect();
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
2905 if( !aInter
.Overlaps( aLine
) )
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();
2919 if (!pFlyFrame
->IsFlySplitAllowed())
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();
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() )
2964 aInter
.Intersection( aLine
);
2965 if( !aInter
.HasArea() )
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
;
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
);
2999 m_pCurr
->SetForcedLeftMargin();
3000 rInf
.ForcedLeftMargin(aInter
.Width());
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
)
3031 if( nAscent
< pFly
->Height() )
3032 pFly
->SetAscent( nAscent
);
3034 pFly
->SetAscent( pFly
->Height() );
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() );
3046 pFly
->Height( aInter
.Height() );
3047 if( nAscent
< pFly
->Height() )
3048 pFly
->SetAscent( nAscent
);
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()));
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
);
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
);
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:
3144 nAscent
< rInf
.GetLast()->GetAscent() )
3146 nAscent
= rInf
.GetLast()->GetAscent();
3148 else if( nAscent
> nFlyAsc
)
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);
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.
3173 if( pRet
->GetAscent() > nAscent
)
3175 aBase
.setY( Y() + pRet
->GetAscent() );
3176 nMode
|= AsCharFlags::UlSpace
;
3177 if( !rInf
.IsTest() )
3180 if ( GetInfo().GetTextFrame()->IsVertical() )
3181 GetInfo().GetTextFrame()->SwitchHorizontalToVertical( aTmpBase
);
3183 pRet
->SetBase( *rInf
.GetTextFrame(), aTmpBase
, nTmpAscent
,
3184 nTmpDescent
, nFlyAsc
, nFlyDesc
, nMode
);
3190 pRet
= sw::DrawFlyCntPortion::Create(*rInf
.GetTextFrame(), *pFrameFormat
, aTmpBase
, nTmpAscent
, nTmpDescent
, nFlyAsc
, nFlyDesc
, nMode
);
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 )
3202 SwDropPortionPart
* pCurrPart
= rPortion
.GetPart();
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() )
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());
3233 rPortion
.SetJoinBorderWithPrev(false);
3234 m_pFirstOfBorderMerge
= &rPortion
;
3237 // Get next portion's font
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
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;
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;
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());
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
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))
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
;
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
) )
3358 OSL_ENSURE( nReformat
< txtFormatInfo
.GetIdx(), "Reformat too small for me!" );
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
) :
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
3376 tools::Long nPOfst
= 0;
3379 TextFrameIndex nIdx
= rThis
.GetInfo().GetLineStart();
3380 SwLinePortion
* pPor
= rCurr
.GetFirstPortion();
3384 if ( pPor
->IsFlyPortion() )
3386 // compare start of fly with former start of fly
3387 if (nCnt
< rFlyStarts
.size() &&
3388 nX
== rFlyStarts
[ nCnt
] &&
3391 // found fix position, nothing has changed left from nX
3392 nPOfst
= nX
+ pPor
->Width();
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
)
3417 const SwScriptInfo
& rSI
= rInf
.GetParaPortion()->GetScriptInfo();
3418 TextFrameIndex nHiddenStart
;
3419 TextFrameIndex nHiddenEnd
;
3420 rSI
.GetBoundsOfHiddenRange( rPos
, nHiddenStart
, nHiddenEnd
);
3430 bool lcl_HasSameBorder(const SwFont
& rFirst
, const SwFont
& rSecond
)
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: */