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