LibreOffice Module editeng (master) 1
impedit3.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20
21#include <vcl/svapp.hxx>
22#include <vcl/metaact.hxx>
23#include <vcl/gdimtf.hxx>
24#include <vcl/settings.hxx>
25#include <vcl/window.hxx>
26
27#include <editeng/outliner.hxx>
28#include <editeng/tstpitem.hxx>
29#include <editeng/lspcitem.hxx>
30#include <editeng/flditem.hxx>
32#include "impedit.hxx"
33#include <editeng/editeng.hxx>
34#include <editeng/editview.hxx>
36#include <editeng/txtrange.hxx>
37#include <editeng/udlnitem.hxx>
38#include <editeng/fhgtitem.hxx>
39#include <editeng/lrspitem.hxx>
40#include <editeng/ulspitem.hxx>
41#include <editeng/fontitem.hxx>
42#include <editeng/wghtitem.hxx>
43#include <editeng/postitem.hxx>
44#include <editeng/langitem.hxx>
47#include <editeng/numitem.hxx>
48#include <outleeng.hxx>
49
50#include <svtools/colorcfg.hxx>
51#include <svl/ctloptions.hxx>
52#include <svl/asiancfg.hxx>
53
54#include <svx/compatflags.hxx>
55#include <sfx2/viewsh.hxx>
56
59
61
62#include <math.h>
63#include <vcl/metric.hxx>
64#include <com/sun/star/i18n/BreakIterator.hpp>
65#include <com/sun/star/i18n/ScriptType.hpp>
66#include <com/sun/star/i18n/InputSequenceChecker.hpp>
69
71#include <comphelper/lok.hxx>
72#include <rtl/ustrbuf.hxx>
73#include <sal/log.hxx>
74#include <o3tl/safeint.hxx>
76#include <osl/diagnose.h>
77#include <comphelper/string.hxx>
78#include <cstddef>
79#include <memory>
80#include <set>
81
83
84#include <unicode/uchar.h>
85
86using namespace ::com::sun::star;
87using namespace ::com::sun::star::uno;
88using namespace ::com::sun::star::beans;
89using namespace ::com::sun::star::linguistic2;
90
91constexpr OUStringLiteral CH_HYPH = u"-";
92
94
95namespace {
96
97struct TabInfo
98{
99 bool bValid;
100
101 SvxTabStop aTabStop;
102 sal_Int32 nTabPortion;
103 tools::Long nStartPosX;
104 tools::Long nTabPos;
105
106 TabInfo()
107 : bValid(false)
108 , nTabPortion(0)
109 , nStartPosX(0)
110 , nTabPos(0)
111 { }
112
113};
114
115}
116
118{
119 switch ( cChar )
120 {
121 case 0x3008: case 0x300A: case 0x300C: case 0x300E:
122 case 0x3010: case 0x3014: case 0x3016: case 0x3018:
123 case 0x301A: case 0x301D: case 0xFF09: case 0xFF3D:
124 case 0xFF5D:
125 {
127 }
128 case 0x3001: case 0x3002: case 0x3009: case 0x300B:
129 case 0x300D: case 0x300F: case 0x3011: case 0x3015:
130 case 0x3017: case 0x3019: case 0x301B: case 0x301E:
131 case 0x301F: case 0xFF08: case 0xFF0C: case 0xFF0E:
132 case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B:
133 {
135 }
136 default:
137 {
138 return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
139 }
140 }
141}
142
143static void lcl_DrawRedLines( OutputDevice& rOutDev,
144 tools::Long nFontHeight,
145 const Point& rPoint,
146 size_t nIndex,
147 size_t nMaxEnd,
149 WrongList const * pWrongs,
150 Degree10 nOrientation,
151 const Point& rOrigin,
152 bool bVertical,
153 bool bIsRightToLeft )
154{
155 // But only if font is not too small...
156 tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height();
157 if (WRONG_SHOW_MIN >= nHeight)
158 return;
159
160 size_t nEnd, nStart = nIndex;
161 bool bWrong = pWrongs->NextWrong(nStart, nEnd);
162
163 while (bWrong)
164 {
165 if (nStart >= nMaxEnd)
166 break;
167
168 if (nStart < nIndex) // Corrected
169 nStart = nIndex;
170
171 if (nEnd > nMaxEnd)
172 nEnd = nMaxEnd;
173
174 Point aPoint1(rPoint);
175 if (bVertical)
176 {
177 // VCL doesn't know that the text is vertical, and is manipulating
178 // the positions a little bit in y direction...
179 tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height();
180 tools::Long nCorrect = 2 * nOnePixel;
181 aPoint1.AdjustY(-nCorrect);
182 aPoint1.AdjustX(-nCorrect);
183 }
184 if (nStart > nIndex)
185 {
186 if (!bVertical)
187 {
188 // since for RTL portions rPoint is on the visual right end of the portion
189 // (i.e. at the start of the first RTL char) we need to subtract the offset
190 // for RTL portions...
191 aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]);
192 }
193 else
194 aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]);
195 }
196 Point aPoint2(rPoint);
197 assert(nEnd > nIndex && "RedLine: aPnt2?");
198 if (!bVertical)
199 {
200 // since for RTL portions rPoint is on the visual right end of the portion
201 // (i.e. at the start of the first RTL char) we need to subtract the offset
202 // for RTL portions...
203 aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]);
204 }
205 else
206 {
207 aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]);
208 }
209
210 if (nOrientation)
211 {
212 rOrigin.RotateAround(aPoint1, nOrientation);
213 rOrigin.RotateAround(aPoint2, nOrientation);
214 }
215
216 {
217 vcl::ScopedAntialiasing a(rOutDev, true);
218 rOutDev.DrawWaveLine(aPoint1, aPoint2);
219 }
220
221 nStart = nEnd + 1;
222 if (nEnd < nMaxEnd)
223 bWrong = pWrongs->NextWrong(nStart, nEnd);
224 else
225 bWrong = false;
226 }
227}
228
229// For Kashidas from sw/source/core/text/porlay.cxx
230
231#define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g )
232#define isAinChar(c) IS_JOINING_GROUP((c), AIN)
233#define isAlefChar(c) IS_JOINING_GROUP((c), ALEF)
234#define isDalChar(c) IS_JOINING_GROUP((c), DAL)
235#define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH))
236#define isGafChar(c) IS_JOINING_GROUP((c), GAF)
237#define isHehChar(c) IS_JOINING_GROUP((c), HEH)
238#define isKafChar(c) IS_JOINING_GROUP((c), KAF)
239#define isLamChar(c) IS_JOINING_GROUP((c), LAM)
240#define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF))
241#define isRehChar(c) IS_JOINING_GROUP((c), REH)
242#define isTahChar(c) IS_JOINING_GROUP((c), TAH)
243#define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA)
244#define isWawChar(c) IS_JOINING_GROUP((c), WAW)
245#define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN))
246
247// Beh and characters that behave like Beh in medial form.
248static bool isBehChar(sal_Unicode cCh)
249{
250 bool bRet = false;
251 switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
252 {
253 case U_JG_BEH:
254 case U_JG_NOON:
255 case U_JG_AFRICAN_NOON:
256 case U_JG_NYA:
257 case U_JG_YEH:
258 case U_JG_FARSI_YEH:
259 case U_JG_BURUSHASKI_YEH_BARREE:
260 bRet = true;
261 break;
262 default:
263 bRet = false;
264 break;
265 }
266
267 return bRet;
268}
269
270// Yeh and characters that behave like Yeh in final form.
271static bool isYehChar(sal_Unicode cCh)
272{
273 bool bRet = false;
274 switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
275 {
276 case U_JG_YEH:
277 case U_JG_FARSI_YEH:
278 case U_JG_YEH_BARREE:
279 case U_JG_BURUSHASKI_YEH_BARREE:
280 case U_JG_YEH_WITH_TAIL:
281 bRet = true;
282 break;
283 default:
284 bRet = false;
285 break;
286 }
287
288 return bRet;
289}
290
292{
293 return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT;
294}
295
296static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh )
297{
298 // Lam + Alef
299 return ( isLamChar ( cCh ) && isAlefChar ( cNextCh ));
300}
301
302static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh )
303{
304 const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE );
305 bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING;
306
307 // check for ligatures cPrevChar + cChar
308 if ( bRet )
309 bRet = ! lcl_IsLigature( cPrevCh, cCh );
310
311 return bRet;
312}
313
314
315
317{
319 return;
320
321 DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" );
322
323 for (EditView* pView : aEditViews)
324 {
325 pView->HideCursor();
326
327 tools::Rectangle aClipRect( aInvalidRect );
328 tools::Rectangle aVisArea( pView->GetVisArea() );
329 aClipRect.Intersection( aVisArea );
330
331 if ( !aClipRect.IsEmpty() )
332 {
333 // convert to window coordinates...
334 aClipRect = pView->pImpEditView->GetWindowPos( aClipRect );
335
336 // moved to one executing method to allow finer control
337 pView->InvalidateWindow(aClipRect);
338
339 pView->InvalidateOtherViewWindows( aClipRect );
340 }
341 }
342
343 if ( pCurView )
344 {
345 bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll();
346 pCurView->ShowCursor( bGotoCursor );
347 }
348
351}
352
353IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void)
354{
355 if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() )
356 DoOnlineSpelling();
357 else
358 aOnlineSpellTimer.Start();
359}
360
361IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void)
362{
363 aIdleFormatter.ResetRestarts();
364
365 // #i97146# check if that view is still available
366 // else probably the idle format timer fired while we're already
367 // downing
368 EditView* pView = aIdleFormatter.GetView();
369 for (EditView* aEditView : aEditViews)
370 {
371 if( aEditView == pView )
372 {
373 FormatAndLayout( pView );
374 break;
375 }
376 }
377}
378
380{
382 // If not idle, but still not formatted:
383 if ( !IsFormatted() )
384 FormatDoc();
385}
386
388{
390}
391
392
394{
395 for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
396 GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 );
397 FormatDoc();
398}
399
401{
402 if (!IsUpdateLayout() || IsFormatting())
403 return;
404
405 bIsFormatting = true;
406
407 // Then I can also start the spell-timer...
408 if ( GetStatus().DoOnlineSpelling() )
410
411 tools::Long nY = 0;
412 bool bGrow = false;
413
414 // Here already, so that not always in CreateLines...
415 bool bMapChanged = ImpCheckRefMapMode();
416 sal_Int32 nParaCount = GetParaPortions().Count();
417 o3tl::sorted_vector<sal_Int32> aRepaintParas;
418 aRepaintParas.reserve(nParaCount);
419
420 for ( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ )
421 {
422 ParaPortion* pParaPortion = GetParaPortions()[nPara];
423 if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) )
424 {
425 // No formatting should be necessary for MustRepaint()!
426 if ( !pParaPortion->IsInvalid() || CreateLines( nPara, nY ) )
427 {
428 if ( !bGrow && GetTextRanger() )
429 {
430 // For a change in height all below must be reformatted...
431 for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ )
432 {
433 ParaPortion* pPP = GetParaPortions()[n];
434 pPP->MarkSelectionInvalid( 0 );
435 pPP->GetLines().Reset();
436 }
437 }
438 bGrow = true;
440 {
442
443 for (EditView* pView : aEditViews)
444 {
445 ImpEditView* pImpView = pView->pImpEditView.get();
446 pImpView->ScrollStateChange();
447 }
448
449 }
450 pParaPortion->SetMustRepaint( false );
451 }
452
453 aRepaintParas.insert(nPara);
454 }
455 nY += pParaPortion->GetHeight();
456 }
457
458 aInvalidRect = tools::Rectangle(); // make empty
459
460 // One can also get into the formatting through UpdateMode ON=>OFF=>ON...
461 // enable optimization first after Vobis delivery...
462 {
463 tools::Long nNewHeightNTP;
464 tools::Long nNewHeight = CalcTextHeight(&nNewHeightNTP);
465 tools::Long nDiff = nNewHeight - nCurTextHeight;
466 if ( nDiff )
467 {
469 { 0, nNewHeight }, { getWidthDirectionAware(aPaperSize), nCurTextHeight }));
471 }
472
473 nCurTextHeight = nNewHeight;
474 nCurTextHeightNTP = nNewHeightNTP;
475
476 if ( aStatus.AutoPageSize() )
478 else if ( nDiff )
479 {
480 for (EditView* pView : aEditViews)
481 {
482 ImpEditView* pImpView = pView->pImpEditView.get();
483 if ( pImpView->DoAutoHeight() )
484 {
485 Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight );
486 if ( aSz.Height() > aMaxAutoPaperSize.Height() )
488 else if ( aSz.Height() < aMinAutoPaperSize.Height() )
491 pImpView->GetOutputArea().TopLeft(), aSz ) );
492 }
493 }
494 }
495
496 if (!aRepaintParas.empty())
497 {
498 auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) {
499 if (aRepaintParas.count(rInfo.nPortion))
500 aInvalidRect.Union(rInfo.aArea);
502 };
503 IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS);
504 }
505 }
506
507 bIsFormatting = false;
508 bFormatted = true;
509
510 if ( bMapChanged )
511 GetRefDevice()->Pop();
512
513 CallStatusHdl(); // If Modified...
514}
515
517{
518 bool bChange = false;
519
520 if ( aStatus.DoFormat100() )
521 {
522 MapMode aMapMode( GetRefDevice()->GetMapMode() );
523 if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() )
524 bChange = true;
525 else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() )
526 bChange = true;
527
528 if ( bChange )
529 {
530 Fraction Scale1( 1, 1 );
531 aMapMode.SetScaleX( Scale1 );
532 aMapMode.SetScaleY( Scale1 );
533 GetRefDevice()->Push();
534 GetRefDevice()->SetMapMode( aMapMode );
535 }
536 }
537
538 return bChange;
539}
540
542{
543 Size aPrevPaperSize( GetPaperSize() );
544 if ( GetStatus().AutoPageWidth() )
546 if ( GetStatus().AutoPageHeight() )
548
549 SetValidPaperSize( aPaperSize ); // consider Min, Max
550
551 if ( aPaperSize == aPrevPaperSize )
552 return;
553
554 if ( ( !IsEffectivelyVertical() && ( aPaperSize.Width() != aPrevPaperSize.Width() ) )
555 || ( IsEffectivelyVertical() && ( aPaperSize.Height() != aPrevPaperSize.Height() ) ) )
556 {
557 // If ahead is centered / right or tabs...
559 for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
560 {
561 // Only paragraphs which are not aligned to the left need to be
562 // reformatted, the height can not be changed here anymore.
563 ParaPortion* pParaPortion = GetParaPortions()[nPara];
564 SvxAdjust eJustification = GetJustification( nPara );
565 if ( eJustification != SvxAdjust::Left )
566 {
567 pParaPortion->MarkSelectionInvalid( 0 );
568 CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange!
569 }
570 }
571 }
572
573 Size aInvSize = aPaperSize;
574 if ( aPaperSize.Width() < aPrevPaperSize.Width() )
575 aInvSize.setWidth( aPrevPaperSize.Width() );
576 if ( aPaperSize.Height() < aPrevPaperSize.Height() )
577 aInvSize.setHeight( aPrevPaperSize.Height() );
578
579 Size aSz( aInvSize );
580 if ( IsEffectivelyVertical() )
581 {
582 aSz.setWidth( aInvSize.Height() );
583 aSz.setHeight( aInvSize.Width() );
584 }
586
587
588 for (EditView* pView : aEditViews)
589 {
590 pView->pImpEditView->RecalcOutputArea();
591 }
592}
593
595{
596 SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") );
597
598 tools::Long nBoxHeight = GetMaxAutoPaperSize().Height();
599 SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight);
600
601 tools::Long nTxtHeight = CalcTextHeight(nullptr);
602 SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight);
603
604 sal_uInt32 nParaCount = GetParaPortions().Count();
605 sal_uInt32 nFirstLineCount = GetLineCount(0);
606 bool bOnlyOneEmptyPara = (nParaCount == 1) &&
607 (nFirstLineCount == 1) &&
608 (GetLineLen(0,0) == 0);
609
610 if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara)
611 {
612 // which paragraph is the first to cause higher size of the box?
613 ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text
614 //aStatus.SetPageOverflow(true);
616 } else
617 {
618 // No overflow if within box boundaries
619 //aStatus.SetPageOverflow(false);
621 }
622
623}
624
625static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight )
626{
627 constexpr const double f120Percent = 12.0 / 10.0;
628 return basegfx::fround(nFontHeight * f120Percent); // + 20%
629}
630
632{
633 assert(mnColumns >= 1);
634 tools::Long nWidth = IsEffectivelyVertical() ? rPaperSize.Height() : rPaperSize.Width();
635 return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns;
636}
637
638bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
639{
640 ParaPortion* pParaPortion = GetParaPortions()[nPara];
641
642 // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False
643 assert( pParaPortion->GetNode() && "Portion without Node in CreateLines" );
644 DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" );
645 DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" );
646
647 bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 );
648 bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger();
649
650
651 // Fast special treatment for empty paragraphs...
652
653 if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() )
654 {
655 // fast special treatment...
656 if ( pParaPortion->GetTextPortions().Count() )
657 pParaPortion->GetTextPortions().Reset();
658 if ( pParaPortion->GetLines().Count() )
659 pParaPortion->GetLines().Reset();
660 CreateAndInsertEmptyLine( pParaPortion );
661 return FinishCreateLines( pParaPortion );
662 }
663
664 sal_Int64 nCurrentPosY = nStartPosY;
665 // If we're allowed to skip parts outside and this cannot possibly fit in the given height,
666 // bail out to avoid possibly formatting a lot of text that will not be used. For the first
667 // paragraph still format at least a bit.
668 if( mbSkipOutsideFormat && nPara != 0
669 && !aStatus.AutoPageHeight() && aPaperSize.Height() < nCurrentPosY )
670 {
671 return false;
672 }
673
674 // Initialization...
675
676 // Always format for 100%:
677 bool bMapChanged = ImpCheckRefMapMode();
678
679 if ( pParaPortion->GetLines().Count() == 0 )
680 {
681 EditLine* pL = new EditLine;
682 pParaPortion->GetLines().Append(pL);
683 }
684
685
686 // Get Paragraph attributes...
687
688 ContentNode* const pNode = pParaPortion->GetNode();
689
690 bool bRightToLeftPara = IsRightToLeft( nPara );
691
692 SvxAdjust eJustification = GetJustification( nPara );
693 bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue();
694 sal_Int32 nSpaceBefore = 0;
695 sal_Int32 nMinLabelWidth = 0;
696 sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth );
697 const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode );
698 const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL );
699 const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue();
700
701 const short nInvalidDiff = pParaPortion->GetInvalidDiff();
702 const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart();
703 const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
704
705 bool bQuickFormat = false;
706 if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) )
707 {
708 if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) &&
709 ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) )
710 {
711 bQuickFormat = true;
712 }
713 else if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) )
714 {
715 // check if delete over the portion boundaries was done...
716 sal_Int32 nStart = nInvalidStart; // DOUBLE !!!!!!!!!!!!!!!
717 sal_Int32 nEnd = nStart - nInvalidDiff; // negative
718 bQuickFormat = true;
719 sal_Int32 nPos = 0;
720 sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
721 for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ )
722 {
723 // There must be no start / end in the deleted area.
724 const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
725 nPos = nPos + rTP.GetLen();
726 if ( ( nPos > nStart ) && ( nPos < nEnd ) )
727 {
728 bQuickFormat = false;
729 break;
730 }
731 }
732 }
733 }
734
735 // Saving both layout mode and language (since I'm potentially changing both)
737
738 ImplInitLayoutMode(*GetRefDevice(), nPara, -1);
739
740 sal_Int32 nRealInvalidStart = nInvalidStart;
741
742 if ( bEmptyNodeWithPolygon )
743 {
744 TextPortion* pDummyPortion = new TextPortion( 0 );
745 pParaPortion->GetTextPortions().Reset();
746 pParaPortion->GetTextPortions().Append(pDummyPortion);
747 }
748 else if ( bQuickFormat )
749 {
750 // faster Method:
751 RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff );
752 }
753 else // nRealInvalidStart can be before InvalidStart, since Portions were deleted...
754 {
755 CreateTextPortions( pParaPortion, nRealInvalidStart );
756 }
757
758
759 // Search for line with InvalidPos, start one line before
760 // Flag the line => do not remove it !
761
762
763 sal_Int32 nLine = pParaPortion->GetLines().Count()-1;
764 for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
765 {
766 EditLine& rLine = pParaPortion->GetLines()[nL];
767 if ( rLine.GetEnd() > nRealInvalidStart ) // not nInvalidStart!
768 {
769 nLine = nL;
770 break;
771 }
772 rLine.SetValid();
773 }
774 // Begin one line before...
775 // If it is typed at the end, the line in front cannot change.
776 if ( nLine && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) )
777 nLine--;
778
779 EditLine* pLine = &pParaPortion->GetLines()[nLine];
780
781 static const tools::Rectangle aZeroArea { Point(), Point() };
782 tools::Rectangle aBulletArea( aZeroArea );
783 if ( !nLine )
784 {
785 aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
786 if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 )
787 pParaPortion->SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
788 else
789 pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly
790 }
791
792
793 // Reformat all lines from here...
794
795 sal_Int32 nDelFromLine = -1;
796 bool bLineBreak = false;
797
798 sal_Int32 nIndex = pLine->GetStart();
799 EditLine aSaveLine( *pLine );
800 SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() );
801
802 KernArray aCharPositionArray;
803
804 bool bSameLineAgain = false; // For TextRanger, if the height changes.
805 TabInfo aCurrentTab;
806
807 bool bForceOneRun = bEmptyNodeWithPolygon;
808 bool bCompressedChars = false;
809
810 while ( ( nIndex < pNode->Len() ) || bForceOneRun )
811 {
812 assert(pLine);
813
814 bForceOneRun = false;
815
816 bool bEOL = false;
817 bool bEOC = false;
818 sal_Int32 nPortionStart = 0;
819 sal_Int32 nPortionEnd = 0;
820
821 tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth);
822 if ( nIndex == 0 )
823 {
825 nStartX += nFI;
826
827 if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) )
828 {
829 nStartX = pParaPortion->GetBulletX();
830 }
831 }
832
833 const bool bAutoSize = IsEffectivelyVertical() ? aStatus.AutoPageHeight() : aStatus.AutoPageWidth();
834 tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? aMaxAutoPaperSize : aPaperSize);
835
836 nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight());
837 nMaxLineWidth -= nStartX;
838
839 // If PaperSize == long_max, one cannot take away any negative
840 // first line indent. (Overflow)
841 if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) )
842 nMaxLineWidth = GetColumnWidth(aPaperSize) - scaleXSpacingValue(rLRItem.GetRight());
843
844 // If still less than 0, it may be just the right edge.
845 if ( nMaxLineWidth <= 0 )
846 nMaxLineWidth = 1;
847
848 // Problem:
849 // Since formatting starts a line _before_ the invalid position,
850 // the positions unfortunately have to be redefined...
851 // Solution:
852 // The line before can only become longer, not smaller
853 // =>...
854 pLine->GetCharPosArray().clear();
855
856 sal_Int32 nTmpPos = nIndex;
857 sal_Int32 nTmpPortion = pLine->GetStartPortion();
858 tools::Long nTmpWidth = 0;
859 tools::Long nXWidth = nMaxLineWidth;
860
861 std::deque<tools::Long>* pTextRanges = nullptr;
862 tools::Long nTextExtraYOffset = 0;
863 tools::Long nTextXOffset = 0;
864 tools::Long nTextLineHeight = 0;
865 if ( GetTextRanger() )
866 {
868
869 tools::Long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine, pLine->GetStart(), GetCursorFlags::NONE ).Top();
870 if ( !bSameLineAgain )
871 {
872 SeekCursor( pNode, nTmpPos+1, aTmpFont );
873 aTmpFont.SetPhysFont(*GetRefDevice());
875
876 if ( IsFixedCellHeight() )
877 nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() );
878 else
879 nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height();
880 // Metrics can be greater
881 FormatterFontMetric aTempFormatterMetrics;
882 RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont );
883 sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight();
884 if ( nLineHeight > nTextLineHeight )
885 nTextLineHeight = nLineHeight;
886 }
887 else
888 nTextLineHeight = pLine->GetHeight();
889
890 nXWidth = 0;
891 while ( !nXWidth )
892 {
893 tools::Long nYOff = nTextY + nTextExtraYOffset;
894 tools::Long nYDiff = nTextLineHeight;
895 if ( IsEffectivelyVertical() )
896 {
897 tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
898 nYOff = nMaxPolygonX-nYOff;
899 nYDiff = -nTextLineHeight;
900 }
901 pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
902 assert( pTextRanges && "GetTextRanges?!" );
903 tools::Long nMaxRangeWidth = 0;
904 // Use the widest range...
905 // The widest range could be a bit confusing, so normally it
906 // is the first one. Best with gaps.
907 assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs");
908 if (!pTextRanges->empty())
909 {
910 tools::Long nA = pTextRanges->at(0);
911 tools::Long nB = pTextRanges->at(1);
912 DBG_ASSERT( nA <= nB, "TextRange distorted?" );
913 tools::Long nW = nB - nA;
914 if ( nW > nMaxRangeWidth )
915 {
916 nMaxRangeWidth = nW;
917 nTextXOffset = nA;
918 }
919 }
920 nXWidth = nMaxRangeWidth;
921 if ( nXWidth )
922 nMaxLineWidth = nXWidth - nStartX - scaleXSpacingValue(rLRItem.GetRight());
923 else
924 {
925 // Try further down in the polygon.
926 // Below the polygon use the Paper Width.
927 nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) );
928 if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() )
929 {
931 if ( !nXWidth ) // AutoPaperSize
932 nXWidth = 0x7FFFFFFF;
933 }
934 }
935 }
936 }
937
938 // search for Portion that no longer fits in line...
939 TextPortion* pPortion = nullptr;
940 sal_Int32 nPortionLen = 0;
941 bool bContinueLastPortion = false;
942 bool bBrokenLine = false;
943 bLineBreak = false;
944 const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() );
945 while ( ( nTmpWidth < nXWidth ) && !bEOL )
946 {
947 const sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count();
948 assert(nTextPortions > 0);
949 bContinueLastPortion = (nTmpPortion >= nTextPortions);
950 if (bContinueLastPortion)
951 {
952 if (nTmpPos >= pNode->Len())
953 break; // while
954
955 // Continue with remainder. This only to have *some* valid
956 // X-values and not endlessly create new lines until DOOM...
957 // Happened in the scenario of tdf#104152 where inserting a
958 // paragraph lead to a11y attempting to format the doc to
959 // obtain content when notified.
960 nTmpPortion = nTextPortions - 1;
961 SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion");
962 }
963
964 nPortionStart = nTmpPos;
965 pPortion = &pParaPortion->GetTextPortions()[nTmpPortion];
966 if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR )
967 {
968 // Throw away a Portion, if necessary correct the one before,
969 // if the Hyph portion has swallowed a character...
970 sal_Int32 nTmpLen = pPortion->GetLen();
971 pParaPortion->GetTextPortions().Remove( nTmpPortion );
972 if (nTmpPortion && nTmpLen)
973 {
974 nTmpPortion--;
975 TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion];
976 DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
977 nTmpWidth -= rPrev.GetSize().Width();
978 nTmpPos = nTmpPos - rPrev.GetLen();
979 rPrev.SetLen(rPrev.GetLen() + nTmpLen);
980 rPrev.setWidth(-1);
981 }
982
983 assert( nTmpPortion < pParaPortion->GetTextPortions().Count() && "No more Portions left!" );
984 pPortion = &pParaPortion->GetTextPortions()[nTmpPortion];
985 }
986
987 if (bContinueLastPortion)
988 {
989 // Note that this may point behind the portion and is only to
990 // be used with the node's string offsets to generate X-values.
991 nPortionLen = pNode->Len() - nPortionStart;
992 }
993 else
994 {
995 nPortionLen = pPortion->GetLen();
996 }
997
998 DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" );
999 DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" );
1000 if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) )
1001 {
1002 SAL_WARN_IF( bContinueLastPortion,
1003 "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong");
1004 sal_uInt16 nWhich = pNextFeature->GetItem()->Which();
1005 switch ( nWhich )
1006 {
1007 case EE_FEATURE_TAB:
1008 {
1009 tools::Long nOldTmpWidth = nTmpWidth;
1010
1011 // Search for Tab-Pos...
1012 tools::Long nCurPos = nTmpWidth + nStartX;
1013 // consider scaling
1014 if (aStatus.DoStretch() && (mfFontScaleX != 100.0))
1015 nCurPos = basegfx::fround(double(nCurPos) * 100.0 / std::max(mfFontScaleX, 1.0));
1016
1017 short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth);
1018 aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, aEditDoc.GetDefTab() );
1019 aCurrentTab.nTabPos = scaleXFontValue(tools::Long(aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText/*rLRItem.GetTextLeft()*/));
1020 aCurrentTab.bValid = false;
1021
1022 // Switch direction in R2L para...
1023 if ( bRightToLeftPara )
1024 {
1025 if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
1026 aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left;
1027 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left )
1028 aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right;
1029 }
1030
1031 if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) ||
1032 ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) ||
1033 ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) )
1034 {
1035 // For LEFT / DEFAULT this tab is not considered.
1036 aCurrentTab.bValid = true;
1037 aCurrentTab.nStartPosX = nTmpWidth;
1038 aCurrentTab.nTabPortion = nTmpPortion;
1039 }
1040
1041 pPortion->SetKind(PortionKind::TAB);
1042 pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() );
1043 pPortion->setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) );
1044
1045 // Height needed...
1046 SeekCursor( pNode, nTmpPos+1, aTmpFont );
1047 pPortion->setHeight( GetRefDevice()->GetTextHeight() );
1048
1049 DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" );
1050
1051 nTmpWidth = aCurrentTab.nTabPos-nStartX;
1052
1053 // If this is the first token on the line,
1054 // and nTmpWidth > aPaperSize.Width, => infinite loop!
1055 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
1056 {
1057 // What now?
1058 // make the tab fitting
1059 pPortion->setWidth( nXWidth-nOldTmpWidth );
1060 nTmpWidth = nXWidth-1;
1061 bEOL = true;
1062 bBrokenLine = true;
1063 }
1065 size_t nPos = nTmpPos - pLine->GetStart();
1066 rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
1067 bCompressedChars = false;
1068 }
1069 break;
1070 case EE_FEATURE_LINEBR:
1071 {
1072 assert( pPortion );
1073 pPortion->setWidth(0);
1074 bEOL = true;
1075 bLineBreak = true;
1076 pPortion->SetKind( PortionKind::LINEBREAK );
1077 bCompressedChars = false;
1079 size_t nPos = nTmpPos - pLine->GetStart();
1080 rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
1081 }
1082 break;
1083 case EE_FEATURE_FIELD:
1084 {
1085 SeekCursor( pNode, nTmpPos+1, aTmpFont );
1086 aTmpFont.SetPhysFont(*GetRefDevice());
1088
1089 OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
1090 // get size, but also DXArray to allow length information in line breaking below
1091 KernArray aTmpDXArray;
1092 pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(),
1093 aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray));
1094
1095 // So no scrolling for oversized fields
1096 if ( pPortion->GetSize().Width() > nXWidth )
1097 {
1098 // create ExtraPortionInfo on-demand, flush lineBreaksList
1099 ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos();
1100
1101 if(nullptr == pExtraInfo)
1102 {
1103 pExtraInfo = new ExtraPortionInfo();
1104 pExtraInfo->nOrgWidth = nXWidth;
1105 pPortion->SetExtraInfos(pExtraInfo);
1106 }
1107 else
1108 {
1109 pExtraInfo->lineBreaksList.clear();
1110 }
1111
1112 // iterate over CellBreaks using XBreakIterator to be on the
1113 // safe side with international texts/charSets
1114 Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator());
1115 const sal_Int32 nTextLength(aFieldValue.getLength());
1116 const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart)));
1117 sal_Int32 nDone(0);
1118 sal_Int32 nNextCellBreak(
1119 xBreakIterator->nextCharacters(
1120 aFieldValue,
1121 0,
1122 aLocale,
1123 css::i18n::CharacterIteratorMode::SKIPCELL,
1124 0,
1125 nDone));
1126 sal_Int32 nLastCellBreak(0);
1127 sal_Int32 nLineStartX(0);
1128
1129 // always add 1st line break (safe, we already know we are larger than nXWidth)
1130 pExtraInfo->lineBreaksList.push_back(0);
1131
1132 for(sal_Int32 a(0); a < nTextLength; a++)
1133 {
1134 if(a == nNextCellBreak)
1135 {
1136 // check width
1137 if(aTmpDXArray[a] - nLineStartX > nXWidth)
1138 {
1139 // new CellBreak does not fit in current line, need to
1140 // create a break at LastCellBreak - but do not add 1st
1141 // line break twice for very tall frames
1142 if(0 != a)
1143 {
1144 pExtraInfo->lineBreaksList.push_back(a);
1145 }
1146
1147 // moveLineStart forward in X
1148 nLineStartX = aTmpDXArray[nLastCellBreak];
1149 }
1150
1151 // update CellBreak iteration values
1152 nLastCellBreak = a;
1153 nNextCellBreak = xBreakIterator->nextCharacters(
1154 aFieldValue,
1155 a,
1156 aLocale,
1157 css::i18n::CharacterIteratorMode::SKIPCELL,
1158 1,
1159 nDone);
1160 }
1161 }
1162 }
1163 nTmpWidth += pPortion->GetSize().Width();
1165 size_t nPos = nTmpPos - pLine->GetStart();
1166 rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
1167 pPortion->SetKind(PortionKind::FIELD);
1168 // If this is the first token on the line,
1169 // and nTmpWidth > aPaperSize.Width, => infinite loop!
1170 if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
1171 {
1172 nTmpWidth = nXWidth-1;
1173 bEOL = true;
1174 bBrokenLine = true;
1175 }
1176 // Compression in Fields????
1177 // I think this could be a little bit difficult and is not very useful
1178 bCompressedChars = false;
1179 }
1180 break;
1181 default: OSL_FAIL( "What feature?" );
1182 }
1183 pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 );
1184 }
1185 else
1186 {
1187 DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" );
1188 SeekCursor( pNode, nTmpPos+1, aTmpFont );
1189 aTmpFont.SetPhysFont(*GetRefDevice());
1191
1192 if (!bContinueLastPortion)
1193 pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) );
1194
1195 if (bContinueLastPortion)
1196 {
1197 Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(),
1198 pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray ));
1199 pPortion->adjustSize(aSize.Width(), 0);
1200 if (pPortion->GetSize().Height() < aSize.Height())
1201 pPortion->setHeight(aSize.Height());
1202 }
1203 else
1204 {
1205 auto aSize = aTmpFont.QuickGetTextSize(GetRefDevice(), pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray);
1206 pPortion->SetSize(aSize);
1207 }
1208
1209 // #i9050# Do Kerning also behind portions...
1210 if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) )
1211 pPortion->adjustSize(aTmpFont.GetFixKerning(), 0);
1212 if ( IsFixedCellHeight() )
1213 {
1215 }
1216 // The array is generally flattened at the beginning
1217 // => Always simply quick inserts.
1218 size_t nPos = nTmpPos - pLine->GetStart();
1220 assert(aCharPositionArray.get_factor() == 1);
1221 std::vector<sal_Int32>& rKernArray = aCharPositionArray.get_subunit_array();
1222 rArray.insert( rArray.begin() + nPos, rKernArray.data(), rKernArray.data() + nPortionLen);
1223
1224 // And now check for Compression:
1225 if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE )
1226 {
1227 sal_Int32* pDXArray = rArray.data() + nTmpPos - pLine->GetStart();
1228 bCompressedChars |= ImplCalcAsianCompression(
1229 pNode, pPortion, nTmpPos, pDXArray, 10000, false);
1230 }
1231
1232 nTmpWidth += pPortion->GetSize().Width();
1233
1234 sal_Int32 _nPortionEnd = nTmpPos + nPortionLen;
1235 if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) )
1236 {
1237 bool bAllow = false;
1238 sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) );
1239 sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) );
1240 if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) )
1241 bAllow = true;
1242
1243 // No spacing within L2R/R2L nesting
1244 if ( bAllow )
1245 {
1246 tools::Long nExtraSpace = pPortion->GetSize().Height() / 5;
1247 nExtraSpace = scaleXSpacingValue(nExtraSpace);
1248 pPortion->adjustSize(nExtraSpace, 0);
1249 nTmpWidth += nExtraSpace;
1250 }
1251 }
1252 }
1253
1254 if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) )
1255 {
1256 tools::Long nWidthAfterTab = 0;
1257 for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++ )
1258 {
1259 const TextPortion& rTP = pParaPortion->GetTextPortions()[n];
1260 nWidthAfterTab += rTP.GetSize().Width();
1261 }
1262 tools::Long nW = nWidthAfterTab; // Length before tab position
1263 if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
1264 {
1265 }
1266 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center )
1267 {
1268 nW = nWidthAfterTab/2;
1269 }
1270 else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal )
1271 {
1272 OUString aText = GetSelected( EditSelection( EditPaM( pParaPortion->GetNode(), nTmpPos ),
1273 EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) );
1274 sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() );
1275 if ( nDecPos != -1 )
1276 {
1277 nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width();
1278 nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(),
1279 nTmpPos, nDecPos, nullptr ).Width();
1280 aCurrentTab.bValid = false;
1281 }
1282 }
1283 else
1284 {
1285 OSL_FAIL( "CreateLines: Tab not handled!" );
1286 }
1287 tools::Long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX;
1288 if ( nW >= nMaxW )
1289 {
1290 nW = nMaxW;
1291 aCurrentTab.bValid = false;
1292 }
1293 TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion];
1294 rTabPortion.setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX );
1295 nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab;
1296 }
1297
1298 nTmpPos = nTmpPos + nPortionLen;
1299 nPortionEnd = nTmpPos;
1300 nTmpPortion++;
1301 if ( aStatus.OneCharPerLine() )
1302 bEOL = true;
1303 }
1304
1305 DBG_ASSERT( pPortion, "no portion!?" );
1306
1307 aCurrentTab.bValid = false;
1308
1309 assert(pLine);
1310
1311 // this was possibly a portion too far:
1312 bool bFixedEnd = false;
1313 if ( aStatus.OneCharPerLine() )
1314 {
1315 // State before Portion (apart from nTmpWidth):
1316 nTmpPos -= pPortion ? nPortionLen : 0;
1317 nPortionStart = nTmpPos;
1318 nTmpPortion--;
1319
1320 bEOL = true;
1321 bEOC = false;
1322
1323 // And now just one character:
1324 nTmpPos++;
1325 nTmpPortion++;
1326 nPortionEnd = nTmpPortion;
1327 // one Non-Feature-Portion has to be wrapped
1328 if ( pPortion && nPortionLen > 1 )
1329 {
1330 DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" );
1331 nTmpWidth -= pPortion->GetSize().Width();
1332 sal_Int32 nP = SplitTextPortion( pParaPortion, nTmpPos, pLine );
1333 nTmpWidth += pParaPortion->GetTextPortions()[nP].GetSize().Width();
1334 }
1335 }
1336 else if ( nTmpWidth >= nXWidth )
1337 {
1338 nPortionEnd = nTmpPos;
1339 nTmpPos -= pPortion ? nPortionLen : 0;
1340 nPortionStart = nTmpPos;
1341 nTmpPortion--;
1342 bEOL = false;
1343 bEOC = false;
1344 if( pPortion ) switch ( pPortion->GetKind() )
1345 {
1346 case PortionKind::TEXT:
1347 {
1348 nTmpWidth -= pPortion->GetSize().Width();
1349 }
1350 break;
1351 case PortionKind::FIELD:
1352 case PortionKind::TAB:
1353 {
1354 nTmpWidth -= pPortion->GetSize().Width();
1355 bEOL = true;
1356 bFixedEnd = true;
1357 }
1358 break;
1359 default:
1360 {
1361 // A feature is not wrapped:
1362 DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" );
1363 bEOL = true;
1364 bFixedEnd = true;
1365 }
1366 }
1367 }
1368 else
1369 {
1370 bEOL = true;
1371 bEOC = true;
1372 pLine->SetEnd( nPortionEnd );
1373 assert( pParaPortion->GetTextPortions().Count() && "No TextPortions?" );
1374 pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 );
1375 }
1376
1377 if ( aStatus.OneCharPerLine() )
1378 {
1379 pLine->SetEnd( nPortionEnd );
1380 pLine->SetEndPortion( nTmpPortion-1 );
1381 }
1382 else if ( bFixedEnd )
1383 {
1384 pLine->SetEnd( nPortionStart );
1385 pLine->SetEndPortion( nTmpPortion-1 );
1386 }
1387 else if ( bLineBreak || bBrokenLine )
1388 {
1389 pLine->SetEnd( nPortionStart+1 );
1390 pLine->SetEndPortion( nTmpPortion-1 );
1391 bEOC = false; // was set above, maybe change the sequence of the if's?
1392 }
1393 else if ( !bEOL && !bContinueLastPortion )
1394 {
1395 DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" );
1396 tools::Long nRemainingWidth = !aStatus.IsSingleLine() ?
1397 nMaxLineWidth - nTmpWidth : pLine->GetCharPosArray()[pLine->GetCharPosArray().size() - 1] + 1;
1398 bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL );
1399 if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed )
1400 {
1401 // I need the manipulated DXArray for determining the break position...
1402 sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart());
1404 pNode, pPortion, nPortionStart, pDXArray, 10000, true);
1405 }
1406 if( pPortion )
1407 ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart,
1408 nRemainingWidth, bCanHyphenate && bHyphenatePara );
1409 }
1410
1411
1412 // Line finished => adjust
1413
1414
1415 // CalcTextSize should be replaced by a continuous registering!
1416 Size aTextSize = pLine->CalcTextSize( *pParaPortion );
1417
1418 if ( aTextSize.Height() == 0 )
1419 {
1420 SeekCursor( pNode, pLine->GetStart()+1, aTmpFont );
1421 aTmpFont.SetPhysFont(*pRefDev);
1423
1424 if ( IsFixedCellHeight() )
1426 else
1427 aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() );
1428 pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) );
1429 }
1430
1431 // The font metrics can not be calculated continuously, if the font is
1432 // set anyway, because a large font only after wrapping suddenly ends
1433 // up in the next line => Font metrics too big.
1434 FormatterFontMetric aFormatterMetrics;
1435 sal_Int32 nTPos = pLine->GetStart();
1436 for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ )
1437 {
1438 const TextPortion& rTP = pParaPortion->GetTextPortions()[nP];
1439 // problem with hard font height attribute, when everything but the line break has this attribute
1440 if ( rTP.GetKind() != PortionKind::LINEBREAK )
1441 {
1442 SeekCursor( pNode, nTPos+1, aTmpFont );
1443 aTmpFont.SetPhysFont(*GetRefDevice());
1445 RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
1446 }
1447 nTPos = nTPos + rTP.GetLen();
1448 }
1449 sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
1450 if ( nLineHeight > pLine->GetHeight() )
1451 pLine->SetHeight( nLineHeight );
1452 pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
1453
1454 bSameLineAgain = false;
1455 if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) )
1456 {
1457 // put down with the other size!
1458 bSameLineAgain = true;
1459 }
1460
1461 if ( !bSameLineAgain && !aStatus.IsOutliner() )
1462 {
1463 if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
1464 {
1465 double fMinHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
1466 sal_uInt16 nMinHeight = basegfx::fround(fMinHeight);
1467
1468 sal_uInt16 nTxtHeight = pLine->GetHeight();
1469 if ( nTxtHeight < nMinHeight )
1470 {
1471 // The Ascent has to be adjusted for the difference:
1472 tools::Long nDiff = nMinHeight - nTxtHeight;
1473 pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) );
1474 pLine->SetHeight( nMinHeight, nTxtHeight );
1475 }
1476 }
1477 else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
1478 {
1479 double fFixHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
1480 sal_uInt16 nFixHeight = basegfx::fround(fFixHeight);
1481
1482 sal_uInt16 nTxtHeight = pLine->GetHeight();
1483 pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
1484 pLine->SetHeight( nFixHeight, nTxtHeight );
1485 }
1486 else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
1487 {
1488 // There are documents with PropLineSpace 0, why?
1489 // (cmc: re above question :-) such documents can be seen by importing a .ppt
1490 sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace();
1491 double fProportionalScale = double(nPropLineSpace) / 100.0;
1492 constexpr const double f80Percent = 8.0 / 10.0;
1493 double fSpacingFactor = mfSpacingScaleY / 100.0;
1494 if (nPropLineSpace && nPropLineSpace < 100)
1495 {
1496 // Adapted code from sw/source/core/text/itrform2.cxx
1497 sal_uInt16 nAscent = pLine->GetMaxAscent();
1498 sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor * fProportionalScale * f80Percent);
1499 if (!nAscent || nAscent > nNewAscent)
1500 pLine->SetMaxAscent(nNewAscent);
1501 sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fProportionalScale * fSpacingFactor);
1502
1503 pLine->SetHeight(nHeight, pLine->GetTxtHeight());
1504 }
1505 else if (nPropLineSpace && nPropLineSpace != 100)
1506 {
1507 sal_uInt16 nTxtHeight = pLine->GetHeight();
1508 sal_Int32 nPropTextHeight = nTxtHeight * fProportionalScale * fSpacingFactor;
1509 // The Ascent has to be adjusted for the difference:
1510 tools::Long nDiff = pLine->GetHeight() - nPropTextHeight;
1511 pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) );
1512 pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight );
1513 }
1514 }
1516 {
1517 if (mfSpacingScaleY < 100.0)
1518 {
1519 double fSpacingFactor = mfSpacingScaleY / 100.0;
1520 sal_uInt16 nPropLineSpace = basegfx::fround(100.0 * fSpacingFactor);
1521 if (nPropLineSpace && nPropLineSpace < 100)
1522 {
1523 // Adapted code from sw/source/core/text/itrform2.cxx
1524 sal_uInt16 nAscent = pLine->GetMaxAscent();
1525 sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor);
1526 if (!nAscent || nAscent > nNewAscent)
1527 pLine->SetMaxAscent(nNewAscent);
1528 sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fSpacingFactor);
1529
1530 pLine->SetHeight(nHeight, pLine->GetTxtHeight());
1531 }
1532
1533 }
1534 }
1535 }
1536
1537 if ( ( !IsEffectivelyVertical() && aStatus.AutoPageWidth() ) ||
1539 {
1540 // If the row fits within the current paper width, then this width
1541 // has to be used for the Alignment. If it does not fit or if it
1542 // will change the paper width, it will be formatted again for
1543 // Justification! = LEFT anyway.
1544 tools::Long nMaxLineWidthFix = GetColumnWidth(aPaperSize) - scaleXSpacingValue(rLRItem.GetRight()) - nStartX;
1545 if ( aTextSize.Width() < nMaxLineWidthFix )
1546 nMaxLineWidth = nMaxLineWidthFix;
1547 }
1548
1549 if ( bCompressedChars )
1550 {
1551 tools::Long nRemainingWidth = nMaxLineWidth - aTextSize.Width();
1552 if ( nRemainingWidth > 0 )
1553 {
1554 ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth );
1555 aTextSize = pLine->CalcTextSize( *pParaPortion );
1556 }
1557 }
1558
1559 if ( pLine->IsHangingPunctuation() )
1560 {
1561 // Width from HangingPunctuation was set to 0 in ImpBreakLine,
1562 // check for rel width now, maybe create compression...
1563 tools::Long n = nMaxLineWidth - aTextSize.Width();
1564 TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()];
1565 sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart();
1566 tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n;
1567 if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size())
1568 {
1569 pLine->GetCharPosArray()[ nPosInArray ] = nNewValue;
1570 }
1571 rTP.adjustSize(n, 0);
1572 }
1573
1574 pLine->SetTextWidth( aTextSize.Width() );
1575 switch ( eJustification )
1576 {
1577 case SvxAdjust::Center:
1578 {
1579 tools::Long n = ( nMaxLineWidth - aTextSize.Width() ) / 2;
1580 n += nStartX; // Indentation is kept.
1581 pLine->SetStartPosX( n );
1582 }
1583 break;
1584 case SvxAdjust::Right:
1585 {
1586 // For automatically wrapped lines, which has a blank at the end
1587 // the blank must not be displayed!
1588 tools::Long n = nMaxLineWidth - aTextSize.Width();
1589 n += nStartX; // Indentation is kept.
1590 pLine->SetStartPosX( n );
1591 }
1592 break;
1593 case SvxAdjust::Block:
1594 {
1595 bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute);
1596 tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width();
1597 pLine->SetStartPosX( nStartX );
1598 if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) )
1599 ImpAdjustBlocks( pParaPortion, pLine, nRemainingSpace );
1600 }
1601 break;
1602 default:
1603 {
1604 pLine->SetStartPosX( nStartX ); // FI, LI
1605 }
1606 break;
1607 }
1608
1609
1610 // Check whether the line must be re-issued...
1611
1612 pLine->SetInvalid();
1613
1614 // If a portion was wrapped there may be far too many positions in
1615 // CharPosArray:
1617 size_t nLen = pLine->GetLen();
1618 if (rArray.size() > nLen)
1619 rArray.erase(rArray.begin()+nLen, rArray.end());
1620
1621 if ( GetTextRanger() )
1622 {
1623 if ( nTextXOffset )
1624 pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset );
1625 if ( nTextExtraYOffset )
1626 {
1627 pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 );
1628 pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) );
1629 }
1630 }
1631
1632 // for <0 think over !
1633 if ( pParaPortion->IsSimpleInvalid() )
1634 {
1635 // Change through simple Text changes...
1636 // Do not cancel formatting since Portions possibly have to be split
1637 // again! If at some point cancelable, then validate the following
1638 // line! But if applicable, mark as valid, so there is less output...
1639 if ( pLine->GetEnd() < nInvalidStart )
1640 {
1641 if ( *pLine == aSaveLine )
1642 {
1643 pLine->SetValid();
1644 }
1645 }
1646 else
1647 {
1648 sal_Int32 nStart = pLine->GetStart();
1649 sal_Int32 nEnd = pLine->GetEnd();
1650
1651 if ( nStart > nInvalidEnd )
1652 {
1653 if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
1654 ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
1655 {
1656 pLine->SetValid();
1657 if (bQuickFormat)
1658 {
1659 bLineBreak = false;
1660 pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
1661 break;
1662 }
1663 }
1664 }
1665 else if (bQuickFormat && (nEnd > nInvalidEnd))
1666 {
1667 // If the invalid line ends so that the next begins on the
1668 // 'same' passage as before, i.e. not wrapped differently,
1669 // then the text width does not have to be determined anew:
1670 if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
1671 {
1672 bLineBreak = false;
1673 pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
1674 break;
1675 }
1676 }
1677 }
1678 }
1679
1680 if ( !bSameLineAgain )
1681 {
1682 nIndex = pLine->GetEnd(); // next line start = last line end
1683 // as nEnd points to the last character!
1684
1685 sal_Int32 nEndPortion = pLine->GetEndPortion();
1686 nCurrentPosY += pLine->GetHeight();
1687
1688 // Next line or maybe a new line...
1689 pLine = nullptr;
1690 if ( nLine < pParaPortion->GetLines().Count()-1 )
1691 pLine = &pParaPortion->GetLines()[++nLine];
1692 if ( pLine && ( nIndex >= pNode->Len() ) )
1693 {
1694 nDelFromLine = nLine;
1695 break;
1696 }
1697 // Stop processing if allowed and this is outside of the paper size height.
1698 // Format at least two lines though, in case something detects whether
1699 // the text has been wrapped or something similar.
1700 if( mbSkipOutsideFormat && nLine > 2
1701 && !aStatus.AutoPageHeight() && aPaperSize.Height() < nCurrentPosY )
1702 {
1703 if ( pLine && ( nIndex >= pNode->Len()) )
1704 nDelFromLine = nLine;
1705 break;
1706 }
1707 if ( !pLine )
1708 {
1709 if ( nIndex < pNode->Len() )
1710 {
1711 pLine = new EditLine;
1712 pParaPortion->GetLines().Insert(++nLine, pLine);
1713 }
1714 else if ( nIndex && bLineBreak && GetTextRanger() )
1715 {
1716 // normally CreateAndInsertEmptyLine would be called, but I want to use
1717 // CreateLines, so I need Polygon code only here...
1718 TextPortion* pDummyPortion = new TextPortion( 0 );
1719 pParaPortion->GetTextPortions().Append(pDummyPortion);
1720 pLine = new EditLine;
1721 pParaPortion->GetLines().Insert(++nLine, pLine);
1722 bForceOneRun = true;
1723 bProcessingEmptyLine = true;
1724 }
1725 }
1726 if ( pLine )
1727 {
1728 aSaveLine = *pLine;
1729 pLine->SetStart( nIndex );
1730 pLine->SetEnd( nIndex );
1731 pLine->SetStartPortion( nEndPortion+1 );
1732 pLine->SetEndPortion( nEndPortion+1 );
1733 }
1734 }
1735 } // while ( Index < Len )
1736
1737 if ( nDelFromLine >= 0 )
1738 pParaPortion->GetLines().DeleteFromLine( nDelFromLine );
1739
1740 DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" );
1741
1742 if ( bLineBreak )
1743 CreateAndInsertEmptyLine( pParaPortion );
1744
1745 bool bHeightChanged = FinishCreateLines( pParaPortion );
1746
1747 if ( bMapChanged )
1748 GetRefDevice()->Pop();
1749
1750 GetRefDevice()->Pop();
1751
1752 return bHeightChanged;
1753}
1754
1756{
1757 DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" );
1758
1759 EditLine* pTmpLine = new EditLine;
1760 pTmpLine->SetStart( pParaPortion->GetNode()->Len() );
1761 pTmpLine->SetEnd( pParaPortion->GetNode()->Len() );
1762 pParaPortion->GetLines().Append(pTmpLine);
1763
1764 bool bLineBreak = pParaPortion->GetNode()->Len() > 0;
1765 sal_Int32 nSpaceBefore = 0;
1766 sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore );
1767 const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() );
1768 const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
1769 tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore);
1770
1771 tools::Rectangle aBulletArea { Point(), Point() };
1772 if ( bLineBreak )
1773 {
1774 nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth);
1775 }
1776 else
1777 {
1778 aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
1779 if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 )
1780 pParaPortion->SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
1781 else
1782 pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly.
1783 if ( pParaPortion->GetBulletX() > nStartX )
1784 {
1785 nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth);
1786 if ( pParaPortion->GetBulletX() > nStartX )
1787 nStartX = pParaPortion->GetBulletX();
1788 }
1789 }
1790
1791 SvxFont aTmpFont;
1792 SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont );
1793 aTmpFont.SetPhysFont(*pRefDev);
1794
1795 TextPortion* pDummyPortion = new TextPortion( 0 );
1796 pDummyPortion->SetSize(aTmpFont.GetPhysTxtSize(pRefDev));
1797 if ( IsFixedCellHeight() )
1798 pDummyPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
1799 pParaPortion->GetTextPortions().Append(pDummyPortion);
1800 FormatterFontMetric aFormatterMetrics;
1801 RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
1802 pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
1803 pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) );
1804 sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
1805 if ( nLineHeight > pTmpLine->GetHeight() )
1806 pTmpLine->SetHeight( nLineHeight );
1807
1808 if ( !aStatus.IsOutliner() )
1809 {
1810 sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
1811 SvxAdjust eJustification = GetJustification( nPara );
1812 tools::Long nMaxLineWidth = GetColumnWidth(aPaperSize);
1813 nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight());
1814 if ( nMaxLineWidth < 0 )
1815 nMaxLineWidth = 1;
1816 if ( eJustification == SvxAdjust::Center )
1817 nStartX = nMaxLineWidth / 2;
1818 else if ( eJustification == SvxAdjust::Right )
1819 nStartX = nMaxLineWidth;
1820 }
1821
1822 pTmpLine->SetStartPosX( nStartX );
1823
1824 if ( !aStatus.IsOutliner() )
1825 {
1826 if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
1827 {
1828 sal_uInt16 nMinHeight = rLSItem.GetLineHeight();
1829 sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
1830 if ( nTxtHeight < nMinHeight )
1831 {
1832 // The Ascent has to be adjusted for the difference:
1833 tools::Long nDiff = nMinHeight - nTxtHeight;
1834 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) );
1835 pTmpLine->SetHeight( nMinHeight, nTxtHeight );
1836 }
1837 }
1838 else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
1839 {
1840 sal_uInt16 nFixHeight = rLSItem.GetLineHeight();
1841 sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
1842
1843 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
1844 pTmpLine->SetHeight( nFixHeight, nTxtHeight );
1845 }
1846 else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
1847 {
1848 sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
1849 if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line
1850 {
1851 // There are documents with PropLineSpace 0, why?
1852 // (cmc: re above question :-) such documents can be seen by importing a .ppt
1853 if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) )
1854 {
1855 sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
1856 sal_Int32 nH = nTxtHeight;
1857 nH *= rLSItem.GetPropLineSpace();
1858 nH /= 100;
1859 // The Ascent has to be adjusted for the difference:
1860 tools::Long nDiff = pTmpLine->GetHeight() - nH;
1861 if ( nDiff > pTmpLine->GetMaxAscent() )
1862 nDiff = pTmpLine->GetMaxAscent();
1863 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) );
1864 pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight );
1865 }
1866 }
1867 }
1868 }
1869
1870 if ( !bLineBreak )
1871 {
1872 tools::Long nMinHeight = aBulletArea.GetHeight();
1873 if ( nMinHeight > static_cast<tools::Long>(pTmpLine->GetHeight()) )
1874 {
1875 tools::Long nDiff = nMinHeight - static_cast<tools::Long>(pTmpLine->GetHeight());
1876 // distribute nDiff upwards and downwards
1877 pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) );
1878 pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) );
1879 }
1880 }
1881 else
1882 {
1883 // -2: The new one is already inserted.
1884#ifdef DBG_UTIL
1885 EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2];
1886 DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" );
1887#endif
1888 sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ;
1889 pTmpLine->SetStartPortion( nPos );
1890 pTmpLine->SetEndPortion( nPos );
1891 }
1892}
1893
1895{
1896// CalcCharPositions( pParaPortion );
1897 pParaPortion->SetValid();
1898 tools::Long nOldHeight = pParaPortion->GetHeight();
1899 CalcHeight( pParaPortion );
1900
1901 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" );
1902 bool bRet = ( pParaPortion->GetHeight() != nOldHeight );
1903 return bRet;
1904}
1905
1906void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate )
1907{
1908 ContentNode* const pNode = pParaPortion->GetNode();
1909
1910 sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart();
1911 sal_Int32 nMax = nBreakInLine + pPortion->GetLen();
1912 while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) )
1913 nBreakInLine++;
1914
1915 sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart();
1916 sal_Int32 nBreakPos = SAL_MAX_INT32;
1917
1918 bool bCompressBlank = false;
1919 bool bHyphenated = false;
1920 bool bHangingPunctuation = false;
1921 sal_Unicode cAlternateReplChar = 0;
1922 sal_Unicode cAlternateExtraChar = 0;
1923 bool bAltFullLeft = false;
1924 bool bAltFullRight = false;
1925 sal_uInt32 nAltDelChar = 0;
1926
1927 if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) )
1928 {
1929 // Break behind the blank, blank will be compressed...
1930 nBreakPos = nMaxBreakPos + 1;
1931 bCompressBlank = true;
1932 }
1933 else
1934 {
1935 sal_Int32 nMinBreakPos = pLine->GetStart();
1936 const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
1937 for (size_t nAttr = rAttrs.size(); nAttr; )
1938 {
1939 const EditCharAttrib& rAttr = *rAttrs[--nAttr];
1940 if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos)
1941 {
1942 nMinBreakPos = rAttr.GetEnd();
1943 break;
1944 }
1945 }
1946 assert(nMinBreakPos <= nMaxBreakPos);
1947
1948 lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) );
1949
1950 Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
1951 const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>(
1953
1954 if (nMinBreakPos == nMaxBreakPos)
1955 {
1956 nBreakPos = nMinBreakPos;
1957 }
1958 else
1959 {
1960 Reference< XHyphenator > xHyph;
1961 if ( bCanHyphenate )
1962 xHyph = GetHyphenator();
1963 i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 );
1964 i18n::LineBreakUserOptions aUserOptions;
1965
1966 const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true );
1967 aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine;
1968 aUserOptions.forbiddenEndCharacters = pForbidden->endLine;
1969 aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue();
1970 aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin;
1971 aUserOptions.allowHyphenateEnglish = false;
1972
1973 if (!aStatus.IsSingleLine())
1974 {
1975 i18n::LineBreakResults aLBR = _xBI->getLineBreak(
1976 pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions );
1977 nBreakPos = aLBR.breakIndex;
1978 }
1979 else
1980 {
1981 nBreakPos = nMaxBreakPos;
1982 }
1983
1984 // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos
1985 if ( nBreakPos < nMinBreakPos )
1986 {
1987 nBreakPos = nMinBreakPos;
1988 }
1989 else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin )
1990 {
1991 OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" );
1992 nBreakPos = nMaxBreakPos;
1993 }
1994 // Hanging punctuation is the only case that increases nBreakPos and makes
1995 // nBreakPos > nMaxBreakPos. It's expected that the hanging punctuation goes over
1996 // the border of the object.
1997 }
1998
1999 // BUG in I18N - the japanese dot is in the next line!
2000 // !!! Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2001 if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos )
2002 {
2003 sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0;
2004 if ( cFirstInNextLine == 12290 )
2005 nBreakPos++;
2006 }
2007
2008 bHangingPunctuation = nBreakPos > nMaxBreakPos;
2009 pLine->SetHangingPunctuation( bHangingPunctuation );
2010
2011 // Whether a separator or not, push the word after the separator through
2012 // hyphenation... NMaxBreakPos is the last character that fits into
2013 // the line, nBreakPos is the beginning of the word.
2014 // There is a problem if the Doc is so narrow that a word is broken
2015 // into more than two lines...
2016 if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() )
2017 {
2018 i18n::Boundary aBoundary = _xBI->getWordBoundary(
2019 pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true);
2020 sal_Int32 nWordStart = nBreakPos;
2021 sal_Int32 nWordEnd = aBoundary.endPos;
2022 DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" );
2023
2024 sal_Int32 nWordLen = nWordEnd - nWordStart;
2025 if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) )
2026 {
2027 // May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD
2028 const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen);
2029 sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter
2030 Reference< XHyphenatedWord > xHyphWord;
2031 if (xHyphenator.is())
2032 xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() );
2033 if (xHyphWord.is())
2034 {
2035 bool bAlternate = xHyphWord->isAlternativeSpelling();
2036 sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
2037
2038 if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) )
2039 {
2040 if ( !bAlternate )
2041 {
2042 bHyphenated = true;
2043 nBreakPos = nWordStart + _nWordLen;
2044 }
2045 else
2046 {
2047 // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*)
2048 OUString aAlt( xHyphWord->getHyphenatedWord() );
2049 std::u16string_view aAltLeft(aAlt.subView(0, _nWordLen));
2050 std::u16string_view aAltRight(aAlt.subView(_nWordLen));
2051 bAltFullLeft = aWord.startsWith(aAltLeft);
2052 bAltFullRight = aWord.endsWith(aAltRight);
2053 nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight);
2054
2055 // NOTE: improved for other cases, see fdo#63711
2056
2057 // We expect[ed] the two cases:
2058 // 1) packen becomes pak-ken
2059 // 2) Schiffahrt becomes Schiff-fahrt
2060 // In case 1, a character has to be replaced
2061 // in case 2 a character is added.
2062 // The identification is complicated by long
2063 // compound words because the Hyphenator separates
2064 // all position of the word. [This is not true for libhyphen.]
2065 // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln"
2066 // We can thus actually not directly connect the index of the
2067 // AlternativeWord to aWord. The whole issue will be simplified
2068 // by a function in the Hyphenator as soon as AMA builds this in...
2069 sal_Int32 nAltStart = _nWordLen - 1;
2070 sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
2071 sal_Int32 nTxtEnd = nTxtStart;
2072 sal_Int32 nAltEnd = nAltStart;
2073
2074 // The regions between the nStart and nEnd is the
2075 // difference between alternative and original string.
2076 while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
2077 aWord[nTxtEnd] != aAlt[nAltEnd] )
2078 {
2079 ++nTxtEnd;
2080 ++nAltEnd;
2081 }
2082
2083 // If a character is added, then we notice it now:
2084 if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
2085 aWord[ nTxtEnd ] == aAlt[nAltEnd] )
2086 {
2087 ++nAltEnd;
2088 ++nTxtStart;
2089 ++nTxtEnd;
2090 }
2091
2092 DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" );
2093
2094 if ( nTxtEnd > nTxtStart )
2095 cAlternateReplChar = aAlt[nAltStart];
2096 else
2097 cAlternateExtraChar = aAlt[nAltStart];
2098
2099 bHyphenated = true;
2100 nBreakPos = nWordStart + nTxtStart;
2101 if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel"
2102 nBreakPos++;
2103 }
2104 }
2105 }
2106 }
2107 }
2108
2109 if ( nBreakPos <= pLine->GetStart() )
2110 {
2111 // No separator in line => Chop!
2112 nBreakPos = nMaxBreakPos;
2113 // I18N nextCharacters !
2114 if ( nBreakPos <= pLine->GetStart() )
2115 nBreakPos = pLine->GetStart() + 1; // Otherwise infinite loop!
2116 }
2117 }
2118
2119 // the dickey portion is the end portion
2120 pLine->SetEnd( nBreakPos );
2121
2122 sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine );
2123
2124 if ( !bCompressBlank && !bHangingPunctuation )
2125 {
2126 // When justification is not SvxAdjust::Left, it's important to compress
2127 // the trailing space even if there is enough room for the space...
2128 // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too...
2129 assert( nBreakPos > pLine->GetStart() && "ImpBreakLines - BreakPos not expected!" );
2130 if ( pNode->GetChar( nBreakPos-1 ) == ' ' )
2131 bCompressBlank = true;
2132 }
2133
2134 if ( bCompressBlank || bHangingPunctuation )
2135 {
2136 TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion];
2137 DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" );
2138 DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" );
2139 sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart();
2140 rTP.setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 );
2141 if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size())
2142 {
2143 pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width();
2144 }
2145 }
2146 else if ( bHyphenated )
2147 {
2148 // A portion for inserting the separator...
2149 TextPortion* pHyphPortion = new TextPortion( 0 );
2150 pHyphPortion->SetKind( PortionKind::HYPHENATOR );
2151 if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported
2152 {
2153 TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion];
2154 DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" );
2155 rPrev.SetLen( rPrev.GetLen() - nAltDelChar );
2156 pHyphPortion->SetLen( nAltDelChar );
2157 if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar );
2158 // Correct width of the portion above:
2159 rPrev.setWidth(
2160 pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] );
2161 }
2162
2163 // Determine the width of the Hyph-Portion:
2164 SvxFont aFont;
2165 SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont );
2166 aFont.SetPhysFont(*GetRefDevice());
2167 pHyphPortion->SetSize(Size(GetRefDevice()->GetTextWidth(CH_HYPH), GetRefDevice()->GetTextHeight()));
2168
2169 pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion);
2170 }
2171 pLine->SetEndPortion( nEndPortion );
2172}
2173
2174void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace )
2175{
2176 DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." );
2177 assert( pLine && "AdjustBlocks: Line ?!" );
2178 if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() )
2179 return ;
2180
2181 const sal_Int32 nFirstChar = pLine->GetStart();
2182 const sal_Int32 nLastChar = pLine->GetEnd() -1; // Last points behind
2183 ContentNode* pNode = pParaPortion->GetNode();
2184
2185 DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" );
2186
2187 // Search blanks or Kashidas...
2188 std::vector<sal_Int32> aPositions;
2189
2190 // Kashidas ?
2191 ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions );
2192 auto nKashidas = aPositions.size();
2193
2194 sal_uInt16 nLastScript = i18n::ScriptType::LATIN;
2195 for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ )
2196 {
2197 EditPaM aPaM( pNode, nChar+1 );
2198 LanguageType eLang = GetLanguage(aPaM).nLang;
2199 sal_uInt16 nScript = GetI18NScriptType(aPaM);
2200 // Arabic script is handled above, but if no Kashida positions are found, use blanks.
2202 continue;
2203
2204 if ( pNode->GetChar(nChar) == ' ' )
2205 {
2206 // Normal latin script.
2207 aPositions.push_back( nChar );
2208 }
2209 else if (nChar > nFirstChar)
2210 {
2211 if (nLastScript == i18n::ScriptType::ASIAN)
2212 {
2213 // Set break position between this and the last character if
2214 // the last character is asian script.
2215 aPositions.push_back( nChar-1 );
2216 }
2217 else if (nScript == i18n::ScriptType::ASIAN)
2218 {
2219 // Set break position between a latin script and asian script.
2220 aPositions.push_back( nChar-1 );
2221 }
2222 }
2223
2224 nLastScript = nScript;
2225 }
2226
2227 if ( aPositions.empty() )
2228 return;
2229
2230 // If the last character is a blank, it is rejected!
2231 // The width must be distributed to the blockers in front...
2232 // But not if it is the only one.
2233 if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) &&
2234 ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ).nLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) )
2235 {
2236 aPositions.pop_back();
2237 sal_Int32 nPortionStart, nPortion;
2238 nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart );
2239 TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
2240 tools::Long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar];
2241 tools::Long nBlankWidth = nRealWidth;
2242 if ( nLastChar > nPortionStart )
2243 nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1];
2244 // Possibly the blank has already been deducted in ImpBreakLine:
2245 if ( nRealWidth == rLastPortion.GetSize().Width() )
2246 {
2247 // For the last character the portion must stop behind the blank
2248 // => Simplify correction:
2249 DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?");
2250 rLastPortion.adjustSize(-nBlankWidth, 0);
2251 nRemainingSpace += nBlankWidth;
2252 }
2253 pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth;
2254 }
2255
2256 size_t nGaps = aPositions.size();
2257 const tools::Long nMore4Everyone = nRemainingSpace / nGaps;
2258 tools::Long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps;
2259
2260 DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" );
2261 DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " );
2262
2263 // Mark Kashida positions, so that VCL knows where to insert Kashida and
2264 // where to only expand the width.
2265 if (nKashidas)
2266 {
2267 pLine->GetKashidaArray().resize(pLine->GetCharPosArray().size(), false);
2268 for (size_t i = 0; i < nKashidas; i++)
2269 {
2270 auto nChar = aPositions[i];
2271 if ( nChar < nLastChar )
2272 pLine->GetKashidaArray()[nChar-nFirstChar] = 1 /*sal_True*/;
2273 }
2274 }
2275
2276 // Correct the positions in the Array and the portion widths:
2277 // Last character won't be considered...
2278 for (auto const& nChar : aPositions)
2279 {
2280 if ( nChar < nLastChar )
2281 {
2282 sal_Int32 nPortionStart, nPortion;
2283 nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true );
2284 TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
2285
2286 // The width of the portion:
2287 rLastPortion.adjustSize(nMore4Everyone, 0);
2288 if (nSomeExtraSpace)
2289 {
2290 rLastPortion.adjustSize(1, 0);
2291 }
2292
2293 // Correct positions in array
2294 sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen();
2295 for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ )
2296 {
2297 pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone;
2298 if ( nSomeExtraSpace )
2299 pLine->GetCharPosArray()[_n-nFirstChar]++;
2300 }
2301
2302 if ( nSomeExtraSpace )
2303 nSomeExtraSpace--;
2304 }
2305 }
2306
2307 // Now the text width contains the extra width...
2308 pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace );
2309}
2310
2311// For Kashidas from sw/source/core/text/porlay.cxx
2312void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray )
2313{
2314 // Kashida glyph looks suspicious, skip Kashida justification
2315 if (GetRefDevice()->GetMinKashida() <= 0)
2316 return;
2317
2318 std::vector<sal_Int32> aKashidaArray;
2319
2320 // the search has to be performed on a per word base
2321
2322 EditSelection aWordSel( EditPaM( pNode, nStart ) );
2323 aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
2324 if ( aWordSel.Min().GetIndex() < nStart )
2325 aWordSel.Min().SetIndex( nStart );
2326
2327 while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) )
2328 {
2329 const sal_Int32 nSavPos = aWordSel.Max().GetIndex();
2330 if ( aWordSel.Max().GetIndex() > nEnd )
2331 aWordSel.Max().SetIndex( nEnd );
2332
2333 OUString aWord = GetSelected( aWordSel );
2334
2335 // restore selection for proper iteration at the end of the function
2336 aWordSel.Max().SetIndex( nSavPos );
2337
2338 sal_Int32 nIdx = 0, nPrevIdx = 0;
2339 sal_Int32 nKashidaPos = -1;
2340 sal_Unicode cCh, cPrevCh = 0;
2341
2342 int nPriorityLevel = 7; // 0..6 = level found
2343 // 7 not found
2344
2345 sal_Int32 nWordLen = aWord.getLength();
2346
2347 // ignore trailing vowel chars
2348 while( nWordLen && isTransparentChar( aWord[ nWordLen - 1 ] ))
2349 --nWordLen;
2350
2351 while ( nIdx < nWordLen )
2352 {
2353 cCh = aWord[ nIdx ];
2354
2355 // 1. Priority:
2356 // after user inserted kashida
2357 if ( 0x640 == cCh )
2358 {
2359 nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
2360 nPriorityLevel = 0;
2361 }
2362
2363 // 2. Priority:
2364 // after a Seen or Sad
2365 if (nPriorityLevel >= 1 && nIdx < nWordLen - 1)
2366 {
2367 if( isSeenOrSadChar( cCh )
2368 && (aWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion
2369 {
2370 nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
2371 nPriorityLevel = 1;
2372 }
2373 }
2374
2375 // 3. Priority:
2376 // before final form of Teh Marbuta, Heh, Dal
2377 if ( nPriorityLevel >= 2 && nIdx > 0 )
2378 {
2379 if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining)
2380 isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word
2381 ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word
2382 {
2383
2384 SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
2385 // check if character is connectable to previous character,
2386 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2387 {
2388 nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
2389 nPriorityLevel = 2;
2390 }
2391 }
2392 }
2393
2394 // 4. Priority:
2395 // before final form of Alef, Tah, Lam, Kaf or Gaf
2396 if ( nPriorityLevel >= 3 && nIdx > 0 )
2397 {
2398 if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word
2399 (( isLamChar ( cCh ) || // Lam,
2400 isTahChar ( cCh ) || // Tah,
2401 isKafChar ( cCh ) || // Kaf (all dual joining)
2402 isGafChar ( cCh ) )
2403 && nIdx == nWordLen - 1)) // only at end of word
2404 {
2405 SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
2406 // check if character is connectable to previous character,
2407 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2408 {
2409 nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
2410 nPriorityLevel = 3;
2411 }
2412 }
2413 }
2414
2415 // 5. Priority:
2416 // before medial Beh-like
2417 if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 )
2418 {
2419 if ( isBehChar ( cCh ) )
2420 {
2421 // check if next character is Reh or Yeh-like
2422 sal_Unicode cNextCh = aWord[ nIdx + 1 ];
2423 if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh ))
2424 {
2425 SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
2426 // check if character is connectable to previous character,
2427 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2428 {
2429 nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
2430 nPriorityLevel = 4;
2431 }
2432 }
2433 }
2434 }
2435
2436 // 6. Priority:
2437 // before the final form of Waw, Ain, Qaf and Feh
2438 if ( nPriorityLevel >= 5 && nIdx > 0 )
2439 {
2440 if ( isWawChar ( cCh ) || // Wav (right joining)
2441 // final form may appear in the middle of word
2442 (( isAinChar ( cCh ) || // Ain (dual joining)
2443 isQafChar ( cCh ) || // Qaf (dual joining)
2444 isFehChar ( cCh ) ) // Feh (dual joining)
2445 && nIdx == nWordLen - 1)) // only at end of word
2446 {
2447 SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
2448 // check if character is connectable to previous character,
2449 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2450 {
2451 nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
2452 nPriorityLevel = 5;
2453 }
2454 }
2455 }
2456
2457 // other connecting possibilities
2458 if ( nPriorityLevel >= 6 && nIdx > 0 )
2459 {
2460 // Reh, Zain
2461 if ( isRehChar ( cCh ) )
2462 {
2463 SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
2464 // check if character is connectable to previous character,
2465 if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
2466 {
2467 nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
2468 nPriorityLevel = 6;
2469 }
2470 }
2471 }
2472
2473 // Do not consider vowel marks when checking if a character
2474 // can be connected to previous character.
2475 if ( !isTransparentChar ( cCh) )
2476 {
2477 cPrevCh = cCh;
2478 nPrevIdx = nIdx;
2479 }
2480
2481 ++nIdx;
2482 } // end of current word
2483
2484 if ( nKashidaPos>=0 )
2485 aKashidaArray.push_back( nKashidaPos );
2486
2487 aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
2488 aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
2489 }
2490
2491 // Validate
2492 std::vector<sal_Int32> aDropped(aKashidaArray.size());
2493 auto nOldLayout = GetRefDevice()->GetLayoutMode();
2495 GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart,
2496 aKashidaArray.size(), aKashidaArray.data(), aDropped.data());
2497 GetRefDevice()->SetLayoutMode(nOldLayout);
2498
2499 for (auto const& pos : aKashidaArray)
2500 if (std::find(aDropped.begin(), aDropped.end(), pos) == aDropped.end())
2501 rArray.push_back(pos);
2502}
2503
2504sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine )
2505{
2506 // The portion at nPos is split, if there is not a transition at nPos anyway
2507 if ( nPos == 0 )
2508 return 0;
2509
2510 assert( pPortion && "SplitTextPortion: Which ?" );
2511
2512 sal_Int32 nSplitPortion;
2513 sal_Int32 nTmpPos = 0;
2514 TextPortion* pTextPortion = nullptr;
2515 sal_Int32 nPortions = pPortion->GetTextPortions().Count();
2516 for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
2517 {
2518 TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion];
2519 nTmpPos = nTmpPos + rTP.GetLen();
2520 if ( nTmpPos >= nPos )
2521 {
2522 if ( nTmpPos == nPos ) // then nothing needs to be split
2523 {
2524 return nSplitPortion;
2525 }
2526 pTextPortion = &rTP;
2527 break;
2528 }
2529 }
2530
2531 DBG_ASSERT( pTextPortion, "Position outside the area!" );
2532
2533 if (!pTextPortion)
2534 return 0;
2535
2536 DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" );
2537
2538 sal_Int32 nOverlapp = nTmpPos - nPos;
2539 pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp );
2540 TextPortion* pNewPortion = new TextPortion( nOverlapp );
2541 pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion);
2542 // Set sizes
2543 if ( pCurLine )
2544 {
2545 // No new GetTextSize, instead use values from the Array:
2546 assert( nPos > pCurLine->GetStart() && "SplitTextPortion at the beginning of the line?" );
2547 pTextPortion->setWidth(pCurLine->GetCharPosArray()[nPos - pCurLine->GetStart() - 1]);
2548
2549 if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed )
2550 {
2551 // We need the original size from the portion
2552 sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion );
2553 SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() );
2554 SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont );
2555 aTmpFont.SetPhysFont(*GetRefDevice());
2558 Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(),
2559 nTxtPortionStart, pTextPortion->GetLen(), nullptr );
2560 GetRefDevice()->Pop();
2561 pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width();
2562 }
2563 }
2564 else
2565 pTextPortion->setWidth(-1);
2566
2567 return nSplitPortion;
2568}
2569
2570void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart )
2571{
2572 sal_Int32 nStartPos = rStart;
2573 ContentNode* pNode = pParaPortion->GetNode();
2574 DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" );
2575
2577 aPositions.insert( 0 );
2578
2579 for (std::size_t nAttr = 0;; ++nAttr)
2580 {
2581 // Insert Start and End into the Array...
2582 // The Insert method does not allow for duplicate values...
2583 EditCharAttrib* pAttrib = GetAttrib(pNode->GetCharAttribs().GetAttribs(), nAttr);
2584 if (!pAttrib)
2585 break;
2586 aPositions.insert( pAttrib->GetStart() );
2587 aPositions.insert( pAttrib->GetEnd() );
2588 }
2589 aPositions.insert( pNode->Len() );
2590
2591 if ( pParaPortion->aScriptInfos.empty() )
2592 InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) );
2593
2594 const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
2595 for (const ScriptTypePosInfo& rType : rTypes)
2596 aPositions.insert( rType.nStartPos );
2597
2598 const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos;
2599 for (const WritingDirectionInfo & rWritingDirection : rWritingDirections)
2600 aPositions.insert( rWritingDirection.nStartPos );
2601
2602 if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) )
2603 {
2604 ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF);
2605 for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
2606 {
2607 if ( mpIMEInfos->pAttribs[n] != nLastAttr )
2608 {
2609 aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
2610 nLastAttr = mpIMEInfos->pAttribs[n];
2611 }
2612 }
2613 aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen );
2614 }
2615
2616 // From ... Delete:
2617 // Unfortunately, the number of text portions does not have to match
2618 // aPositions.Count(), since there might be line breaks...
2619 sal_Int32 nPortionStart = 0;
2620 sal_Int32 nInvPortion = 0;
2621 sal_Int32 nP;
2622 for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ )
2623 {
2624 const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP];
2625 nPortionStart = nPortionStart + rTmpPortion.GetLen();
2626 if ( nPortionStart >= nStartPos )
2627 {
2628 nPortionStart = nPortionStart - rTmpPortion.GetLen();
2629 rStart = nPortionStart;
2630 nInvPortion = nP;
2631 break;
2632 }
2633 }
2634 DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" );
2635 if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
2636 {
2637 // prefer one in front...
2638 // But only if it was in the middle of the portion of, otherwise it
2639 // might be the only one in the row in front!
2640 nInvPortion--;
2641 nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen();
2642 }
2643 pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
2644
2645 // A portion may also have been formed by a line break:
2646 aPositions.insert( nPortionStart );
2647
2648 auto nInvPos = aPositions.find( nPortionStart );
2649 DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" );
2650
2651 auto i = nInvPos;
2652 ++i;
2653 while ( i != aPositions.end() )
2654 {
2655 TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ );
2656 pParaPortion->GetTextPortions().Append(pNew);
2657 }
2658
2659 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" );
2660#if OSL_DEBUG_LEVEL > 0
2661 OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" );
2662#endif
2663}
2664
2665void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars )
2666{
2667 DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" );
2668 DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" );
2669
2670 ContentNode* const pNode = pParaPortion->GetNode();
2671 if ( nNewChars > 0 )
2672 {
2673 // If an Attribute begins/ends at nStartPos, then a new portion starts
2674 // otherwise the portion is extended at nStartPos.
2675 if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) )
2676 {
2677 sal_Int32 nNewPortionPos = 0;
2678 if ( nStartPos )
2679 nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1;
2680
2681 // A blank portion may be here, if the paragraph was empty,
2682 // or if a line was created by a hard line break.
2683 if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) &&
2684 !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
2685 {
2686 TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos];
2687 DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" );
2688 rTP.SetLen( rTP.GetLen() + nNewChars );
2689 }
2690 else
2691 {
2692 TextPortion* pNewPortion = new TextPortion( nNewChars );
2693 pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion);
2694 }
2695 }
2696 else
2697 {
2698 sal_Int32 nPortionStart;
2699 const sal_Int32 nTP = pParaPortion->GetTextPortions().
2700 FindPortion( nStartPos, nPortionStart );
2701 TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
2702 rTP.SetLen( rTP.GetLen() + nNewChars );
2703 rTP.setWidth(-1);
2704 }
2705 }
2706 else
2707 {
2708 // Shrink or remove portion if necessary.
2709 // Before calling this method it must be ensured that no portions were
2710 // in the deleted area!
2711
2712 // There must be no portions extending into the area or portions starting in
2713 // the area, so it must be:
2714 // nStartPos <= nPos <= nStartPos - nNewChars(neg.)
2715 sal_Int32 nPortion = 0;
2716 sal_Int32 nPos = 0;
2717 sal_Int32 nEnd = nStartPos-nNewChars;
2718 sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
2719 TextPortion* pTP = nullptr;
2720 for ( nPortion = 0; nPortion < nPortions; nPortion++ )
2721 {
2722 pTP = &pParaPortion->GetTextPortions()[ nPortion ];
2723 if ( ( nPos+pTP->GetLen() ) > nStartPos )
2724 {
2725 DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" );
2726 DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" );
2727 break;
2728 }
2729 nPos = nPos + pTP->GetLen();
2730 }
2731 assert( pTP && "RecalcTextPortion: Portion not found" );
2732 if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
2733 {
2734 // Remove portion;
2735 PortionKind nType = pTP->GetKind();
2736 pParaPortion->GetTextPortions().Remove( nPortion );
2738 {
2739 TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ];
2740 if ( !rNext.GetLen() )
2741 {
2742 // Remove dummy portion
2743 pParaPortion->GetTextPortions().Remove( nPortion );
2744 }
2745 }
2746 }
2747 else
2748 {
2749 DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! ");
2750 pTP->SetLen( pTP->GetLen() + nNewChars );
2751 }
2752
2753 sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count();
2754 assert( nPortionCount );
2755 if (nPortionCount)
2756 {
2757 // No HYPHENATOR portion is allowed to get stuck right at the end...
2758 sal_Int32 nLastPortion = nPortionCount - 1;
2759 pTP = &pParaPortion->GetTextPortions()[nLastPortion];
2760 if ( pTP->GetKind() == PortionKind::HYPHENATOR )
2761 {
2762 // Discard portion; if possible, correct the ones before,
2763 // if the Hyphenator portion has swallowed one character...
2764 if ( nLastPortion && pTP->GetLen() )
2765 {
2766 TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1];
2767 DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
2768 rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() );
2769 rPrev.setWidth(-1);
2770 }
2771 pParaPortion->GetTextPortions().Remove( nLastPortion );
2772 }
2773 }
2774 }
2775#if OSL_DEBUG_LEVEL > 0
2776 OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" );
2777#endif
2778}
2779
2780void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger )
2781{
2782 pTextRanger = std::move(pRanger);
2783
2784 for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
2785 {
2786 ParaPortion* pParaPortion = GetParaPortions()[nPara];
2787 pParaPortion->MarkSelectionInvalid( 0 );
2788 pParaPortion->GetLines().Reset();
2789 }
2790
2791 FormatFullDoc();
2793 if ( IsUpdateLayout() && GetActiveView() )
2794 pActiveView->ShowCursor(false, false);
2795}
2796
2797void ImpEditEngine::SetVertical( bool bVertical)
2798{
2799 if ( IsEffectivelyVertical() != bVertical)
2800 {
2801 GetEditDoc().SetVertical(bVertical);
2802 bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
2803 GetEditDoc().CreateDefFont( bUseCharAttribs );
2804 if ( IsFormatted() )
2805 {
2806 FormatFullDoc();
2808 }
2809 }
2810}
2811
2813{
2814 if (GetEditDoc().GetRotation() == nRotation)
2815 return; // not modified
2816 GetEditDoc().SetRotation(nRotation);
2817 bool bUseCharAttribs = bool(aStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
2818 GetEditDoc().CreateDefFont( bUseCharAttribs );
2819 if ( IsFormatted() )
2820 {
2821 FormatFullDoc();
2823 }
2824}
2825
2826void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing)
2827{
2828 assert(nColumns >= 1);
2829 if (mnColumns != nColumns || mnColumnSpacing != nSpacing)
2830 {
2831 if (nColumns == 0)
2832 {
2833 SAL_WARN("editeng", "bad nColumns value, ignoring");
2834 nColumns = 1;
2835 }
2836 mnColumns = nColumns;
2837 mnColumnSpacing = nSpacing;
2838 if (IsFormatted())
2839 {
2840 FormatFullDoc();
2842 }
2843 }
2844}
2845
2846void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight )
2847{
2848 if ( IsFixedCellHeight() != bUseFixedCellHeight )
2849 {
2850 GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight );
2851 if ( IsFormatted() )
2852 {
2853 FormatFullDoc();
2855 }
2856 }
2857}
2858
2859void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut )
2860{
2861 // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would
2862 // only be searched anew at the StartPosition.
2863 // Problem: There would be two lists to consider/handle:
2864 // OrderedByStart,OrderedByEnd.
2865
2866 if ( nPos > pNode->Len() )
2867 nPos = pNode->Len();
2868
2869 rFont = pNode->GetCharAttribs().GetDefFont();
2870
2871 /*
2872 * Set attributes for script types Asian and Complex
2873 */
2874 short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) );
2875 SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
2876 if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) )
2877 {
2878 const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ));
2879 rFont.SetFamilyName( rFontItem.GetFamilyName() );
2880 rFont.SetFamily( rFontItem.GetFamily() );
2881 rFont.SetPitch( rFontItem.GetPitch() );
2882 rFont.SetCharSet( rFontItem.GetCharSet() );
2883 Size aSz( rFont.GetFontSize() );
2884 aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() );
2885 rFont.SetFontSize( aSz );
2886 rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() );
2887 rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() );
2888 rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() );
2889 }
2890
2891 sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue();
2892
2893 /*
2894 * Set output device's line and overline colors
2895 */
2896 if ( pOut )
2897 {
2898 const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE );
2899 if ( rTextLineColor.GetColor() != COL_TRANSPARENT )
2900 pOut->SetTextLineColor( rTextLineColor.GetColor() );
2901 else
2902 pOut->SetTextLineColor();
2903
2904 const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE );
2905 if ( rOverlineColor.GetColor() != COL_TRANSPARENT )
2906 pOut->SetOverlineColor( rOverlineColor.GetColor() );
2907 else
2908 pOut->SetOverlineColor();
2909 }
2910
2911 const SvxLanguageItem* pCJKLanguageItem = nullptr;
2912
2913 /*
2914 * Scan through char attributes of pNode
2915 */
2916 if ( aStatus.UseCharAttribs() )
2917 {
2919 size_t nAttr = 0;
2920 EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr);
2921 while ( pAttrib && ( pAttrib->GetStart() <= nPos ) )
2922 {
2923 // when seeking, ignore attributes which start there! Empty attributes
2924 // are considered (used) as these are just set. But do not use empty
2925 // attributes: When just set and empty => no effect on font
2926 // In a blank paragraph, set characters take effect immediately.
2927 if ( ( pAttrib->Which() != 0 ) &&
2928 ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) )
2929 || ( !pNode->Len() ) ) )
2930 {
2931 DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " );
2932 if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) )
2933 {
2934 pAttrib->SetFont( rFont, pOut );
2935 // #i1550# hard color attrib should win over text color from field
2936 if ( pAttrib->Which() == EE_FEATURE_FIELD )
2937 {
2938 EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos );
2939 if ( pColorAttr )
2940 pColorAttr->SetFont( rFont, pOut );
2941 }
2942 }
2943 if ( pAttrib->Which() == EE_CHAR_FONTWIDTH )
2944 nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue();
2945 if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK )
2946 pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() );
2947 }
2948 pAttrib = GetAttrib( rAttribs, ++nAttr );
2949 }
2950 }
2951
2952 if ( !pCJKLanguageItem )
2953 pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK );
2954
2955 rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() );
2956
2957 if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) )
2958 rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian );
2959
2960 if ( aStatus.DoNotUseColors() )
2961 {
2962 rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK );
2963 }
2964
2965 if ( aStatus.DoStretch() || ( nRelWidth != 100 ) )
2966 {
2967 // For the current Output device, because otherwise if RefDev=Printer its looks
2968 // ugly on the screen!
2969 OutputDevice* pDev = pOut ? pOut : GetRefDevice();
2970 rFont.SetPhysFont(*pDev);
2971 FontMetric aMetric( pDev->GetFontMetric() );
2972
2973 // before forcing nPropr to 100%, calculate a new escapement relative to this fake size.
2974 sal_uInt8 nPropr = rFont.GetPropr();
2975 sal_Int16 nEsc = rFont.GetEscapement();
2976 if ( nPropr && nEsc && nPropr != 100 && abs(nEsc) != DFLT_ESC_AUTO_SUPER )
2977 rFont.SetEscapement( 100.0/nPropr * nEsc );
2978
2979 // Set the font as we want it to look like & reset the Propr attribute
2980 // so that it is not counted twice.
2981 Size aRealSz( aMetric.GetFontSize() );
2982 rFont.SetPropr( 100 );
2983
2984 if ( aStatus.DoStretch() )
2985 {
2986 if (mfFontScaleY != 100.0)
2987 {
2988 double fHeightRounded = roundToNearestPt(aRealSz.Height());
2989 double fNewHeight = fHeightRounded * (mfFontScaleY / 100.0);
2990 fNewHeight = roundToNearestPt(fNewHeight);
2991 aRealSz.setHeight(basegfx::fround(fNewHeight));
2992 }
2993 if (mfFontScaleX != 100.0)
2994 {
2995 if (mfFontScaleX == mfFontScaleY && nRelWidth == 100 )
2996 {
2997 aRealSz.setWidth( 0 );
2998 }
2999 else
3000 {
3001 double fWidthRounded = roundToNearestPt(aRealSz.Width());
3002 double fNewWidth = fWidthRounded * (mfFontScaleX / 100.0);
3003 fNewWidth = roundToNearestPt(fNewWidth);
3004 aRealSz.setWidth(basegfx::fround(fNewWidth));
3005
3006 // Also the Kerning: (long due to handle Interim results)
3007 tools::Long nKerning = rFont.GetFixKerning();
3008/*
3009 The consideration was: If negative kerning, but StretchX = 200
3010 => Do not double the kerning, thus pull the letters closer together
3011 ---------------------------
3012 Kern StretchX =>Kern
3013 ---------------------------
3014 >0 <100 < (Proportional)
3015 <0 <100 < (Proportional)
3016 >0 >100 > (Proportional)
3017 <0 >100 < (The amount, thus disproportional)
3018*/
3019 if (nKerning < 0 && mfFontScaleX > 100.0)
3020 {
3021 // disproportional
3022 nKerning = basegfx::fround((double(nKerning) * 100.0) / mfFontScaleX);
3023 }
3024 else if ( nKerning )
3025 {
3026 // Proportional
3027 nKerning = basegfx::fround((double(nKerning) * mfFontScaleX) / 100.0);
3028 }
3029 rFont.SetFixKerning( static_cast<short>(nKerning) );
3030 }
3031 }
3032 }
3033 if ( nRelWidth != 100 )
3034 {
3035 aRealSz.setWidth( aRealSz.Width() * nRelWidth );
3036 aRealSz.setWidth( aRealSz.Width() / 100 );
3037 }
3038 rFont.SetFontSize( aRealSz );
3039 // Font is not restored...
3040 }
3041
3042 if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut )
3043 {
3044 // #i75566# Do not use AutoColor when printing OR Pdf export
3045 const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType());
3046 const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType());
3047
3048 if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting)
3049 {
3050 // Never use WindowTextColor on the printer
3051 rFont.SetColor( GetAutoColor() );
3052 }
3053 else
3054 {
3055 if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() )
3056 rFont.SetColor( COL_WHITE );
3057 else
3058 rFont.SetColor( COL_BLACK );
3059 }
3060 }
3061
3062 if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) &&
3063 ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
3064 return;
3065
3066 ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
3067 if ( nAttr & ExtTextInputAttr::Underline )
3069 else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
3071 else if ( nAttr & ExtTextInputAttr::BoldUnderline )
3073 else if ( nAttr & ExtTextInputAttr::DottedUnderline )
3075 else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
3077 else if ( nAttr & ExtTextInputAttr::RedText )
3078 rFont.SetColor( COL_RED );
3079 else if ( nAttr & ExtTextInputAttr::HalfToneText )
3080 rFont.SetColor( COL_LIGHTGRAY );
3081 if ( nAttr & ExtTextInputAttr::Highlight )
3082 {
3083 const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
3084 rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
3085 rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
3086 rFont.SetTransparent( false );
3087 }
3088 else if ( nAttr & ExtTextInputAttr::GrayWaveline )
3089 {
3091 if( pOut )
3092 pOut->SetTextLineColor( COL_LIGHTGRAY );
3093 }
3094}
3095
3097{
3098 // for line height at high / low first without Propr!
3099 sal_uInt16 nPropr = rFont.GetPropr();
3100 DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" );
3101 if ( nPropr != 100 )
3102 {
3103 rFont.SetPropr( 100 );
3104 rFont.SetPhysFont(*pRefDev);
3105 }
3106 sal_uInt16 nAscent, nDescent;
3107
3108 FontMetric aMetric( pRefDev->GetFontMetric() );
3109 nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
3110 if ( IsAddExtLeading() )
3111 nAscent = sal::static_int_cast< sal_uInt16 >(
3112 nAscent + aMetric.GetExternalLeading() );
3113 nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
3114
3115 if ( IsFixedCellHeight() )
3116 {
3117 nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() );
3118 nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent );
3119 }
3120 else
3121 {
3122 sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0;
3123 // Fonts without leading cause problems
3124 if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) )
3125 {
3126 // Lets see what Leading one gets on the screen
3127 VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() );
3128 rFont.SetPhysFont(*pVDev);
3129 aMetric = pVDev->GetFontMetric();
3130
3131 // This is so that the Leading does not count itself out again,
3132 // if the whole line has the font, nTmpLeading.
3133 nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
3134 nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
3135 }
3136 }
3137 if ( nAscent > rCurMetrics.nMaxAscent )
3138 rCurMetrics.nMaxAscent = nAscent;
3139 if ( nDescent > rCurMetrics.nMaxDescent )
3140 rCurMetrics.nMaxDescent= nDescent;
3141 // Special treatment of high/low:
3142 if ( !rFont.GetEscapement() )
3143 return;
3144
3145 // Now in consideration of Escape/Propr
3146 // possibly enlarge Ascent or Descent
3147 short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100);
3148 if ( rFont.GetEscapement() > 0 )
3149 {
3150 nAscent = static_cast<sal_uInt16>(static_cast<tools::Long>(nAscent)*nPropr/100 + nDiff);
3151 if ( nAscent > rCurMetrics.nMaxAscent )
3152 rCurMetrics.nMaxAscent = nAscent;
3153 }
3154 else // has to be < 0
3155 {
3156 nDescent = static_cast<sal_uInt16>(static_cast<tools::Long>(nDescent)*nPropr/100 - nDiff);
3157 if ( nDescent > rCurMetrics.nMaxDescent )
3158 rCurMetrics.nMaxDescent= nDescent;
3159 }
3160}
3161
3163{
3164 return !IsEffectivelyVertical() ? sz.Width() : sz.Height();
3165}
3166
3168{
3169 return !IsEffectivelyVertical() ? sz.Height() : sz.Width();
3170}
3171
3173{
3174 if (!IsEffectivelyVertical())
3175 pt.AdjustX(x);
3176 else
3177 pt.AdjustY(IsTopToBottom() ? x : -x);
3178}
3179
3181{
3182 if (!IsEffectivelyVertical())
3183 pt.AdjustY(y);
3184 else
3185 pt.AdjustX(IsTopToBottom() ? -y : y);
3186}
3187
3188void ImpEditEngine::setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const
3189{
3190 if (!IsEffectivelyVertical())
3191 ptDest.setX(ptSrc.X());
3192 else
3193 ptDest.setY(ptSrc.Y());
3194}
3195
3196void ImpEditEngine::setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const
3197{
3198 if (!IsEffectivelyVertical())
3199 ptDest.setY(ptSrc.Y());
3200 else
3201 ptDest.setX(ptSrc.Y());
3202}
3203
3205 const tools::Rectangle& rectMax) const
3206{
3207 tools::Long nRes;
3208 if (!IsEffectivelyVertical())
3209 nRes = pt.Y() - rectMax.Bottom();
3210 else if (IsTopToBottom())
3211 nRes = rectMax.Left() - pt.X();
3212 else
3213 nRes = pt.X() - rectMax.Right();
3214 return std::max(nRes, tools::Long(0));
3215}
3216
3218{
3219 if (!IsEffectivelyVertical())
3220 return pt.X() > rectMax.Right();
3221
3222 if (IsTopToBottom())
3223 return pt.Y() > rectMax.Bottom();
3224 else
3225 return pt.Y() < rectMax.Top();
3226}
3227
3229{
3230 if (!IsEffectivelyVertical())
3231 return rect.Bottom();
3232
3233 if (IsTopToBottom())
3234 return -rect.Left();
3235 else
3236 return rect.Right();
3237}
3238
3240{
3241 if (!IsEffectivelyVertical())
3242 return { rect.Left(), rect.Top() };
3243
3244 if (IsTopToBottom())
3245 return { rect.Top(), -rect.Right() };
3246 else
3247 return { -rect.Bottom(), rect.Left() };
3248}
3249
3250// Returns the resulting shift for the point; allows to apply the same shift to other points
3252 Point& rMovePos, // [in, out] Point that will move to the next line
3253 tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware)
3254 sal_Int16& rColumn, // [in, out] current column number
3255 Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column
3256 tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed
3257) const
3258{
3259 const Point aOld = rMovePos;
3260
3261 // Move the point by the requested distance in Y direction
3262 adjustYDirectionAware(rMovePos, nLineHeight);
3263 // Check if the resulting position has moved beyond the limits, and more columns left.
3264 // The limits are defined by a rectangle starting from aOrigin with width of aPaperSize
3265 // and height of nCurTextHeight
3266 Point aOtherCorner = aOrigin;
3269 tools::Long nNeeded
3270 = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Normalize(aOrigin, aOtherCorner));
3271 if (pnHeightNeededToNotWrap)
3272 *pnHeightNeededToNotWrap = nNeeded;
3273 if (nNeeded && rColumn < mnColumns)
3274 {
3275 ++rColumn;
3276 // If we didn't fit into the last column, indicate that only by setting the column number
3277 // to the total number of columns; do not adjust
3278 if (rColumn < mnColumns)
3279 {
3280 // Set Y position of the point to that of aOrigin
3281 setYDirectionAwareFrom(rMovePos, aOrigin);
3282 // Move the point by the requested distance in Y direction
3283 adjustYDirectionAware(rMovePos, nLineHeight);
3284 // Move the point by the column+spacing distance in X direction
3286 }
3287 }
3288
3289 return rMovePos - aOld;
3290}
3291
3292// TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication
3293
3294void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation )
3295{
3296 if ( !IsUpdateLayout() && !bStripOnly )
3297 return;
3298
3299 if ( !IsFormatted() )
3300 FormatDoc();
3301
3302 tools::Long nFirstVisXPos = - rOutDev.GetMapMode().GetOrigin().X();
3303 tools::Long nFirstVisYPos = - rOutDev.GetMapMode().GetOrigin().Y();
3304
3305 DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" );
3306 SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() );
3307 vcl::PDFExtOutDevData* const pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( rOutDev.GetExtOutDevData() );
3308
3309 // In the case of rotated text is aStartPos considered TopLeft because
3310 // other information is missing, and since the whole object is shown anyway
3311 // un-scrolled.
3312 // The rectangle is infinite.
3313 const Point aOrigin( aStartPos );
3314
3315 // #110496# Added some more optional metafile comments. This
3316 // change: factored out some duplicated code.
3317 GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
3318 const bool bMetafileValid( pMtf != nullptr );
3319
3320 const tools::Long nVertLineSpacing = CalcVertLineSpacing(aStartPos);
3321
3322 sal_Int16 nColumn = 0;
3323
3324 // Over all the paragraphs...
3325
3326 for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ )
3327 {
3328 const ParaPortion* const pPortion = GetParaPortions()[n];
3329 assert( pPortion && "NULL-Pointer in TokenList in Paint" );
3330 // if when typing idle formatting, asynchronous Paint.
3331 // Invisible Portions may be invalid.
3332 if ( pPortion->IsVisible() && pPortion->IsInvalid() )
3333 return;
3334
3335 if ( pPDFExtOutDevData )
3337
3338 const tools::Long nParaHeight = pPortion->GetHeight();
3339 if ( pPortion->IsVisible() && (
3340 ( !IsEffectivelyVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) ||
3341 ( IsEffectivelyVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) ||
3342 ( IsEffectivelyVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) )
3343
3344 {
3345 Point aTmpPos;
3346
3347 // Over the lines of the paragraph...
3348
3349 const sal_Int32 nLines = pPortion->GetLines().Count();
3350 const sal_Int32 nLastLine = nLines-1;
3351
3352 bool bEndOfParagraphWritten(false);
3353
3354 adjustYDirectionAware(aStartPos, pPortion->GetFirstLineOffset());
3355
3356 const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
3357 sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
3358 ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
3359 bool bPaintBullet (false);
3360
3361 for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
3362 {
3363 const EditLine* const pLine = &pPortion->GetLines()[nLine];
3364 assert( pLine && "NULL-Pointer in the line iterator in UpdateViews" );
3365 sal_Int32 nIndex = pLine->GetStart();
3366 tools::Long nLineHeight = pLine->GetHeight();
3367 if (nLine != nLastLine)
3368 nLineHeight += nVertLineSpacing;
3369 MoveToNextLine(aStartPos, nLineHeight, nColumn, aOrigin);
3370 aTmpPos = aStartPos;
3371 adjustXDirectionAware(aTmpPos, pLine->GetStartPosX());
3372 adjustYDirectionAware(aTmpPos, pLine->GetMaxAscent() - nLineHeight);
3373
3374 if ( ( !IsEffectivelyVertical() && ( aStartPos.Y() > aClipRect.Top() ) )
3375 || ( IsEffectivelyVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() )
3376 || ( IsEffectivelyVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) )
3377 {
3378 bPaintBullet = false;
3379
3380 // Why not just also call when stripping portions? This will give the correct values
3381 // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call
3382 // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine
3383 // does, too. No change for not-layouting (painting).
3384 if(0 == nLine) // && !bStripOnly)
3385 {
3386 Point aLineStart(aStartPos);
3387 adjustYDirectionAware(aLineStart, -nLineHeight);
3388 GetEditEnginePtr()->PaintingFirstLine(n, aLineStart, aOrigin, nOrientation, rOutDev);
3389
3390 // Remember whether a bullet was painted.
3392 bPaintBullet = rBulletState.GetValue();
3393 }
3394
3395
3396 // Over the Portions of the line...
3397
3398 bool bParsingFields = false;
3399 std::vector< sal_Int32 >::iterator itSubLines;
3400
3401 for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ )
3402 {
3403 DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" );
3404 const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion];
3405
3406 const tools::Long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion );
3407 setXDirectionAwareFrom(aTmpPos, aStartPos);
3408 adjustXDirectionAware(aTmpPos, nPortionXOffset);
3409 if (isXOverflowDirectionAware(aTmpPos, aClipRect))
3410 break; // No further output in line necessary
3411
3412 switch ( rTextPortion.GetKind() )
3413 {
3414 case PortionKind::TEXT:
3415 case PortionKind::FIELD:
3417 {
3418 SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev );
3419
3420 bool bDrawFrame = false;
3421
3422 if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() &&
3423 ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() &&
3424 ( IsAutoColorEnabled() && ( rOutDev.GetOutDevType() != OUTDEV_PRINTER ) ) )
3425 {
3426 aTmpFont.SetTransparent( true );
3427 rOutDev.SetFillColor();
3428 rOutDev.SetLineColor( GetAutoColor() );
3429 bDrawFrame = true;
3430 }
3431
3432#if OSL_DEBUG_LEVEL > 2
3433 // Do we really need this if statement?
3434 if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
3435 {
3436 aTmpFont.SetFillColor( COL_LIGHTGRAY );
3437 aTmpFont.SetTransparent( sal_False );
3438 }
3439 if ( rTextPortion.IsRightToLeft() )
3440 {
3441 aTmpFont.SetFillColor( COL_LIGHTGRAY );
3442 aTmpFont.SetTransparent( sal_False );
3443 }
3444 else if ( GetI18NScriptType( EditPaM( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX )
3445 {
3446 aTmpFont.SetFillColor( COL_LIGHTCYAN );
3447 aTmpFont.SetTransparent( sal_False );
3448 }
3449#endif
3450 aTmpFont.SetPhysFont(rOutDev);
3451
3452 // #114278# Saving both layout mode and language (since I'm
3453 // potentially changing both)
3455 ImplInitLayoutMode(rOutDev, n, nIndex);
3456 ImplInitDigitMode(rOutDev, aTmpFont.GetLanguage());
3457
3458 OUString aText;
3459 sal_Int32 nTextStart = 0;
3460 sal_Int32 nTextLen = 0;
3462 o3tl::span<const sal_Bool> pKashidaArray;
3463 KernArray aTmpDXArray;
3464
3465 if ( rTextPortion.GetKind() == PortionKind::TEXT )
3466 {
3467 aText = pPortion->GetNode()->GetString();
3468 nTextStart = nIndex;
3469 nTextLen = rTextPortion.GetLen();
3470 pDXArray = o3tl::span(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()),
3471 pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart()));
3472
3473 if (!pLine->GetKashidaArray().empty())
3474 {
3475 pKashidaArray = o3tl::span(pLine->GetKashidaArray().data() + (nIndex - pLine->GetStart()),
3476 pLine->GetKashidaArray().size() - (nIndex - pLine->GetStart()));
3477 }
3478
3479 // Paint control characters (#i55716#)
3480 /* XXX: Given that there's special handling
3481 * only for some specific characters
3482 * (U+200B ZERO WIDTH SPACE and U+2060 WORD
3483 * JOINER) it is assumed to be not relevant
3484 * for MarkUrlFields(). */
3485 if ( aStatus.MarkNonUrlFields() )
3486 {
3487 sal_Int32 nTmpIdx;
3488 const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen();
3489
3490 for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx )
3491 {
3492 const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ?
3493 aText[nTmpIdx] :
3494 0;
3495
3496 if ( 0x200B == cChar || 0x2060 == cChar )
3497 {
3498 tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev,
3499 " ", 0, 1, nullptr ).Width() / 2;
3500
3501 const tools::Long nAdvanceX = ( nTmpIdx == nTmpEnd ?
3502 rTextPortion.GetSize().Width() :
3503 pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth;
3504 const tools::Long nAdvanceY = -pLine->GetMaxAscent();
3505
3506 Point aTopLeftRectPos( aTmpPos );
3507 adjustXDirectionAware(aTopLeftRectPos, nAdvanceX);
3508 adjustYDirectionAware(aTopLeftRectPos, nAdvanceY);
3509
3510 Point aBottomRightRectPos( aTopLeftRectPos );
3511 adjustXDirectionAware(aBottomRightRectPos, 2 * nHalfBlankWidth);
3512 adjustYDirectionAware(aBottomRightRectPos, pLine->GetHeight());
3513
3516 rOutDev.SetFillColor( COL_LIGHTGRAY );
3517 rOutDev.SetLineColor( COL_LIGHTGRAY );
3518
3519 const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos );
3520 rOutDev.DrawRect( aBackRect );
3521
3522 rOutDev.Pop();
3523 rOutDev.Pop();
3524
3525 if ( 0x200B == cChar )
3526 {
3527 const OUString aSlash( '/' );
3528 const short nOldEscapement = aTmpFont.GetEscapement();
3529 const sal_uInt8 nOldPropr = aTmpFont.GetPropr();
3530
3531 aTmpFont.SetEscapement( -20 );
3532 aTmpFont.SetPropr( 25 );
3533 aTmpFont.SetPhysFont(rOutDev);
3534
3535 const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev,
3536 aSlash, 0, 1, nullptr );
3537 Point aSlashPos( aTmpPos );
3538 const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2;
3539 setXDirectionAwareFrom(aSlashPos, aTopLeftRectPos);
3540 adjustXDirectionAware(aSlashPos, nAddX);
3541
3542 aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1, {} );
3543
3544 aTmpFont.SetEscapement( nOldEscapement );
3545 aTmpFont.SetPropr( nOldPropr );
3546 aTmpFont.SetPhysFont(rOutDev);
3547 }
3548 }
3549 }
3550 }
3551 }
3552 else if ( rTextPortion.GetKind() == PortionKind::FIELD )
3553 {
3554 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3555 assert( pAttr && "Field not found");
3556 DBG_ASSERT( dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Field of the wrong type! ");
3557 aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue();
3558 nTextStart = 0;
3559 nTextLen = aText.getLength();
3560 ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos();
3561 // Do not split the Fields into different lines while editing
3562 // With EditView on Overlay bStripOnly is now set for stripping to
3563 // primitives. To stay compatible in EditMode use pActiveView to detect
3564 // when we are in EditMode. For whatever reason URLs are drawn as single
3565 // line in edit mode, originally clipped against edit area (which is no
3566 // longer done in Overlay mode and allows to *read* the URL).
3567 // It would be difficult to change this due to needed adaptations in
3568 // EditEngine (look for lineBreaksList creation)
3569 if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() )
3570 {
3571 bParsingFields = true;
3572 itSubLines = pExtraInfo->lineBreaksList.begin();
3573 }
3574
3575 if( bParsingFields )
3576 {
3577 if( itSubLines != pExtraInfo->lineBreaksList.begin() )
3578 {
3579 // only use GetMaxAscent(), pLine->GetHeight() will not
3580 // proceed as needed (see PortionKind::TEXT above and nAdvanceY)
3581 // what will lead to a compressed look with multiple lines
3582 const sal_uInt16 nMaxAscent(pLine->GetMaxAscent());
3583
3584 aTmpPos += MoveToNextLine(aStartPos, nMaxAscent,
3585 nColumn, aOrigin);
3586 }
3587 std::vector< sal_Int32 >::iterator curIt = itSubLines;
3588 ++itSubLines;
3589 if( itSubLines != pExtraInfo->lineBreaksList.end() )
3590 {
3591 nTextStart = *curIt;
3592 nTextLen = *itSubLines - nTextStart;
3593 }
3594 else
3595 {
3596 nTextStart = *curIt;
3597 nTextLen = nTextLen - nTextStart;
3598 bParsingFields = false;
3599
3600 if (nLine + 1 < nLines)
3601 {
3602 // tdf#148966 don't paint the line break following a
3603 // multiline field based on a compat flag
3604 OutlinerEditEng* pOutlEditEng{ dynamic_cast<OutlinerEditEng*>(pEditEngine) };
3605 if (pOutlEditEng
3606 && pOutlEditEng->GetCompatFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField)
3607 .value_or(false))
3608 {
3609 int nStartNextLine = pPortion->GetLines()[nLine + 1].GetStartPortion();
3610 const TextPortion& rNextTextPortion = pPortion->GetTextPortions()[nStartNextLine];
3611 if (rNextTextPortion.GetKind() == PortionKind::LINEBREAK)
3612 ++nLine; //ignore the following linebreak
3613 }
3614 }
3615 }
3616 }
3617
3618 aTmpFont.SetPhysFont(*GetRefDevice());
3619 aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen,
3620 &aTmpDXArray );
3621 assert(aTmpDXArray.get_factor() == 1);
3622 std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array();
3623 pDXArray = rKernArray;
3624
3625 // add a meta file comment if we record to a metafile
3626 if( bMetafileValid )
3627 {
3628 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3629 if( pFieldItem )
3630 {
3631 const SvxFieldData* pFieldData = pFieldItem->GetField();
3632 if( pFieldData )
3633 pMtf->AddAction( pFieldData->createBeginComment() );
3634 }
3635 }
3636
3637 }
3638 else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
3639 {
3640 if ( rTextPortion.GetExtraValue() )
3641 aText = OUString(rTextPortion.GetExtraValue());
3642 aText += CH_HYPH;
3643 nTextStart = 0;
3644 nTextLen = aText.getLength();
3645
3646 // crash when accessing 0 pointer in pDXArray
3647 aTmpFont.SetPhysFont(*GetRefDevice());
3648 aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(),
3649 &aTmpDXArray );
3650 assert(aTmpDXArray.get_factor() == 1);
3651 std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array();
3652 pDXArray = rKernArray;
3653 }
3654
3655 tools::Long nTxtWidth = rTextPortion.GetSize().Width();
3656
3657 Point aOutPos( aTmpPos );
3658 Point aRedLineTmpPos = aTmpPos;
3659 // In RTL portions spell markup pos should be at the start of the
3660 // first chara as well. That is on the right end of the portion
3661 if (rTextPortion.IsRightToLeft())
3662 aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() );
3663
3664 if ( bStripOnly )
3665 {
3666 EEngineData::WrongSpellVector aWrongSpellVector;
3667
3668 if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen())
3669 {
3670 WrongList* pWrongs = pPortion->GetNode()->GetWrongList();
3671
3672 if(pWrongs && !pWrongs->empty())
3673 {
3674 size_t nStart = nIndex, nEnd = 0;
3675 bool bWrong = pWrongs->NextWrong(nStart, nEnd);
3676 const size_t nMaxEnd(nIndex + rTextPortion.GetLen());
3677
3678 while(bWrong)
3679 {
3680 if(nStart >= nMaxEnd)
3681 {
3682 break;
3683 }
3684
3685 if(nStart < o3tl::make_unsigned(nIndex))
3686 {
3687 nStart = nIndex;
3688 }
3689
3690 if(nEnd > nMaxEnd)
3691 {
3692 nEnd = nMaxEnd;
3693 }
3694
3695 // add to vector
3696 aWrongSpellVector.emplace_back(nStart, nEnd);
3697
3698 // goto next index
3699 nStart = nEnd + 1;
3700
3701 if(nEnd < nMaxEnd)
3702 {
3703 bWrong = pWrongs->NextWrong(nStart, nEnd);
3704 }
3705 else
3706 {
3707 bWrong = false;
3708 }
3709 }
3710 }
3711 }
3712
3713 const SvxFieldData* pFieldData = nullptr;
3714
3715 if(PortionKind::FIELD == rTextPortion.GetKind())
3716 {
3717 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3718 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3719
3720 if(pFieldItem)
3721 {
3722 pFieldData = pFieldItem->GetField();
3723 }
3724 }
3725
3726 // support for EOC, EOW, EOS TEXT comments. To support that,
3727 // the locale is needed. With the locale and a XBreakIterator it is
3728 // possible to re-create the text marking info on primitive level
3729 const lang::Locale aLocale(GetLocale(EditPaM(pPortion->GetNode(), nIndex + 1)));
3730
3731 // create EOL and EOP bools
3732 const bool bEndOfLine(nPortion == pLine->GetEndPortion());
3733 const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
3734
3735 // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in
3736 // consequence, but also already set at rOutDev)
3737 const Color aOverlineColor(rOutDev.GetOverlineColor());
3738
3739 // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in
3740 // consequence, but also already set at rOutDev)
3741 const Color aTextLineColor(rOutDev.GetTextLineColor());
3742
3743 // Unicode code points conversion according to ctl text numeral setting
3744 aText = convertDigits(aText, nTextStart, nTextLen,
3745 ImplCalcDigitLang(aTmpFont.GetLanguage()));
3746
3747 // StripPortions() data callback
3748 GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray,
3749 aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
3750 !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr,
3751 pFieldData,
3752 bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments
3753 &aLocale,
3754 aOverlineColor,
3755 aTextLineColor);
3756
3757 // #108052# remember that EOP is written already for this ParaPortion
3758 if(bEndOfParagraph)
3759 {
3760 bEndOfParagraphWritten = true;
3761 }
3762 }
3763 else
3764 {
3765 short nEsc = aTmpFont.GetEscapement();
3766 if ( nOrientation )
3767 {
3768 // In case of high/low do it yourself:
3769 if ( aTmpFont.GetEscapement() )
3770 {
3771 tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L;
3772 adjustYDirectionAware(aOutPos, -nDiff);
3773 aRedLineTmpPos = aOutPos;
3774 aTmpFont.SetEscapement( 0 );
3775 }
3776
3777 aOrigin.RotateAround(aOutPos, nOrientation);
3778 aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation );
3779 aTmpFont.SetPhysFont(rOutDev);
3780
3781 }
3782
3783 // Take only what begins in the visible range:
3784 // Important, because of a bug in some graphic cards
3785 // when transparent font, output when negative
3786 if ( nOrientation || ( !IsEffectivelyVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) )
3787 || ( IsEffectivelyVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) )
3788 {
3789 if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) )
3790 {
3791 // Paint the high/low without underline,
3792 // Display the Underline on the
3793 // base line of the original font height...
3794 // But only if there was something underlined before!
3795 bool bSpecialUnderline = false;
3797 if ( pPrev )
3798 {
3799 SvxFont aDummy;
3800 // Underscore in front?
3801 if ( pPrev->GetStart() )
3802 {
3803 SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy );
3804 if ( aDummy.GetUnderline() != LINESTYLE_NONE )
3805 bSpecialUnderline = true;
3806 }
3807 if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) )
3808 {
3809 SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy );
3810 if ( aDummy.GetUnderline() != LINESTYLE_NONE )
3811 bSpecialUnderline = true;
3812 }
3813 }
3814 if ( bSpecialUnderline )
3815 {
3816 Size aSz = aTmpFont.GetPhysTxtSize( &rOutDev, aText, nTextStart, nTextLen );
3817 sal_uInt8 nProp = aTmpFont.GetPropr();
3818 aTmpFont.SetEscapement( 0 );
3819 aTmpFont.SetPropr( 100 );
3820 aTmpFont.SetPhysFont(rOutDev);
3821 OUStringBuffer aBlanks(nTextLen);
3822 comphelper::string::padToLength( aBlanks, nTextLen, ' ' );
3823 Point aUnderlinePos( aOutPos );
3824 if ( nOrientation )
3825 {
3826 aUnderlinePos = aTmpPos;
3827 aOrigin.RotateAround(aUnderlinePos, nOrientation);
3828 }
3829 rOutDev.DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen );
3830
3831 aTmpFont.SetUnderline( LINESTYLE_NONE );
3832 if ( !nOrientation )
3833 aTmpFont.SetEscapement( nEsc );
3834 aTmpFont.SetPropr( nProp );
3835 aTmpFont.SetPhysFont(rOutDev);
3836 }
3837 }
3838 Point aRealOutPos( aOutPos );
3839 if ( ( rTextPortion.GetKind() == PortionKind::TEXT )
3840 && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed
3841 && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation )
3842 {
3843 aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX );
3844 }
3845
3846 // RTL portions with (#i37132#)
3847 // compressed blank should not paint this blank:
3848 if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 &&
3849 pDXArray[ nTextLen - 1 ] ==
3850 pDXArray[ nTextLen - 2 ] &&
3851 ' ' == aText[nTextStart + nTextLen - 1] )
3852 --nTextLen;
3853
3854 // output directly
3855 aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray );
3856
3857 if ( bDrawFrame )
3858 {
3859 Point aTopLeft( aTmpPos );
3860 aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
3861 if ( nOrientation )
3862 aOrigin.RotateAround(aTopLeft, nOrientation);
3863 tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
3864 rOutDev.DrawRect( aRect );
3865 }
3866
3867 // PDF export:
3868 if ( pPDFExtOutDevData )
3869 {
3870 if ( rTextPortion.GetKind() == PortionKind::FIELD )
3871 {
3872 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3873 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3874 if( pFieldItem )
3875 {
3876 const SvxFieldData* pFieldData = pFieldItem->GetField();
3877 if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) )
3878 {
3879 Point aTopLeft( aTmpPos );
3880 aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
3881
3882 tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
3884 aBookmark.nLinkId = pPDFExtOutDevData->CreateLink(aRect, pUrlField->GetRepresentation());
3885 aBookmark.aBookmark = pUrlField->GetURL();
3886 std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
3887 rBookmarks.push_back( aBookmark );
3888 }
3889 }
3890 }
3891 }
3892 }
3893
3894 const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList();
3895 if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() )
3896 {
3897 {//#105750# adjust LinePos for superscript or subscript text
3898 short _nEsc = aTmpFont.GetEscapement();
3899 if( _nEsc )
3900 {
3901 tools::Long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L;
3902 adjustYDirectionAware(aRedLineTmpPos, -nShift);
3903 }
3904 }
3905 Color aOldColor( rOutDev.GetLineColor() );
3906 rOutDev.SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor );
3907 lcl_DrawRedLines( rOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsEffectivelyVertical(), rTextPortion.IsRightToLeft() );
3908 rOutDev.SetLineColor( aOldColor );
3909 }
3910 }
3911
3912 rOutDev.Pop();
3913
3914 if ( rTextPortion.GetKind() == PortionKind::FIELD )
3915 {
3916 // add a meta file comment if we record to a metafile
3917 if( bMetafileValid )
3918 {
3919 const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
3920 assert( pAttr && "Field not found" );
3921
3922 const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
3923 DBG_ASSERT( pFieldItem != nullptr, "Wrong type of field!" );
3924
3925 if( pFieldItem )
3926 {
3927 const SvxFieldData* pFieldData = pFieldItem->GetField();
3928 if( pFieldData )
3930 }
3931 }
3932
3933 }
3934
3935 }
3936 break;
3937 case PortionKind::TAB:
3938 {
3939 if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) )
3940 {
3941 SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev );
3942 aTmpFont.SetTransparent( false );
3943 aTmpFont.SetEscapement( 0 );
3944 aTmpFont.SetPhysFont(rOutDev);
3945 tools::Long nCharWidth = aTmpFont.QuickGetTextSize( &rOutDev,
3946 OUString(rTextPortion.GetExtraValue()), 0, 1, {} ).Width();
3947 sal_Int32 nChars = 2;
3948 if( nCharWidth )
3949 nChars = rTextPortion.GetSize().Width() / nCharWidth;
3950 if ( nChars < 2 )
3951 nChars = 2; // is compressed by DrawStretchText.
3952 else if ( nChars == 2 )
3953 nChars = 3; // looks better
3954
3955 OUStringBuffer aBuf(nChars);
3956 comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue());
3957 OUString aText(aBuf.makeStringAndClear());
3958 aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength(), {} );
3959 rOutDev.DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText );
3960
3961 if ( bStripOnly )
3962 {
3963 // create EOL and EOP bools
3964 const bool bEndOfLine(nPortion == pLine->GetEndPortion());
3965 const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
3966
3967 const Color aOverlineColor(rOutDev.GetOverlineColor());
3968 const Color aTextLineColor(rOutDev.GetTextLineColor());
3969
3970 // StripPortions() data callback
3971 GetEditEnginePtr()->DrawingTab( aTmpPos,
3972 rTextPortion.GetSize().Width(),
3973 OUString(rTextPortion.GetExtraValue()),
3974 aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
3975 bEndOfLine, bEndOfParagraph,
3976 aOverlineColor, aTextLineColor);
3977 }
3978 }
3979 else if ( bStripOnly )
3980 {
3981 // #i108052# When stripping, a callback for _empty_ paragraphs is also needed.
3982 // This was optimized away (by not rendering the space-only tab portion), so do
3983 // it manually here.
3984 const bool bEndOfLine(nPortion == pLine->GetEndPortion());
3985 const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
3986
3987 const Color aOverlineColor(rOutDev.GetOverlineColor());
3988 const Color aTextLineColor(rOutDev.GetTextLineColor());
3989
3991 aTmpPos, OUString(), 0, 0, {}, {},
3992 aTmpFont, n, 0,
3993 nullptr,
3994 nullptr,
3995 bEndOfLine, bEndOfParagraph,
3996 nullptr,
3997 aOverlineColor,
3998 aTextLineColor);
3999 }
4000 }
4001 break;
4002 case PortionKind::LINEBREAK: break;
4003 }
4004 if( bParsingFields )
4005 nPortion--;
4006 else
4007 nIndex = nIndex + rTextPortion.GetLen();
4008
4009 }
4010 }
4011
4012 if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() )
4013 {
4014 adjustYDirectionAware(aStartPos, nSBL);
4015 }
4016
4017 // no more visible actions?
4018 if (getYOverflowDirectionAware(aStartPos, aClipRect))
4019 break;
4020 }
4021
4022 if ( !aStatus.IsOutliner() )
4023 {
4024 const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
4025 tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
4026 adjustYDirectionAware(aStartPos, nUL);
4027 }
4028
4029 // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion
4030 // EOP is not written, do it now. This will be safer than before. It has shown
4031 // that the reason for #i108052# was fixed/removed again, so this is a try to fix
4032 // the number of paragraphs (and counting empty ones) now independent from the
4033 // changes in EditEngine behaviour.
4034 if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly)
4035 {
4036 const Color aOverlineColor(rOutDev.GetOverlineColor());
4037 const Color aTextLineColor(rOutDev.GetTextLineColor());
4038
4040 aTmpPos, OUString(), 0, 0, {}, {},
4041 aTmpFont, n, 0,
4042 nullptr,
4043 nullptr,
4044 false, true, // support for EOL/EOP TEXT comments
4045 nullptr,
4046 aOverlineColor,
4047 aTextLineColor);
4048 }
4049 }
4050 else
4051 {
4052 adjustYDirectionAware(aStartPos, nParaHeight);
4053 }
4054
4055 if ( pPDFExtOutDevData )
4056 pPDFExtOutDevData->EndStructureElement();
4057
4058 // no more visible actions?
4059 if (getYOverflowDirectionAware(aStartPos, aClipRect))
4060 break;
4061 }
4062}
4063
4064void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice )
4065{
4066 if ( !IsUpdateLayout() || IsInUndo() )
4067 return;
4068
4069 assert( pView && "No View - No Paint!" );
4070
4071 // Intersection of paint area and output area.
4072 tools::Rectangle aClipRect( pView->GetOutputArea() );
4073 aClipRect.Intersection( rRect );
4074
4075 OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : *pView->GetWindow()->GetOutDev();
4076
4077 Point aStartPos;
4078 if ( !IsEffectivelyVertical() )
4079 aStartPos = pView->GetOutputArea().TopLeft();
4080 else
4081 {
4082 if( IsTopToBottom() )
4083 aStartPos = pView->GetOutputArea().TopRight();
4084 else
4085 aStartPos = pView->GetOutputArea().BottomLeft();
4086 }
4087 adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft()));
4088 adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop()));
4089
4090 // If Doc-width < Output Area,Width and not wrapped fields,
4091 // the fields usually protrude if > line.
4092 // (Not at the top, since there the Doc-width from formatting is already
4093 // there)
4094 if ( !IsEffectivelyVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) )
4095 {
4096 tools::Long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width();
4097 if ( aClipRect.Left() > nMaxX )
4098 return;
4099 if ( aClipRect.Right() > nMaxX )
4100 aClipRect.SetRight( nMaxX );
4101 }
4102
4103 bool bClipRegion = rTarget.IsClipRegion();
4104 vcl::Region aOldRegion = rTarget.GetClipRegion();
4105 rTarget.IntersectClipRegion( aClipRect );
4106
4107 Paint(rTarget, aClipRect, aStartPos);
4108
4109 if ( bClipRegion )
4110 rTarget.SetClipRegion( aOldRegion );
4111 else
4112 rTarget.SetClipRegion();
4113
4114 pView->DrawSelectionXOR(pView->GetEditSelection(), nullptr, &rTarget);
4115}
4116
4117void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos )
4118{
4119 DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " );
4120 DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" );
4121 GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>( pNode ));
4122 aEditDoc.Insert(nPos, pNode);
4125}
4126
4127EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos )
4128{
4129 ContentNode* pNode = aEditDoc.GetObject( nNode );
4130 DBG_ASSERT( pNode, "Invalid Node in SplitContent" );
4131 DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" );
4132 DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" );
4133 EditPaM aPaM( pNode, nSepPos );
4134 return ImpInsertParaBreak( aPaM );
4135}
4136
4137EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward )
4138{
4139 ContentNode* pLeftNode = aEditDoc.GetObject( nLeftNode );
4140 ContentNode* pRightNode = aEditDoc.GetObject( nLeftNode+1 );
4141 DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents ");
4142 DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents ");
4143 return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward );
4144}
4145
4146bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUpdate )
4147{
4148 const bool bPrevUpdateLayout = bUpdateLayout;
4149 const bool bChanged = (bUpdateLayout != bUp);
4150
4151 // When switching from true to false, all selections were visible,
4152 // => paint over
4153 // the other hand, were all invisible => paint
4154 // If !bFormatted, e.g. after SetText, then if UpdateMode=true
4155 // formatting is not needed immediately, probably because more text is coming.
4156 // At latest it is formatted at a Paint/CalcTextWidth.
4157 bUpdateLayout = bUp;
4158 if ( bUpdateLayout && ( bChanged || bForceUpdate ) )
4159 FormatAndLayout( pCurView );
4160 return bPrevUpdateLayout;
4161}
4162
4163void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow )
4164{
4165 ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
4166 DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! ");
4167 if ( !(pPPortion && ( pPPortion->IsVisible() != bShow )) )
4168 return;
4169
4170 pPPortion->SetVisible( bShow );
4171
4172 if ( !bShow )
4173 {
4174 // Mark as deleted, so that no selection will end or begin at
4175 // this paragraph...
4176 aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph ));
4178 // The region below will not be invalidated if UpdateMode = sal_False!
4179 // If anyway, then save as sal_False before SetVisible !
4180 }
4181
4182 if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) )
4183 {
4184 if ( !GetTextRanger() )
4185 {
4186 if ( pPPortion->IsInvalid() )
4187 {
4188 CreateLines( nParagraph, 0 ); // 0: No TextRanger
4189 }
4190 else
4191 {
4192 CalcHeight( pPPortion );
4193 }
4194 nCurTextHeight += pPPortion->GetHeight();
4195 }
4196 else
4197 {
4198 nCurTextHeight = 0x7fffffff;
4199 }
4200 }
4201
4202 pPPortion->SetMustRepaint( true );
4203 if ( IsUpdateLayout() && !IsInUndo() && !GetTextRanger() )
4204 {
4205 aInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ),
4208 }
4209}
4210
4211EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView )
4212{
4213 DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" );
4214 if ( GetParaPortions().Count() == 0 )
4215 return EditSelection();
4216 aOldPositions.Normalize();
4217
4218 EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) );
4219
4220 if ( nNewPos >= GetParaPortions().Count() )
4221 nNewPos = GetParaPortions().Count() - 1;
4222
4223 // Where the paragraph was inserted it has to be properly redrawn:
4224 // Where the paragraph was removed it has to be properly redrawn:
4225 // ( and correspondingly in between as well...)
4226 if ( pCurView && IsUpdateLayout() )
4227 {
4228 // in this case one can redraw directly without invalidating the
4229 // Portions
4230 sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
4231 sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos );
4232
4233 ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion );
4234 ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion );
4235 if (pUpperPortion && pLowerPortion)
4236 {
4237 aInvalidRect = tools::Rectangle(); // make empty
4238 aInvalidRect.SetLeft( 0 );
4240 aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) );
4241 aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() );
4242
4243 UpdateViews( pCurView );
4244 }
4245 }
4246 else
4247 {
4248 // redraw from the upper invalid position
4249 sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
4250 InvalidateFromParagraph( nFirstInvPara );
4251 }
4252 return aSel;
4253}
4254
4255void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara )
4256{
4257 // The following paragraphs are not invalidated, since ResetHeight()
4258 // => size change => all the following are re-issued anyway.
4259 ParaPortion* pTmpPortion;
4260 if ( nFirstInvPara != 0 )
4261 {
4262 pTmpPortion = GetParaPortions()[nFirstInvPara-1];
4263 pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 );
4264 }
4265 else
4266 {
4267 pTmpPortion = GetParaPortions()[0];
4268 pTmpPortion->MarkSelectionInvalid( 0 );
4269 }
4270 pTmpPortion->ResetHeight();
4271}
4272
4273IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void)
4274{
4275 CallStatusHdl();
4276}
4277
4279{
4280 if ( aStatusHdlLink.IsSet() && bool(aStatus.GetStatusWord()) )
4281 {
4282 // The Status has to be reset before the Call,
4283 // since other Flags might be set in the handler...
4284 EditStatus aTmpStatus( aStatus );
4285 aStatus.Clear();
4286 aStatusHdlLink.Call( aTmpStatus );
4287 aStatusTimer.Stop(); // If called by hand...
4288 }
4289}
4290
4292{
4293 const ParaPortion* pPortion = FindParaPortion( pCurNode );
4294 DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" );
4295 pPortion = GetPrevVisPortion( pPortion );
4296 if ( pPortion )
4297 return pPortion->GetNode();
4298 return nullptr;
4299}
4300
4302{
4303 const ParaPortion* pPortion = FindParaPortion( pCurNode );
4304 DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" );
4305 pPortion = GetNextVisPortion( pPortion );
4306 if ( pPortion )
4307 return pPortion->GetNode();
4308 return nullptr;
4309}
4310
4312{
4313 sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
4314 DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" );
4315 const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
4316 while ( pPortion && !pPortion->IsVisible() )
4317 pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
4318
4319 return pPortion;
4320}
4321
4323{
4324 sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
4325 DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" );
4326 const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara );
4327 while ( pPortion && !pPortion->IsVisible() )
4328 pPortion = GetParaPortions().SafeGetObject( ++nPara );
4329
4330 return pPortion;
4331}
4332
4334{
4335 tools::Long nTotalOccupiedHeight = 0;
4336 sal_Int32 nTotalLineCount = 0;
4337 const ParaPortionList& rParaPortions = GetParaPortions();
4338 sal_Int32 nParaCount = rParaPortions.Count();
4339
4340 for (sal_Int32 i = 0; i < nParaCount; ++i)
4341 {
4343 // All paragraphs must have the block justification set.
4344 return 0;
4345
4346 const ParaPortion* pPortion = rParaPortions[i];
4347 nTotalOccupiedHeight += pPortion->GetFirstLineOffset();
4348
4349 const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
4350 sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
4351 ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
4352
4353 const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
4354 tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
4355
4356 const EditLineList& rLines = pPortion->GetLines();
4357 sal_Int32 nLineCount = rLines.Count();
4358 nTotalLineCount += nLineCount;
4359 for (sal_Int32 j = 0; j < nLineCount; ++j)
4360 {
4361 const EditLine& rLine = rLines[j];
4362 nTotalOccupiedHeight += rLine.GetHeight();
4363 if (j < nLineCount-1)
4364 nTotalOccupiedHeight += nSBL;
4365 nTotalOccupiedHeight += nUL;
4366 }
4367 }
4368
4370 nTotalSpace -= nTotalOccupiedHeight;
4371 if (nTotalSpace <= 0 || nTotalLineCount <= 1)
4372 return 0;
4373
4374 // Shift the text to the right for the asian layout mode.
4376 adjustYDirectionAware(rStartPos, -nTotalSpace);
4377
4378 return nTotalSpace / (nTotalLineCount-1);
4379}
4380
4382{
4383 EditPaM aPaM;
4384 if ( nPara != 0 )
4385 {
4386 ContentNode* pNode = GetEditDoc().GetObject( nPara-1 );
4387 if ( !pNode )
4388 pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 );
4389 assert(pNode && "Not a single paragraph in InsertParagraph ?");
4390 aPaM = EditPaM( pNode, pNode->Len() );
4391 }
4392 else
4393 {
4394 ContentNode* pNode = GetEditDoc().GetObject( 0 );
4395 aPaM = EditPaM( pNode, 0 );
4396 }
4397
4398 return ImpInsertParaBreak( aPaM );
4399}
4400
4401std::optional<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara )
4402{
4403 std::optional<EditSelection> pSel;
4404 ContentNode* pNode = GetEditDoc().GetObject( nPara );
4405 SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" );
4406 if ( pNode )
4407 pSel.emplace( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) );
4408
4409 return pSel;
4410}
4411
4412void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo )
4413{
4414 if ( bDowning )
4415 return ;
4416
4417 if ( IsInUndo() )
4418 IdleFormatAndLayout( pCurView );
4419 else
4420 {
4421 if (bCalledFromUndo)
4422 // in order to make bullet points that have had their styles changed, redraw themselves
4423 for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
4424 GetParaPortions()[nPortion]->MarkInvalid( 0, 0 );
4425 FormatDoc();
4426 UpdateViews( pCurView );
4427 }
4428
4430 GetNotifyHdl().Call(aNotify);
4431}
4432
4434{
4435 if ( bFlat != aStatus.UseCharAttribs() )
4436 return;
4437
4438 if ( !bFlat )
4440 else
4442
4443 aEditDoc.CreateDefFont( !bFlat );
4444
4445 FormatFullDoc();
4446 UpdateViews();
4447 if ( pActiveView )
4449}
4450
4451void ImpEditEngine::setScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY)
4452{
4453 bool bChanged;
4454
4455 if (!IsEffectivelyVertical())
4456 {
4457 bChanged = mfFontScaleX != fFontScaleX || mfFontScaleY != fFontScaleY ||
4458 mfSpacingScaleX != fSpacingScaleX || mfSpacingScaleY != fSpacingScaleY;
4459 mfFontScaleX = fFontScaleX;
4460 mfFontScaleY = fFontScaleY;
4461 mfSpacingScaleX = fSpacingScaleX;
4462 mfSpacingScaleY = fSpacingScaleY;
4463 }
4464 else
4465 {
4466 bChanged = mfFontScaleX != fFontScaleY || mfFontScaleY != fFontScaleX ||
4467 mfSpacingScaleX != fSpacingScaleY || mfSpacingScaleY != fSpacingScaleX;
4468 mfFontScaleX = fFontScaleY;
4469 mfFontScaleY = fFontScaleX;
4470 mfSpacingScaleX = fSpacingScaleY;
4471 mfSpacingScaleY = fSpacingScaleX;
4472 }
4473
4474 if (bChanged && aStatus.DoStretch())
4475 {
4476 FormatFullDoc();
4477 // (potentially) need everything redrawn
4478 aInvalidRect = tools::Rectangle(0, 0, 1000000, 1000000);
4480 }
4481}
4482
4484{
4485 const SvxNumberFormat *pRes = nullptr;
4486
4487 if (pNode)
4488 {
4489 // get index of paragraph
4490 sal_Int32 nPara = GetEditDoc().GetPos( pNode );
4491 DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" );
4492 if (nPara < EE_PARA_NOT_FOUND)
4493 {
4494 // the called function may be overridden by an OutlinerEditEng
4495 // object to provide
4496 // access to the SvxNumberFormat of the Outliner.
4497 // The EditEngine implementation will just return 0.
4498 pRes = pEditEngine->GetNumberFormat( nPara );
4499 }
4500 }
4501
4502 return pRes;
4503}
4504
4506 const ContentNode *pNode,
4507 sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const
4508{
4509 // nSpaceBefore matches the ODF attribute text:space-before
4510 // nMinLabelWidth matches the ODF attribute text:min-label-width
4511
4512 const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode );
4513
4514 // if no number format was found we have no Outliner or the numbering level
4515 // within the Outliner is -1 which means no number format should be applied.
4516 // Thus the default values to be returned are 0.
4517 sal_Int32 nSpaceBefore = 0;
4518 sal_Int32 nMinLabelWidth = 0;
4519
4520 if (pNumFmt)
4521 {
4522 nMinLabelWidth = -pNumFmt->GetFirstLineOffset();
4523 nSpaceBefore = pNumFmt->GetAbsLSpace() - nMinLabelWidth;
4524 DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" );
4525 }
4526 if (pnSpaceBefore)
4527 *pnSpaceBefore = nSpaceBefore;
4528 if (pnMinLabelWidth)
4529 *pnMinLabelWidth = nMinLabelWidth;
4530
4531 return nSpaceBefore + nMinLabelWidth;
4532}
4533
4535{
4537}
4538
4539// select a representative text language for the digit type according to the
4540// text numeral setting:
4542{
4544 return LANGUAGE_ENGLISH_US;
4545
4546 // #114278# Also setting up digit language from Svt options
4547 // (cannot reliably inherit the outdev's setting)
4548
4549 LanguageType eLang = eCurLang;
4551
4552 if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals )
4554 else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals )
4555 eLang = LANGUAGE_ENGLISH;
4556 else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals )
4558
4559 return eLang;
4560}
4561
4562OUString ImpEditEngine::convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang)
4563{
4564 OUStringBuffer aBuf(rString);
4565 for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx)
4566 {
4567 sal_Unicode cChar = aBuf[nIdx];
4568 if (cChar >= '0' && cChar <= '9')
4569 aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang);
4570 }
4571 return aBuf.makeStringAndClear();
4572}
4573
4574// Either sets the digit mode at the output device
4576{
4577 rOutDev.SetDigitLanguage(ImplCalcDigitLang(eCurLang));
4578}
4579
4580void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex)
4581{
4582 bool bCTL = false;
4583 bool bR2L = false;
4584 if ( nIndex == -1 )
4585 {
4586 bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX );
4587 bR2L = IsRightToLeft( nPara );
4588 }
4589 else
4590 {
4591 ContentNode* pNode = GetEditDoc().GetObject( nPara );
4592 short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
4593 bCTL = nScriptType == i18n::ScriptType::COMPLEX;
4594 // this change was discussed in issue 37190
4595 bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0;
4596 // it also works for issue 55927
4597 }
4598
4599 vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode();
4600
4601 // We always use the left position for DrawText()
4602 nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiRtl;
4603
4604 if ( !bCTL && !bR2L)
4605 {
4606 // No Bidi checking necessary
4608 }
4609 else
4610 {
4611 // Bidi checking necessary
4612 // Don't use BIDI_STRONG, VCL must do some checks.
4613 nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
4614
4615 if ( bR2L )
4617 }
4618
4619 rOutDev.SetLayoutMode( nLayoutMode );
4620
4621 // #114278# Also setting up digit language from Svt options
4622 // (cannot reliably inherit the outdev's setting)
4624 ImplInitDigitMode(rOutDev, eLang);
4625}
4626
4627Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const
4628{
4629 if ( !xBI.is() )
4630 {
4631 Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
4632 xBI = i18n::BreakIterator::create( xContext );
4633 }
4634 return xBI;
4635}
4636
4637Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const
4638{
4639 if ( !xISC.is() )
4640 {
4641 Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
4642 xISC = i18n::InputSequenceChecker::create( xContext );
4643 }
4644 return xISC;
4645}
4646
4648{
4649 Color aColor;
4650
4652 {
4653 // Get document background color from current view instead
4655 if (aColor.IsDark())
4656 aColor = COL_WHITE;
4657 else
4658 aColor = COL_BLACK;
4659 }
4660 else
4661 {
4663
4664 if ( GetBackgroundColor() != COL_AUTO )
4665 {
4666 if ( GetBackgroundColor().IsDark() && aColor.IsDark() )
4667 aColor = COL_WHITE;
4668 else if ( GetBackgroundColor().IsBright() && aColor.IsBright() )
4669 aColor = COL_BLACK;
4670 }
4671 }
4672
4673 return aColor;
4674}
4675
4677 TextPortion* pTextPortion, sal_Int32 nStartPos,
4678 sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax,
4679 bool bManipulateDXArray)
4680{
4681 DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" );
4682 DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" );
4683
4684 // Percent is 1/100 Percent...
4685 if ( n100thPercentFromMax == 10000 )
4686 pTextPortion->SetExtraInfos( nullptr );
4687
4688 bool bCompressed = false;
4689
4690 if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN )
4691 {
4692 tools::Long nNewPortionWidth = pTextPortion->GetSize().Width();
4693 sal_Int32 nPortionLen = pTextPortion->GetLen();
4694 for ( sal_Int32 n = 0; n < nPortionLen; n++ )
4695 {
4697
4699 bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana );
4700
4701 // create Extra infos only if needed...
4702 if ( bCompressPunctuation || bCompressKana )
4703 {
4704 if ( !pTextPortion->GetExtraInfos() )
4705 {
4706 ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo;
4707 pTextPortion->SetExtraInfos( pExtraInfos );
4708 pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width();
4710 }
4711 pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax;
4712 pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType;
4713
4714 tools::Long nOldCharWidth;
4715 if ( (n+1) < nPortionLen )
4716 {
4717 nOldCharWidth = pDXArray[n];
4718 }
4719 else
4720 {
4721 if ( bManipulateDXArray )
4722 nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX;
4723 else
4724 nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth;
4725 }
4726 nOldCharWidth -= ( n ? pDXArray[n-1] : 0 );
4727
4728 tools::Long nCompress = 0;
4729
4730 if ( bCompressPunctuation )
4731 {
4732 nCompress = nOldCharWidth / 2;
4733 }
4734 else // Kana
4735 {
4736 nCompress = nOldCharWidth / 10;
4737 }
4738
4739 if ( n100thPercentFromMax != 10000 )
4740 {
4741 nCompress *= n100thPercentFromMax;
4742 nCompress /= 10000;
4743 }
4744
4745 if ( nCompress )
4746 {
4747 bCompressed = true;
4748 nNewPortionWidth -= nCompress;
4749 pTextPortion->GetExtraInfos()->bCompressed = true;
4750
4751
4752 // Special handling for rightpunctuation: For the 'compression' we must
4753 // start the output before the normal char position...
4754 if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) )
4755 {
4756 if ( !pTextPortion->GetExtraInfos()->pOrgDXArray )
4757 pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 );
4758
4760 {
4761 // If it's the first char, I must handle it in Paint()...
4762 if ( n )
4763 {
4764 // -1: No entry for the last character
4765 for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ )
4766 pDXArray[i] -= nCompress;
4767 }
4768 else
4769 {
4770 pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true;
4771 pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress;
4772 }
4773 }
4774 else
4775 {
4776 // -1: No entry for the last character
4777 for ( sal_Int32 i = n; i < (nPortionLen-1); i++ )
4778 pDXArray[i] -= nCompress;
4779 }
4780 }
4781 }
4782 }
4783 }
4784
4785 if ( bCompressed && ( n100thPercentFromMax == 10000 ) )
4786 pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth;
4787
4788 pTextPortion->setWidth(nNewPortionWidth);
4789
4790 if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) )
4791 {
4792 // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected
4793 tools::Long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->